Главная страница Межпроцессное взаимодействие (состязание) то load dir попытается загрузить в таблицы г-узел для /usr/ast и вернуть указатель на него. Если файл foobar еще не существует, этот г-узел скоро понадобится, чтобы добавить новый файл в каталог. Другие системные вызовы, добавляющие или удаляющие файлы, также используют load dir. Если new node обнаруживает, что файл не существует, она вызывает allocjnode, тем самым выделяя новый г-узел, и возвращает указатель на него. Если свободных г-узлов не осталось, назад вернется код NILJNODE. Если выделить г-узел удалось, его поля заполняются и он записывается обратно на диск, а в конечный каталог заносится запись с именем файла. Опять же, здесь мы видим, что файловая система обязана постоянно проверять, не произошло ли ошибки. Если ошибка происходит, необходимо без паники аккуратно освободить все ресурсы, такие как г-узел и удерживаемый блок. Если вместо того, чтобы, скажем, при нехватке г-узлов мы позволили бы MINIX просто завершиться с фатальной ошибкой вместо того, чтобы тщательно отменять все действия вызова и возвращать код ошибки, файловая система была бы ощутимо проще, но и, сами понимаете... Ранее упоминалось, что каналы ввода/вывода требуют особых действий. Когда с каналом не связан хотя бы один процесс, читатель либо писатель, функция р1ре ореп приостанавливает процесс-инициатор вызова. В противном случае она вызывает процедуру release, которая ищет в таблице процессов те, что ожидают данный канал. Если такие процессы найдены, они запускаются. Системный вызов mknod выполняется функцией do mknod. Эта процедура аналогична do creat, за исключением того, что она лишь создает г-узел и делает для него запись каталога. Фактически большую часть работы выполняет функция new node (за три строки перед завершающим оператором return). Если г-узел уже существует, возвращается код ошибки. Код ошибки тот же самый, который был приемлемым при открытии файла, но в данном случае код возвращается в вызывающую программу, которая, предположительно, подобающим образом его интерпретирует. Подробный анализ отдельных случаев, который мы видели в common open, здесь не нужен. Функция do mkdir выполняет системный вызов mkdir. Как и в случае с предыдущими системными вызовами, важную роль здесь играет new node. Каталоги в отличие от файлов никогда не бывают пусты, так как любой каталог содержит, по крайней мере, две записи, . и первая из которых ссылается на сам каталог, а вторая - на вышестоящий. Количество ссылок на один файл ограничено константой LINK MAX (в файле include/limits.h для стандартной конфигурации MINIX ей присваивается значение 127). Поэтому, раз дочерний каталог содержит ссылку на родительский, функция do mkdir сначала проверяет, можно ли добавить новую ссылку на родительский каталог. Когда эта проверка пройдена, вызывается new node. Если и этот вызов удался, создаются записи для каталогов . и Описанный алгоритм прямолинеен, но учитывает возможность сбоев (например, переполнение диска). Чтобы ничего не испортить, обеспечивается откат к исходному состоянию, если процесс не может быть завершен. Закрыть файл всегда проще, чем открыть. Поэтому функции do close для обычных файлов, фактически, требуется только уменьшить значение счетчика в flip и, если оно достигло нуля, возвратить г-узел при помощи put node. (Каналам же и специальным файлам требуется особое внимание.) На последнем шаге аннулируются все захваты, связанные с этим файлом, и пробуждаются все процессы, приостановленные по факту захвата. Заметьте, что возврат г-узла означает только, что уменьшается счетчик в таблице inode, следовательно, в конце концов, он может быть удален из таблицы. Эта операция не имеет ничего общего с освобождением г-узла (то есть со сбросом бита в битовой карте г-узлов). Объект г-узла освобождается только тогда, когда файл больше не находится ни в одном из каталогов. Последняя процедура в ореп.с, do seek, вызывается, когда осуществляется переход на заданную позицию в файле. При этом блокируется упреждающее чтение, поскольку явное перемещение на заданную позицию в файле несовместимо с последовательным доступом. Чтение файла Открыв файл, его можно читать или записывать в него данные. Таких функций больше, чем достаточно, и все связанные с чтением можно найти в файле read.c. Мы обсудим сначала их, а затем перейдем к следующему файлу, write, с, чтобы взглянуть на код, предназначенный специально для записи. Чтение и запись во многом различны, но у них довольно много общего, поэтому все, что требуется от do read, - это вызвать общую процедуру read write с флагом READING. В следующем разделе мы увидим, что do read столь же проста. Первый условный оператор связан с обработкой особых действий, при помощи которых файловая система загружает целые сегменты для менеджера памяти. Обычная работа начинается с альтернативной ветви условного оператора. Затем следуют несколько проверок корректности операции (скажем, не делается ли попытка читать из файла, открытого только на запись) и инициализируются некоторые глобальные переменные. Чтение из специальных символьных файлов производится в обход кэша блоков, поэтому такие файлы фильтруются отдельным условным оператором. Если файл обычный, выполняются следующие проверки, имеющие отношение только к записи в файлы, где есть опасность превышения емкости устройства, а также относительно записи после конца файла, что может привести к образованию дыр . Как уже упоминалось в обзоре MINIX, наличие нескольких блоков в зоне приводит к некоторым проблемам, с которыми приходится бороться. Каналы ввода/вывода также являются особым случаем. Следующий далее по коду цикл является сердцем механизма чтения, по крайней мере, для обычных файлов. В цикле данные разбиваются на куски, каждый из которых умещается в один дисковый блок. Порция начинается с текущей позиции и считается завершенной при выполнении одного из трех условий: ♦ считаны все байты; ♦ встретилась граница блока; ♦ достигнут конец файла. Эти правила означают, что одна порция никогда не может занимать два блока. На рис. 5.31 показано, как определяется квота размера, для порций размером шесть, два и один байт соответственно. Номера байтов 0 12 3 9 10 11 12 13 14 15 16 -Блок- -Блок- -Текущая позиция = 1 Текущая позиция = 6 - Текущая позиция = 9 Порция данных 6 Порция данных 2 Порция данных 1 Рис. 5.31. Три примера того, как для 10-байтового файла определяется размер первой порции данных. Размер блока равен 8 байтам, запрашивается 6 байт. Порция отмечена штриховкой Считывание выполняется функцией rw chunk. Когда эта функция возвращает управление, увеличиваются значения различных счетчиков и продвигаются указатели, и начинается следующая итерация. После того как цикл завершится, текущая позиция в файле и другие переменные (например, указатели канала) могут быть обновлены. Наконец, если было запрошено упреждающее чтение, позиция и i-узел, откуда его нужно начинать, сохраняются в глобальных переменных, чтобы файловая система, отправив пользователю ответное сообщение, могла бы начать считывать следующий блок. Часто при этом файловая система блокируется, ожидая считывания блока, и у пользовательского процесса есть время начать работать с только что полученными данными. Такое чередование обработки и ввода/вывода может значительно улучшить производительность. Процедура rw chunk получает на входе i-узел и позицию в файле, преобразует эти значения в физический номер блока на диске и запрашивает передачу этого блока (или его части) в область данных пользователя. Преобразование относительной позиции в файле в физический адрес на диске выполняется функцией read map, которая осведомлена о структуре i-узлов и блоков косвенной адресации. Первый условный оператор функции read map проверяет, является ли файл обычным, и если да, в переменные b и dev записываются соответственно физический номер блока и номер устройства. Далее, в 260-й строке вызывается функция
|
© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования. |