Wayback Machine
SEP JAN Feb
Previous capture 15 Next capture
2007 2009 2010
12 captures
1 May 05 - 15 Jan 09
sparklines
Close Help
полная версия

Замок Дракона

Б   Е   З       Б   А   Ш   Н   И

На главную
/ Архивы Замка Дракона / Лекции ВМиК / Системное программное обеспечение / Лекции 7-9

Лекция 7

 

ОС Unix

Сегодня мы с вами переходим к началу рассмотрения ОС Unix, поскольку многие решения, которые принимаются в ОС мы будем рассматривать на примере этой ОС.

В середине 60х годов в Bell лаборатории фирмы AT&T проводились исследования и разработка одной из первых ОС в современном ее понимании — ОС Multics. Это ОС разделения времени, многопользовательская, а также в этой системе были предложены фактически основные решения по организации файловых систем. В частности, была предложена иерархическая древообразная файловая система. Это, ориентировочно, 1965 год. От этой разработки через некоторое время получила начало ОС Unix. Одна из предысторий говорит, что на фирме был ненужный компьютер PDP8 с очень малоразвитым программным обеспечением. А требовалась машина, которая бы позволяла организовывать удобную работу пользователя, в частности, удобный ввод информации. И известная группа людей — Томпсон и Ритчи занялись разработкой на этой машине новой ОС. Другой вариант был таков, что они занимались реализацией новой игры, а те средства, которые имелись были недоступны или неудобны, и они решили поиграться с этой машиной. Результатом стало появление ОС Unix. Особенностью этой системой являлось то, что она являлась первой системной программой написанной на языке, отличном от языка ассемблера. Дляцели написания этого системного программного обеспечения, в частности, ОС Unix, также параллельно проводились работы, которые начинались от языка BCPL, из него был образован язык B, который оперировал с машинными словами, далее абстракция машинных слов — BN и, наконец, язык “C”. И после 1973 года ОС Unix была переписана окончательно на язык “С”. В результате появилась ОС, 90% кода которой было написано на языке высокого уровня, языке, не зависящем от архитектуры машины и системы команд, а 10% было написанона ассемблере, в эти 10% входят наиболее критичные к реализации по времени части ядра ОС.

Многих программистов в то время это немного шокировало, мало кто верил, что такая ОС способна жить, поскольку всегда язык высокого уровня ассоциировался с большой неэффективностью. Но язык “С” тем не менее был сконструирован таким образом, что позволял писать эффективные программы и транслировать их в также достаточно эффективный машинный код.

Из таких конструктивных свойств следует отметить то, что “С” сильно построен на работе с указателями. Когда мы пишем программу на ассемблере, то очень часто для достижения требуемого результата нам нужно манипулировать с адресами. Возможность оперировать указателями — первое свойство “С”, которое позволяет эффективно транслировать программу на этом языке в машинный код.

Если мы посмотрим на нормальную программу на ассемблере, то заметим следующее — при программировании каких-то блоков мы часто используем побочный эффект (например, во время вычисления выражения мы можем получать и куда-то откладывать промежуточные результаты), также можно поступать и в языке “С”. Таким образом, понятие выражения в “С” было гораздо шире, чем в других языках того времени. И в выражениях, кроме новых операций, таких как работа с указателем, смещения сдвиги и т.п., появилась принципиально новая операция — операция присваивания. Почему она новая? Потому что во многих языках до “С”, а также и после него не было операции присваивания — был оператор присваивания. Разница в одном — если мы имеем оператор присваивания, во-первых, требуется, чтобы в правой части такой операции уже не было (мы не можем использовать побочный эффект), и второе — левая часть оператора присваивания — это некоторая ссылка на единичную область памяти. Внесение оператора присваивания внутрь выражения позволило решать проблему побочных эффектов (значения подвыражений, которые могут быть использованы во вне — а они в свою очередь сокращают число обменов с ОЗУ), а это средство эффективности.

Эти и, наверное, только эти свойства языка определили его живучесть, пригодность для программирования системных компонентов и возможность оптимальной трансляции кода различных машин. С профессиональной точки зрения, язык “С” — ужасный язык. Основным требованием, которое предъявляется сегодня к языкам программирования является безопасность программирования. То есть средства языка должны минимизировать количество возможных ошибок.

И свойствам таких языков относится следующее:

    1. Жесткий контроль типов. То есть если мы попробуем умножить целочисленную переменную на плавающую, то язык выдаст ошибку. Все преобразования типов по умолчанию недопустимы.
    2. Обеспечение контроля за доступом в память программы. Это означает, что если у нас в памяти число было записано, как целое, то и считать его оттуда мы можем только как целое, а не как плавающее или символ. В “С” же и других языках бесконтрольный доступ к памяти предоставляет указатель, более того, через указатель мы с одной стороны теряем любую информацию о типе, а с другой стороны мы можем обманывать функциипо части фактических и формальных параметров.
    3. Контроль за взаимодействием модулей. Суть этого свойства в том, что много ошибок появляется в том случае, что если функция продекларировала один набор параметров, а обращение к ней идет с другим набором, причем различие может быть как в количестве, так и в типах. Язык “С”, несмотря даже на версию ANSI C, которая пыталась отчасти решить эту проблему — всегда остается возможность обмануть функцию и передать ей параметр другого типа, вместо шести параметров можно передать один параметр.

Вот по этим трем позициям язык “С” является нехорошим языком. Но тем не менее это “менталитет” программистов, который заключается в том, что почему-то наиболее живучими языками являются концептуально плохие языки, к таким языкам помимо “С” можно добавить еще Фортран.

Итак, 1973 год. Появление ОС Unix, причем она уже была написана на языке “С”. Какими основными свойствами уже тогда обладала эта ОС. Первое свойство — концепция файлов, основным объектом, которым оперирует ОС — это файл. Файл — это набор данных, файл с точки зрения Unix — это внешнее устройство, файл — это каталог, который содержит информацию о принадлежащих ему файлах и т.д. На сегодняшний день стратегия файлов распространена в Unix’е практически на все. Второе свойство, которое является продолжением или следствием первого, это то, что ОС построена очень интересно. В отличии от предыдущих ОС, где каждая команда была зашита внутрь, и эту команду нельзя было модифицировать, убрать из системы, создать новую команду — вUnix’е проблемы команд пользователя решены очень элегантно за счет двух моментов. Первый — Unix декларирует стандартный интерфейс передачи параметров извне внутрь процесса. Второй — все команды реализованы в виде файлов, это означает, что можно свободно добавлять новые команды в систему, которые будут доступны либо мне, либо группе пользователей, либо всем, а можно удалять команды.

Давайте начнем рассмотрение конкретных свойств ОС Unix. Первое, что мы будем рассматривать, это файловая система, организация работы с файлами.

 

Файловая система Unix

Файловая система Unix, это иерархическая, многопользовательская файловая система. Ее можно представить в виде дерева:

В корне дерева находится “корневой каталог”, узлами, отличными от листьев дерева являются каталоги. Листьями могут являться: файлы (в традиционном понимании — именованные наборы данных), пустые каталоги (каталоги, с которыми не ассоциировано ни одного файла). В системе определено понятие имени файла — это имя, которое ассоциировано с набором данных в рамках каталога, которому принадлежит этот файл. Например, каталогу D1 принадлежат файлы: N1, N2, N3; каталогу D0 принадлежат: N4, N5 и D1, последний тоже является файлом, но специальный. Итак, имя — это имя, которое ассоциировано снабором данных в контексте принадлежности каталогу. Кроме того, есть понятие полного имени. Полное имя — это уникальный путь от корня файловой системы до конкретного файла. Первый символ имени — это корневой каталог “/”, а далее через наклонную черту перечислены все каталоги, пока не дойдет до нужного файла. Например, файл N3 имеет полное имя “/D0/D1/N3”. За счет того, что такой путь для каждого файла в любом каталоге уникален, то мы можем именовать одинаковыми именами файлы в различных каталогах. Например, имя N4 присутствует в каталогах D0 и D4, но это разные файлы, так как полные пути к ним различны (/D4/N4, /D0/N4).

Замечание. На самом деле файловая система Unix не является древообразной. Все то, что говорилось выше — правильно, но в системе имеется возможность нарушения красивой и удобной иерархии в виде дерева, так как имеется возможность ассоциировать несколько имен с одним и тем же содержимым файла. И могут возникать такие ситуации, когда, например, “/D4/N3” и “/D0/D1/N1” являются, по сути дела, одним файлом с двумя именами.

Еще одно замечание. В ОС Unix используется трехуровневая иерархия пользователей:

 

Первый уровень — все пользователи. Они подразделены на группы и, соответственно, группы состоят из реальных пользователей. В связи с этой трехуровневой организацией пользователей каждый файл обладает тремя атрибутами:

1) Владелец файла. Этот атрибут связан с одним конкретным пользователем, который автоматически назначается системой владельцем файла. Владельцем можно стать по умолчанию, создав файл, а также есть команда, которая позволяет менять владельца файла.

2) Защита доступа к файлу. Доступ к каждому файлу (от файла ядра системы до обыкновенного текстового файла) лимитируется по трем категориям:

 права владельца (что может делать владелец с этим файлом, в общем случае — не обязательно все, что угодно);

 права группы, которой принадлежит владелец файла. Владелец сюда не включается (например, файл может быть закрыт на чтение для владельца, а все остальные члены группы могут свободно читать из этого файла;

 все остальные пользователи системы;

По этим трем категориям регламентируются три действия: чтение из файла, запись в файл и исполнение файла (в мнемонике системы R,W,X, соответственно). В каждом файле по этим трем категориям определено — какой пользователь может читать, какой писать, а кто может запускать его в качестве процесса.

Это некоторые предварительные данные по файловой системе. Теперь давайте рассмотрим структуру файловой системы на диске.

Сначала определим некоторые понятия:

Для любой вычислительной системы определено понятие системного внешнего запоминающего устройства (ВЗУ). Это устройство, к которому осуществляет доступ аппаратный загрузчик системы с целью запуска ОС. Суть заключается в следующем — практически любая вычислительная система имеет диапазон адресного пространства оперативной памяти, размещенной в ПЗУ. В ПЗУ размещается небольшая программа (хотя понятие размера относительно, но она действительно небольшая), которая при включении вычислительной машины обращается к фиксированному блоку ВЗУ, считывает его в память и передает управление на фиксированный адрес, относящийся к считанному блоку данных.

Считается, что считанный блок данных является программным загрузчиком и программный загрузчик раскручивает запуск ОС. Следует отметить, что если аппаратный загрузчик в подавляющем большинстве машин системно независим (то есть он не знает, какая ОС будет загружена), то программный загрузчик — это уже компонент ОС, ему известно, что будет загружаться конкретная ОС, он знает, где размещаются нужные для загрузки данные.

В любой системе принято разбиение пространства ВЗУ на некоторые области данных, которые называются блоками. Размер блока (логического блока в ОС) является фиксированным атрибутом. В ОС Unix в различных ее вариациях размер блока был параметром меняющимся в зависимости от варианта ОС. Для простоты и единообразия мы будем считать, что логический блок ВЗУ равен 512 байт.

Итак, рассмотрим структуру файловой системы. Представим адресное пространство системного ВЗУ в виде последовательности блоков.

Блок начальной загрузки

Суперблок

Индексные дескрипторы

Блоки файлов

Область сохранения

0

     

N-M+1

Будем считать, что этих блоков N+M-1.

Первый блок — это блок начальной загрузки. Размещение этого блока в нулевом блоке системного устройства определяется аппаратурой, так как аппаратной загрузчик всегда обращается к конкретному блоку системного устройства (к нулевому блоку). Это последний компонент файловой системы, который зависит от аппаратуры.

Следующий блок — суперблок файловой системы. Он содержит оперативную информацию о состоянии файловой системы, а также данные о параметрах настройки файловой системы. В частности суперблок имеет информацию о

Третий блок — область индексных дескрипторов. ИД — это специальная структура данных файловой системы, которая взаимооднозначно соответствует файлу. С каждым содержимым файла связан один и только один ИД. ИД организуют не один блок, а пространство блоков, размеры которого определяются параметром генерации файловой системы (определяется по количеству ИД,указанном в суперблоке). Соответственно, каждый индексный регистр содержит следующую информацию:

Далее идут блоки файлов. Это пространство ВЗУ, в котором размещается вся информация, находящаяся в файлах и о файлах, которая не поместилась в уже перечисленных блоках.

Последняя область данных (она в разных системах размещается по-разному), но для простоты изложения мы будем считать, что эта область находится сразу за блоками файлов — это область сохранения.

Это концептуальная схема структуры файловой системы. Теперь давайте вернемся и рассмотрим некоторые ее части более детально.

Прежде всего интерес вызывают области свободных блоков файлов и свободных ИД. В Unix видно влияние двух факторов: первый — это то, что файловая система разрабатывалась тогда, когда ВЗУ объемом 5-10Мб считалось очень большим и в реализации алгоритмов по работе с системой видны старания автором по оптимизации этого процесса; и второй — это свойства файловой системы по оптимизации доступа, критерием которого является количество обменов, которые файловая система производит для своих нужд, не связанных с чтением или записью информации файлов.

Суперблок содержит список свободных блоков файлов, он состоит из 50 элементов. Суть работы с этим списком заключается в следующем — в буфере, состоящем из 50 элементов (при условии того, что блок — 512 байт, 1 блок — 16 битное слово), в них записаны номера свободных блоков пространства блоков файлов с 2 до 49. В 0 элементе содержится указатель на продолжение массива, а в последнем элементе содержится указатель на свободный элемент в массиве.

Если какому-то процессу для расширения файла требуется свободный блок, то система по указателю N/B (номер блока) выбирает элемент массива, и этот блок предоставляется файлу. Если происходит сокращение файла, то высвободившиеся номера добавляются в массив свободных блоков и корректируется указатель N/B.

Так как размер массива — 50 элементов, то возможны две критические ситуации:

    1. Когда мы освобождаем блоки файлов, а они не могут поместиться в этом массиве. В этом случае из файловой системы выбирается один свободный блок и заполненный полностью массив свободных блоков копируется в этот блок, после этого значение указателя N/B обнуляется, а в нулевой элемент массива, который находится в суперблоке, записывается номер блока, который мы выбрали для копирования содержимого массива. Таким образом, если мы постоянно освобождаем блоки, то образуется список, в котором будут размещены все свободные блоки файловой системы.
    2. Когда мы выбрали все свободные блоки и содержимое элементов массива свободных блоков исчерпалось. Если нулевой элемент массива равен нулю, то это означает, что исчерпано все пространство файловой системы. Если этот элемент нулю не равен, то это означает, что существует продолжение массива. Это продолжение считывается в копию суперблока в оперативной памяти.

Для получения свободного блока и его освобождения в большинстве случаев не требуется дополнительного обмена. Дополнительный обмен требуется тогда, когда исчерпается содержимое 49 блоков. У нас получается хорошая буферизация, которая сокращает накладные расходы ОС.

Список свободных ИД. Это буфер, состоящий из 100 элементов. В нем находится информация о 100 номерах ИД, которые свободны в данный момент. Соответственно, когда нужен новый ИД, то его номер берется из списка свободных ИД, если номер освобождается, то заносится в этот массив. Если же массив переполнен, а освобождается 101 элемент, то это никуда не записывается. Если список ИД переполняется, то система “пробегает” по списку и формирует содержимое этого буфера заново.

В ситуации, когда нужно создать файл и нужен новый ИД, а в массиве нет ни одного элемента — запускается процесс поиска нового ИД, и он ничего не находит. Тогда возможны две ситуации:

    1. Больше нет свободных блоков для файлов;
    2. Нет больше новых ИД.

Вот информация о суперблоке. Какие можно сделать выводы и замечания?

 

Лекция 8

Индексные Дескрипторы

Рассмотрим подробнее Индексные Дескрипторы. ИД — это объект Unix, который ставится во взаимнооднозначное соответствие с содержимым файла. То есть для каждого ИД существует только одно содержимое и наоборот, за исключением лишь той ситуации, когда файл ассоциирован с каким-либо внешним устройством. Напомним содержимое ИД:

Как видно — в ИД нет имени файла. Давайте посмотрим, как организована адресация блоков, в которых размещается файл.

В поле адресации находятся номера первых десяти блоков файла, то есть если файл небольшой, то вся информация о размещении данных файла находится непосредственно в ИД. Если файл превышает десять блоков, то начинает работать некая списочная структура, а именно, 11й элемент поля адресации содержит номер блока из пространства блоков файлов, в которых размещены 128 ссылок на блоки данного файла. В том случае, если файл еще больше — то используется 12й элемент поля адресации. Сутьего в следующем — он содержит номер блока, в котором содержится 128 записей о номерах блоках, где каждый блок содержит 128 номеров блоков файловой системы. А если файл еще больше, то используется 13 элемент — где глубина вложенности списка увеличена еще на единицу.

Таким образом мы можем получить файл размером (10+128+1282+1283)*512.

Если мы зададим вопрос — зачем все это надо (таблицы свободных блоков, ИД и т.д.), то вспомним, что мы рассматриваем взаимосвязь между аппаратными и программными средствами вычислительной системы, а в данном случае подобное устройство файловой системы позволяет сильно сократить количество реальных обменов с ВЗУ, причем эшелонированная буферизация в ОС Unix делает число этих обменов еще меньше.

Рассмотрим следующую область — область сохранения. На схеме она изображена сразу за блоками файлов. На самом же деле она может размещаться по-разному: перед блоками файлов, в каком-нибудь файле или еще где-нибудь, например, на другом ЗУ. Все это зависит от конкретной реализации системы.

В область сохранения происходит откачка процессов, она же используется для оптимизации запуска наиболее часто запускающихся процессов (использование так называемого T-бита файла).

Мы с вами рассмотрели структуру файловой системы и ее организацию на системном устройстве. Эта структура и алгоритмы работы с ней достаточно простые, это сделано для того, чтобы накладные расходы, связанные с функционированием системы, не выходили за пределы разумного.

 

 

Элементы файловой системы:

Каталоги

Мы говорили, что вся информация в Unix размещается в файлах. Нету каких-то специальных таблиц, которые размещены вне файловой системы и используются системой, за исключением тех таблиц, которые создает ОС во время работы в пространстве оперативной памяти.

Каталог с точки зрения ОС — это файл, обычный файл, в котором размещены данные о всех файлах, которые принадлежат каталогу.

Мы говорим, что в каталоге “А” содержатся файлы: “B”, “C” и “D” — из которых “В” и “С” могут быть как файлами, так и каталогами, а “D” — заведомо каталог.

Каталог имеет следующую структуру. Он состоит из элементов, объединяющих в себе два поля — номер ИД и имя файла:

Каталог = { {ИД, Имя}, {ИД,Имя}, ..., {ИД, Имя}}

Что есть номер ИД? — это порядковый номер элемента в списке индексных дескрипторов. Так, первый элемент этого списка — ИД#1 принадлежит корневому каталогу “.”.

В общем случае, в каталоге могут неоднократно встречаться записи, ссылающиеся на один и тот же ИД, но в каталоге не могут встречаться записи с одинаковыми именами. То есть с содержимым файла может быть связано произвольное количество имен. При создании каталога в нем всегда создаются две записи:

{ИД_самого_каталога, “.”} и {ИД_родительского_каталога, “..”}

Так на картинке файл “А” имеет ИД#7, “D” — ИД#5, “F” — ИД#10, “G” — ИД#101. В этом случае файл-каталог D будет иметь следующее содержимое:

{{ 5, “.” },

{ 7, “..”},

{10, “F”},

{101,”G”}}

(Для корневого каталога родитель ссылается на него же самого.)

Чем отличается файл-каталог от обычного файла? Он отличается полем типа в индексном дескрипторе.

Давайте посмотрим, как схематично могут использоваться полные имена и ссылки на каталоги. В системе в каждый момент времени определен для пользователя текущий каталог. То есть каталог, полное имя которого подставляется ко всем файлам, имя которых не начинается с символа “/”. Если текущий каталог “D”, то можно говорить просто о файле “F” или файле “G”, если же текущий каталог “D”, а требуется добраться до файла “B”, то оперировать просто с именем“B” нельзя, так как он не принадлежит каталогу “D”, файл “B” можно достать, указав его полное имя от корня, либо использовать специальный файл “..”, в этом случае файл “B” будет иметь имя: “../B”. Если при открытии мы ссылаемся на “..”

Для того, чтобы в этом случае открыть файл “B”, придется выполнить ряд косвенных операций — взять ИД родитель, и по нему выбирается содержимое файла-каталога “А”, в “А” мы выбираем строку с именем “B” и определяем его ИД. Эта процедура достаточно трудоемка, но так как открытие и закрытие файлов происходит достаточно редко, то “криминала” в этом никакого нету.

За счет такой организации каталогов у нас содержимое файла разорвано с его именем. Имя может быть определено неоднозначно.

Так как с одним файлом может ассоциировано несколько имен, то можно говорить о том, что этот файл может быть одновременно открыт несколькими процессами (вообще говоря, имея одно имя мы тоже можем открыть этот файл из нескольких процессов, суть проблемы от этого уточнения не изменяется). Как организуется синхронизация в этом случае? Как мы увидим позже, здесь все решается корректно.

 

Файлы устройств

Эта разновидность файлов характеризуется типом и их интерпретация происходит следующим образом. В принципе, содержимого у файлов устройств нету, то есть это лишь ИД и имя, которое с ним ассоциировано. В ИД указывается информация о том, какой тип устройства ассоциирован с этим файлом, соответственно, система Unix все устройства подразделяет на два типа: байт- и блок-ориентированные. Байт-ориентированные устройства — это те устройства, обмен с которыми происходит по байтам (например, клавиатура), блок-ориентированные — это такие устройства, обмен с которыми происходит блоками. В ИД имеется поле, указывающее эту характеристику, там же имеется поле, определяющее номер драйвера, связанного с этим устройством. В системе каждый драйвер связан с конкретным одним устройством, но у устройства может быть несколько драйверов. Это поле, определяющее номер драйвера, на самом деле есть номер в таблице драйверов соответствующего класса устройств (имеются две таблицы — для блок- и байт- устройств). Также в ИД существует некоторый цифровой параметр, который может быть передан драйверу в качестве параметра, уточняющего информацию о работе.

Это то, что можно сказать о специальных файлах, связанных с внешними устройствами.

 

Обмен данными с файлами

Следующее из системной организации файловой системы — это организация обменом данными с файлом. Определим понятия, связанные с низкоуровневым вводом/выводом. В Unix определены специальные функции, которые называются системными вызовами. Эти вызовы осуществляют непосредственное обращение к ОС, они выполняют некоторые системные функции. По употреблению они практически не отличаются от использования библиотечных функций, тогда как по реализации и действии их отличие достаточно существенное. Библиотечная функция будет загружена в тело процесса, а системный вызов сразу передает управление ОС, и последняя выполняет заказанное действие. В Unix для обеспечения низкоуровневого (путем системных вызовов) ввода-вывода имеется набор этих функций:

open(...) — для работы с содержимым файла процесс должен зарегистрировать в системе этот факт, параметрами этой функции являются строка, содержащая имя файла и атрибуты на режим работы с файлом (только чтение, чтение-запись и т.п.), а возвращает эта функция некоторое число, которое называется файловым дескриптором (ФД). В теле процесса пользователя, а также данных, ассоциированных с этим процессом, размещается некая служебная информация. В частности, размещается таблица файловых дескрипторов. Она, как и все таблицы в Unix — позиционна, то есть номер строки в таблице соответствует ФД с этим номером. С ФД ассоциировано имя файла и прочие атрибуты. Нумерация ФД — прерогатива процесса, то есть ФД уникальны в пределах одного процесса.

Количество одновременно открытых файлов (точнее, максимальное количество ФД, ассоциированных с файлами) для процесса регламентируется системой.

Итак, функция open(...) — открытие существующего файла.

creat(...) — это функция открытия нового файла, ее параметрами служат: имя файла и некоторые параметры открытия, также как и у open.

read(...)/write(...) — их параметрами являются номер ФД и некоторые параметры доступа. Эти функции служат для чтение/записи из или в файл.

close(...) — завершение работы с файлом. После выполнения этой функции ФД этого файла освобождается.

Все это системные вызовы. Также в Unix можно осуществлять ввод-вывод через библиотечные функции (например, fopen, fread, fwrite, fclose, ...).

Рассмотрим организацию обмена с системной точки зрения в Unix.

При организации обмена система подразделяет все данные на две категории — первая, это данные, ассоциированные с процессом пользователя, и данные, ассоциированные с ОС.

Первая таблица данных, связанных с ОС — это таблица индексных дескрипторов открытых файлов (ТИДОФ), эта таблица содержит записи, каждая из которых содержит копию ИД для каждого открытого в системе файла. Через копию ИД мы осуществляем доступ к блокам файла. Каждая из этих записей содержитполе, характеризующее количество открытых файлов в системе, использующих данные ИД. То есть, если мы открываем один и тот же файлов от имени двух процессов, то запись в ТИДОФ создается одна, но каждое открытие этого ИД увеличивает счетчик на единицу.

Следующее. Таблица файлов — эта таблица содержит информацию об имени открытого файла и имеет ссылку на ИД данного файла в ТИДОФ.

Подробнее эта схема будет рассмотрена на следующей лекции.

 

 

Лекция 9

Мы начали рассмотрение принципов организации работы ОС Unix с файловой системы. Точнее организации обработки ввода-вывода.

Теперь давайте посмотрим, как организуется обработка низкоуровневого обмена с точки зрения ОС. Понятно, что мы будем рассматривать чуть более общую модель и не уделять времени не самым значащим деталям.

Для поддержания ввода-вывода в системе все данные в системе подразделяются на два типа: общесистемные данные (ТИДОФ, например). Размер ТИДОФ фиксирован — это еще один параметр настройки системы. Каждая запись этой таблицы содержит некоторую информацию, из которой нас будет интересовать следующее:

    1. Копия ИД открытого файла. То есть для любого открытого файла ИД, который характеризует содержимое этого файла, копируется и размещается в ТИДОФ. После этого все манипуляции с файлами происходят через копию ИД. Не с ИД, который на диске, а с его копией. Таким образом доступ к информации осуществляется оперативно.
    2. Счетчик открытых в данный момент файлов, связанных в данный момент с данным ИД. Это означает, что на любое количество открытий файла, связанного с данным ИД, система работает с одной копией этого ИД.

Рассмотрим следующую таблицу — Таблицу Файлов (ТФ) — она также состоит из определенного числа записей. Каждая запись в ТФ соответствует открытому в системе файлу. При этом в подавляющем большинстве случаев это есть взаимооднозначное соответствие (исключения мы рассмотрим позже). Каждая запись ТФ содержит указатели чтения-записи по файлу. Это означает, что если мы открыли один и тот же файл в двух или в одном процессе, то с каждым таким открытием связано по одному указателю, и они друг с другом независимы, то есть если мы изменяем один указатель, то другой остается в прежнем состоянии. И так почти всегда, за исключением некоторых случаев. Кроме того, в строке ТФ содержится некоторая запись — число, которое называется индексом наследственности. Это данные уровня ОС, то есть данные, которые описывают состояние системы в целом. С каждым процессом связана так называемая таблица открытых файлов (ТОФ).

Номер записи в данной таблице — номер файлового дескриптора, каждая строка имеет ссылку на соответствующую строку ТФ. Это означает, что информация об указателях как бы разорвана. То есть файловый дескриптор, являющийся атрибутом процесса, с другой стороны является атрибутом ОС.

Для того, чтобы мы имели возможность рассмотреть все грани данного вопроса — забежим немного вперед и рассмотрим некоторые вещи, связанные с формированием процесса.

ОС Unix имеет функцию, которая называется fork(...) — это системный вызов, при обращении к которому происходит некоторое “бесполезное” действие — создается процесс двойник — полная (с некоторыми замечаниями) копия процесса, в котором встретилась эта функция. Для чего это нужно? Нужно, для чего — увидим позже.

При формировании процесса двойника есть две особенности:

 

Предположим, у нас есть процесс #1 и с ним ассоциирована ТОФ1, в этом процессе открыт файл с именем “name”, ему соответствует ФД i. Это означает, что строка i ТОФ будет иметь ссылку на строку из ТФ, где содержится системная информация о файле, в том числе указатели чтения/записи. Записи в ТФ имеют ссылку на строку ТИДОФ, где находится копия ИД файла с именем “name”. Предположим, что в этом же процессе мы открыли еще раз файл с именем “name”. Система поставила ему в соответствие ФД j, это означает, что в записи j ТОФ будет ссылка на запись в ТФ, соответствующую второму открытию файла “name”.Индексы наследственности в обоих случаях будут равны единице.

Соответственно, когда мы изменяем указатель файла i (читаем или пишем), то файловый указатель j изменяться не будет. Обе записи в ТФ ссылаются на один и тот же ИД файла.

Теперь предположим, что процесс #1 выполнил обращение к функции fork(...). Образовалась копия этого процесса, причем обе копии начинают работать на выходе из процесса. И, соответственно, со вторым процессом будет ассоциирована ТОФ2.

Файлы “name” с дескрипторами i,j будут также открыты во втором процессе. Но, когда процесс получает открытые файлы в наследство от родителя, то ссылки из соответствующих строк таблицы ТОФ будут происходить не на новые строки ТФ, а на те же самые, на которые ссылались ФД родителя. Это означает, что у обоих процессов будут одинаковые указатели файлов — при перемещении указателя для ФД i в процессе-отце будет также изменен файловый указатель для ФД i в сыне и наоборот. Вообще говоря, два процесса по ФД i (или любому переданному в наследство открытому файлу) будут иметь общий указатель файла.

Вот это случай, когда нет взаимооднозначного соответствия между строками ТФ и ТОФ. И во время создания процесса сына счетчик наследственности увеличивается на единицу.

Что означает такая организация доступа к данным файла? Это означает, что этот доступ осуществляется централизованно, то есть в конечном итоге все заказы на обмен идут через одну единственную запись, сколько бы раз файл ни был открыт в системе. Отсюда мы получаем отсутствие путаницы при доступе к файлу.

При любом формировании нового процесса, система автоматически связывает 0, 1 и 2 ФД с предопределенными файлами:

0 — системный файл ввода (обычно — файл устройства клавиатура);

1 — системный файл вывода (обычно — файл устройства монитор);

2 — файл вывода диагностических сообщений (обычно — также файл устройства монитор).

Рассмотрим типовые действия при обращении к тем или иными системным вызовам.

При обращении к функции fork(...) система создает копию процесса и дублирует ТОФ родителя в ТОФ сыновнего процесса, а также увеличивает на единицу индексы наследственности в соответствующих строках ТФ, и увеличивает счетчик связей в ТИДОФ.

При выполнении системного вызова open(...):

    1. По полному имени определяется каталог, в котором размещается файл;
    2. Определяется номер ИД файла;
    3. По номеру ИД осуществляется поиск в ТИДОФ, если запись с данным номером обнаружена, то номер соответствующей строки ТИДОФ фиксируется и переходим к шагу 5;
    4. Если такой записи не обнаружено, происходит формирование новой строки в ТИДОФ, соответствующей новому ИД и фиксируется ее номер;
    5. Корректируется счетчик ссылок (количество открытых файлов, использующих данный ИД) в ТИДОФ. Номер строки ТИДОФ записывается в строку ТФ, а ее номер возвращается в ТОФ;

При операциях ввода-вывода мы идем по ссылкам и добираемся до нужного блока данных.

 

Взаимодействие с устройствами.

Мы говорили, что все устройства, которые обслуживает ОС Unix, могут быть подразделены на два типа: байт- и блок-ориентированные. С первыми устройствами все обмены осуществляются порциями по одному байту, с остальными — некоторыми порциями байт. С точки зрения ОС одно и то же устройство может рассматриваться как байт- и как блок-ориентированное. Примером такого устройства может быть оперативная память.

Различие составляет наличие или отсутствие соответствующих драйверов, ибо существует две таблицы — байт- и блок-ориентированных драйверов. На эти таблицы имеются ссылки в ИД специальных файлов.

Основной особенностью организации работы с блок-ориентированными устройствами является возможность буферизации обмена. В оперативной памяти организован пул буферов.

       

...

 
           

Каждый из буферов пула состоит из буфера размером один блок. Каждый из этих блоков может быть ассоциирован с драйвером одного из блок-ориентированных устройств. Посмотрим, какие происходят действия при выполнения заказа на чтение блока.

Пусть поступил заказ на чтение Nного блока из устройства с номером M. Тогда:

    1. Среди буферов пула ищется содержащий Nный блок Mмого устройства. Если он найден, то фиксируется номер этого буфера (следует отметить, что в этом случае реального обращения к устройству нет, а чтением информации является предоставление информации из найденного буфера) и переходим на четвертый шаг;
    2. Если поиск оказался неудачным, то в пуле осуществляется поиск буфера для чтения и размещения содержимого данного блока. Если есть свободный буфер (реально такая ситуация может быть только при старте системы), то фиксируем его номер и переходим к пункту 3. Если же свободного буфера нет, то выбирается буфер, обращений к которому не было самое длительное время. Если для него имеется установленный признак записи информации в буфер (при последнем обращении была произведена запись) — происходит запись информации из буфера на физическое устройство (если признака записи не было, то просто игнорируем содержимое), и, фиксируя номер, переходим к шагу 3.
    3. Осуществляется чтение Nного блока устройства M в найденный буфер.
    4. Происходит обнуление счетчика времени для данного буфера, а счетчики времени всех остальных буферов пула увеличиваем на единицу.
    5. Передаем в качестве результата содержимое буфера.

Это последовательность действий, связанных с операцией чтения блока. Мы видим, что здесь есть элемент оптимизации связанный с количеством реальных обращений к физическому устройству. Запись блоков осуществляется по аналогичной схеме.

Очевидны преимущества, недостатком же является то, что система в случае буферизации является критичной к несанкционированным выключениям системы. То есть в ситуации, когда буфера системы не выгружены, а происходит нештатное завершение программ ОС, может произойти потеря данных.

Другой недостаток — то, что при буферизации разорваны во времени обращения к системе за обменом и реальные обмены. Этот недостаток проявляется в том, что если при записи в буфер система возвращает процессу результат, что запись прошла успешно, а при реальном обмене с физическом устройстве происходит сбой — эта ситуация плоха.

Для борьбы с вероятностью потери информации во время появления внештатных ситуаций система действует следующим образом — в ОС есть некоторый параметр, который определяет периоды времени, через которые происходит сброс буферов. Второе — имеется команда, которая может быть доступна пользователю — команда sync() — по этой команде осуществляется сброс данных на диск. И третье — система обладает некоторое избыточностью, позволяющей в случае потери информации произвести набор действий, которые эту информацию восстановят полностью. Если же для некоторых блоков принадлежность к файлу установить не получается, то эти блоки будут записаны в отдельные файлы.

Но на самом деле с развитием ОС и аппаратуры фатальные потери информации встречаются редко.

Мы начинали разговор о том, что существуют системные вызовы, а существуют библиотеки ввода-вывода. Библиотеки ввода-вывода также позволяют оптимизировать работу системы.

Рассмотрим стандартную библиотеку stdio.h. Концептуальная суть обменов через нее такая же, как и через системные вызовы. Но если open возвращает номер ФД, то fopen возвращает указатель на некоторую структуру — это первое. Второе и основное — многие функции сервиса, которые предоставляет библиотека реализуются внутри адресного пространства процесса, в частности такой функцией сервиса является еще один уровень буферизации ввода-вывода. Суть его в том, что на ресурсах процесса можно выделить буфер, который будет работать аналогично буферному пулу ОС и будет минимизировать обращение процесса к системным вызовам ввода-вывода.

Двойная буферизация, очевидно, вещь полезная — если мы обращаемся за обменом, например, на полблока, то буфер в процессе соберет эти половинки и системный вызов будет запрашивать уже обмен с целым блоком. Что невыгодно? То, что буферизация существует в пределах адресного пространства процесса, и мы теряем всякую синхронизацию между процессами по обмену с общим открытым файлом, так как в каждом процессе может быть свой внутренний буфер. Но тем не менее стандартная библиотека ввода-вывода удобна. А уж пользователь берет на себя проблемы синхронизации, да и сама библиотека позволяет блокировать внутреннюю буферизацию.


[Наверх: в начало разделаНазад: Лекции 1-3Вперед: Лекции 10-12Здесь: Лекции 7-9]