Андрей Робачевский - Операционная система UNIX
Рис. 2.8. Чтение файла с использованием нескольких буферов
Функции write(2) и writev(2)
Функции write(2) и writev(2) очень похожи на функции read(2) и readv(2), но используются для записи данных в файл. Функции имеют следующий вид:
#include <unistd.>
ssize_t write(int fildes, void *buf, size_t nbyte);
#include <sys/types.h>
#include <sys/uio.h>
ssize_t writev(int fildes, struct iovec *iov, int iovcnt);
Аргументы, передаваемые функции write(2), указывают, что следует записать nbyte байт в файл, связанный с дескриптором fildes, начиная с текущего значения файлового указателя. Данные для записи находятся в буфере приложения, указанном аргументом buf. После завершения операции значение файлового указателя будет увеличено на nbyte.
Аналогично функции readv(2), функция writev(2) позволяет выполнить iovcnt последовательных операций записи за одно обращение к writev(2).
Такая операция ввода/вывода получила название gather (собирать), а функции ввода/вывода, использующие набор буферов, — общее название scatter-gather.
Функция pipe(2)
Функция pipe(2) служит для создания однонаправленного (симплексного) канала (также называемого анонимным каналом) обмена данными между двумя родственными процессами. Дело в том, что только родственные процессы (например, родительский и дочерний) имеют возможность получить доступ к одному и тому же каналу. Этот аспект станет более понятным в ходе обсуждения в разделе "Создание и управление процессами" далее в этой главе. Функция имеет вид:
#include <unistd.h>
int pipe(int fildes[2]);
Функция возвращает два файловых дескриптора в массиве fildes[], причем fildes[0] служит для чтения данных из канала, a fildes[1] — для записи данных в канал.
Каналы являются одним из способов организации межпроцессного взаимодействия и будут подробно рассмотрены в главе 3. В качестве примера использования pipe(2) можно привести возможность командного интерпретатора — создание программных каналов, рассмотренное в главе 1.
Отметим, что буферизация данных в канале стандартно осуществляется путем выделения дискового пространства в структуре файловой системы. Таким образом, чтение и запись в канал связаны с дисковым вводом/выводом, что, безусловно, сказывается на производительности этого механизма. Современные операционные системы наряду с более совершенными средствами межпроцессного взаимодействия предлагают и более эффективные механизмы каналов. Так, например, SCO UNIX (OpenServer 5.0) обеспечивает работу каналов через специальную файловую систему — HPPS (High Performance Pipe System). С помощью HPPS данные буферизуются в оперативной памяти, что существенно ускоряет операции записи и чтения.
Функция fcntl(2)
После открытия файла и получения ссылки на него в виде файлового дескриптора процесс может производить различные файловые операции. Функция fcntl(2) позволяет процессу выполнить ряд действий с файлом, используя его дескриптор, передаваемый в качестве первого аргумента:
#include <fcntl.h>
int fcntl (int fildes, int cmd, ...);
Функция fcntl(2) выполняет действие cmd с файлом, а возможный третий аргумент зависит от конкретного действия:
F_DUPFD Разместить новый файловый дескриптор, значение которого больше или равно значению третьего аргумента. Новый файловый дескриптор будет указывать на тот же открытый файл, что и fildes. Действие аналогично вызову функции dup(2) или dup2(2): fddup = fcntl(fd, F_DUPFD, fildes2) F_GETFD Возвратить признак сохранения дескриптора при запуске новой программы (выполнении системного вызова exec(2)) — флаг close-on-exec (FD_CLOEXEC). Если флаг установлен, то при вызове exec(2) файл, ассоциированный с данным дескриптором, будет закрыт F_SETFD Установить флаг close-on-exec согласно значению, заданному третьим аргументом F_GETFL Возвратить режим доступа к файлу, ассоциированному с данным дескриптором. Флаги, установленные в возвращаемом значении, полностью соответствуют режимам открытия файла, задаваемым функции open(2). Их значения приведены в табл. 2.8. Рассмотрим пример: oflags = fcntl(fd, F_GETFL, 0); /* Выделим биты, определяющие режим доступа */ accbits = oflags & O_ACCMODE; if (accbits == O_RDONLY) printf("Файл открыт только для чтенияn"); else if (accbits == O_WRONLY) printf("Файл открыт только для записиn"); else if (accbits == O_RDWR) printf("Файл открыт для чтения и записиn"); F_SETFL Установить режим доступа к файлу согласно значению, переданному в третьем аргументе. Могут быть изменены только флаги O_APPEND, O_NONBLOCK, O_SYNC и O_ASYNC. F_GETLK Проверить существование блокирования записи файла. Блокирование записи, подлежащее проверке, описывается структурой flock, указатель на которую передается в качестве третьего аргумента. Если существующие установки не позволяют выполнить блокирование, определенное структурой flock, последняя будет возвращена с описанием текущего блокирования записи. Данная команда не устанавливает блокирование, а служит для проверки его возможности. Более подробно блокирование записей описано в главе 4, в разделе "Блокирование доступа к файлу". F_SETLK Установить блокирование записи файла. Структура flock описывает блокирование, и указатель на нее передается в качестве третьего аргумента. При невозможности блокирования fcntl(2) возвращается С ошибкой EACCESS или EAGAIN. F_SETLKW Аналогично предыдущему, но при невозможности блокирования по причине уже существующих блокировок, процесс переходит в состояние сна, ожидая, пока последние будут освобождены. Последняя буква W в названии действия означает wait (ждать).Стандартная библиотека ввода/вывода
Функции, которые мы только что рассмотрели представляют интерфейс ввода/вывода между приложениями и ядром операционной системы. Хотя их использование напоминает использование библиотечных функций С, по существу они представляют собой лишь "обертки" к функциям ядра UNIX, фактически выполняющим операции ввода/вывода.
Однако программисты редко используют этот интерфейс низкого уровня, предпочитая возможности, предоставляемые стандартной библиотекой ввода/вывода. Функции этой библиотеки обеспечивают буферизованный ввод/вывод и более удобный стиль программирования. Для использования функций этой библиотеки в программу должен быть включен файл заголовков <stdio.h>. Эти функции входят в стандартную библиотеку С (libc.so или libc.a), которая, как правило, подключается по умолчанию на этапе связывания.
Вместо использования файлового дескриптора библиотека определяет указатель на специальную структуру данных (структура FILE), называемый потоком или файловым указателем. Стандартные потоки ввода/вывода обозначаются символическими именами stdin, stdout, stderr соответственно для потоков ввода, вывода и сообщений об ошибках. Они определены следующим образом:
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
Связь потоков стандартной библиотеки с файловыми дескрипторами приведена в табл. 2.9.
Таблица 2.9. Стандартные потоки и их дескрипторы
Файловый дескриптор Поток (указатель) Описание 0 stdin Стандартный ввод 1 stdout Стандартный вывод 2 stderr Сообщения об ошибкахТаблица 2.10. Наиболее употребительные функции стандартной библиотеки ввода/вывода
Функция Назначение fopen(3S) Открывает файл с указанным именем и возвращает файловый указатель, ассоциированный с данным файлом fclose(3S) Закрывает поток, освобождая буферы fflush(3S) Очищает буфер потока, открытого на запись getc(3S) Считывает символ из потока putc(3S) Записывает символ в поток gets(3S) Считывает строку из потока puts(3S) Записывает строку в поток fread(3S) Считывает указанное число байтов из потока (бинарный ввод) fwrite(3S) Записывает указанное число байтов в поток (бинарный вывод) fseek(3S) Позиционирует указатель в потоке printf(3S) Производит форматированный вывод scanf(3S) Производит форматированный ввод fileno(3S) Возвращает файловый дескриптор данного потокаВыбор между функциями интерфейса системных вызовов и стандартной библиотеки зависит от многих факторов, в частности, степени контроля ввода/вывода, переносимости программы, простоты. Взгляните, например, на следующие эквивалентные строки программы: