В последней главе рассматривались внутренние структуры данных для файло-
вой системы и алгоритмы работы с ними. В этой главе речь пойдет о системных
функциях для работы с файловой системой с использованием понятий, введенных
в предыдущей главе. Рассматриваются системные функции, обеспечивающие обра-
щение к существующим файлам, такие как open, read, write, lseek и close, за-
тем функции создания новых файлов, а именно, creat и mknod, и, наконец, фун-
кции для работы с индексом или для передвижения по файловой системе: chdir,
chroot, chown, stat и fstat. Исследуются более сложные системные функции:
pipe и dup имеют важное значение для реализации каналов в shell'е; mount и
umount расширяют видимое для пользователя дерево файловых систем; link и
unlink изменяют иерархическую структуру файловой системы. Затем дается пред-
ставление об абстракциях, связанных с файловой системой, в отношении поддер-
жки различных файловых систем, подчиняющихся стандартным интерфейсам. В пос-
леднем разделе главы речь пойдет о сопровождении файловой системы. Глава
знакомит с тремя структурами данных ядра: таблицей файлов, в которой каждая
запись связана с одним из открытых в системе файлов, таблицей пользователь-
ских дескрипторов файлов, в которой каждая запись связана с файловым деск-
риптором, известным процессу, и таблицей монтирования, в которой содержится
информация по каждой активной файловой системе.

Функции для работы с файловой системой
+----------------------------------------------------------------+
+------+--------------+--------+-------+-------+---------+-------+
| Воз- | Используют | Назна- | Рабо- | Ввод- | Работа- | Управ-|
| вра- | алгоритм | чают | тают | вывод | ют со | ление |
| щают | namei | индек- | с ат- | из | структу-| де- |
| деск-| | сы | рибу- | файла | рой фай-| ревь- |
| рип- | | | тами | | ловых | ями |
| торы | | | файла | | систем | |
| файла| | | | | | |
+------+--------------+--------+-------+-------+---------+-------+
| open | open stat | | | | | |
| creat| creat link | creat | chown | read | | |
| dup | chdir unlink| mknod | chmod | write | mount | chdir |
| pipe | chroot mknod | link | stat | lseek | umount | chown |
| close| chown mount | unlink | | | | |
| | chmod umount| | | | | |
+------+--------------+--------+-------+-------+---------+-------+
+---+--+--------------+--------+-------+-------+---------+----+--+
| Алгоритмы работы с файловой системой на нижнем уровне |
+-------------+------------------+------------------------+
| namei | | |
+-------------+ ialloc ifree | alloc free bmap |
| iget iput | | |
+-------------+------------------+------------------------+
+---------------------------------------------------------+
| алгоритмы работы с буферами |
+---------------------------------------------------------+
| getblk brelse bread breada bwrite |
+---------------------------------------------------------+

Рисунок 5.1. Функции для работы с файловой системой и их
связь с другими алгоритмами

85

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

* Системные функции, возвращающие дескрипторы файлов для использования
другими системными функциями;
* Системные функции, использующие алгоритм namei для анализа имени пути
поиска;
* Системные функции, назначающие и освобождающие индекс с использованием
алгоритмов ialloc и ifree;
* Системные функции, устанавливающие или изменяющие атрибуты файла;
* Системные функции, позволяющие процессу производить ввод-вывод данных с
использованием алгоритмов alloc, free и алгоритмов выделения буфера;
* Системные функции, изменяющие структуру файловой системы;
* Системные функции, позволяющие процессу изменять собственное представле-
ние о структуре дерева файловой системы.


    5.1 OPEN



Вызов системной функции open (открыть файл) - это первый шаг, который
должен сделать процесс, чтобы обратиться к данным в файле. Синтаксис вызова
функции open:

fd = open(pathname,flags,modes);

где pathname - имя файла, flags указывает режим открытия (например, для чте-
ния или записи), а modes содержит права доступа к файлу в случае, если файл
создается. Системная функция open возвращает целое число (*), именуемое
пользовательским дескриптором файла. Другие операции над файлами, такие как
чтение, запись, по-
зиционирование головок чтения-записи, воспроизведение дескриптора файла, ус-
тановка параметров ввода-вывода, определение статуса файла и закрытие файла,
используют значение дескриптора файла, возвращаемое системной функцией open.
Ядро просматривает файловую систему в поисках файла по его имени, ис-
пользуя алгоритм namei (см. Рисунок 5.2). Оно проверяет права на открытие
файла после того, как обнаружит копию индекса файла в памяти, и выделяет от-
крываемому файлу запись в таблице файлов. Запись таблицы файлов содержит
указатель на индекс открытого файла и поле, в котором хранится смещение в
байтах от начала файла до места, откуда предполагается начинать выполнение
последующих операций чтения или записи. Ядро сбрасывает это смещение в 0 во
время открытия файла, имея в виду, что исходная операция чтения или записи
по умолчанию будет производиться с начала файла. С другой стороны, процесс
может открыть файл в режиме записи в конец, в этом случае ядро устанавливает
значение смещения, равное размеру файла. Ядро выделяет запись в личной (зак-
рытой) таблице в адресном пространстве задачи, выделенном процессу (таблица
эта называется таблицей пользовательских дескрипторов файлов), и запоминает
указатель на эту запись. Указателем выступает дескриптор файла, возвращаемый
пользователю. Запись в таблице пользовательских файлов указывает на запись в
глобальной таблице файлов.


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



86

+------------------------------------------------------------+
| алгоритм open |
| входная информация: имя файла |
| режим открытия |
| права доступа (при создании файла) |
| выходная информация: дескриптор файла |
| { |
| превратить имя файла в идентификатор индекса (алгоритм |
| namei); |
| если (файл не существует или к нему не разрешен доступ) |
| возвратить (код ошибки); |
| выделить для индекса запись в таблице файлов, инициали- |
| зировать счетчик, смещение; |
| выделить запись в таблице пользовательских дескрипторов |
| файла, установить указатель на запись в таблице файлов;|
| если (режим открытия подразумевает усечение файла) |
| освободить все блоки файла (алгоритм free); |
| снять блокировку (с индекса); /* индекс заблокирован |
| выше, в алгоритме |
| namei */ |
| возвратить (пользовательский дескриптор файла); |
| } |
+------------------------------------------------------------+

Рисунок 5.2. Алгоритм открытия файла


Предположим, что процесс, открывая файл "/etc/passwd" дважды, один раз
только для чтения и один раз только для записи, и однажды файл "local" для
чтения и для записи (**), выполняет следующий набор операторов:

fd1 = open("/etc/passwd",O_RDONLY);
fd2 = open("local",O_RDWR);
fd3 = open("/etc/passwd",O_WRONLY);

На Рисунке 5.3 показана взаимосвязь между таблицей индексов, таблицей
файлов и таблицей пользовательских дескрипторов файла. Каждый вызов функции
open возвращает процессу дескриптор файла, а соответствующая запись в табли-
це пользовательских дескрипторов файла указывает на уникальную запись в таб-
лице файлов ядра, пусть даже один и тот же файл ("/etc/passwd") открывается
дважды.
Записи в таблице файлов для всех экземпляров одного и того же открытого
файла указывают на одну запись в таблице индексов, хранящихся в памяти. Про-
цесс может обращаться к файлу "/etc/passwd" с чтением или записью, но только
через дескрипторы файла, имеющие значения 3 и 5 (см. рисунок).Ядро запомина-
ет разрешение на чтение или запись в файл в строке таблицы файлов,выделенной
во время выполнения функции open. Предположим, что второй процесс выполняет
следующий набор операторов:


---------------------------------------
(**) В описании вызова системной функции open содержатся три параметра (тре-
тий используется при открытии в режиме создания), но программисты обыч-
но используют только первые два из них. Компилятор с языка Си не прове-
ряет правильность количества параметров. В системе первые два параметра
и третий (с любым "мусором", что бы ни произошло в стеке) передаются
обычно ядру. Ядро не проверяет наличие третьего параметра, если только
необходимость в нем не вытекает из значения второго параметра, что поз-
воляет программистам указать только два параметра.

87

таблица пользова-
тельских дескрип-
торов файла таблица файлов таблица индексов
+---------+ +------------+ +--------------+
0| | | | | - |
+---------+ | | | - |
1| | | | | - |
+---------+ +------------+ | - |
2| | | - | | - |
+---------+ | - | | - |
3| ----+----+ | - | | - |
+---------+ | | - | +--------------+
4| ----+---+| | - | +---->| счет- |
+---------+ || | - | |+--->| чик (/etc/ |
5| ----+--+|| +------------+ || | 2 passwd)|
+---------+ ||| | счет- | || +--------------+
6| | ||+-->| чик Чтение+--+| | - |
+---------+ || | 1 | | | - |
7| | || +------------+ | | - |
+---------+ || | - | | | - |
| - | || | - | | | - |
+---------+ || +------------+ | | - |
|| | счет- Чте-| | | - |
|+--->| чик ние-+---|-+ | - |
| | 1 Запись| | | | - |
| +------------+ | | | - |
| | - | | | +--------------+
| | - | | | | счет- |
| | - | | +->| чик (local)|
| | - | | | 1 |
| | - | | +--------------+
| +------------+ | | - |
| | счет- | | | - |
+---->| чик Запись+---+ | - |
| 1 | | - |
+------------+ | - |
| - | | - |
| - | | - |
+------------+ +--------------+


Рисунок 5.3. Структуры данных после открытия


fd1 = open("/etc/passwd",O_RDONLY);
fd2 = open("private",O_RDONLY);

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


88

таблицы пользова-
тельских дескрип-
торов файла
(процесс A) таблица файлов таблица индексов
+---------+ +------------+ +--------------+
0| | | | | - |
+---------+ | | | - |
1| | | | | - |
+---------+ +------------+ | - |
2| | | - | | - |
+---------+ | - | | - |
3| ----+----+ | - | | - |
+---------+ | | - | +--------------+
4| ----+---+| | - | +---->| счет- |
+---------+ || | - | |+--->| чик (/etc/ |
5| ----+--+|| +------------+ ||+-->| 3 passwd)|
+---------+ ||| | счет- | ||| +--------------+
| - | ||+-->| чик Чтение+--+|| | - |
| - | || | 1 | || | - |
| - | || +------------+ || | - |
+---------+ || | - | || | - |
|| | - | || | - |
(процесс B) || | - | || | - |
+---------+ || | - | || | - |
0| | || +------------+ || | - |
+---------+ || | счет- Чте-| || | - |
1| | |+--->| чик ние-+---||+ | - |
+---------+ | | 1 Запись| ||| | - |
2| | | +------------+ ||| | - |
+---------+ | | - | ||| +--------------+
3| ----+--|--+ | - | ||| | счет- |
+---------+ | | | - | ||+->| чик (local)|
4| ----+-+| | | - | || | 1 |
+---------+ || | | - | || +--------------+
5| | || | | - | || | - |
+---------+ || | +------------+ || | - |
| - | || | | счет- | || | - |
| - | || +->| чик Чтение+---+| | - |
| - | || | 1 | | | - |
+---------+ || +------------+ | | - |
|| | - | | | - |
|| | - | | +--------------+
|| | - | | | счет- |
|| +------------+ |+->| чик (private)|
|| | счет- | || | 1 |
|+---->| чик Запись+----+| +--------------+
| | 1 | | | - |
| +------------+ | | - |
| | - | | +--------------+
| | - | |
| +------------+ |
| | счет- | |
+----->| чик Чтение+-----+
| 1 |
+------------+

Рисунок 5.4. Структуры данных после того, как два процесса
произвели открытие файлов


89



писями в таблице файлов ядра типа "один к одному". Томпсон, однако, отмеча-
ет, что им была реализована таблица файлов как отдельная структура, позволя-
ющая совместно использовать один и тот же указатель смещения нескольким
пользовательским дескрипторам файла (см. [Thompson 78], стр.1943). В систем-
ных функциях dup и fork, рассматриваемых в разделах 5.13 и 7.1, при работе
со структурами данных допускается такое совместное использование.

Первые три пользовательских дескриптора (0, 1 и 2) именуются дескрипто-
рами файлов: стандартного ввода, стандартного вывода и стандартного файла
ошибок. Процессы в системе UNIX по договоренности используют дескриптор фай-
ла стандартного ввода при чтении вводимой информации, дескриптор файла стан-
дартного вывода при записи выводимой информации и дескриптор стандартного
файла ошибок для записи сообщений об ошибках. В операционной системе нет ни-
какого указания на то, что эти дескрипторы файлов являются специальными.
Группа пользователей может условиться о том, что файловые дескрипторы, имею-
щие значения 4, 6 и 11, являются специальными, но более естественно начинать
отсчет с 0 (как в языке Си). Принятие соглашения сразу всеми пользователь-
скими программами облегчит связь между ними при использовании каналов, в чем
мы убедимся в дальнейшем, изучая главу 7. Обычно операторский терминал (см.
главу 10) служит и в качестве стандартного ввода, и в качестве стандартного
вывода и в качестве стандартного устройства вывода сообщений об ошибках.


    5.2 READ



Синтаксис вызова системной функции read (читать):
number = read(fd,buffer,count)

где fd - дескриптор файла, возвращаемый функцией open, buffer - адрес струк-
туры данных в пользовательском процессе, где будут размещаться считанные
данные в случае успешного завершения выполнения функции read, count - коли-
чество байт, которые пользователю нужно прочитать, number - количество фак-
тически прочитанных байт. На Рисунке 5.5 приведен алгоритм read, выполняющий
чтение обычного файла. Ядро обращается в таблице файлов к записи, которая
соответствует значению пользовательского дескриптора файла, следуя
за указателем (см. Рисунок 5.3). Затем оно устанавливает значения нескольких
параметров ввода-вывода в адресном пространстве процесса (Рисунок 5.6), тем
самым устраняя необходимость в их передаче в качестве параметров функции. В
частности, ядро указывает в качестве режима ввода-вывода "чтение", устанав-
ливает флаг, свидетельствующий о том, что ввод-вывод направляется в адресное
пространство пользователя, значение поля счетчика байтов приравнивает коли-
честву байт, которые будут прочитаны, устанавливает адрес пользовательского
буфера данных и, наконец, значение смещения (из таблицы файлов), равное сме-
щению в байтах внутри файла до места, откуда начинается ввод-вывод. После
того, как ядро установит значения параметров ввода-вывода в адресном прост-
ранстве процесса, оно обращается к индексу, используя указатель из таблицы
файлов, и блокирует его прежде, чем начать чтение из файла.

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







90
+------------------------------------------------------------+
| алгоритм read |
| входная информация: пользовательский дескриптор файла |
| адрес буфера в пользовательском про- |
| цессе |
| количество байт, которые нужно прочи- |
| тать |
| выходная информация: количество байт, скопированных в поль-|
| зовательское пространство |
| { |
| обратиться к записи в таблице файлов по значению пользо-|
| вательского дескриптора файла; |
| проверить доступность файла; |
| установить параметры в адресном пространстве процесса, |
| указав адрес пользователя, счетчик байтов, параметры |
| ввода-вывода для пользователя; |
| получить индекс по записи в таблице файлов; |
| заблокировать индекс; |
| установить значение смещения в байтах для адресного |
| пространства процесса по значению смещения в таблице |
| файлов; |
| выполнить (пока значение счетчика байтов не станет удов-|
| летворительным) |
| { |
| превратить смещение в файле в номер дискового блока |
| (алгоритм bmap); |
| вычислить смещение внутри блока и количество байт, |
| которые будут прочитаны; |
| если (количество байт для чтения равно 0) |
| /* попытка чтения конца файла */ |
| прерваться; /* выход из цикла */ |
| прочитать блок (алгоритм breada, если производится |
| чтение с продвижением, и алгоритм bread - в против- |
| ном случае); |
| скопировать данные из системного буфера по адресу |
| пользователя; |
| скорректировать значения полей в адресном простран- |
| стве процесса, указывающие смещение в байтах внутри |
| файла, количество прочитанных байт и адрес для пе- |
| редачи в пространство пользователя; |
| освободить буфер; /* заблокированный в алгоритме |
| bread */ |
| } |
| разблокировать индекс; |
| скорректировать значение смещения в таблице файлов для |
| следующей операции чтения; |
| возвратить (общее число прочитанных байт); |
| } |
+------------------------------------------------------------+
Рисунок 5.5. Алгоритм чтения из файла

+------------------------------------------------------+
| mode чтение или запись |
| count количество байт для чтения или записи |
| offset смещение в байтах внутри файла |
| address адрес места, куда будут копироваться данные,|
| в памяти пользователя или ядра |
| flag отношение адреса к памяти пользователя или |
| к памяти ядра |
+------------------------------------------------------+
Рисунок 5.6. Параметры ввода-вывода, хранящиеся в пространстве процесса
91

горитм bmap, и вычисляет смещение внутри блока до места, откуда следует на-
чать ввод-вывод, а также количество байт, которые будут прочитаны из блока.
После считывания блока в буфер, возможно, с продвижением (алгоритмы bread и
breada) ядро копирует данные из блока по назначенному адресу в пользователь-
ском процессе. Оно корректирует параметры ввода-вывода в адресном пространс-
тве процесса в соответствии с количеством прочитанных байт, увеличивая зна-
чение смещения в байтах внутри файла и адрес места в пользовательском про-
цессе, куда будет доставлена следующая порция данных, и уменьшая число байт,
которые необходимо прочитать, чтобы выполнить запрос пользователя. Если зап-
рос пользователя не удовлетворен, ядро повторяет весь цикл, преобразуя сме-
щение в байтах внутри файла в номер блока, считывая блок с диска в системный
буфер, копируя данные из буфера в пользовательский процесс, освобождая буфер
и корректируя значения параметров ввода-вывода в адресном пространстве про-
цесса. Цикл завершается, либо когда ядро выполнит запрос пользователя пол-
ностью, либо когда в файле больше не будет данных, либо если ядро обнаружит
ошибку при чтении данных с диска или при копировании данных в пространство
пользователя. Ядро корректирует значение смещения в таблице файлов в соот-
ветствии с количеством фактически прочитанных байт; поэтому успешное выпол-
нение операций чтения выглядит как последовательное считывание данных из
файла. Системная операция lseek (раздел 5.6) устанавливает значение смещения
в таблице файлов и изменяет порядок, в котором процесс читает или записывает
данные в файле.

+------------------------------------------------------+
| #include |
| main() |
| { |
| int fd; |
| char lilbuf[20],bigbuf[1024]; |
| |
| fd = open("/etc/passwd",O_RDONLY); |
| read(fd,lilbuf,20); |
| read(fd,bigbuf,1024); |
| read(fd,lilbuf,20); |
| } |
+------------------------------------------------------+

Рисунок 5.7. Пример программы чтения из файла


Рассмотрим программу, приведенную на Рисунке 5.7. Функция open возвраща-
ет дескриптор файла, который пользователь засылает в переменную fd и исполь-
зует в последующих вызовах функции read. Выполняя функцию read, ядро прове-
ряет, правильно ли задан параметр "дескриптор файла", а также был ли файл
предварительно открыт процессом для чтения. Оно сохраняет значение адреса
пользовательского буфера, количество считываемых байт и начальное смещение в
байтах внутри файла (соответственно: lilbuf, 20 и 0), в пространстве процес-
са. В результате вычислений оказывается, что нулевое значение смещения соот-
ветствует нулевому блоку файла, и ядро возвращает точку входа в индекс, со-
ответствующую нулевому блоку. Предполагая, что такой блок существует, ядро
считывает полный блок размером 1024 байта в буфер, но по адресу lilbuf копи-
рует только 20 байт. Оно увеличивает смещение внутри пространства процесса
на 20 байт и сбрасывает счетчик данных в 0. Поскольку операция read выполни-
лась, ядро переустанавливает значение смещения в таблице файлов на 20, так
что последующие операции чтения из файла с данным дескриптором начнутся с
места, расположенного со смещением 20 байт от начала файла, а системная фун-
кция возвращает число байт, фактически прочитанных, т.е. 20.
При повторном вызове функции read ядро вновь проверяет корректность ука-
зания дескриптора и наличие соответствующего файла, открытого процессом для

92

чтения, поскольку оно никак не может узнать, что запрос пользователя на чте-
ние касается того же самого файла, существование которого было установлено
во время последнего вызова функции. Ядро сохраняет в пространстве процесса
пользовательский адрес bigbuf, количество байт, которые нужно прочитать про-
цессу (1024), и начальное смещение в файле (20), взятое из таблицы файлов.
Ядро преобразует смещение внутри файла в номер дискового блока, как раньше,
и считывает блок. Если между вызовами функции read прошло непродолжительное
время, есть шансы, что блок находится в буферном кеше. Однако, ядро не может
полностью удовлетворить запрос пользователя на чтение за счет содержимого
буфера, поскольку только 1004 байта из 1024 для данного запроса находятся в
буфере. Поэтому оно копирует оставшиеся 1004 байта из буфера в пользователь-
скую структуру данных bigbuf и корректирует параметры в пространстве процес-
са таким образом, чтобы следующий шаг цикла чтения начинался в файле с байта
1024, при этом данные следует копировать по адресу байта 1004 в bigbuf в об-
ъеме 20 байт, чтобы удовлетворить запрос на чтение.
Теперь ядро переходит к началу цикла, содержащегося в алгоритме read.
Оно преобразует смещение в байтах (1024) в номер логического блока (1), об-
ращается ко второму блоку прямой адресации, номер которого хранится в индек-
се, и отыскивает точный дисковый блок, из которого будет производиться чте-
ние. Ядро считывает блок из буферного кеша или с диска, если в кеше данный
блок отсутствует. Наконец, оно копирует 20 байт из буфера по уточненному ад-
ресу в пользовательский процесс. Прежде чем выйти из системной функции, ядро
устанавливает значение поля смещения в таблице файлов равным 1044, то есть
равным значению смещения в байтах до места, куда будет производиться следую-
щее обращение. В последнем вызове функции read из примера ядро ведет себя,
как и в первом обращении к функции, за исключением того, что чтение из файла
в данном случае начинается с байта 1044, так как именно это значение будет
обнаружено в поле смещения той записи таблицы файлов, которая соответствует
указанному дескриптору.
Пример показывает, насколько выгодно для запросов ввода-вывода работать
с данными, начинающимися на границах блоков файловой системы и имеющими раз-
мер, кратный размеру блока. Это позволяет ядру избегать дополнительных ите-
раций при выполнении цикла в алгоритме read и всех вытекающих последствий,