Функция OPEN весьма сходна с функцией FOPEN, рассмотрен- ной в главе 7, за исключением
того, что вместо возвращения указателя файла она возвращает дескриптор файла, который
яв- ляется просто целым типа INT.
INT FD; FD=OPEN(NAME,RWMODE);
Как и в случае FOPEN, аргумент NAME является символьной строкой, соответствующей
внешнему имени файла. Однако аргу- мент, определяющий режим доступа, отличен: RWMODE
равно: 0 - для чтения, 1 - для записи, 2 - для чтения и записи. Если происходит
какая-то ошибка, функция OPEN возвращает "-1"; в противном случае она возвращает
действительный дескриптор файла.
Попытка открыть файл, который не существует, является ошибкой. Точка входа CREAT
предоставляет возможность созда- ния новых файлов или перезаписи старых. В результате
обраще- ния
FD=CREAT(NAME,PMODE);
возвращает дескриптор файла, если оказалось возможным соз- дать файл с именем
NAME, и "-1" в противном случае. Если файл с таким именем уже существует, CREAT
усечет его до ну- левой длины; создание файла, который уже существует, не яв- ляется
ошибкой.
Если файл является совершенно новым, то CREAT создает его с определенным режимом
защиты, специфицируемым аргумен- том PMODE. В системе файлов на UNIX с файлом связываются
де- вять битов защиты информации, которые управляют разрешением на чтение, запись
и выполнение для владельца файла, для группы владельцев и для всех остальных пользователей.
Таким образом, трехзначное восьмеричное число наиболее удобно для спецификации разрешений.
Например, число 0755 свидетельству- ет о разрешении на чтение, запись и выполнение
для владельца и о разрешении на чтение и выполнение для группы и всех ос- тальных.
Для иллюстрации ниже приводится программа копирования одного файла в другой,
являющаяся упрощенным вариантом ути- литы CP системы UNIX. (Основное упрощение заключается
в том, что наш вариант копирует только один файл и что второй аргу- мент не должен
быть справочником).
#DEFINE NULL 0
#DEFINE BUFSIZE 512
#DEFINE PMODE 0644/*RW FOR OWNER,R FOR GROUP,OTHERS*/
MAIN(ARGC,ARGV) /*CP: COPY F1 TO F2*/
INT ARGC;
CHAR *ARGV[];
\(
INT F1, F2, N;
CHAR BUF[BUFSIZE];
IF (ARGC ! = 3)
ERROR("USAGE:CP FROM TO", NULL);
IF ((F1=OPEN(ARGV[1],0))== -1)
ERROR("CP:CAN'T OPEN %S", ARGV[1]);
IF ((F2=CREAT(ARGV[2],PMODE))== -1)
ERROR("CP: CAN'T CREATE %S", ARGV[2]);
WHILE ((N=READ(F1,BUF,BUFSIZE))>0)
IF (WRITE(F2,BUF,N) !=N)
ERROR("CP: WRITE ERROR", NULL);
EXIT(0);
\)
ERROR(S1,S2) /*PRINT ERROR MESSAGE AND DIE*/
CHAR *S1, S2;
\(
PRINTF(S1,S2);
PRINTF("\N");
EXIT(1);
\)
Существует ограничение (обычно 15 - 25) на количество файлов, которые программа
может иметь открытыми одновремен- но. В соответствии с этим любая программа, собирающаяся
ра- ботать со многими файлами, должна быть подготовлена к пов- торному использованию
дескрипторов файлов. Процедура CLOSE прерывает связь между дескриптором файла и
открытым файлом и освобождает дескриптор файла для использования с некоторым другим
файлом. Завершение выполнения программы через EXIT или в результате возврата из
ведущей программы приводит к закрытию всех открытых файлов.
Функция расцепления UNLINK (FILENAME) удаляет из системы файлов файл с именем
FILENAME ( из данного справочного фай- ла. Файл может быть сцеплен с другим справочником,
возможно, под другим именем - примеч.переводчика).
Упражнение 8-1
Перепишите программу CAT из главы 7, используя функции READ, WRITE, OPEN и CLOSE
вместо их эквивалентов из стандар- тной библиотеки. Проведите эксперименты для определения
от- носительной скорости работы этих двух вариантов.
8.4. Произвольный доступ - SEEK и LSEEK
Нормально при работе с файлами ввод и вывод осуществля- ется последовательно:
при каждом обращении к функциям READ и WRITE чтение или запись начинаются с позиции,
непосредствен- но следующей за предыдущей обработанной. Но при необходимос- ти файл
может читаться или записываться в любом произвольном порядке. Обращение к системе
с помощью функции LSEEK позво- ляет передвигаться по файлу, не производя фактического
чте- ния или записи. В результате обращения
LSEEK(FD,OFFSET,ORIGIN);
текущая позиция в файле с дескриптором FD передвигается на позицию OFFSET (смещение),
которая отсчитывается от места, указываемого аргументом ORIGIN (начало отсчета).
Последующее чтение или запись будут теперь начинаться с этой позиции. Аргумент OFFSET
имеет тип LONG; FD и ORIGIN имеют тип INT. Аргумент ORIGIN может принимать значения
0,1 или 2, указывая на то, что величина OFFSET должна отсчитываться соответст- венно
от начала файла, от текущей позиции или от конца фай- ла. Например, чтобы дополнить
файл, следует перед записью найти его конец:
LSEEK(FD,0L,2);
чтобы вернуться к началу ("перемотать обратно"), можно напи- сать:
LSEEK(FD,0L,0);
обратите внимание на аргумент 0L; его можно было бы записать и в виде (LONG)
0.
Функция LSEEK позволяет обращаться с файлами примерно так же, как с большими
массивами, правда ценой более медлен- ного доступа. следующая простая функция, например,
считывает любое количество байтов, начиная с произвольного места в файле.
GET(FD,POS,BUF,N) /*READ N BYTES FROM POSITION POS*/
INT FD, N;
LONG POS;
CHAR *BUF;
\(
LSEEK(FD,POS,0); /*GET TO POS*/
RETURN(READ(FD,BUF,N));
\)
В более ранних редакциях, чем редакция 7 системы UNIX, основная точка входа в
систему ввода-вывода называется SEEK. Функция SEEK идентична функции LSEEK, за исключением
того, что аргумент OFFSET имеет тип INT, а не LONG. в соответствии с этим, поскольку
на PDP-11 целые имеют только 16 битов, ар- гумент OFFSET, указываемый функции SEEK,
ограничен величиной 65535; по этой причине аргумент ORIGIN может иметь значения
3, 4, 5, которые заставляют функцию SEEK умножить заданное значение OFFSET на 512
(количество байтов в одном физическом блоке) и затем интерпретировать ORIGIN, как
если это 0, 1 или 2 соответственно. Следовательно, чтобы достичь произ- вольного
места в большом файле, нужно два обращения к SEEK: сначала одно, которое выделяет
нужный блок, а затем второе, где ORIGIN имеет значение 1 и которое осуществляет
передви- жение на желаемый байт внутри блока.
Упражнение 8-2
Очевидно, что SEEK может быть написана в терминалах LSEEK и наоборот. напишите
каждую функцию через другую.
8.5. Пример - реализация функций FOPEN и GETC
Давайте теперь на примере реализации функций FOPEN и GETC из стандартной библиотеки
подпрограмм продемонстрируем, как некоторые из описанных элементов объединяются
вместе.
Напомним, что в стандартной библиотеке файлы описыватся посредством указателей
файлов, а не дескрипторов. Указатель файла является указателем на структуру, которая
содержит несколько элементов информации о файле: указатель буфера, чтобы файл мог
читаться большими порциями; счетчик числа символов, оставшихся в буфере; указатель
следующей позиции символа в буфере; некоторые признаки, указывающие режим чте- ния
или записи и т.д.; дескриптор файла.
Описывающая файл структура данных содержится в файле STDIO.H, который должен
включаться (посредством #INCLUDE) в любой исходный файл, в котором используются
функции из стан- дартной библиотеки. Он также включается функциями этой биб- лиотеки.
В приводимой ниже выдержке из файла STDIO.H имена, предназначаемые только для использования
функциями библиоте- ки, начинаются с подчеркивания, с тем чтобы уменьшить веро-
ятность совпадения с именами в программе пользователя.
DEFINE _BUFSIZE 512
DEFINE _NFILE 20 /*FILES THAT CAN BE HANDLED*/
TYPEDEF STRUCT _IOBUF \(
CHAR *_PTR; /*NEXT CHARACTER POSITION*/
INT _CNT; /*NUMBER OF CHARACTERS LEFT*/
CHAR *_BASE; /*LOCATION OF BUFFER*/
INT _FLAG; /*MODE OF FILE ACCESS*/
INT _FD; /*FILE DESCRIPTOR*/
) FILE;
XTERN FILE _IOB[_NFILE];
DEFINE STDIN (&_IOB[0])
DEFINE STDOUT (&_IOB[1])
DEFINE STDERR (&_IOB[2])
DEFINE _READ 01 /* FILE OPEN FOR READING */
DEFINE _WRITE 02 /* FILE OPEN FOR WRITING */
DEFINE _UNBUF 04 /* FILE IS UNBUFFERED */
DEFINE _BIGBUF 010 /* BIG BUFFER ALLOCATED */
DEFINE _EOF 020 /* EOF HAS OCCURRED ON THIS FILE */
DEFINE _ERR 040 /* ERROR HAS OCCURRED ON THIS FILE */
DEFINE NULL 0
DEFINE EOF (-1)
DEFINE GETC(P) (--(P)->_CNT >= 0 \
? *(P)->_PTR++ & 0377 : _FILEBUF(P))
DEFINE GETCHAR() GETC(STDIN)
DEFINE PUTC(X,P) (--(P)->_CNT >= 0 \
? *(P)->_PTR++ = (X) : _FLUSHBUF((X),P))
DEFINE PUTCHAR(X) PUTC(X,STDOUT)
В нормальном состоянии макрос GETC просто уменьшает счетчик, передвигает указатель
и возвращает символ. (Если определение #DEFINE слишком длинное, то оно продолжается
с помощью обратной косой черты). Если однако счетчик становит- ся отрицательным,
то GETC вызывает функцию _FILEBUF, которая снова заполняет буфер, реинициализирует
содержимое структуры и возвращает символ. Функция может предоставлять переносимый
интерфейс и в то же время содержать непереносимые конструк- ции: GETC маскирует
символ числом 0377, которое подавляет знаковое расширение, осуществляемое на PDP-11,
и тем самым гарантирует положительность всех символов.
Хотя мы не собираемся обсуждать какие-либо детали, мы все же включили сюда определение
макроса PUTC, для того что- бы показать, что она работает в основном точно также,
как и GETC, обращаясь при заполнении буфера к функции _FLUSHBUF.
Теперь может быть написана функция FOPEN. Большая часть программы функции FOPEN
связана с открыванием файла и распо- ложением его в нужном месте, а также с установлением
битов признаков таким образом, чтобы они указывали нужное состоя- ние. Функция FOPEN
не выделяет какой-либо буферной памяти; это делается функцией _FILEBUF при первом
чтении из файла.
#INCLUDE <STDIO.H>
#DEFINE PMODE 0644 /*R/W FOR OWNER;R FOR OTHERS*/
FILE *FOPEN(NAME,MODE) /*OPEN FILE,RETURN FILE PTR*/
REGISTER CHAR *NAME, *MODE;
\(
REGISTER INT FD;
REGISTER FILE *FP;
IF(*MODE !='R'&&*MODE !='W'&&*MODE !='A') \(
FPRINTF(STDERR,"ILLEGAL MODE %S OPENING %S\N",
MODE,NAME);
EXIT(1);
\)
FOR (FP=_IOB;FP<_IOB+_NFILE;FP++)
IF((FP->_FLAG & (_READ \! _WRITE))==0)
BREAK; /*FOUND FREE SLOT*/
IF(FP>=_IOB+_NFILE) /*NO FREE SLOTS*/
RETURN(NULL);
IF(*MODE=='W') /*ACCESS FILE*/
FD=CREAT(NAME,PMODE);
ELSE IF(*MODE=='A') \(
IF((FD=OPEN(NAME,1))==-1)
FD=CREAT(NAME,PMODE);
LSEEK(FD,OL,2);
\) ELSE
FD=OPEN(NAME,0);
IF(FD==-1) /*COULDN'T ACCESS NAME*/
RETURN(NULL);
FP->_FD=FD;
FP->_CNT=0;
FP->_BASE=NULL;
FP->_FLAG &=(_READ \! _WRITE);
FP->_FLAG \!=(*MODE=='R') ? _READ : _WRITE;
RETURN(FP);
\)
Функция _FILEBUF несколько более сложная. Основная труд- ность заключается в
том, что _FILEBUF стремится разрешить доступ к файлу и в том случае, когда может
не оказаться дос- таточно места в памяти для буферизации ввода или вывода. ес- ли
пространство для нового буфера может быть получено обра- щением к функции CALLOC,
то все отлично; если же нет, то _FILEBUF осуществляет небуферизованный ввод/ вывод,
исполь- зуя отдельный символ, помещенный в локальном массиве.
#INCLUDE <STDIO.H>
_FILLBUF(FP) /*ALLOCATE AND FILL INPUT BUFFER*/
REGISTER FILE *FP;
(
STATIC CHAR SMALLBUF(NFILE);/*FOR UNBUFFERED 1/0*/
CHAR *CALLOC();
IF((FR->_FLAG&_READ)==0\!\!(FP->_FLAG&(EOF\!_ERR))\!=0
RETURN(EOF);
WHILE(FP->_BASE==NULL) /*FIND BUFFER SPACE*/
IF(FP->_FLAG & _UNBUF) /*UNBUFFERED*/
FP->_BASE=&SMALLBUF[FP->_FD];
ELSE IF((FP->_BASE=CALLOC(_BUFSIZE,1))==NULL)
FP->_FLAG \!=_UNBUF; /*CAN'T GET BIG BUF*/
ELSE
FP->_FLAG \!=_BIGBUF; /*GOT BIG ONE*/
FP->_PTR=FP->_BASE;
FP->_CNT=READ(FP->_FD, FP->_PTR,
FP->_FLAG & _UNBUF ? 1 : _BUFSIZE);
FF(--FP->_CNT<0) \(
IF(FP->_CNT== -1)
FP->_FLAG \! = _EOF;
ELSE
FP->_FLAG \! = _ ERR;
FP->_CNT = 0;
RETURN(EOF);
\)
RETURN(*FP->_PTR++ & 0377); /*MAKE CHAR POSITIVE*/
)
При первом обращении к GETC для конкретного файла счетчик оказывается равным
нулю, что приводит к обращению к _FILEBUF. Если функция _FILEBUF найдет, что этот
файл не от- крыт для чтения, она немедленно возвращает EOF. В противном случае она
пытается выделить большой буфер, а если ей это не удается, то буфер из одного символа.
При этом она заносит в _FLAG соответствующую информацию о буферизации.
Раз буфер уже создан, функция _FILEBUF просто вызывает функцию READ для его заполнения,
устанавливает счетчик и указатели и возвращает символ из начала буфера.
Единственный оставшийся невыясненным вопрос состоит в том, как все начинается.
Массив _IOB должен быть определен и инициализирован для STDIN, STDOUT и STDERR:
FILE _IOB[NFILE] = \(
(NULL,0,_READ,0), /*STDIN*/
(NULL,0,NULL,1), /*STDOUT*/
(NULL,0,NULL,_WRITE \! _UNBUF,2) /*STDERR*/ );
Из инициализации части _FLAG этого массива структур видно, что файл STDIN предназначен
для чтения, файл STDOUT - для записи и файл STDERR - для записи без использования
буфера.
Упражнение 8-3
Перепишите функции FOPEN и _FILEBUF, используя поля вместо явных побитовых операций.
Упражнение 8-4
Разработайте и напишите функции _FLUSHBUF и FCLOSE.
Упражнение 8-5
Стандартная библиотека содержит функцию
FSEEK(FP, OFFSET, ORIGIN)
которая идентична функции LSEEK, исключая то, что FP являет- ся указателем файла,
а не дескриптором файла. Напишите FSEEK. Убедитесь, что ваша FSEEK правильно согласуется
с бу- феризацией, сделанной для других функций библиотеки.
8.6. Пример - распечатка справочников