ГЛАВА 5. СИСТЕМНЫЕ ОПЕРАЦИИ ДЛЯ РАБОТЫ С ФАЙЛОВОЙ СИСТЕМОЙ В последней главе рассматривались внутренние структуры данных для файло- вой системы и алгоритмы работы с ними. В этой главе речь пойдет о системных функциях для работы с файловой системой с использованием понятий, введенных в предыдущей главе. Рассматриваются системные функции, обеспечивающие обра- щение к существующим файлам, такие как 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 и всех вытекающих последствий, связанных с дополнительными обращениями к индексу в поисках номера блока, который содержит данные, и с конкуренцией за использование буферного пула. Библиотека стандартных модулей ввода-вывода создана таким образом, чтобы скрыть от пользователей размеры буферов ядра; ее использование позволяет из- бежать потерь производительности, присущих процессам, работающим с небольши- ми порциями данных, из-за чего их функционирование на уровне файловой систе- мы неэффективно (см. упражнение 5.4). Выполняя цикл чтения, ядро определяет, является ли файл объектом чтения с продвижением: если процесс считывает последовательно два блока, ядро пред- полагает, что все очередные операции будут производить последовательное чте- ние, до тех пор, пока не будет утверждено обратное. На каждом шаге цикла яд- ро запоминает номер следующего логического блока в копии индекса, хранящейся в памяти, и на следующем шаге сравнивает номер текущего логического блока со значением, запомненным ранее. Если эти номера равны, ядро вычисляет номер физического блока для чтения с продвижением и сохраняет это значение в прос- транстве процесса для использования в алгоритме breada. Конечно же, пока процесс не считал конец блока, ядро не запустит алгоритм чтения с продвиже- нием для следующего блока. Обратившись к Рисунку 4.9, вспомним, что номера некоторых блоков в ин- дексе или в блоках косвенной адресации могут иметь нулевое значение, пусть даже номера последующих блоков и ненулевые. Если процесс попытается прочи- тать данные из такого блока, ядро выполнит запрос, выделяя произвольный бу- фер в цикле read, очищая его содержимое и копируя данные из него по адресу пользователя. Этот случай не имеет ничего общего с тем случаем, когда про- цесс обнаруживает конец файла, говорящий о том, что после этого места запись информации никогда не производилась. Обнаружив конец файла, ядро не возвра- 93 щает процессу никакой информации (см. упражнение 5.1). Когда процесс вызывает системную функцию read, ядро блокирует индекс на время выполнения вызова. Впоследствии, этот процесс может приостановиться во время чтения из буфера, ассоциированного с данными или с блоками косвенной адресации в индексе. Если еще одному процессу дать возможность вносить изме- нения в файл в то время, когда первый процесс приостановлен, функция read может возвратить несогласованные данные. Например, процесс может считать из файла несколько блоков; если он приостановился во время чтения первого бло- ка, а второй процесс собирался вести запись в другие блоки, возвращаемые данные будут содержать старые данные вперемешку с новыми. Таким образом, ин- декс остается заблокированным на все время выполнения вызова функции read для того, чтобы процессы могли иметь целостное видение файла, то есть виде- ние того образа, который был у файла перед вызовом функции. Ядро может выгружать процесс, ведущий чтение, в режим задачи на время между двумя вызовами функций и планировать запуск других процессов. Так как по окончании выполнения системной функции с индекса снимается блокировка, ничто не мешает другим процессам обращаться к файлу и изменять его содержи- мое. Со стороны системы было бы несправедливо держать индекс заблокированным все время от момента, когда процесс открыл файл, и до того момента, когда файл будет закрыт этим процессом, поскольку тогда один процесс будет держать все время файл открытым, тем самым не давая другим процессам возможности об- ратиться к файлу. Если файл имеет имя "/etc/ passwd", то есть является фай- лом, используемым в процессе регистрации для проверки пользовательского па- роля, один пользователь может умышленно (или, возможно, неумышленно) воспре- пятствовать регистрации в системе всех остальных пользователей. Чтобы пре- дотвратить возникновение подобных проблем, ядро снимает с индекса блокировку по окончании выполнения каждого вызова системной функции, использующей ин- декс. Если второй процесс внесет изменения в файл между двумя вызовами функ- ции read, производимыми первым процессом, первый процесс может прочитать непредвиденные данные, однако структуры данных ядра сохранят свою согласо- ванность. Предположим, к примеру, что ядро выполняет два процесса, конкурирующие +------------------------------------------------------------+ | #include | | /* процесс A */ | | main() | | { | | int fd; | | char buf[512]; | | fd = open("/etc/passwd",O_RDONLY); | | read(fd,buf,sizeof(buf)); /* чтение1 */ | | read(fd,buf,sizeof(buf)); /* чтение2 */ | | } | | | | /* процесс B */ | | main() | | { | | int fd,i; | | char buf[512]; | | for (i = 0; i < sizeof(buf); i++) | | buf[i] = 'a'; | | fd = open("/etc/passwd",O_WRONLY); | | write(fd,buf,sizeof(buf)); /* запись1 */ | | write(fd,buf,sizeof(buf)); /* запись2 */ | | } | +------------------------------------------------------------+ Рисунок 5.8. Процессы, ведущие чтение и запись 94 между собой (Рисунок 5.8). Если допустить, что оба процесса выполняют опера- цию open до того, как любой из них вызывает системную функцию read или write, ядро может выполнять функции чтения и записи в любой из шести после- довательностей: чтение1, чтение2, запись1, запись2, или чтение1, запись1, чтение2, запись2, или чтение1, запись1, запись2, чтение2 и т.д. Состав ин- формации, считываемой процессом A, зависит от последовательности, в которой система выполняет функции, вызываемые двумя процессами; система не гаранти- рует, что данные в файле останутся такими же, какими они были после открытия файла. Использование возможности захвата файла и записей (раздел 5.4) позво- ляет процессу гарантировать сохранение целостности файла после его открытия. Наконец, программа на Рисунке 5.9 показывает, как процесс может откры- вать файл более одного раза и читать из него, используя разные файловые дес- крипторы. Ядро работает со значениями смещений в таблице файлов, ассоцииро- ванными с двумя файловыми дескрипторами, независимо, и поэтому массивы buf1 и buf2 будут по завершении выполнения процесса идентичны друг другу при ус- ловии, что ни один процесс в это время не производил запись в файл "/etc/passwd". 5.3 WRIТЕ Синтаксис вызова системной функции write (писать): number = write(fd,buffer,count); где переменные fd, buffer, count и number имеют тот же смысл, что и для вы- зова системной функции read. Алгоритм записи в обычный файл похож на алго- ритм чтения из обычного файла. Однако, если в файле отсутствует блок, соот- ветствующий смещению в байтах до места, куда должна производиться запись, ядро выделяет блок, используя алгоритм alloc, и присваивает ему номер в со- ответствии с точным указанием места в таблице содержимого индекса. Если сме- щение в байтах совпадает со смещением для блока косвенной адресации, ядру, возможно, придется выделить несколько блоков для использования их в качестве блоков косвенной адресации и информаци- +------------------------------------------------------------+ | #include | | main() | | { | | int fd1,fd2; | | char buf1[512],buf2[512]; | | | | fd1 = open("/etc/passwd",O_RDONLY); | | fd2 = open("/etc/passwd",O_RDONLY); | | read(fd1,buf1,sizeof(buf1)); | | read(fd2,buf2,sizeof(buf2)); | | } | +------------------------------------------------------------+ Рисунок 5.9. Чтение из файла с использованием двух дескрипторов онных блоков. Индекс блокируется на все время выполнения функции write, так как ядро может изменить индекс, выделяя новые блоки; разрешение другим про- цессам обращаться к файлу может разрушить индекс, если несколько процессов выделяют блоки одновременно, используя одни и те же значения смещений. Когда запись завершается, ядро корректирует размер файла в индексе, если файл уве- 95 личился в размере. Предположим, к примеру, что процесс записывает в файл байт с номером 10240, наибольшим номером среди уже записанных в файле. Обратившись к байту в файле по алгоритму bmap, ядро обнаружит, что в файле отсутствует не только соответствующий этому байту блок, но также и нужный блок косвенной адреса- ции. Ядро назначает дисковый блок в качестве блока косвенной адресации и за- писывает номер блока в копии индекса, хранящейся в памяти. Затем оно выделя- ет дисковый блок под данные и записывает его номер в первую позицию вновь созданного