ГЛАВА 8


Дополнительные возможности файловых систем

  • Специальные файлы и аппаратные драйверы
  • Специальные файлы как универсальный интерфейс
  • Структурирование аппаратных драйверов
  • Структура драйвера Windows NT
  • Структура драйвера UNIX
  • Отображаемые в память файлы
  • Дисковый кэш
  • Традиционный дисковый кэш
  • Дисковый кэш на основе виртуальной памяти
  • Отказоустойчивость файловых и дисковых систем
  • Восстанавливаемостьфайловых систем
  • Избыточные дисковые подсистемы RAID
  • Обмен данными между процессами и потоками
  • Конвейеры
  • Именованные конвейеры
  • Очереди сообщений
  • Разделяемая память
  • Выводы
  • Задачи и упражнения

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

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

 

Специальные файлы и аппаратные драйверы

Специальные файлы как универсальный интерфейс

Понятие «специальный файл» появилось в операционной системе UNIX. Специальный файл, называемый также виртуальным файлом, связан с некоторым устройством ввода-вывода и представляет его для остальной части операционной системы и прикладных процессов в виде неструктурированного набора байт, то есть в виде обычного файла. Однако в отличие от обычного файла специальный файл не хранит статичные данные, а является интерфейсом к одному из аппаратных драйверов ОС.

Использование специальных файлов во многих случаях существенно упрощает программирование операций с внешними устройствами. Со специальным файлом можно работать так же, как и с обычным, то есть открывать, считывать из него или же записывать в него определенное количество байт, а после завершения операции закрывать. Для этого используются привычные многим программистам системные вызовы для работы с обычными файлами: open, create, read, write и close. Кроме того, имеется несколько системных вызовов, используемых только при работе со специальными файлами, например вызов loctl, с помощью которого можно передать команду контроллеру устройства. Для того чтобы вывести на алфавитно-цифровой терминал, с которым связан специальный файл /dev/tty3, сообщение «Hello, friendsl», достаточно открыть этот файл с помощью системного вызова open:

fd =open (7dev/tty3", 2)

Затем можно вывести сообщение с помощью системного вызова write:

write (fd, "Hello, friends! ", 15)

Для устройств прямого доступа имеет смысл также указатель текущего положения в файле, которым можно управлять с помощью системного вызова lseek.

Очевидно, что представление устройства в виде файла и использование для управления устройством файловых системных вызовов позволяет выполнять только простые операции управления, которые сводятся к передаче в устройство последовательности байт. Для некоторых устройств такие операции вполне адекватны—в основном это устройства, отображающие строки символов (алфавитно-цифровые терминалы, алфавитно-цифровые принтеры) или принимающие от пользователя строки символов (клавиатура). Форматирование ввода-вывода в устройствах этого класса осуществляется с помощью служебных символов начала кодовой таблицы и их последовательностей, например для перевода строки и возврата каретки принтера или терминала достаточно к последовательности символов текста добавить восьмеричные коды <12> <15>.

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

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

Рис. 8.1. Работа с диском как со специальным файлом

В UNIX специальные файлы традиционно помещаются в каталог /dev, хотя ничто не мешает созданию их в любом каталоге файловой системы. При появлении нового устройства и соответственно нового драйвера администратор системы может создать новую запись с помощью команды mknod. Например, следующая команда создает блок-ориентированный специальный файл для представления третьего раздела на втором диске четвертого SCSI-контроллера:

mknod /dev/dsk/scs1 b 32 33

Связь специального файла с драйвером устанавливается за счет информации, находящейся в индексном дескрипторе специального файла.

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

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

Адресная информация специального файла состоит из двух элементов:

  • major — номер драйвера;
  • minor — номер устройства.

Значение major (номер драйвера) определяет выбор драйвера, обслуживающего данный специальный файл, а значение minor (номер устройства) передается драйверу в качестве параметра вызова и указывает ему на одно из нескольких однотипных устройств, которыми драйвер может управлять. Например, для дисковых драйверов номер устройства задает не только диск, но и раздел на диске.

В приведенном выше примере команды создания специального файла /dev/dsk/scsi аргумент b определяет создание специального файла для блок-ориентированного драйвера, аргумент 32 определяет номер драйвера, который будет вызываться при открытии устройства /dev/dsk/scsi, а аргумент 33 декодируется самим драйвером (в нем закодированы данные о том, что нужно управлять третьим разделом на втором диске четвертого SCSI-контроллера).

ОС UNIX использует для хранения информации об установленных аппаратных драйверах две системные таблицы:

  • bdevsw — таблица блок-ориентированных драйверов;
  • cdevsw — таблица байт-ориентированных драйверов.

Номер драйвера (major) является индексом соответствующей таблицы. При открытии специального файла операционная система обнаруживает, что она имеет дело со специальным файлом только после того, как прочитает с диска или найдет в системном буфере его индексный дескриптор. При этом она узнает, является ли вызываемый драйвер блок- или байт-ориентированным, после чего использует номер драйвера для обращения к определенной строке одной из двух таблиц: bdevsw или cdevsw (рис. 8.2).

Рис. 8.2. Организация связи ядра UNIX с драйверами

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

Концепция специальных файлов ОС UNIX была реализована во многих операционных системах, хотя для связи с драйверами в них часто используются механйзмы, отличные от описанного выше. Так, в OG Windows NT для связи виртуальных устройств (аналогов специальных файлов) с драйверами используется механизм объектов. При этом в объектах-устройствах имеются ссылки на объекты-драйверы, за счет чего при открытии виртуального устройства система находит нужный драйвер.

Структурирование аппаратных драйверов

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

Более точно, аппаратный драйвер имеет дело не с устройством, а с его контроллером. Контроллер, как правило, выполняет достаточно простые функции, например преобразует поток бит в блоки данных и осуществляют контроль и исправление возникающих в процессе обмена данными ошибок. Каждый контроллер имеет несколько регистров, которые используются для взаимодействия с центральным процессором. Обычно у контроллера имеются регистры данных, через которые осуществляется обмен данными между драйвером и устройством, и управляющие регистры, в которые драйвер помещает команды. В некоторых типах компьютеров регистры являются частью физического адресного пространства, при этом в таких компьютерах отсутствуют специальные инструкции ввода-вывода — их функции выполняют инструкции обмена с памятью. В других компьютерах адреса регистров ввода-вывода, называемых часто портами, образуют собственное адресное пространство за счет введения специальных операций ввода-вывода (например, команд IN и OUT в процессорах Intel Pentium).

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

Аппаратный драйвер выполняет ввод-вывод данных, записывая команды в регистры контроллера. Например, контроллер диска персонального компьютера принимает такие команды, как READ, WRITE, SEEK, FORMAT и т. д. Когда команда принята, процессор оставляет контроллер и занимается другой работой. По завершении команды контроллер генерирует запрос прерывания для того, чтобы передать управление процессором операционной системе, которая должна проверить результаты операции. Процессор получает результаты и данные о статусе устройства, читая информацию из регистров контроллера.

Аппаратные драйверы могут в своей работе опираться на микропрограммные драйверы (firmware drivers), поставляемые производителем компьютера и находящиеся в постоянной памяти компьютера (в персональных компьютерах это программное обеспечение получило название BIOS — Basic Input-Output System). Микропрограммное обеспечение представляет собой самый нижний слой программного обеспечения компьютера, управляющий устройствами. Модули этого слоя выполняют функции транслирующих драйверов и конверторов, экранирующих специфические интерфейсы аппаратуры дайной компьютерной системы от операционной системы и ее драйверов.

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

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

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

В подсистеме ввода-вывода каждой современной операционной системы существует стандарт на структуру драйверов. Несмотря на специфику управляемых устройств, в любом драйвере можно выделить некоторые общие части, выполняющие определенный набор действий, такие как запуск операции ввода-вывода, обработка прерывания от контроллера устройства и т. п. Рассмотрим принципы структуризации драйверов на примере операционных систем Windows NT и UNIX.

Структура драйвера Windows NT

Особенностью Windows NT является общая структура драйверов любого уровня и расширенное толкование самого понятия «драйвер». В Windows NT и аппаратный драйвер диска, и высокоуровневый драйвер файловой системы построены единообразно, поэтому другие модули ОС взаимодействуют с драйверами одним и тем же способом.

Драйвер Windows NT состоит из следующих (не обязательно всех) процедур:

  • Процедура инициализации драйвера. Эта процедура выполняется при загрузке драйвера в подсистему ввода-вывода, при этом создаются системные объекты, которые позволяют менеджеру ввода-вывода найти нужный драйвер для управления определенным устройством или выполнения некоторых высокоуровневых функций с информацией, получаемой от устройства или передаваемой на устройство.
  • Набор диспетчерских процедур. Эти процедуры составляют основу драйвера, так как именно они выполняют операции ввода-вывода, поддерживаемые данным драйвером, например чтение данных, запись данных, перемотку ленты и т. п.
  • Стартовая процедура предназначена для приведения устройства в исходное состояние перед началом очередной операции. Выполняет «открытие» (open) устройства.
  • Процедура обработки прерывания (ISR) включает наиболее важные действия, которые нужно выполнить при возникновении очередного аппаратного прерывания от контроллера устройства. Процедура обработки прерывания драйвера имеет достаточно высокий приоритет запроса прерывания IRQL (например, выше приоритета диспетчера потоков), поэтому в нее рекомендуется включать только те функции, которые требуют незамедлительной реакции, чтобы не задерживать надолго работу других модулей и процессов. По завершении работы ISR может поставить в очередь диспетчера прерываний процедуру DPC драйвера.
  • Процедура отложенных вызовов (DPC). Эта процедура также состоит из функций, которые нужно выполнить при возникновении прерывания от контроллера устройства, однако эти функции не требует такой быстрой реакции, как функции ISR. В результате процедура DPC обслуживается с более низким значением приоритета IRQL, давая возможность процедурам ISR и другим приоритетным запросам обслуживаться в первую очередь. Обычно большая часть действий драйвера по обработке прерывания включается в процедуру DPC.
  • Процедура завершения операции уведомляет менеджер ввода-вывода о том, что операция завершена и данные находятся в системной области памяти. Менеджер при этом может вызвать драйвер более высокого уровня для продолжения обработки данных или же вызывать процедуру АРС, рассмотренную в разделе «Процедуры обработки прерываний и текущий процесс» главы 4 «Процессы и потоки» для копирования данных из системной области в область памяти пользовательского процесса.
  • Процедура отмены ввода-вывода. Для разных стадий выполнения операции могут существовать разные процедуры отмены.
  • Процедура выгрузки драйвера вызывается при динамической выгрузке драйвера из подсистемы ввода-вывода. Удаляет созданные для драйвера объекты и освобождает системную память.
  • Процедура регистрации ошибок. При возникновении ошибки в процессе выполнения операции данная процедура уведомляет о ней менеджера ввода-вывода, который в свою очередь делает соответствующую запись в журнале регистрации.

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

Большое количество стандартизованных функций драйвера Windows NT обусловлено желанием разработчиков этой ОС использовать единую модель для драйверов всех типов, от сравнительно простого аппаратного драйвера СОМ-порта до весьма сложного драйвера файловой системы NTFS. В результате некоторые функции для некоторого драйвера могут оказаться невостребованными. Например, для высокоуровневых драйверов не нужна секция обработки прерываний ISR, так как прерывания от устройства обрабатывает соответствующий низкоуровневый драйвер, который затем вызывает высокоуровневый драйвер с помощью менеджера ввода-вывода, не используя механизм прерываний.

Рассмотрим особенности вызова функций аппаратного драйвера Windows NT на примере выполнения операции чтения с -диска (рис. 8.3). Диск рассматривается в этой операции как виртуальное устройство, следовательно, слой драйверов файловых систем в выполнении операции не участвует.

Рис. 8.3. Работа аппаратного драйвера Windows NT

Пусть в некоторый момент времени выполнения в пользовательской фазе процесс А запрашивает с помощью соответствующего системного вызова чтение некоторого количества блоков диска, начиная с блока определенного номера. Процесс А при этом переходит в состояние ожидания завершения запрошенной операции, а планировщик/диспетчер Sh активизирует ожидавший выполнения процесс В.

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

После завершения чтения порции данных контроллер генерирует аппаратный запрос прерывания, который вызывает процедуру обработки прерываний драйвера диска ISR, имеющую высокий уровень IRQL. После короткого периода выполнения самых необходимых действий с регистрами контроллера (этот период для упрощения рисунка не показан) эта процедура делает запрос на выполнение менее срочной DPC-процедуры драйвера, которая должна выполнить передачу имеющейся у контроллера порции данных в системную область. Запрос на выполнение DPC-процедуры драйвера DDA некоторое время стоит в очереди уровня DPC, так как в это время в процессоре выполняются более приоритетные ISR-процедуры DSB (драйвера стриммера для процесса В) и DPF (драйвера принтера для процесса F). После завершения этих процедур начинается выполнение DPC-процедуры драйвера DDA, при этом текущим для ОС процессом является процесс В, сменивший процесс А и прерванный на время ISR-процедурами. Однако на выполнение DPC-процедуры драйвера диска это обстоятельство не оказывает никакого влияния, так как данные перемещаются в системную область, общую для всех процессов.

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

DPC-процедура вызывается раньше, так как имеет более высокий приоритет в очереди диспетчера прерываний. АРС- процедура ждет дольше, так как она имеет более низкий приоритет и, кроме того, она обязана ждать до тех пор, пока текущим процессом не станет процесс А. DPC-процедура после выполнения своей работы фиксирует в операционной системе событие — завершение операции ввода-вывода. По наступлении события вызывается планировщик потоков, который переводит процесс А в состояние готовности (но не ставит его на выполнение, так как текущий процесс С еще не исчерпал своего кванта времени). И только после того, как планировщик снимает процесс С с выполнения и делает текущим процесс А, вызывается АРС- процедура, которая вытесняет пользовательский код процесса А, имеющий низший приоритет IRQL. АРС- процедура переписывает считанные с диска данные из системного буфера в область данных процесса А. Для доступа к системному буферу АРС- процедура должна иметь нужный уровень привилегий. После завершения работы АРС- процедуры управление возвращается пользовательскому коду приложения А, который обрабатывает запрошенные у диска данные.

Структура драйвера UNIX

В ОС UNIX вместо одной общей структуры драйвера существуют две стандартные структуры, одна — для блок-ориентированных драйверов, а другая — для байт-ориентированных. По этой причине в UNIX используются две таблицы, bdevsw и cdevsw, хранящие точки входа в функции драйверов. Каждая из таблиц имеет свою структуру, соответствующую стандартным функциям блок-ориентированных и байт-ориентированных драйверов.