Тема 10. Механизмы ввода-вывода

Совокупность операций, при помощи которых программа читает и записывает файлы, называется вводом-выводом (input/output). Если программа читает и записывает файлы посредством прямого обращения к ядру операционной системы, то такой ввод-вывод называется низкоуровневым.

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

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

1. абстракция файла;

2. механизмы открытия и закрытия файлов;

3. механизмы чтения и записи файлов;

4. механизмы произвольного доступа к данным в файле.

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

Абстракция файла в стандартной библиотеке языка С реализуется через тип данных file. Для этого создается указатель, который в дальнейшем будет участвовать в файловых операциях:

FILE * myfile;

Особого рассмотрения требуют следующие глобальные экземпляры:

extern FILE * stdin;

extern FILE * stdout;

extern FILE * stderr;

Эта "троица" представляет собой стандартный ввод (stdin), стандартный вывод (stdout) и стандартный поток ошибок (stderr). Ввод-вывод в Linux построен на одной аксиоме, которая гласит, что любая операция чтения или записи данных сводится к файловому вводу-выводу. Таким образом, любое "нечто", способное принимать или отправлять данные (клавиатура, экран, аппаратные устройства, сетевые соединения и т. п.) можно представить в виде файла.

Стандартный ввод, стандартный вывод и стандартный поток ошибок — это виртуальные "порталы", через которые система взаимодействует с пользователем. Как правило, стандартный ввод связан с драйвером клавиатуры, а стандартный вывод и стандартный поток ошибок ассоциируются с дисплеем компьютера. Но можно, например, связать стандартный вывод с драйвером принтера (который тоже представлен в виде файла). В этом случае вы будете получать информацию не на экране, а на бумаге. И это не просто концепции, а реальность, которую можно воплотить в жизнь одной командой, наподобие этой:

$ bash 1>/dev/printer

Механизмы открытия и закрытия файлов в стандартной библиотеке языка С представлены следующими функциями:

FILE * fopen (const char * FLOCATION, const char * OPENJMODE);

FILE * freopen (const char * FLOCATION, const char * OPEN_MODE,FILE * FP);

FILE * fClose (FILE * FP);

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

int fgetc (FILE * FP);

int fputc (int byte, FILE * FP);

char * fgets (char * STR, int SIZE, FILE * FP);

int fputs (const char * STR, FILE * FP);

int fscanf (FILE * FP, const char * FMT,...);

int fprintf (FILE * FP, const char * FMT,...);

int vfscanf (FILE * FP, const char * FMT, va_list ap);

int vfprintf (FILE * FP, const char * FMT, va_list ap);

Файловый ввод-вывод предполагает, что чтение и запись данных осуществляются последовательно. Для этого к абстракции файла привязывается понятие текущей позиции ввода-вывода. Текущая позиция устанавливается в начало файла в момент его открытия. Операции чтения/записи смещают эту позицию вперед на количество прочитанных или записанных байтов. Для принудительного изменения текущей позиции используются механизмы произвольного доступа (random access). В стандартной библиотеке языка С произвольный доступ к данным осуществляется при помощи следующих механизмов:

* #define SEEK_SET 0

* «define SEEK_CUR 1

* «define SEEK_END 2

* typedef struct {...} fpos_t;

* int fseek (FILE * FP, long int OFFSET, int WHENCE);.

* int fgetpos (FILE * FP, fpos_t * POSITION);

* int fsetpos (FILE * FP, fpos_t * POSITION!;

* long int ftell (FILE * FP);

* void rewind (FILE * FP);

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

Программа mytail.с

#include <stdlib.h>

#include <stdio.h>

int main (int argc, char ** argv) {

FILE * infile;

int ch;

}

long int nback;

if (argc < 3) {

fprintf (stderr, "Too few arguments\n");

return 1; }

infile = fopen (argv[1], "r");

if (infile == NULL) {

fprintf (stderr, "Cannot open "

"input file (%s)\n", argv[1]); return 1; }

nback = abs (atoi (argv[2])); fseek (infile, 0, SEEK_END);

if (nback > ftell (infile))

fseek (infile, 0, SEEK_SET);

else

fseek (infile, -nback, SEEK_END);

while ((ch = fgetc (infile))!= EOF) fputc (ch, stdout);

fclose (infile); return 0;

Рассмотрим вкратце, что делает эта программа. Сначала создается абстракция файла— указатель infile. Функция fopen о открывает файл в режиме "только для чтения" (read only). Обратите внимание, что введенный размер "хвоста" может превышать размер файла. Поэтому в программу включен алгоритм проверки, который устанавливает текущую позицию ввода-вывода в начало файла, если "хвост" слишком велик.

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

Функция f close () закрывает файл. В этом процессе есть свои тонкости. Дело в том, что в Linux применяются механизмы буферизации файлового ввода-вывода. Это означает, что функция fputc (), например, реально записывает данные не в устройство ввода-вывода, а в буфер, находящийся в оперативной памяти. Реальная запись данных в устройство производится по усмотрению системы. Функция fclose о отправляет системе запрос на синхронизацию. Но следует понимать, что такой запрос лишь принимается на рассмотрение, но не гарантирует реальную запись данных.

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

int fflush (FILE * FP);

Существует еще один нюанс: в стандартной библиотеке языка С используется собственный механизм буферизации. Таким образом, f flush о отвечает лишь за отправку на запись собственных буферов, а заставить ядро синхронизировать данные средствами стандартной библиотеки языка С практически невозможно.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: