|
|
Интерфейс системы UNIX
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;
extern 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((fp->_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,_WRITE,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 правильно согласуется с буферизацией, сделанной для других функций библиотеки.
