Skip | Return | Title | Contents |
Основным назначением системы управления вводом-выводом является реализация интерфейса между прикладными программами пользователей и внешними устройствами ЭВМ. К системе ввода-вывода обычно пред'являются следующие требования:
Выполнение этих требований в OS UNIX осуществляет 4-х уровневая система управления вводом-выводом, структура которой показана на следующем рисунке.
|
Прикладная программа |
|
Системные вызовы Файловая система Специальные файлы Драйверы внешних устройств |
|
Внешние устройства |
|
Как видно из рисунка 1-й уровень управления вводом-выводом обеспечивают системные вызовы ядра OS UNIX. Они реализуют программный интерфейс прикладной программы с резидентными системными таблицами ядра через пользовательские дескрипторы открытых файлов в контексте процесса выполнения программы.
На уровне файловой системы управление вводом-выводом реализуется путем отображения пользовательских дескрипторов открытых файлов в контексте процесса выполнения прикладной программы на множество индексных дескрипторов файловой системы. Это отображение осуществляется на основе взаимодействия 3-х резидентных системных таблиц.
На уровне специальных файлов по индексному дескриптору конкретизируется класс, тип и номер внешнего устройства. По этой информации, через таблицы специальных файлов, осуществляется выбор драйвера ввода-вывода и его настройка на конкретное внешнее устройство.
На последнем уровне управление вводом-выводом осуществляют драйверы внешних устройств, которые обеспечивают реальное выполнение операций обмена данными с внешним устройством на основе механизма обработки аппаратных прерываний.
Взаимосвязь между соседними уровнями рассмотренной структуры управления вводом-выводом обеспечивается посредством программного интерфейса, реализующего операции ввода-вывода. Каждая операция ввода-вывода в OS UNIX может реализовывать два способа передачи данных: побайтный и логическими блоками. Подобное разделение обусловлено необходимостью оптимизировать обмен данными с внешним устройством по времени выполнения.
Системные вызовы OS UNIX оформляются как отдельные функции в терминах системы программирования C, но являются директивами ядра, поэтому их код не принадлежит адресному пространству выполняемого файла прикладной программы. Большинство системных вызовов OS UNIX ориентировано на выполнение операций ввода-вывода.
Для программной обработки файлов обычно используются следующие системные вызовы ввода-вывода: access, creat, open, close, lseek, dup, dup2, fcntl, ioctl, read и write. При успешном завершении они инициируют неотрицательный целочисленный код возврата. При аварийном завершении их код возврата равен (-1). Причина аварийного завершения фиксируется системной переменной errno, которая используется библиотечной функцией perror для диагностики ошибок. Связь имени файла с системой ввода-вывода в системных вызовах OS UNIX обеспечивает номер пользовательского дескриптора файла в контексте процесса выполнения прикладной программы. Краткое описание наиболее популярных системных вызовов приведено ниже.
Системный вызов access проверяет доступность файла с заданным именем в указанном режиме. Тестируемый режим доступа к файлу определяют следующие флаги:
Указанные флаги доступа макроопределены в заголовочном файле unistd.h системы программирования C. При положительном результате проверки системный вызов access возвращает код 0. При отсутствии указанных прав доступа к специфицированному файлу возвращается код (-1).
Системный вызов creat создает новый или подготавливает для перезаписи существующий файл с заданным именем и специфицированным кодом защиты. Существующий файл обрезается до нулевой длины, сохраняя прежние полномочия доступа. Новый файл получает права доступа, образованные битовой операцией & из двоичного дополнения маски прав доступа процесса и кода защиты, который специфицирован в системном вызове creat. В обоих случаях, при успешном завершении системный вызов creat возвращает номер пользовательского дескриптора файла в контексте процесса.
Системный вызов open открывает существующий файл с заданным именем и специфицированным режимом обработки. Режим обработки устанавливается следующими основными флагами:
O_RDONLY | - | только чтение данных, |
O_WRONLY | - | только запись данных, |
O_RDWR | - | чтение и запись данных одновременно, |
O_APPEND | - | запись данных только в конец файла, |
O_NONBLOCK | - | запрет блокировки чтения с установкой ошибки, |
O_NDELAY | - | запрет блокировки чтения без установки ошибки, |
O_SYNC | - | ожидание физической записи данных, |
O_CREAT | - | создать файл, если он не существует, |
O_TRUNC | - | уничтожить данные файла. |
Значения режимных флагов макропределены в заголовочном файле fcntl.h системы программирования C. При успешном завершении системного вызова open в контекст процесса возвращается номер пользовательского дескриптора файла. Указатель чтения-записи устанавливается на начало файла.
Системный вызов dup возвращает наименьший свободный номер пользовательского дескриптора, который дублирует в контексте процесса заданный дескриптор открытого файла для всех операций его последующей обработки. Системный вызов dup обычно используется для перенаправления операций ввода-вывода, в частности, при передаче данных между процессами через программный канал.
Системный вызов fcntl предназначен для управления режимом обработки открытого файла, который специфицирован пользовательским дескриптором в его первом параметре. Остальные параметры системного вызова определяют требуемую команду управления и ее аргументы, если они необходимы. Коды допустимых команд управления макроопределены в заголовочном файле fcntl.h системы программирования C. Тип необходимых аргументов зависит от кода команды управления и может быть либо int, либо адрес структуры блокировки файла struct flock. При аварийном завершении системный вызов fcntl возвращает код (-1), сохраняя код ошибки во внешней переменной errno. При успешном завершении возвращается неотрицательный код, который зависит от вида специфицированной команды управления. Ниже перичислены различные команды управления, которые упорядочены по типу своих аргументов.
F_GETFL | - | возвращает атрибуты открытого файла, которые позволяют по маске O_ACCMODE узнать в каком режиме доступа он был открыт и определить текущий режим его обработки по маскам O_NDELAY, O_NONBLOCK или O_SYNC; |
F_GETFD | - | возвращает значение атрибута FD_CLOEXEC открытого файла, который определяет необходимо или нет автоматически закрыть файл при выполнении системных вызовов семейства exec. |
F_SETFL | - | устанавливает режим обработки открытого файла по значению аргумента, заданному масками O_NDELAY, O_NONBLOCK, O_APPEND и O_SYNC; |
F_SETFD | - | устанавливает атрибут FD_CLOEXEC открытого файла; |
F_DUPFD | - | возвращает дубликат пользовательского дескриптора открытого файла, значение которого больше или равно заданной величине аргумента. |
F_SETLK | - | устанвливает или снимает блокировку участка файла по описанию, заданному структурой struct flock; |
F_GETLK | - | возвращает описание блокировки участка файла в структуре struct flock. |
Системный вызов lseek позиционирует указатель чтения-записи в открытом файле, специфицированном пользовательским дескриптором в контексте процесса, на заданную величину смещения в байтах относительно зафиксированной точки отсчета. Точку отсчета смещения устанавливает параметр, который может принимать одно из следующих 3-х значений:
Эти значения макроопределены в заголовочном файле unistd.h системы программирования C. Системный вызов lseek допускает позиционирование указателя чтения-записи в любую точку, в том числе, за конец файла. При позиционировании за конец файла с последующей записью данных образуется промежуток, который не занимает физического пространства в файловой системе. Чтение незаполненной области будет возвращать соответствующее число нулевых байт, пока в нее не произведена запись данных. В любом случае, системный вызов lseek не изменяет содержание и размер файла. При успешном завершении он возвращает установленную величину смещения от начала файла. В случае обнаружения ошибки возвращается отрицательный код без изменения текущей позиции указателя чтения-записи.
Системные вызовы read и write обеспечивают обработку данных открытого файла, специфицированного пользовательским дескриптором в контексте процесса. Эти системные вызовы читают и записывают требуемое число байт по заданному адресу, сдвигая указатели чтения-записи на соответствующее число позиций файла. Они возвращают реальное число записанных или прочитанных байт файла, которое может отличаться от декларированного об'ема обработки. В частности, нулевой код возврата системного вызова read означает, что достигнут конец файла.
Системный вызов close применяется для завершения обработки файла, чтобы освободить специфицированный пользовательский дескриптор в контексте процесса. Его использование целесообразно, так как число одновременно открытых файлов для одного процесса ограничено предельным числом номеров пользовательских дескрипторов в контексте процесса.
Следующий фрагмент C-кода демонстрирует применение наиболее популярных системных вызовов ввода-вывода OS UNIX в программе копирования файла, аргументами командной строки вызова которой являюся имена файлов оригинала и копии.
#include | <stdio.h> |
#include | <fcntl.h> |
#include | <unistd.h> |
#include | <errno.h> |
main(argc, argv) | ||
int argc; | ||
char* argv[]; | ||
{ | ||
int fd[2]; | /* | дескрипторы файлов оригинала и копии */ |
char buf[BUFSIZ]; | /* | буфер копирования */ |
int n; | /* | операционный об'eм копирования */ |
if | ((fd[0] = open(argv[1], | O_RDONLY)) < 0) { |
perror(argv[0]); | ||
exit(errno); | ||
} | /* if */ | |
if | ((fd[1] = creat(argv[2], | 0644)) < 0) { |
perror(argv[1]); | ||
exit(errno); | ||
} | /* if */ | |
while((n = read(fd[0], | buf, BUFSIZ)) > 0) |
write(fd[1], buf, n); | |
close(fd[0]); |
close(fd[1]); |
exit(0); |
В приведенной программе файл-оригинал, имя которого передается через 1-й аргумент командной строки, открывается для копирования с помощью системного вызова open, который возвращает в целочисленную переменную fd[0] пользовательский дескриптор файла в контексте процесса или код (-1), если файл-оригинал не может быть открыт по чтению, например, когда он не существует или процесс копирования не имеет требуемых полномочий обработки.
Системный вызов creat создает для хранения копируемых данных файл нулевой длины, который должен быть доступен по чтению-записи для владельца и по чтению для членов группы владельца или других пользователей. Если требуемый режим доступа к файлу-копии может быть реализован, его пользовательский дескриптор возвращается в целочисленную переменную fd[1]. Иначе возвращается код (-1). Содержание созданного файла формируется в последующем цикле копирования.
Диагностику ошибок системных вызовов open и creat в программе обеспечивает библиотечная функция perror, которая отображает сообщение об ошибке через поток стандартной диагностики. Содержание сообщения определяет текущее значение внешней переменной errno, которая фиксирует код ошибки. Эта переменная декларирована в заголовочном файле errno.h системы программирования C. Для аварийного завершения процесса с кодом ошибки используется системный вызов exit.
При отсутствии ошибок системных вызовов open и creat выполняется цикл копирования данных блоками, размер которых определяет макрос BUFSIZ, определенный в заголовочном файле stdio.h системы программирования C. Передача данных реализуется системными вызовами read и write, через буферный массив символов buf, об'емом BUFSIZ байт. Буферизация данных позволяет сократить время копирования за счет уменьшения числа обращений к системным вызовам read и write. В каждой итерации цикла копирования системный вызов read возвращает число прочитанных байт в целочисленную переменную n, значение которой использует системный вызов write, чтобы записать в файл копии столько байт из буферного массива, сколько было прочитано из файла оригинала системным вызовом read. Когда достигнут конец файла оригинала, системный вызов read возвращает 0 прочитанных байт, индицируя конец файла для завершения цикла копирования.
После завершения цикла копирования целесообразно освободить дескрипторы файлов копии и оригинала в контексте процесса системным вызовом close и завершить выполнение процесса системным вызовом exit с нулевым кодом.
Для управления файлами в файловой системе OS UNIX используются три резидентные системные таблицы:
Элементами таблицы индексных дескрипторов являются резидентные копии индексных дескрипторов из индексного файла, по одному на каждый файл файловой системы, к которому была осуществлена попытка доступа. Любые модификации характеристик обрабатываемого файла регистрируются в резидентном образе его индексного дескриптора таблицы inode. После завершения всех процессов обработки файла, резидентный образ его индексного дескриптора удаляется из таблицы индексных дескрипторов и при наличии изменений копируется в соответствующий элемент индексного файла во внешней памяти.
Резидентная таблица индексных дескрипторов (inode) может содержать до 400 элементов. Элементы этой таблицы формально отражает структура struct inode, близкая по составу полей структуре struct dinode, которая задает формат индексного дескриптора в индексном файле. Дополнительно по отношению к структуре struct dinode в структуру struct inode введены следующие поля:
i_forw, i_back | - | ссылки на предыдущий и последующий элементы, соответственно; |
i_count | - | число открытий файла во всех процессах; |
i_number | - | номер индексного дескриптора; |
Каждому элементу таблицы индексных дескрипторов (inode) соответствует один или несколько элементов системной таблицы файлов (file). Кратность соответствия зависит от числа открытий файла во всех процессах его обработки и отражается значением поля i_count структуры struct inode. Системная таблица файлов состоит из записей - по одной на каждое открытие файла в одном или различных процессах. Каждая запись системной таблицы файлов формально отражается структурой struct file, которая имеет следующие поля:
f_next, f_prev | - | указатели на следующую и предыдущую запись в системной таблице файлов; |
f_flag | - | режим открытия файла (запись, дополнение, чтение и т.д.); |
f_offset | - | смещение указателей чтение-записи при работе с файлом; |
f_count | - | число ссылок на данную запись из таблицы открытых файлов в контекстах процессов; |
f_inode | - | ссылка на соответствующий индексный дескриптор в резидентной таблице индексных дескрипторов. |
Расширение системной таблицы файлов - результат открытия или создания файлов в пользовательских процессах. При завершении обработки любого файла значение поля f_count структуры struct file в соответствующей записи системной таблицы файлов уменьшается на 1. Запись с нулевым значением поля f_count удаляется из системной таблицы файлов и вместе с ней исчезает ссылка (f_inode) на соответствующий образ индексного дескриптора в таблице inode, уменьшая на 1 значение поля i_count в его структуре struct inode.
Для связи с системной таблицей файлов в контексте каждого процесса введено поле u_ofile, элементами которого являются ссылки на записи таблицы file. Ссылки образуются в результате открытия существующих или при создании новых файлов в данном процессе. Размер поля u_ofile ограничен 20-ю ссылками. Это означает, что любой процесс может вести одновременную обработку не более 20-ти файлов. Ненулевые ссылки поля u_ofile в контексте процесса образуют таблицу открытых файлов процесса. Элементы этой таблицы индексируют пользовательские дескрипторы файлов, которые открыты процессом. Пользовательские дескрипторы задаются целыми числами в диапазоне от 0 до 19. Они возвращаются процессу при открытии файла и специфицируют файл в ходе его обработки. При открытии или создании файла в поле u_ofile контекста процесса индексируется элемент с минимальным свободным номером, в котором размещается ссылка на новую запись системной таблицы файлов. Завершение работы с файлом в процессе освобождает его пользовательский дескриптор и удаляет ссылку на соответствующую запись в системной таблице файлов.
Модификацию таблицы открытых файлов в контексте процесса можно также осуществить путем дублирования пользовательских дескрипторов. При этом в поле u_ofile контекста процесса образуется дополнительная ссылка на существующую запись системной таблицы файлов, в структуре struct file которой значение поля f_count увеличивается на единицу. Дублирование пользовательских дескрипторов применяется для программной реализации перенаправления ввода-вывода, а также при обработке стандартных потоков ввода (stdin), вывода (stdout) и протокола диагностики (stderr), которые связаны со специальным файлом ведущего терминала сеанса работы пользователя с OS UNIX.
Поток стандартного ввода индексируется пользовательским дескриптором 0 в контексте процесса интерпретации команд, который является лидером сеанса и открывает специальный файл ведущего терминала сеанса. Потоки стандартного вывода и протокола диагностики индексируются в контексте этого процесса пользовательскими дескрипторами 1 и 2, соответственно. Они образуются путем дублирования пользовательского дескриптора потока стандартного ввода. Поскольку процесс интерпретации команд является глобальным предком всех процессов сеанса, а процессы-потомки наследуют контекст предка, то пользовательские дескрипторы 0, 1, 2 будут зарезервированы для стандартных потоков ввода, вывода и протокола диагностики в контекстах прямых или отдаленных потомков интерпретатора команд, если они не были принудительно освобождены в каком-либо из промежуточных предков данного процесса. Через эту тройку дескрипторов любой процесс может вести обработку стандартных потоков, не заботясь о предварительном открытии специального файла ведущего терминала сеанса.
Из приведенного описания 3-х системных таблиц следует, что управление файлами обеспечивает отображение пользовательских дескрипторов, специфицирующих файлы в процессах их обработки, в индексные дескрипторы файлов, через которые осуществляется доступ к блокам данных файловой системы. Такая организация управления файлами гарантирует целостность файловой системы, когда один файл одновременно обрабатывается в нескольких процессах, поскольку все модификации файла отражает единственный образ его индексного дескриптора. Данная схема управления не исключает возможных конфликтов, например, при одновременной записи данных в один файл различными процессами, что может привести к разрушению информации в файле. Однако, даже при возникновении подобной маловероятной ситуации может иметь место только искажение информации в отдельном файле, но не нарушение целостности всей файловой системы.
Взаимосвязь системных таблиц иллюстрирует логическая схема управления параллельной обработкой 2-х файлов в 2-х процессах, показанная на следующем рисунке.
|
|
|
|
|||||||||||
|
|
|||||||||||||
|
|
|
||||||||||||
|
|
|
|
|||||||||||
|
|
|
|
|
||||||||||
|
||||||||||||||
|
|
|
|
|||||||||||
|
|
|
|
|||||||||||
|
|
> |
|
|
||||||||||
|
|
|||||||||||||
|
|
|
||||||||||||
|
Процесс I обрабатывает файлы F2 и F1, которые открыты в нем для чтения. Их пользовательские дескрипторы равны 3 и 4 в контексте процесса I. Им соответствуют разные записи в системной таблице файлов и различные индексные дескрипторы в резидентной таблице индексных дескрипторов. Процесс II обрабатывает только файл F2, который открыт в нем отдельно по чтению и записи. Эти варианты доступа индексируются пользовательскими дескрипторами 3 и 4 в контексте процесса II. Им соответствуют разные записи в системной таблице файлов, но общий индексный дескриптор, причем тот же, что для файла F2, открытого процессом I.
Различные способы именования специальных файлов позволяют различать периферийные устройства на пользовательском уровне. На уровне системы различие типов внешних устройств и возможное наличие нескольких устройств одного типа отражается в индексных дескрипторах специальных файлов структурой из 2-х полей d_minor и d_major. Эти поля размещены в 2-х первых элементах символьного массива di_addr[40] адресного поля индексного дескриптора dinode, который у каталогов и обычных файлов используется для адресации блоков данных на внешнем устройстве. Для специального файла значение в di_addr[1] (di_major) задает тип внешнего устройства и определяет выбор драйвера для управления им. Порядковый номер устройства данного типа хранится в di_addr[0] (di_minor). Он передается функциям драйвера как параметр, определяющий об'ект настройки драйвера. Значения полей di_major и di_minor задаются неотрицательными целыми числами.
Доступ к драйверам внешних устройств с блочной и символьной структурой осуществляется через системные таблицы bdevsw и cdevsw, соответственно. Эти таблицы состоят из записей, которые содержат адреса драйверных функций байт- и блок-ориентированных устройств, соответствующего типа. Формат записей таблиц cdevsw и bdevsw декларируют следующие структуры:
struct | cdevsw { | struct | cdevsw { | |
int | (*d_open)(); | int | (*d_open)(); | |
int | (*d_close)(); | int | (*d_close)(); | |
int | (*d_read)(); | int | (*d_strategy)(); | |
int | (*d_write)(); | int | (*d_print)(); | |
int | (*d_ioctl)(); | int | (*d_size)(); | |
}; | }; |
Сами таблицы cdevsw и bdevsw задают внешние массивы указанных структур:
extern struct cdevsw cdevsw[]
extern struct cdevsw cdevsw[],
поля записей которых инициализированы адресами точек входа функций драйверов периферийных устройств, соответствующих типов. Драйверные функции (с именами точек входа XXfunc) активизируются в следующих случаях:
- XXopen
- вызывается, когда нужно открыть устройство для операций ввода-вывода системным вызовом open;
- XXclose
- вызывается, когда нужно закрыть устройство после завершения ввода-вывода системным вызовом close;
- XXstrategy
- определяет стратегию блокориентированного ввода (read) и вывода (write);
- XXprint
- вызывается для вывода на консоль сообщений об ошибках работы с устройством;
- XXsize
- вычисляет размер файловой системы для проверки возможности реализации запроса вывода данных;
- XXread
- вызывается для реализации байториентированного ввода системным вызовом read;
- XXwrite
- вызывается для реализации байториентированного вывода системным вызовом write;
- XXioctl
- обеспечивает управление режимами работы устройств с символьной структурой ввода-вывода.
Префикс XX в именах точек входа драйверных функций имеет конкретное обозначение, отражающее тип устройства, например, lp в именах точек входа функций драйвера принтера. Так как принтер является устройством с символьной структурой ввода-вывода, то интерфейс с драйвером принтера осуществляется через таблицу cdevsw, в соответствующую запись которой подставляются адреса точек входа функций драйвера принтера: &lpopen, &lpclose, &lpwrite, &lpioctl.
В зависимости от типа периферийного устройства любое поле в структурах struct cdevsw и struct bdevsw может быть инициализировано адресом &nulldev, если соответствующая функция игнорируется, или заглушкой &nodev, когда требуемая функция отсутствует или является ошибочной, например, чтение для принтера.
Выбор таблицы bdevsw или cdevsw осуществляется по значениям битов с маской 060000 и 020000 в поле di_mode структуры индексного дескриптора struct dinode, определяющих блокориентированный или байториентированный интерфейс с устройством, имя специального файла которого задано для выполнения операций ввода-вывода. Выбор типа внешнего устройства и, соответственно обслуживающего драйвера, осуществляется по значению d_major в нулевом байте поля di_addr структуры индексного дескриптора struct dinode. Таблицы bdevsw и cdevsw имеют независимую нумерация типов устройств. Порядковый номер устройства данного типа, определяемый значением d_minor в 1-м байте поля di_addr, передается функциям драйвера как параметр.
Для обмена с блокориентированным устройством, физическая организация которого предполагает блочную структуру информации, хранящейся на нем, наиболее эффективна передача данных логическими блоками. Размер логического блока зависит от конкретной реализации OS UNIX и может различаться даже у компьютеров с одинаковой архитектурой. Для известных реализаций OS UNIX размер логического блока может колебаться от 512-ти байт до 8-ми килобайт, но всегда кратен величине 512 байт.
Наиболее важной особенностью блокориентированного интерфейса является буферизация передаваемых блоков данных на основе поддерживаемого ядром буферного пула (кэша), с емкостью от 20 до 40 буферов. Размер буферного пула задается при генерации OS UNIX. На время выполнения операций ввода-вывода буфера пула ассоциируются с драйверами внешних устройств по мере необходимости. Например, если выполняется операция ввода, то, прежде чем осуществить считывание очередного логического блока с внешнего устройства, производится проверка его наличия в буферном пуле. Реальное считывание происходит только при отрицательном результате этой проверки, для чего с драйвером ассоциируется неиспользованный буфер в пуле. Если в пуле нет свободного буфера, то занимается один из буферов, ассоциированных ранее с другим драйвером, который не используется в данный момент. Логическая схема реализации блокориентированного ввода-вывода на устройство с блочной структурой показана на следующем рисунке.
|
|
|
|||||
--------> | --------> | ||||||
<-------- | <-------- | ||||||
Управление блокориентированным устройством осуществляется при помощи набора драйверных функций, обрабатывающих блоки данных для различных устройств. Их основная задача обеспечить мультипрограммный доступ нескольких процессов к одному и тому же блоку некоторого устройства и увеличить эффективность обмена за счет хранения копий наиболее часто используемых блоков в буферном пуле.
Чтобы специфицировать организацию буферного пула в драйверных функциях, каждый буфер снабжен заголовком, который определяет организацию данных в нем через поля структуры struct buf. Структура struct buf имеет следующие поля:
b_flags - флаги состояния буфера и направления передачи данных; b_bufsize - размер буфера; b_un - адрес данных в буфере; b_blkno - номер начала блока данных на устройстве ввода-вывода; b_bcount - число байтов, которые требуется передать из/на устройство ввода-вывода; b_resid - число байт, не переданных из-за ошибки; b_dev - тип и номер устройства ввода-вывода; av_forw,
av_back- указатели 2-х связного списка свободных буферов пула; b_forw,
b_back- указатели 2-х связного списка буферов пула, ассоциированных с устройством ввода-вывода.
Адрес структуры struct buf используется драйверной функцией XXstrategy для реализации требуемой стратегии (чтение или запись) ввода-вывода данных через буфера пула. Необходимое направление передачи данных задается флагом B_READ или B_WRITE в поле b_flags структуры struct buf. Состояние буфера, для которого ожидается или выполняется ввод-вывод, фиксируется флагом B_BUSY. Этот флаг блокирует использование буфера в других процессах. После завершения обмена в поле b_flags заголовка буфера устанавливается флаг состояния B_DONE или B_ERROR, если обнаружены ошибки ввода-вывода. Тип и номер устройства ввода-вывода, с которым ассоциирован каждый буфер пула, задает поле b_dev в структуре struct buf его заголовка, где записаны мажор и минор устройства. Буфера, ассоциированные с каждым устройством ввода-вывода, для которых активизированы операции ввода-вывода, упорядочены в 2-х связный список через поля b_forw и b_back структуры struct buf. Если данные в любом ассоциированном буфере устарели, то в поле b_flags его заголовка устанавливается флаг B_AGE, чтобы поместить буфер в очередь свободных блоков. Очередь свободных буферов, также организована в 2-х связный список через поля av_forw и av_back структуры struct buf.
Таким образом, доступ к блокориентированным устройствам в OS UNIX осуществляется через дополнительный слой программного интерфейса, который выполняет буферизацию блоков данных. Драйвер ввода-вывода осуществляет только преобразование логического номера блока в его физический адрес на внешнем устройстве, вычисляя, например, для жесткого диска номер цилиндра, сектора и головки накопителя. К функциям драйвера относится также передача блоков между буферным пулом и внешним устройством. Взаимодействие буферного пула с файловой системой OS UNIX обеспечивается средствами ядра.
Использование механизма буферизации повышает эффективность обмена с внешним устройством за счет сокращения числа реальных обращений к нему. Однако, при этом возникает ряд проблем.
Первая проблема связана с возможным нарушением корректности файловой системы при аппаратных сбоях из-за наличия временного несоответствия содержания резидентных в RAM системных таблиц, отражающих текущее состояние файловой системы, и их образов на внешнем устройстве. Эта проблема решается периодическим выполнением системного вызова sync следящим процессом update в многопользовательском или вручную, в монопольном режиме функцонирования OS UNIX. Системный вызов sync очищает буферный пул, выталкивая данные из его буферов в нужном направлении, восстанавливая соответствие резидентных таблиц файловой системы и их образов на внешнем носителе. Если нарушение файловой системы все же произошло, то ее целостность может быть восстановлена командой fsck.
Вторая проблема буферизации заключается в невозможности программного анализа ошибок вывода, поскольку контролю подвергаются только операция записи в буферный пул, которая завершается успешно при наличии свободных буферов, даже если исчерпан список свободных блоков файловой системы. Информация об ошибках подобного рода будет отражаться только на системной консоле.
Третья проблема буферизации связана с тем, что порядок заполнения буферов пула и последовательность выборки логических блоков из пула не обязаны совпадать, так как эти процедуры выполняются программными средствами разного уровня, между которыми нет синхронизации. Указанное несоответствие может иметь негативные последствия при некорректном построении процедур параллельного чтения и записи данных одного файла в прикладной программе.
Байториентированный интерфейс ввода-вывода оказывается наиболее эффективным при организации обмена данными с внешним устройством, которое ориентировано на обработку информации, имеющей символьную структуру. В этом случае драйве осуществляет передачу данных между прикладной программой и внешним устройством без использования буферного пула, но через очереди ввода-вывода, память под элементы которых динамически распределяется в адресном пространстве ядра. Для каждого драйвера максимальная длина очереди ограничена некоторым пределом, величина которого определяется быстродействием устройства. Драйверы байториентированных устройств имеют 2-х уровневую логическую структуру, которую иллюстрирует следующий рисунок.
|
||||||
passc(c) | cpass() | |||||
|
||||||
getc(&q) | putc(c,&q) | |||||
|
|
|||||
putc(c,&q) | getc(&q) | |||||
|
||||||
|
Верхний уровень управления драйвера функционирует синхронно с прикладной программой. Нижний уровень управления драйвера функционирует синхронно с аппаратурой внешнего устройства. Обмен данными между уровнями осуществляется через очереди ввода-вывода. В результате, функционирование обоих уровней осуществляется асинхронно по отношению друг к другу.
При выполнении байториентированного вывода, данные, которые генерирует прикладная программа, последовательно поступают в очередь вывода драйвера после завершения системного вызова вывода. После заполнения очереди модулями синхронного управления процесс выполнения прикладной программы принудительно приостанавливается. Непосредственный вывод данных из очереди на внешнее устройство обеспечивает нижний уровень управления драйвера на основе механизма обработки аппаратных прерываний.
При выполнении байториентированного ввода данные передаются в очередь ввода модулями нижнего уровня управления драйвера на основе механизма обработки аппаратных прерываний. Из очереди ввода информация последовательно копируется в область данных прикладной программы системным вызовом ввода и удаляется из очереди модулями синхронного управления драйвера. Если очередь ввода пуста при наличии запроса ввода в прикладной программе, то в зависимости от установленного режима работы устройства, прикладная программа может либо перейти в состояние ожидания ввода, либо завершить операцию ввода. Необходимый режим работы устройства может программироваться системным вызовом ioctl.
Работу с очередями ввода-вывода обеспечивают две драйверные функции: putc и getc. Функция putc(c,&q) помещает байт в очередь, а функция getc(&q) выбирает байт из очереди. В сигнатуре аргументов этих функций "c" обозначает байт данных, а "&q" - адрес очереди, к которой происходит обращение. Драйверные очереди ввода-вывода имеют стандартный заголовок, заданный структурой struct clist, которая имеет следующие поля:
Функция getc возвращает код (-1), когда очередь ввода пуста. Функция putc возвращает код (-1) при переполнении очереди вывода. Остановку процесса выполнения прикладной программы в обоих случаях обеспечивает драйверная функция sleep. Разрешение продолжить приостановленный процесс обеспечивает драйверная функция wakeup.
Извлечение данных из области памяти процесса выполнения прикладной программы перед перемещением их в очередь вывода обеспечивает драйверная функция cpass(). Обратную операцию передачи данных в память процесса после из очереди ввода выполняет драйверная функция passc(c).
Основная прблема байториентированного интерфейса связана с переполнением очередей ввода-вывода из-за недостаточно высокого быстродействия либо прикладной программы, либо внешнего устройства. Указанная проблема обычно проявляется при интенсивной реализации операций ввода-вывода. Частичное решение проблемы быстродействия может быть достигнуто на основе синхронизации обоих уровней управления драйвером путем программной настройки системным вызовом ioctl или командой stty для терминалов.
Дополнительно к двум основным интерфейсам ввода-вывода OS UNIX позволяет осуществлять байториентированный обмен с некоторыми блокориентированными устройствами, реализуя так называемый "прозрачный" (raw) режим доступа. Прозрачный интерфейс с устройством блочной структуры является особенно целесообразным, когда необходимо осуществить обмен данными с внешним устройством в системно-независимом формате или копирование информации "байт в байт" (например, командой dd), игнорируя наличие какой-либо логической структуры данных.
Основная особенность прозрачного ввода-вывода состоит в том, что передача данных между прикладной программой и внешним устройством происходит без промежуточного накопления данных в буферном пуле блокориентированного интерфейса или в очередях ввода-вывода драйвера байториентированного устройства. При этом длина передачи может быть произвольной и ограничивается только архитектурой компьютера. Очевидно, что все рассмотренные выше проблемы буферизации блокориентированного ввода-вывода в прозрачном режиме будут автоматически сняты. Однако, мобильность программ, работающих с прозрачным интерфейсом, существенно хуже, а быстродействие - ниже, чем в случае использования канонического способа организации блокориентированного ввода-вывода.
More | Less | Title | Contents |