| iput); |
| если (новая вершина является блочным или символьным спе-|
| циальным файлом) |
| записать старший и младший номера в структуру индек-|
| са; |
| освободить индекс новой вершины (алгоритм iput); |
| } |
+------------------------------------------------------------+

Рисунок 5.13. Алгоритм создания новой вершины

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

101

отсутствовать записи с именами "." и ".."). В упражнении 5.33 рассматривают-
ся шаги, необходимые для преобразования содержимого каталога в правильный
формат.

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

Рисунок 5.14. Алгоритм смены текущего каталога



    5.9 СМЕНА ТЕКУЩЕГО И КОРНЕВОГО КАТАЛОГА



Когда система загружается впервые, нулевой процесс делает корневой ката-
лог файловой системы текущим на время инициализации. Для индекса корневого
каталога нулевой процесс выполняет алгоритм iget, сохраняет этот индекс в
пространстве процесса в качестве индекса текущего каталога и снимает с ин-
декса блокировку. Когда с помощью функции fork создается новый процесс, он
наследует текущий каталог старого процесса в своем адресном пространстве, а
ядро, соответственно, увеличивает значение счетчика ссылок в индексе.
Алгоритм chdir (Рисунок 5.14) изменяет имя текущего каталога для процес-
са. Синтаксис вызова системной функции chdir:

chdir(pathname);

где pathname - каталог, который становится текущим для процесса. Ядро анали-
зирует имя каталога, используя алгоритм namei, и проверяет, является ли дан-
ный файл каталогом и имеет ли владелец процесса право доступа к каталога.
Ядро снимает с нового индекса блокировку, но удерживает индекс в качестве
выделенного и оставляет счетчик ссылок без изменений, освобождает индекс
прежнего текущего каталога (алгоритм iput), хранящийся в пространстве про-
цесса, и запоминает в этом пространстве новый индекс. После смены процессом
текущего каталога алгоритм namei использует индекс в качестве начального ка-
талога при анализе всех имен путей, которые не берут начало от корня. По
окончании выполнения системной функции chdir счетчик ссылок на индекс нового
каталога имеет значение, как минимум, 1, а счетчик ссылок на индекс прежнего
текущего каталога может стать равным 0. В этом отношении функция chdir похо-
жа на функцию open, поскольку обе функции обращаются к файлу и оставляют его
индекс в качестве выделенного. Индекс, выделенный во время выполнения функ-
ции chdir, освобождается только тогда, когда процесс меняет текущий каталог
еще раз или когда процесс завершается.

102

Процессы обычно используют глобальный корневой каталог файловой системы
для всех имен путей поиска, начинающихся с "/". Ядро хранит глобальную пере-
менную, которая указывает на индекс глобального корня, выделяемый по алго-
ритму iget при загрузке системы. Процессы могут менять свое представление о
корневом каталоге файловой системы с помощью системной функции chroot. Это
бывает полезно, если пользователю нужно создать модель обычной иерархической
структуры файловой системы и запустить процессы там. Синтаксис вызова функ-
ции:

chroot(pathname);

где pathname - каталог, который впоследствии будет рассматриваться ядром в
качестве корневого каталога для процесса. Выполняя функцию chroot, ядро сле-
дует тому же алгоритму, что и при смене текущего каталога. Оно запоминает
индекс нового корня в пространстве процесса, снимая с индекса блокировку по
завершении выполнения функции. Тем не менее, так как умолчание на корень для
ядра хранится в глобальной переменной, ядро освобождает индекс прежнего кор-
ня не автоматически, а только после того, как оно само или процесс-предок
исполнят вызов функции chroot. Новый индекс становится логическим корнем
файловой системы для процесса (и для всех порожденных им процессов) и это
означает, что все пути поиска в алгоритме namei, начинающиеся с корня ("/"),
возьмут начало с данного индекса и что все попытки войти в каталог ".." над
корнем приведут к тому, что рабочим каталогом процесса останется новый ко-
рень. Процесс передает всем вновь порождаемым процессам этот каталог в ка-
честве корневого подобно тому, как передает свой текущий каталог.


    5.10 СМЕНА ВЛАДЕЛЬЦА И РЕЖИМА ДОСТУПА К ФАЙЛУ



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

chown(pathname,owner,group)
chmod(pathname,mode)

Для того, чтобы поменять владельца файла, ядро преобразует имя файла в
идентификатор индекса, используя алгоритм namei. Владелец процесса должен
быть суперпользователем или владельцем файла (процесс не может распоряжаться
тем, что не принадлежит ему). Затем ядро назначает файлу нового владельца и
нового группового пользователя, сбрасывает флаги прежних установок (см. раз-
дел 7.5) и освобождает индекс по алгоритму iput. После этого прежний владе-
лец теряет право "собственности" на файл. Для того, чтобы поменять режим
доступа к файлу, ядро выполняет процедуру, подобную описанной, вместо кода
владельца меняя флаги, устанавливающие режим доступа.


    5.11 STAT И FSTАТ



Системные функции stat и fstat позволяют процессам запрашивать информа-
цию о статусе файла: типе файла, владельце файла, правах доступа, размере
файла, числе связей, номере индекса и времени доступа к файлу. Синтаксис вы-
зова функций:

stat(pathname,statbuffer);
fstat(fd,statbuffer);

где pathname - имя файла, fd - дескриптор файла, возвращаемый функцией open,
statbuffer - адрес структуры данных пользовательского процесса, где будет

103

храниться информация о статусе файла после завершения выполнения вызова.
Системные функции просто переписывают поля из индекса в структуру
statbuffer. Программа на Рисунке 5.33 иллюстрирует использование функций
stat и fstat.

Вызывает канал Не могут совместно использовать
- канал
- - -
- - -
- - -
- Процесс A -
- | -
- +-------------+--------------+ -
- | | -
Процесс B Процесс C
|
+-------+-------+
| |
Процесс D - Процесс E
- - -
- - -
- - -
-
Совместно используют
канал

Рисунок 5.15. Дерево процессов и совместное использование каналов



    5.12 КАНАЛЫ



Каналы позволяют передавать данные между процессами в порядке поступле-
ния ("первым пришел - первым вышел"), а также синхронизировать выполнение
процессов. Их использование дает процессам возможность взаимодействовать
между собой, пусть даже не известно, какие процессы находятся на другом кон-
це канала. Традиционная реализация каналов использует файловую систему для
хранения данных. Различают два вида каналов: поименованные каналы и, за от-
сутствием лучшего термина, непоименованные каналы, которые идентичны между
собой во всем, кроме способа первоначального обращения к ним процессов. Для
поименованных каналов процессы используют системную функцию open, а систем-
ную функцию pipe - для создания непоименованного канала. Впоследствии, при
работе с каналами процессы пользуются обычными системными функциями для фай-
лов, такими как read, write и close. Только связанные между собой процессы,
являющиеся потомками того процесса, который вызвал функцию pipe, могут раз-
делять доступ к непоименованным каналам. Например (см. Рисунок 5.15), если
процесс B создает канал и порождает процессы D и E, эти три процесса разде-
ляют между собой доступ к каналу, в отличие от процессов A и C. Однако, все
процессы могут обращаться к поименованному каналу независимо от взаимоотно-
шений между ними, при условии наличия обычных прав доступа к файлу. Посколь-
ку непоименованные каналы встречаются чаще, они будут рассмотрены первыми.


    5.12.1 Системная функция pipе



Синтаксис вызова функции создания канала:

pipe(fdptr);


104

где fdptr - указатель на массив из двух целых переменных, в котором будут
храниться два дескриптора файла для чтения из канала и для записи в канал.
Поскольку ядро реализует каналы внутри файловой системы и поскольку канал не
существует до того, как его будут использовать, ядро должно при создании ка-
нала назначить ему индекс. Оно также назначает для канала пару пользователь-
ских дескрипторов и соответствующие им записи в таблице файлов: один из дес-
крипторов для чтения из канала, а другой для записи в канал. Поскольку ядро
пользуется таблицей файлов, интерфейс для вызова функций read, write и др.
согласуется с интерфейсом для обычных файлов. В результате процессам нет на-
добности знать, ведут ли они чтение или запись в обычный файл или в канал.

+------------------------------------------------------------+
| алгоритм pipe |
| входная информация: отсутствует |
| выходная информация: дескриптор файла для чтения |
| дескриптор файла для записи |
| { |
| назначить новый индекс из устройства канала (алгоритм |
| ialloc); |
| выделить одну запись в таблице файлов для чтения, одну -|
| для переписи; |
| инициализировать записи в таблице файлов таким образом, |
| чтобы они указывали на новый индекс; |
| выделить один пользовательский дескриптор файла для чте-|
| ния, один - для записи, проинициализировать их таким |
| образом, чтобы они указывали на соответствующие точки |
| входа в таблице файлов; |
| установить значение счетчика ссылок в индексе равным 2; |
| установить значение счетчика числа процессов, производя-|
| щих чтение, и процессов, производящих запись, равным 1;|
| } |
+------------------------------------------------------------+

Рисунок 5.16. Алгоритм создания каналов (непоименованных)


На Рисунке 5.16 показан алгоритм создания непоименованных каналов. Ядро
назначает индекс для канала из файловой системы, обозначенной как "устройст-
во канала", используя алгоритм ialloc. Устройство канала - это именно та
файловая система, из которой ядро может назначать каналам индексы и выделять
блоки для данных. Администраторы системы указывают устройство канала при
конфигурировании системы и эти устройства могут совпадать у разных файловых
систем. Пока канал активен, ядро не может переназначить индекс канала и ин-
формационные блоки канала другому файлу.
Затем ядро выделяет в таблице файлов две записи, соответствующие деск-
рипторам для чтения и записи в канал, и корректирует "бухгалтерскую" инфор-
мацию в копии индекса в памяти. В каждой из выделенных записей в таблице
файлов хранится информация о том, сколько экземпляров канала открыто для
чтения или записи (первоначально 1), а счетчик ссылок в индексе указывает,
сколько раз канал был "открыт" (первоначально 2 - по одному для каждой запи-
си таблицы файлов). Наконец, в индексе записываются смещения в байтах внутри
канала до места, где будет начинаться следующая операция записи или чтения.
Благодаря сохранению этих смещений в индексе имеется возможность производить
доступ к данным в канале в порядке их поступления в канал ("первым пришел -
первым вышел"); этот момент является особенностью каналов, поскольку для
обычных файлов смещения хранятся в таблице файлов. Процессы не могут менять
эти смещения с помощью системной функции lseek и поэтому произвольный доступ
к данным канала невозможен.


105

    5.12.2 Открытие поименованного канала



Поименованный канал - это файл, имеющий почти такую же семантику, как и
непоименованный канал, за исключением того, что этому файлу соответствует
запись в каталоге и обращение к нему производится по имени. Процессы откры-
вают поименованные каналы так же, как и обычные файлы, и, следовательно, с
помощью поименованных каналов могут взаимодействовать между собой даже про-
цессы, не имеющие друг к другу близкого отношения. Поименованные каналы пос-
тоянно присутствуют в иерархии файловой системы (из которой они удаляются с
помощью системной функции unlink), а непоименованные каналы являются времен-
ными: когда все процессы заканчивают работу с каналом, ядро отбирает назад
его индекс.
Алгоритм открытия поименованного канала идентичен алгоритму открытия
обычного файла. Однако, перед выходом из функции ядро увеличивает значения
тех счетчиков в индексе, которые показывают количество процессов, открывших
поименованный канал для чтения или записи. Процесс, открывающий поименован-
ный канал для чтения, приостановит свое выполнение до тех пор, пока другой
процесс не откроет поименованный канал для записи, и наоборот. Не имеет
смысла открывать канал для чтения, если процесс не надеется получить данные;
то же самое касается записи. В зависимости от того, открывает ли процесс по-
именованный канал для записи или для чтения, ядро возобновляет выполнение
тех процессов, которые были приостановлены в ожидании процесса, записывающе-
го в поименованный канал или считывающего данные из канала (соответственно).
Если процесс открывает поименованный канал для чтения, причем процесс,
записывающий в канал, существует, открытие завершается. Или если процесс от-
крывает поименованный файл с параметром "no delay", функция open возвращает
управление немедленно, даже когда нет ни одного записывающего процесса. Во
всех остальных случаях процесс приостанавливается до тех пор, пока записыва-
ющий процесс не откроет канал. Аналогичные правила действуют для процесса,
открывающего канал для записи.


    5.12.3 Чтение из каналов и запись в каналы



Канал следует рассматривать под таким углом зрения, что процессы ведут
запись на одном конце канала, а считывают данные на другом конце. Как уже
говорилось выше, процессы обращаются к данным в канале в порядке их поступ-
ления в канал; это означает, что очередность, в которой данные записываются
в канал, совпадает с очередностью их выборки из канала. Совпадение количест-
ва процессов, считывающих данные из канала, с количеством процессов, ведущих
запись в канал, совсем не обязательно; если одно число отличается от другого
более, чем на 1, процессы должны координировать свои действия по использова-
нию канала с помощью других механизмов. Ядро обращается к данным в канале
точно так же, как и к данным в обычном файле: оно сохраняет данные на уст-
ройстве канала и назначает каналу столько блоков, сколько нужно, во время
выполнения функции write. Различие в выделении памяти для канала и для

+--------------------+--------------------+
| Указатель чтения | Указатель записи |
+----------+---------+----------+---------+
| +----------------+
+-- | --------------+
v v
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+
Блоки прямой адресации в индексе

Рисунок 5.17. Логическая схема чтения и записи в канал

106

обычного файла состоит в том, что канал использует в индексе только блоки
прямой адресации в целях повышения эффективности работы, хотя это и наклады-
вает определенные ограничения на объем данных, одновременно помещающихся в
канале. Ядро работает с блоками прямой адресации индекса как с циклической
очередью, поддерживая в своей структуре указатели чтения и записи для обес-
печения очередности обслуживания "первым пришел - первым вышел" (Рисунок
5.17).
Рассмотрим четыре примера ввода-вывода в канал: запись в канал, в кото-
ром есть место для записи данных; чтение из канала, в котором достаточно
данных для удовлетворения запроса на чтение; чтение из канала, в котором
данных недостаточно; и запись в канал, где нет места для записи.
Рассмотрим первый случай, в котором процесс ведет запись в канал, имею-
щий место для ввода данных: сумма количества записываемых байт с числом
байт, уже находящихся в канале, меньше или равна емкости канала. Ядро следу-
ет алгоритму записи данных в обычный файл, за исключением того, что оно уве-
личивает размер канала автоматически после каждого выполнения функции write,
поскольку по определению объем данных в канале растет с каждой операцией за-
писи. Иначе происходит увеличение размера обычного файла: процесс увеличива-
ет размер файла только тогда, когда он при записи данных переступает границу
конца файла. Если следующее смещение в канале требует использования блока
косвенной адресации, ядро устанавливает значение смещения в пространстве
процесса таким образом, чтобы оно указывало на начало канала (смещение в
байтах, равное 0). Ядро никогда не затирает данные в канале; оно может сбро-
сить значение смещения в 0, поскольку оно уже установило, что данные не бу-
дут переполнять емкость канала. Когда процесс запишет в канал все свои дан-
ные, ядро откорректирует значение указателя записи (в индексе) канала таким
образом, что следующий процесс продолжит запись в канал с того места, где
остановилась предыдущая операция write. Затем ядро возобновит выполнение
всех других процессов, приостановленных в ожидании считывания данных из ка-
нала.
Когда процесс запускает функцию чтения из канала, он проверяет, пустой
ли канал или нет. Если в канале есть данные, ядро считывает их из канала
так, как если бы канал был обычным файлом, выполняя соответствующий алго-
ритм. Однако, начальным смещением будет значение указателя чтения, храняще-
гося в индексе и показывающего протяженность прочитанных ранее данных. После
считывания каждого блока ядро уменьшает размер канала в соответствии с коли-
чеством считанных данных и устанавливает значение смещения в пространстве
процесса так, чтобы при достижении конца канала оно указывало на его начало.
Когда выполнение системной функции read завершается, ядро возобновляет вы-
полнение всех приостановленных процессов записи и запоминает текущее значе-
ние указателя чтения в индексе (а не в записи таблицы файлов).
Если процесс пытается считать больше информации, чем фактически есть в
канале, функция read завершится успешно, возвратив все данные, находящиеся в
данный момент в канале, пусть даже не полностью выполнив запрос пользовате-
ля. Если канал пуст, процесс обычно приостанавливается до тех пор, пока ка-
кой-нибудь другой процесс не запишет данные в канал, после чего все приоста-
новленные процессы, ожидающие ввода данных, возобновят свое выполнение и
начнут конкурировать за чтение из канала. Если, однако, процесс открывает
поименованный канал с параметром "no delay" (без задержки), функция read
возвратит управление немедленно, если в канале отсутствуют данные. Операции
чтения и записи в канал имеют ту же семантику, что и аналогичные операции
для терминальных устройств (глава 10), она позволяет процессам игнорировать
тип тех файлов, с которыми эти программы имеют дело.
Если процесс ведет запись в канал и в канале нет места для всех данных,
ядро помечает индекс и приостанавливает выполнение процесса до тех пор, пока
канал не начнет очищаться от данных. Когда впоследствии другой процесс будет
считывать данные из канала, ядро заметит существование процессов, приоста-
новленных в ожидании очистки канала, и возобновит их выполнение подобно то-
му, как это было объяснено выше. Исключением из этого утверждения является

107

ситуация, когда процесс записывает в канал данные, объем которых превышает
емкость канала (то есть, объем данных, которые могут храниться в блоках пря-
мой адресации); в этом случае ядро записывает в канал столько данных, сколь-
ко он может вместить в себя, и приостанавливает процесс до тех пор, пока не
освободится дополнительное место. Таким образом, возможно положение, при ко-
тором записываемые данные не будут занимать непрерывное место в канале, если
другие процессы ведут запись в канал в то время, на которое первый процесс
прервал свою работу.
Анализируя реализацию каналов, можно заметить, что интерфейс процессов
согласуется с интерфейсом обычных файлов, но его воплощение отличается, так
как ядро запоминает смещения для чтения и записи в индексе вместо того, что-
бы делать это в таблице файлов. Ядро вынуждено хранить значения смещений для
поименованных каналов в индексе для того, чтобы процессы могли совместно ис-
пользовать эти значения: они не могли бы совместно использовать значения,
хранящиеся в таблице файлов, так как процесс получает новую запись в таблице
файлов по каждому вызову функции open. Тем не менее, совместное использова-
ние смещений чтения и записи в индексе наблюдалось и до реализации поимено-
ванных каналов. Процессы, обращающиеся к непоименованным каналам, разделяют
доступ к каналу через общие точки входа в таблицу файлов, поэтому они могли
бы по умолчанию хранить смещения записи и чтения в таблице файлов, как это
принято для обычных файлов. Это не было сделано, так как процедуры низкого
уровня, работающие в ядре, больше не имеют доступа к записям в таблице фай-
лов: программа упростилась за счет того, что процессы совместно используют
значения смещений, хранящиеся в индексе.


.te1 5.12.4 Закрытие каналов

При закрытии канала процесс выполняет ту же самую процедуру, что и при
закрытии обычного файла, за исключением того, что ядро, прежде чем освобо-
дить индекс канала, выполняет специальную обработку. Оно уменьшает количест-
во процессов чтения из канала или записи в канал в зависимости от типа фай-
лового дескриптора. Если значение счетчика числа записывающих в канал про-
цессов становится равным 0 и имеются процессы, приостановленные в ожидании
чтения данных из канала, ядро возобновляет выполнение последних и они завер-
шают свои операции чтения без возврата каких-либо данных. Если становится
равным 0 значение счетчика числа считывающих из канала процессов и имеются
процессы, приостановленные в ожидании возможности записи данных в канал, яд-
ро возобновляет выполнение последних и посылает им сигнал (глава 7) об ошиб-
ке. В обоих случаях не имеет смысла продолжать держать процессы приостанов-
ленными, если нет надежды на то, что состояние канала когда-нибудь изменит-
ся. Например, если процесс ожидает возможности производить чтение из непои-
менованного канала и в системе больше нет процессов, записывающих в этот ка-
нал, значит, записывающий процесс никогда не появится. Несмотря на то, что
если канал поименованный, в принципе возможно появление нового считывающего
или записывающего процесса, ядро трактует эту ситуацию точно так же, как и
для непоименованных каналов. Если к каналу не обращается ни один записываю-
щий или считывающий процесс, ядро освобождает все информационные блоки кана-
ла и переустанавливает индекс таким образом, чтобы он указывал на то, что
канал пуст. Когда ядро освобождает индекс обычного канала, оно освобождает
для переназначения и дисковую копию этого индекса.



    5.12.5 Примеры



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


108

+---------------------------------+
| char string[] = "hello"; |
| main() |
| { |
| char buf[1024]; |
| char *cp1,*cp2; |
| int fds[2]; |
| |
| cp1 = string; |
| cp2 = buf; |
| while(*cp1) |
| *cp2++ = *cp1++; |
| pipe(fds); |
| for (;;) |
| { |
| write(fds[1],buf,6); |
| read(fds[0],buf,6); |
| } |
| } |
+---------------------------------+

Рисунок 5.18. Чтение из канала и запись в канал



строку символов "hello" и считывая ее из канала. Ядру не нужно ни знать о
том, что процесс, ведущий запись в канал, является и процессом, считывающим