Управление вводом и выводом

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

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

Некоторые функции используют для ввода-вывода данных стандартные файлы (устройства) ввода-вывода. Однако для большинства функций вы можете определять свои собственные файлы для ввода и вывода данных. Для многих функций можно использовать указатель на файл, т.е. на структуру, содержащую имя файла; для других вы можете использовать дескриптор файла (положительный целый идентификатор, присваиваемый файлу при его открытии).

Функции ввода-вывода, которые хранятся в библиотеке языка C (libc.a), выполняют потоковый ввод-вывод. Для доступа к этим функциям необходимо включить в программу файл stdio.h следующим образом:

#include <stdio.h>

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

  • Для макрокоманды нельзя установить контрольную точку с помощью программы dbx.
  • Макрокоманды обычно выполняются быстрее, чем функции, так как препроцессор заменяет макрокоманду на строки кода.
  • При компиляции макрокоманд создается более громоздкий объектный код.
  • При работе с функциями могут возникать побочные эффекты.

Файлы, команды и функции, применяемые для управления вводом-выводом, предоставляют следующие типы интерфейсов:

Низкий уровень
Низкоуровневый интерфейс предоставляет базовые функции открытия и закрытия файлов и устройств.
Поток
Потоковый интерфейс предоставляет функции чтения и записи для каналов и файлов FIFO.
Терминал
Интерфейс терминала обеспечивает форматированный вывод и буферизацию.
Асинхронный кабель
Асинхронный интерфейс обеспечивает одновременное выполнение операций ввода-вывода и обработки.
Язык ввода
Интерфейс языка ввода применяет команды lex и yacc для создания лексического анализатора, а также программу анализатора для интерпретации ввода-вывода.

Низкоуровневые интерфейсы ввода-вывода

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

Для чтения одной строки из стандартного ввода предназначена команда line. Другие операции низкоуровневого ввода-вывода выполняются следующими функциями:

open, openx или creat
Подготовка файла или другого объекта каталога для чтения или записи путем присвоения ему дескриптора файла
read, readx, readv или readvx
Чтение данных из открытого файла с указанным дескриптором
write, writex, writev или writevx
Запись данных в открытый файл с указанным дескриптором
close
Освобождение дескриптора файла

Функции open и creat помещают записи в три системные таблицы. В первую таблицу, выполняющую функции области данных (доступ к которой имеют функции чтения и записи) для процесса, записываются индексы дескрипторов файлов. Каждая запись в этой таблице содержит указатель на соответствующую запись во второй таблице.

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

В таблице файлов содержатся записи для всех экземпляров функции open или create в файле, а в таблицу i-узлов заносится только по одной записи для каждого файла.

Прим.: При обработке функций open или creat для конкретного файла система всегда вызывает функцию open для устройства, которая выполняет некоторые специальные операции (например, перематывает магнитную ленту или принимает сигнал DTR (сигнал готовности терминала) модема). Напротив, функция close используется системой только при закрытии файла последним процессом (т.е. при освобождении записи таблицы i-узлов файла). Это означает, что устройство может не зависеть от числа пользователей, за исключением случая, когда оно работает в монопольном режиме использования (в котором исключается его повторное открытие перед закрытием).

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

  • Пользовательский адрес целевой области ввода-вывода
  • Счетчик передаваемых байтов
  • Текущее положение в файле

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

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

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

Потоковые интерфейсы ввода-вывода

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

Потоковый ввода-вывод может быть организован в виде каналов или файлов FIFO ("первым вошел - первым вышел"). Файлы FIFO схожи с каналами, поскольку данные в них могут двигаться только в одном направлении (слева направо). Однако, в отличие от канала, файлу FIFO можно присваивать имя и к нему могут обращаться не связанные с ним процессы. Иногда файлы FIFO называют именованными каналами. Поскольку у файла FIFO есть имя, его можно открывать с помощью стандартной функции fopen. Процедура открытия канала сложнее: необходимо вызвать функцию pipe, возвращающую дескриптор файла, а затем с помощью стандартной функции ввода-вывода fdopen связать дескриптор открытого файла со стандартным потоком ввода-вывода.

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

Доступ к потоковым интерфейсам ввода-вывода можно получить с помощью следующих функций и макрокоманд:

fclose
Закрывает поток
feof, ferror, clearerr или fileno
Проверяет состояние потока
fflush
Записывает все буферизованные на данный момент символы из потока
fopen, freopen или fdopen
Открывает поток
fread или fwrite
Выполняет двоичный ввод
fseek, rewind, ftell, fgetpos или fsetpos
Перемещает указатель файла в потоке
getc, fgetc, getchar или getw
Извлекает символ или слово из входного потока
gets или fgets
Получает строку из потока
getwc, fgetwc или getwchar
Извлекает "широкий" символ из входного потока
getws или fgetws
Получает строку из потока
printf, fprintf, sprintf, wsprintf, vprintf, vfprintf, vsprintf или vwsprintf
Печатает форматированный вывод
putc, putchar, fputc или putw
Помещает символ или слово в поток
puts или fputs
Записывает строку в проток
putwc, putwchar или fputwc
Помещает символ или слово в поток
putws или fputws
Помещает строку "широких" символов в поток
scanf, fscanf, sscanf или wsscanf
Преобразует форматированный ввод
setbuf, setvbuf, setbuffer или setlinebuf
Выделяет буфер для потока
ungetc или ungetwc
Возвращает извлеченный символ обратно во входной поток

Терминальные интерфейсы ввода-вывода

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

Ниже перечислены функции, позволяющие включать или отключать определенные параметры терминала:

cfgetospeed, cfsetospeed, cfgetispeed или cfsetispeed
Считывает и устанавливает значение скорости передачи в бодах
ioctl
Выполняет функции управления терминалом
termdef
Запрашивает характеристики терминала
tcdrain
Ожидает завершения вывода
tcflow
Выполняет функции управления потоком
tcflush
Очищает указанную очередь
tcgetpgrp
Возвращает идентификатор интерактивной группы процессов
tcsendbreak
Передает сигнал прерывания по асинхронной последовательной линии
tcsetattr
Задает состояние терминала
ttylock, ttywait, ttyunlock или ttylocked
Управляет функциями блокировки терминала
ttyname и isatty
Получает имя терминала
ttyslot
Выполняет поиск участка в файле utmp для текущего пользователя

Асинхронные интерфейсы ввода-вывода

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

Можно установить режим, при котором ядро будет уведомлять процесс, выполняющий асинхронный ввод-вывод, о готовности определенного дескриптора к вводу-выводу (так называемый ввод-вывод по сигналу). При работе с LEGACY AIO, для уведомления пользовательского процесса ядро использует сигнал SIGIO. При работе с POSIX AIO, для уведомления пользовательского процесса ядро использует структуру sigevent. Применяются следующие сигналы: SIGIO, SIGUSR1 и SIGUSR2.

Для работы в режиме асинхронного ввода-вывода процесс должен выполнить следующие действия:

  1. Установить обработчик сигнала SIGIO. Этот шаг необходим только в том случае, если установлен режим уведомления с помощью сигнала.
  2. Установить ИД процесса или ИД группы процессов, которые будут принимать сигналы SIGIO. Этот шаг необходим только в том случае, если установлен режим уведомления с помощью сигнала.
  3. Включить режим асинхронного ввода-вывода. Обычно решение о том, включать ли (загружать ли) этот режим, принимает системный администратор. Включение режима происходит при запуске системы.

Предусмотрены следующие функции асинхронного ввода-вывода:

aio_cancel
Отменяет один или несколько ожидающих запросов асинхронного ввода-вывода
aio_error
Получает информацию о состоянии ошибки запроса асинхронного ввода-вывода
aio_fsync
Синхронизирует асинхронные файлы.
aio_nwait
Приостанавливает вызывающий процесс до выполнения определенного числа запросов ввода-вывода.
aio_read
Считывает информацию в асинхронном режиме из файла с указанным дескриптором
aio_return
Определяет код возврата запроса асинхронного ввода-вывода
aio_suspend
Переводит вызывающий процесс в состояние ожидания, пока не завершится один или несколько запросов асинхронного ввода-вывода
aio_write
Записывает информацию в асинхронном режиме в файл с указанным дескриптором
lio_listio
Инициализирует список запросов асинхронного ввода-вывода посредством одного вызова
poll и select
Проверяет состояние ввода-вывода нескольких дескрипторов файлов и очередей сообщений

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

poll.h
Определяет структуры и флаги, используемые функцией poll
aio.h
Определяет структуры и флаги, используемые функциями aio_read, aio_write и aio_suspend