Опубликован: 05.07.2006 | Доступ: свободный | Студентов: 4680 / 885 | Оценка: 4.12 / 3.74 | Длительность: 18:59:00
Лекция 9:

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