

6. Системные вызовы UNIX для управления вводом/выводом
Для доступа (т.е. для получения возможности последующего выполнения операций ввода/вывода) к файлу любого вида (включая специальные файлы) пользовательский процесс должен выполнить предварительное подключение к файлу с помощью одного из системных вызовов open, creat, dup или pipe. Программные каналы и соответствующие системные вызовы мы рассмотрим в п. 3.4.3, а пока несколько более подробно, чем в п. 2.3.3, рассмотрим другие "инициализирующие" системные вызовы.
Последовательность действий системного вызова open (pathname, mode) следующая:
анализируется непротиворечивость входных параметров (главным образом, относящихся к флагам режима доступа к файлу);
выделяется или находится пространство для описателя файла в системной области данных процесса (u-области);
в общесистемной области выделяется или находится существующее пространство для размещения системного описателя файла (структуры file);
производится поиск в архиве файловой системы объекта с именем "pathname" и образуется или обнаруживается описатель файла уровня файловой системы (vnode в терминах UNIX V System 4);
выполняется связывание vnode с ранее образованной структурой file.
Системные вызовы open и creat (почти) функционально эквивалентны. Любой существующий файл можно открыть с помощью системного вызова creat, и любой новый файл можно создать с помощью системного вызова open. Однако, применительно к системному вызову creat мы должны подчеркнуть, что в случае своего естественного применения (для создания файла) этот системный вызов создает новый элемент соответствующего каталога (в соответствии с заданным значением pathname), а также создает и соответствующим образом инициализирует новый i-узел.
Наконец, системный вызов dup (duplicate - скопировать) приводит к образованию нового дескриптора уже открытого файла. Этот специфический для ОС UNIX системный вызов служит исключительно для целей перенаправления ввода/вывода (см. п. 2.1.8). Его выполнение состоит в том, что в u-области системного пространства пользовательского процесса образуется новый описатель открытого файла, содержащий вновь образованный дескриптор файла (целое число), но ссылающийся на уже существующую общесистемную структуру file и содержащий те же самые признаки и флаги, которые соответствуют открытому файлу-образцу.
Другими важными системными вызовами являются системные вызовы read и write. Системный вызов read выполняется следующим образом:
в общесистемной таблице файлов находится дескриптор указанного файла, и определяется, законно ли обращение от данного процесса к данному файлу в указанном режиме;
на некоторое (короткое) время устанавливается синхронизационная блокировка на vnode данного файла (содержимое описателя не должно изменяться в критические моменты операции чтения);
выполняется собственно чтение с использованием старого или нового механизма буферизации, после чего данные копируются, чтобы стать доступными в пользовательском адресном пространстве.
Операция записи выполняется аналогичным образом, но меняет содержимое буфера буферного пула.
Системный вызов close приводит к тому, что драйвер обрывает связь с соответствующим пользовательским процессом и (в случае последнего по времени закрытия устройства) устанавливает общесистемный флаг "драйвер свободен".
Наконец, для специальных файлов поддерживается еще один "специальный" системный вызов ioctl. Это единственный системный вызов, который обеспечивается для специальных файлов и не обеспечивается для остальных разновидностей файлов. Фактически, системный вызов ioctl позволяет произвольным образом расширить интерфейс любого драйвера. Параметры ioctl включают код операции и указатель на некоторую область памяти пользовательского процесса. Всю интерпретацию кода операции и соответствующих специфических параметров проводит драйвер.
Естественно, что поскольку драйверы главным образом предназначены для управления внешними устройствами, программный код драйвера должен содержать соответствующие средства для обработки прерываний от устройства. Вызов индивидуальной программы обработки прерываний в драйвере происходит из ядра операционной системы. Подобным же образом в драйвере может быть объявлен вход "timeout", к которому обращается ядро при истечении ранее заказанного драйвером времени (такой временной контроль является необходимым при управлении не слишком интеллектуальными устройствами).
Общая схема интерфейсной организации драйверов показана на рисунке 3.5. Как показывает этот рисунок, с точки зрения интерфейсов и общесистемного управления различаются два вида драйверов - символьные и блочные. С точки зрения внутренней организации выделяется еще один вид драйверов - потоковые (stream) драйверы (мы уже упоминали о потоках в п. 2.7.1). Однако по своему внешнему интерфейсу потоковые драйверы не отличаются от символьных.
Системный вызов fcntl был введен для управления уже открытыми файлами. Это довольно странная конструкция, которая может выполнять разные функции.
Описание.
#include
#include
#include
int fcntl(int filedes, int cmd, ...);
Системный вызов fcntl работает с открытым файлом, заданным дескриптором файла filedes. Конкретная выполняемая функция задается выбором одного из значений параметра cmd из файла. Тип третьего параметра зависит от значения параметра cmd. Если вызов fcntl используется для установки флагов статуса файла, тогда третий параметр будет целым числом. Если же вызов fcntl будет использоваться для блокировки файла, то третий параметр будет указателем на структуру lock. Иногда третий параметр вообще не используется.
При задании параметра F_GETFL вызов fcntl возвращает текущие флаги статуса файла, установленные вызовом open.
Например:
int arg;
...
arg = fcntl(fd, F_GETFL);
Значение F_SETFL используется для переустановки связанных с файлом флагов статуса. Новые флаги задаются в третьем аргументе вызова fcntl. При этом могут быть установлены только некоторые флаги.
int open(const char *pathname, int flags, [mode_t mode]);
Первый аргумент, pathname, является указателем на строку маршрутного имени открываемого файла. Значение pathname может быть абсолютным путем. Данный путь задает положение файла по отношению к корневому каталогу. Аргумент pathname может также быть относительным путем, задающим маршрут от текущего каталога к файлу. В общем случае, если один из аргументов системного вызова или библиотечной процедуры - имя файла, то в качестве него можно задать любое допустимое маршрутное имя файла Unix.
Второй аргумент системного вызова flags имеет целочисленный тип и определяет метод доступа. Параметр flags принимает одно из значений, заданных постоянными в заголовочном файле при помощи директивы препроцессора #define.
Последовательность действий системного вызова open (pathname, mode) следующая:
анализируется непротиворечивость входных параметров (главным образом, относящихся к флагам режима доступа к файлу);
выделяется или находится пространство для описателя файла в системной области данных процесса (u-области);
в общесистемной области выделяется или находится существующее пространство для размещения системного описателя файла (структуры file);
производится поиск в архиве файловой системы объекта с именем "pathname" и образуется или обнаруживается описатель файла уровня файловой системы (vnode в терминах UNIX V System 4);
выполняется связывание vnode с ранее образованной структурой file.
Системные вызовы open и creat (почти) функционально эквивалентны. Любой существующий файл можно открыть с помощью системного вызова creat, и любой новый файл можно создать с помощью системного вызова open. Однако, применительно к системному вызову creat мы должны подчеркнуть, что в случае своего естественного применения (для создания файла) этот системный вызов создает новый элемент соответствующего каталога (в соответствии с заданным значением pathname), а также создает и соответствующим образом инициализирует новый i-узел.
Наконец, системный вызов dup (duplicate - скопировать) приводит к образованию нового дескриптора уже открытого файла. Этот специфический для ОС UNIX системный вызов служит исключительно для целей перенаправления ввода/вывода (см. п. 2.1.8). Его выполнение состоит в том, что в u-области системного пространства пользовательского процесса образуется новый описатель открытого файла, содержащий вновь образованный дескриптор файла (целое число), но ссылающийся на уже существующую общесистемную структуру file и содержащий те же самые признаки и флаги, которые соответствуют открытому файлу-образцу.
Другими важными системными вызовами являются системные вызовы read и write. Системный вызов read выполняется следующим образом:
в общесистемной таблице файлов находится дескриптор указанного файла, и определяется, законно ли обращение от данного процесса к данному файлу в указанном режиме;
на некоторое (короткое) время устанавливается синхронизационная блокировка на vnode данного файла (содержимое описателя не должно изменяться в критические моменты операции чтения);
выполняется собственно чтение с использованием старого или нового механизма буферизации, после чего данные копируются, чтобы стать доступными в пользовательском адресном пространстве.
Операция записи выполняется аналогичным образом, но меняет содержимое буфера буферного пула.
Системный вызов close приводит к тому, что драйвер обрывает связь с соответствующим пользовательским процессом и (в случае последнего по времени закрытия устройства) устанавливает общесистемный флаг "драйвер свободен".
Наконец, для специальных файлов поддерживается еще один "специальный" системный вызов ioctl. Это единственный системный вызов, который обеспечивается для специальных файлов и не обеспечивается для остальных разновидностей файлов. Фактически, системный вызов ioctl позволяет произвольным образом расширить интерфейс любого драйвера. Параметры ioctl включают код операции и указатель на некоторую область памяти пользовательского процесса. Всю интерпретацию кода операции и соответствующих специфических параметров проводит драйвер.
Естественно, что поскольку драйверы главным образом предназначены для управления внешними устройствами, программный код драйвера должен содержать соответствующие средства для обработки прерываний от устройства. Вызов индивидуальной программы обработки прерываний в драйвере происходит из ядра операционной системы. Подобным же образом в драйвере может быть объявлен вход "timeout", к которому обращается ядро при истечении ранее заказанного драйвером времени (такой временной контроль является необходимым при управлении не слишком интеллектуальными устройствами).
Общая схема интерфейсной организации драйверов показана на рисунке 3.5. Как показывает этот рисунок, с точки зрения интерфейсов и общесистемного управления различаются два вида драйверов - символьные и блочные. С точки зрения внутренней организации выделяется еще один вид драйверов - потоковые (stream) драйверы (мы уже упоминали о потоках в п. 2.7.1). Однако по своему внешнему интерфейсу потоковые драйверы не отличаются от символьных.
Системный вызов fcntl был введен для управления уже открытыми файлами. Это довольно странная конструкция, которая может выполнять разные функции.
Описание.
#include
#include
#include
int fcntl(int filedes, int cmd, ...);
Системный вызов fcntl работает с открытым файлом, заданным дескриптором файла filedes. Конкретная выполняемая функция задается выбором одного из значений параметра cmd из файла
При задании параметра F_GETFL вызов fcntl возвращает текущие флаги статуса файла, установленные вызовом open.
Например:
int arg;
...
arg = fcntl(fd, F_GETFL);
Значение F_SETFL используется для переустановки связанных с файлом флагов статуса. Новые флаги задаются в третьем аргументе вызова fcntl. При этом могут быть установлены только некоторые флаги.
int open(const char *pathname, int flags, [mode_t mode]);
Первый аргумент, pathname, является указателем на строку маршрутного имени открываемого файла. Значение pathname может быть абсолютным путем. Данный путь задает положение файла по отношению к корневому каталогу. Аргумент pathname может также быть относительным путем, задающим маршрут от текущего каталога к файлу. В общем случае, если один из аргументов системного вызова или библиотечной процедуры - имя файла, то в качестве него можно задать любое допустимое маршрутное имя файла Unix.
Второй аргумент системного вызова flags имеет целочисленный тип и определяет метод доступа. Параметр flags принимает одно из значений, заданных постоянными в заголовочном файле