<<
>>

Чтение файла

Открыв файл, его можно читать или записывать в него данные. Функций для этого больше, чем достаточно, — все связанные с чтением функции можно найти в файле read. с. Мы обсудим сначала их, а затем перейдем к следующему файлу, write.
с, чтобы взглянуть на код, предназначенный специально для записи. Механизмы чтения и записи во многом различны, но у них довольно много общего, поэтому все, что требуется от do_read (строка 25030), — это вызвать общую процедуру read_write с флагом READING. В следующем разделе мы увидим, что функция do_write столь же проста.

Функция read_write начинается в строке 25038. Код в строках 25063-25066 используется менеджером процессов, чтобы файловая система загрузила для него целые сегменты в пользовательском пространстве. Обработкой обычных вызовов занимается код, начинающийся в строке 25068. В первую очередь выполняется несколько проверок корректности операции (скажем, не делается ли попытка читать из файла, открытого только на запись) и инициализируются некоторые глобальные переменные.

Чтение из специальных символьных файлов производится в обход кэша блоков, поэтому такие файлы фильтруются в строке 25122.

Проверки в строках 25132-25145 имеют место только при записи и работают с файлами, размер которых может превысить размер устройства, а также предотвращают попытки записи за пределы конца файла, вызывающие образование «дыр». Как уже упоминалось в обзоре MINIX 3, наличие нескольких блоков в зоне приводит к некоторым проблемам, с которыми приходится бороться. Каналы ввода-вывода также являются особым случаем.

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

♦ считаны все байты;

+ встретилась граница блока;

♦ достигнут конец файла.

Эти правила означают, что один фрагмент не может занимать два блока. На рисунке 5.36 показано, как определяется квота размера, для фрагментов размером 6, 2 и 1 байт соответственно. Фактический расчет осуществляется в строках 25159-25169.

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

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

Рис. 5.36. Три примера того, как для 10-байтового файла определяется размер первого фрагмента данных. Размер блока равен 8 байт, запрашивается 6 байт. Фрагмент отмечен штриховкой

Процедура rw_chunk (строка 25251) получает на входе индексный узел и позицию в файле, преобразует эти значения в физический номер блока на диске и запрашивает передачу этого блока (или его части) в область пользовательских данных. Преобразование относительной позиции в файле в физический адрес на диске выполняется функцией read_map, которая осведомлена о структуре индексных узлов и блоков косвенной адресации. Для обычного файла переменные b и dev в строках 25280 и 25281 содержат соответственно физический номер блока и номер устройства. Вызов get_block (строка 25303) запрашивает у обработчика кэша блоков нужный блок, считывая его при необходимости. Затем вызов rahead (строка 25295) убеждается в том, что блок считан в кэш.

После того как указатель на блок получен, вызов sys_vircopy ядра в строке 25317 обеспечивает копирование данных в пользовательское пространство. Затем блок освобождается, чтобы позже он мог быть удален из кэша. (После того как вызов get_block находит нужный блок, тот исключается из LRU-списка и пребывает вне его до тех пор, пока счетчик использования в заголовке буфера не обнулится. Вызов put_block уменьшает значение этого счетчика и, если настало время, возвращает буфер в LRU-список.) Код в строке 25327 определяет, заполнился ли блок при записи. Правда, на данном этапе это уже не принципиально, так как функция put_block теперь игнорирует значение, передаваемое ей во втором аргументе, и всегда добавляет освободившиеся блоки в конец LRU-списка.

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

Для блоков, расположенных дальше, может понадобиться считать один или больше блоков косвенной адресации.

Функция rd_indir (строка 25400) вызывается, когда необходимо считать косвенный блок. Комментарии к ней несколько устарели; во-первых, код поддержки процессора 68000 удален, во-вторых, файловая система MINIX 1 больше не используется и соответствующий ей код также можно исключить. Тем не менее следует отметить, что в случае организации поддержки других файловых систем или платформ, проблемы иных форматов сохранения данных на диске, типов данных и порядка следования байтов можно решить в этом файле. Если без трудных для понимания преобразований данных не обойтись, то совершив их здесь, вы обеспечите единую форму представления данных во всей файловой системе.

Функция read_ahead (строка 25432) преобразует логическое положение в физический адрес блока, вызывает функцию get_block, в результате чего блок оказывается в кэше, и немедленно возвращает его.

В конце концов, сделать с ним она все равно ничего не может. Задача read_ahead лишь в том, чтобы повысить вероятность найти данные в кэше, если они скоро понадобятся.

Обратите внимание, что функция read_ahead вызывается только из главного цикла в main. Ее вызов не является частью вызова read. Важно понять, что эта функция вызывается после того, как пользователю отправлен ответ, чтобы он мог продолжать свою работу, пока файловая система дожидается завершения упреждающего чтения.

Сама функция read_ahead написана так, чтобы запрашивать всего один блок. Реально трудится вызываемая ею подпрограмма г ahead. В г ahead (строка 25451) заложена та жизненная концепция, что если немного больше — хорошо, а намного больше — еще лучше. Так как дискам и прочим накопителям часто требуется много времени, чтобы найти первый запрошенный блок, но зато они могут быстро считать несколько смежных блоков, делается ставка на то, что получится считать много последовательных блоков ценой небольших дополнительных затрат. Запрос на упреждающую выборку передается функции get_block, подготавливающей кэш блоков к получению нескольких блоков за раз. Затем вызывается функция rw_scattered, которой передается список блоков. Работу этой функции мы уже обсуждали. Вспомните, что rw_scattered передает запрос драйверу устройства, который в ответ имеет право обслужить столько запросов, сколько он способен выполнить эффективно. Все это звучит довольно витиевато, но зато позволяет на нужное «много» повысить быстродействие приложений, считывающих с диска ожидаемое «больше» данных.

На рис. 5.37 показаны взаимосвязи между основными подпрограммами, участвующими в чтении файла. В том числе указано, кто кого вызывает.

<< | >>
Источник: Э. ТАНЕНБАУМ, А. ВУДХАЛЛ. ОПЕРАЦИОННЫЕ СИСТЕМЫ Разработка и реализация 3-е издание. 2007

Еще по теме Чтение файла:

  1. ЧТЕНИЕ ГУБНОЕ
  2. Чтение между строк
  3. 13.10.1. Раннее чтение
  4. Чтение небесных посланий
  5. Чтение мысли дня:
  6. Чтение мысли дня:
  7. Чтение мысли дня:
  8. Чтение мысли дня:
  9. Чтение мысли дня:
  10. Чтение мысли дня: