Материал этой лекции относится к интерфейсу между с-
Эта лекция делится на три основные части:
В лекции №7 мы имели дело с системным интерфейсом, который одинаков для
всего многообразия
В
В наиболее общем случае перед чтением из файла или записью в файл
необходимо сообщить системе о вашем намерении; этот процесс называется
"открытием" файла. Система выясняет, имеете ли вы право поступать таким образом
(существует ли этот файл? имеется ли у вас разрешение на обращение к нему?), и
если все в порядке, возвращает в read (5,...)
и write (6,...) в фортране). Вся информация об открытом файле содержится в
системе;
Для удобства выполнения обычных операций shell ") прогоняет
Пользователь shell
" < " и " > ":
prog <infile>outfile
В этом случае shell
изменит присваивание по умолчанию
дескрипторов файлов 0 и 1 с shell, а не
Самый низкий уровень read
и write.
Первым
n_read=read(fd,buf,n); n_written=write(fd,buf,n);
При каждом обращении возвращается счетчик байтов, указывающий фактическое
число переданных байтов. При чтении возвращенное число байтов может оказаться
меньше, чем запрошенное число. Возвращенное нулевое число байтов означает
Количество байтов, подлежащих чтению или записи, может быть совершенно
произвольным. Двумя самыми распространенными величинами являются "1", которая
означает передачу одного символа за обращение (т.е. без использования буфера), и
"512", которая соответствует физическому размеру блока на многих периферийных
устройствах. Этот последний размер будет наиболее эффективным, но даже
Объединив все эти факты, мы написали простую
#define bufsize 512 /*best size for pdp-11 unix*/
main() /*copy input to output*/
{
char buf[bufsize];
int n;
while((n=read(0,buf,bufsize))>0)
write(1,buf,n);
}
Если размер файла не будет кратен bufsize,
то при некотором обращении к read
будет возвращено меньшее число байтов, которые затем записываются с помощью write ; при следующем после этого обращении к read будет возвращен нуль.
Поучительно разобраться, как можно использовать read и write для
построения getchar, putchar и т.д.
Вот, например, вариант getchar,
осуществляющий
#define cmask 0377 /*for making char's > 0*/
getchar() /*unbuffered single character input*/
{
char c;
return((read(0,c,1)>0 ? c cmask : EOF);
}
char,
потому что read принимает
Второй вариант getchar осуществляет
#define cmask 0377 /*for making char's>0*/
#define bufsize 512
getchar() /*buffered version*/
{
static char buf[bufsize];
static char *bufp = buf;
static int n = 0;
if (n==0) { /*buffer is empty*/
n=read(0,buf,bufsize);
bufp = buf;
}
return((--n>=0) ? *bufp++ cmask : EOF);
}
Кроме случая, когда по умолчанию определены стандартные open
и creat.
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.
В системе файлов на
Для иллюстрации ниже приводится cp системы
#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 вместо их эквивалентов из
Нормально при работе с 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 системы seek. seek идентична lseek,
за исключением того, что offset
имеет int, а не long. В
соответствии с этим, поскольку на offset, указываемый seek,
origin
может иметь значения 3, 4, 5, которые заставляют seek
умножить заданное значение offset на 512
(количество байтов в одном физическом блоке) и затем интерпретировать origin, как если это 0, 1 или 2 соответственно.
Следовательно, чтобы достичь произвольного места в большом файле, нужно два
обращения к seek: сначала одно, которое выделяет
нужный блок, а затем второе, где origin имеет
значение 1 и которое осуществляет передвижение на желаемый
байт внутри блока.
Упражнение 8-2
Очевидно, что seek может быть написана
в lseek и наоборот.
напишите каждую
Давайте теперь на примере реализации 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, которое подавляет знаковое
расширение, осуществляемое на
Хотя мы не собираемся обсуждать какие-либо детали, мы все же включили сюда 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
правильно согласуется с буферизацией, сделанной для других
Иногда требуется другой вид взаимодействия с системой файлов - ls ("список справочника")
системы
Поскольку, по крайней мере, на системе ls нет ничего особенного;
она читает файл и выделяет нужные части из находящейся там информации.
Однако формат информации определяется системой, так что ls
должна знать, в каком виде все представляется в системе.
Мы это частично проиллюстрируем при написании fsize. fsize представляет
собой специальную форму ls, которая печатает размеры всех
файлов, указанных в fsize обращается сама
к себе рекурсивно. если же
Для начала дадим краткий обзор sys/dir.h, который содержит
#define dirsiz 14 /*max length of file name*/
struct direct /*structure of directory entry*/
{
ino_t _ino; /*inode number*/
char _name[dirsiz]; /*file name*/
};
"Тип" ino_t - это определяемый
посредством typedef unsigned, но это
не тот сорт информации, который помещают внутрь typedef. Полный набор
"системных" sys/types.h.
stat берет имя файла и
возвращает всю содержащуюся в I-узле
информацию об этом файле (или -1, если имеется ошибка). Таким образом, в
результате
struct stat stbuf; char *name; stat(name,stbuf);
stbuf наполняется информацией
из I-узла о файле с именем name. stat
информацию, находится в файле sys/stat.h и
выглядит следующим образом:
struct stat /*structure returned by stat*/
{
dev_t st_dev; /* device of inode */
ino_t st_ino; /* inode number */
short st_mode /* mode bits */
short st_nlink; / *number of links to file */
short st_uid; /* owner's user id */
short st_gid; /* owner's group id */
dev_t st_rdev; /* for special files */
off_t st_size; /* file size in characters */
time_t st_atime; /* time last accessed */
time_t st_mtime; /* time last modified */
time_t st_ctime; /* time originally created */
}
Большая часть этой информации объясняется в st.mode содержит набор флагов,
описывающих файл; для удобства sys/stat.h.
#define s_ifmt 0160000 /* type of file */ #define s_ifdir 0040000 /* directory */ #define s_ifchr 0020000 /* character special */ #define s_ifblk 0060000 /* block special */ #define s_ifreg 0100000 /* regular */ #define s_isuid 04000 /* set user id on execution */ #define s_isgid 02000 /* set group id on execution */ #define s_isvtx 01000 /*save swapped text after use*/ #define s_iread 0400 /* read permission */ #define s_iwrite 0200 /* write permission */ #define s_iexec 0100 /* execute permission */
Теперь мы в состоянии написать fsize. Если полученный от stat
режим указывает, что файл не является справочником, то его размер
уже под рукой и может быть напечатан непосредственно. Если же он оказывается
справочником, то мы должны обрабатывать этот справочник отдельно для каждого
файла; так как справочник может в свою очередь содержать подсправочники, этот
процесс обработки является рекурсивным.
Как обычно, ведущая fsize в большой буфер.
#include <stdio.h>
#include <sys/types.h>> /*typedefs*/
#include <sys/dir.h> /*directory entry structure*/
#include <sys/stat.h> /*structure returned by stat*/
#define bufsize 256
main(argc,argv) /*fsize:print file sizes*/
char *argv[];
{
char buf[bufsize];
if(argc==1) { /*default:current directory*/
strcpy(buf,".");
fsize(buf);
} else
while(--argc>0) {
strcpy(buf,*++argv);
fsize(buf);
}
}
fsize печатает размер файла.
Если однако файл оказывается справочником, то fsize сначала вызывает directory для обработки всех
указанных в нем файлов. Обратите внимание на
использование имен флагов s_ifmt
и _ifdir из файла stat.h.
fsize(name) /*print size for name*/
char *name;
{
struct stat stbuf;
if(stat(name,stbuf)== -1) {
fprintf(stderr,"fsize:can't find %s\n",name);
return;
}
if((stbuf.st_mode s_ifmt)==s_ifdir)
directory(name);
printf("%8ld %s\n",stbuf.st_size,name);
}
directory является самой сложной.
Однако значительная ее часть связана с созданием для
обрабатываемого в данный момент файла его полного имени,
по которому можно восстановить путь в дереве.
directory(name) /*fsize for all files in name*/
char *name;
{
struct direct dirbuf;
char *nbp, *nep;
int i, fd;
nbp=name+strlen(name);
*nbp++='/'; /*add slash to directory name*/
if(nbp+dirsiz+2>=name+bufsize) /*name too long*/
return;
if((fd=open(name,0))== -1)
return;
while(read(fd,(char *)dirbuf,sizeof(dirbuf))>0) \(
if(dirbuf.d_ino==0) /*slot not in use*/
continue;
if(strcmp (dirbuf.d_name,".")==0
|| strcmp(dirbuf.d_name,"..")==0
continue; /*skip self and parent*/
for (i=0,nep=nbp;i<dirsiz;i++)
*nep++=dirbuf.d_name[i];
*nep++='\0';
fsize(name);
}
close(fd);
*--nbp='\0'; /*restore name*/
}
Если некоторая дыра в справочнике в настоящее время не используется
(потому что файл был удален), то в соответствующее I-узловое число равно нулю,
и эта позиция пропускается. Каждый справочник также содержит запись в самом
себе, называемую ".", и о своем родителе, ".."; они, очевидно, также должны
быть пропущены, а то
Хотя fsize довольно
специализированна, она все же демонстрирует
пару важных идей. во-первых, многие stat.h и dir.h,
и что
В лекции №5 мы написали бесхитростный вариант alloc. Вариант,
который мы напишем теперь, не содержит ограничений: alloc
и free могут перемежаться в любом порядке;
когда это необходимо, alloc
обращается к typedef.
Вместо того, чтобы выделять память из скомпилированного внутри alloc будет по мере
необходимости обращаться за памятью к alloc, не
может быть непрерывной. В силу этого свободная память хранится в виде цепочки
свободных блоков. Каждый блок включает размер,
При поступлении запроса список свободных блоков просматривается до тех
пор, пока не будет найден достаточно большой блок. Если этот блок имеет в
точности требуемый размер, то он отцепляется от списка и передается
пользователю. Если же этот блок слишком велик, то он разделяется, нужное
количество передается пользователю, а остаток возвращается в свободный
список. Если достаточно большого блока найти не удается, то
Освобождение памяти также влечет за собой просмотр свободного списка в
поиске подходящего места для введения освобожденного блока. Если этот
освободившийся блок с какой-либо стороны примыкает к блоку из списка свободных
блоков, то они объединяются в один блок большего размера, так что память не
становится слишком раздробленной. Обнаружить смежные блоки просто, потому что
свободный список содержится в порядке возрастания
Одна из проблем, о которой мы упоминали в лекции №5, заключается в
обеспечении того, чтобы возвращаемая alloc
память была выровнена подходящим образом для тех объектов,
которые будут в ней храниться. Хотя машины
и различаются, для каждой машины существует double ; на int.
Свободный блок содержит
typedef int align; /*forces alignment on pdp-11*/
union header { /*free block header*/
struct {
union header *ptr; /*next free block*/
unsigned size; /*size of this free block*/
} s;
align x; /*force alignment of blocks*/
};
typedef union header header;
alloc округляет требуемый
размер в символах до нужного числа
единиц размера заголовка; фактический блок, который будет выделен, содержит на
одну единицу больше, предназначаемую для самого заголовка, и это и есть
значение, которое записывается в поле size
заголовка. alloc,
указывает на свободное пространство, а не на сам заголовок.
static header base; /*empty list to get started*/
static header *allocp=null; /*last allocated block*/
char *alloc(nbytes)/*general-purpose storage allocator*/
unsigned nbytes;
{
header *morecore();
register header *p, *g;
register int nunits;
nunits=1+(nbytes+sizeof(header)-1)/sizeof(header);
if ((g=allocp)==null) \( /*no free list yet*/
base.s ptr=allocp=g=base;
base.s.size=0;
}
for (p=g>s.ptr; ; g=p, p=p->s.ptr) {
if (p->s.size>=nunits) { /*big enough*/
if (p->s.size==nunits) /*exactly*/
g->s.ptr=p->s.ptr;
else { /*allocate tail end*/
p->s.size-=nunits;
p+=p->s.size;
p->s.size=nunits;
}
allocp=g;
return((char *)(p+1));
}
if(p==allocp) /*wrapped around free list*/
if((p=morecore(nunits))==null)
return(null); /*none left*/
}
}
base используется для
начала работы. Если allocp имеет значение null, как в случае первого обращения к alloc, то создается вырожденный свободный
список: он состоит из свободного блока размера нуль и allocp ),
где был найден последний блок; такая стратегия
помогает сохранить однородность диска. Если найден
слишком большой блок, то пользователю предлагается его хвостовая часть; это
приводит к тому, что в заголовке исходного блока нужно изменить только его
размер. Во всех случаях возвращаемый пользователю alloc
перед возвращением " p " преобразует его
в
morecore получает память от sbrk(n) возвращает n "
дополнительных байтов памяти.( alloc. Поэтому morecore
округляет затребованное число единиц до большего значения; этот больший блок
будет затем разделен так, как необходимо. Масштабирующая величина является
параметром, который может быть подобран в соответствии с необходимостью.
#define nalloc 128 /*#units to allocate at once*/
static header *morecore(nu) /*ask system for memory*/
unsigned nu;
{
char *sbrk();
register char *cp;
register header *up;
register int rnu;
rnu=nalloc*((nu+nalloc-1)/nalloc);
cp=sbrk(rnu*sizeof(header));
if ((int)cp==-1) /*no space at all*/
return(null);
up=(header *)cp;
up->s.size=rnu;
free((char *)(up+1));
return(allocp);
}
Если больше не осталось свободного пространства,
то sbrk возвращает " -1 ", хотя null был бы лучшим выбором. Для надежности сравнения " -1 "
должна быть преобразована к int.
Снова приходится многократно использовать явные преобразования
(перевод)
И последнее - сама free.
Начиная с allocp, она просто
просматривает свободный список в поиске места для введения свободного блока. Это
место находится либо между двумя существующими блоками, либо в одном из концов
списка. В любом случае, если освободившийся блок примыкает к одному из соседних,
смежные блоки объединяются. Следить нужно только затем, чтобы
free(ap) /*put blocke ap in free list*/
char *ap;
{
register header *p, *g;
p=(header*)ap-1; /*point to header*/
for (g=allocp; !(p>g p>g->s.ptr);g=g->s.ptr)
if (g>=g->s.ptr (p>g || p<g->s.ptr))
break; /*at one end or other*/
if (p+p->s.size==g->s.ptr) { /*join to upper nbr*/
p->s.size += g->s.ptr->s.size;
p->s.ptr = g->s.ptr->s.ptr;
} else
p->s.ptr = g->s.ptr;
if (g+g->s.size==p) { /*join to lower nbr*/
g->s.size+=p->s.size;
g->s.ptr=p->s.ptr;
} else
g->s.ptr=p;
allocp = g;
}
Хотя распределение памяти по своей сути зависит от используемой машины,
приведенная выше typedef и union позволяет справиться с
выравниванием (при условии, что sbrk обеспечивает
подходящий
Упражнение 8-6
calloc(n,size) возвращает n "
объектов размера size, причем соответствующая память инициализируется на
нуль. Напишите calloc,
используя alloc либо в качестве
образца, либо как
Упражнение 8-7
alloc принимает затребованный размер, не проверяя его
правдоподобности; free полагает, что тот блок, который она должна
освободить, содержит правильное значение в поле размера. Усовершенствуйте эти
Упражнение 8-8
Напишите bfree(p,n), которая включает произвольный
блок " p " из " n " символов в список свободных блоков,
управляемый alloc и free. С
помощью bfree пользователь может в любое время
добавлять в свободный список статический или внешний
Материал этой лекции относится к интерфейсу между с-
Эта лекция делится на три основные части:
В лекции №7 мы имели дело с системным интерфейсом, который одинаков для
всего многообразия
В
В наиболее общем случае перед чтением из файла или записью в файл
необходимо сообщить системе о вашем намерении; этот процесс называется
"открытием" файла. Система выясняет, имеете ли вы право поступать таким образом
(существует ли этот файл? имеется ли у вас разрешение на обращение к нему?), и
если все в порядке, возвращает в read (5,...)
и write (6,...) в фортране). Вся информация об открытом файле содержится в
системе;
Для удобства выполнения обычных операций shell ") прогоняет
Пользователь shell
" < " и " > ":
prog <infile>outfile
В этом случае shell
изменит присваивание по умолчанию
дескрипторов файлов 0 и 1 с shell, а не
Самый низкий уровень read
и write.
Первым
n_read=read(fd,buf,n); n_written=write(fd,buf,n);
При каждом обращении возвращается счетчик байтов, указывающий фактическое
число переданных байтов. При чтении возвращенное число байтов может оказаться
меньше, чем запрошенное число. Возвращенное нулевое число байтов означает
Количество байтов, подлежащих чтению или записи, может быть совершенно
произвольным. Двумя самыми распространенными величинами являются "1", которая
означает передачу одного символа за обращение (т.е. без использования буфера), и
"512", которая соответствует физическому размеру блока на многих периферийных
устройствах. Этот последний размер будет наиболее эффективным, но даже
Объединив все эти факты, мы написали простую
#define bufsize 512 /*best size for pdp-11 unix*/
main() /*copy input to output*/
{
char buf[bufsize];
int n;
while((n=read(0,buf,bufsize))>0)
write(1,buf,n);
}
Если размер файла не будет кратен bufsize,
то при некотором обращении к read
будет возвращено меньшее число байтов, которые затем записываются с помощью write ; при следующем после этого обращении к read будет возвращен нуль.
Поучительно разобраться, как можно использовать read и write для
построения getchar, putchar и т.д.
Вот, например, вариант getchar,
осуществляющий
#define cmask 0377 /*for making char's > 0*/
getchar() /*unbuffered single character input*/
{
char c;
return((read(0,c,1)>0 ? c cmask : EOF);
}
char,
потому что read принимает
Второй вариант getchar осуществляет
#define cmask 0377 /*for making char's>0*/
#define bufsize 512
getchar() /*buffered version*/
{
static char buf[bufsize];
static char *bufp = buf;
static int n = 0;
if (n==0) { /*buffer is empty*/
n=read(0,buf,bufsize);
bufp = buf;
}
return((--n>=0) ? *bufp++ cmask : EOF);
}
Кроме случая, когда по умолчанию определены стандартные open
и creat.
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.
В системе файлов на
Для иллюстрации ниже приводится cp системы
#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 вместо их эквивалентов из
Нормально при работе с 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 системы seek. seek идентична lseek,
за исключением того, что offset
имеет int, а не long. В
соответствии с этим, поскольку на offset, указываемый seek,
origin
может иметь значения 3, 4, 5, которые заставляют seek
умножить заданное значение offset на 512
(количество байтов в одном физическом блоке) и затем интерпретировать origin, как если это 0, 1 или 2 соответственно.
Следовательно, чтобы достичь произвольного места в большом файле, нужно два
обращения к seek: сначала одно, которое выделяет
нужный блок, а затем второе, где origin имеет
значение 1 и которое осуществляет передвижение на желаемый
байт внутри блока.
Упражнение 8-2
Очевидно, что seek может быть написана
в lseek и наоборот.
напишите каждую
Давайте теперь на примере реализации 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, которое подавляет знаковое
расширение, осуществляемое на
Хотя мы не собираемся обсуждать какие-либо детали, мы все же включили сюда 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
правильно согласуется с буферизацией, сделанной для других
Иногда требуется другой вид взаимодействия с системой файлов - ls ("список справочника")
системы
Поскольку, по крайней мере, на системе ls нет ничего особенного;
она читает файл и выделяет нужные части из находящейся там информации.
Однако формат информации определяется системой, так что ls
должна знать, в каком виде все представляется в системе.
Мы это частично проиллюстрируем при написании fsize. fsize представляет
собой специальную форму ls, которая печатает размеры всех
файлов, указанных в fsize обращается сама
к себе рекурсивно. если же
Для начала дадим краткий обзор sys/dir.h, который содержит
#define dirsiz 14 /*max length of file name*/
struct direct /*structure of directory entry*/
{
ino_t _ino; /*inode number*/
char _name[dirsiz]; /*file name*/
};
"Тип" ino_t - это определяемый
посредством typedef unsigned, но это
не тот сорт информации, который помещают внутрь typedef. Полный набор
"системных" sys/types.h.
stat берет имя файла и
возвращает всю содержащуюся в I-узле
информацию об этом файле (или -1, если имеется ошибка). Таким образом, в
результате
struct stat stbuf; char *name; stat(name,stbuf);
stbuf наполняется информацией
из I-узла о файле с именем name. stat
информацию, находится в файле sys/stat.h и
выглядит следующим образом:
struct stat /*structure returned by stat*/
{
dev_t st_dev; /* device of inode */
ino_t st_ino; /* inode number */
short st_mode /* mode bits */
short st_nlink; / *number of links to file */
short st_uid; /* owner's user id */
short st_gid; /* owner's group id */
dev_t st_rdev; /* for special files */
off_t st_size; /* file size in characters */
time_t st_atime; /* time last accessed */
time_t st_mtime; /* time last modified */
time_t st_ctime; /* time originally created */
}
Большая часть этой информации объясняется в st.mode содержит набор флагов,
описывающих файл; для удобства sys/stat.h.
#define s_ifmt 0160000 /* type of file */ #define s_ifdir 0040000 /* directory */ #define s_ifchr 0020000 /* character special */ #define s_ifblk 0060000 /* block special */ #define s_ifreg 0100000 /* regular */ #define s_isuid 04000 /* set user id on execution */ #define s_isgid 02000 /* set group id on execution */ #define s_isvtx 01000 /*save swapped text after use*/ #define s_iread 0400 /* read permission */ #define s_iwrite 0200 /* write permission */ #define s_iexec 0100 /* execute permission */
Теперь мы в состоянии написать fsize. Если полученный от stat
режим указывает, что файл не является справочником, то его размер
уже под рукой и может быть напечатан непосредственно. Если же он оказывается
справочником, то мы должны обрабатывать этот справочник отдельно для каждого
файла; так как справочник может в свою очередь содержать подсправочники, этот
процесс обработки является рекурсивным.
Как обычно, ведущая fsize в большой буфер.
#include <stdio.h>
#include <sys/types.h>> /*typedefs*/
#include <sys/dir.h> /*directory entry structure*/
#include <sys/stat.h> /*structure returned by stat*/
#define bufsize 256
main(argc,argv) /*fsize:print file sizes*/
char *argv[];
{
char buf[bufsize];
if(argc==1) { /*default:current directory*/
strcpy(buf,".");
fsize(buf);
} else
while(--argc>0) {
strcpy(buf,*++argv);
fsize(buf);
}
}
fsize печатает размер файла.
Если однако файл оказывается справочником, то fsize сначала вызывает directory для обработки всех
указанных в нем файлов. Обратите внимание на
использование имен флагов s_ifmt
и _ifdir из файла stat.h.
fsize(name) /*print size for name*/
char *name;
{
struct stat stbuf;
if(stat(name,stbuf)== -1) {
fprintf(stderr,"fsize:can't find %s\n",name);
return;
}
if((stbuf.st_mode s_ifmt)==s_ifdir)
directory(name);
printf("%8ld %s\n",stbuf.st_size,name);
}
directory является самой сложной.
Однако значительная ее часть связана с созданием для
обрабатываемого в данный момент файла его полного имени,
по которому можно восстановить путь в дереве.
directory(name) /*fsize for all files in name*/
char *name;
{
struct direct dirbuf;
char *nbp, *nep;
int i, fd;
nbp=name+strlen(name);
*nbp++='/'; /*add slash to directory name*/
if(nbp+dirsiz+2>=name+bufsize) /*name too long*/
return;
if((fd=open(name,0))== -1)
return;
while(read(fd,(char *)dirbuf,sizeof(dirbuf))>0) \(
if(dirbuf.d_ino==0) /*slot not in use*/
continue;
if(strcmp (dirbuf.d_name,".")==0
|| strcmp(dirbuf.d_name,"..")==0
continue; /*skip self and parent*/
for (i=0,nep=nbp;i<dirsiz;i++)
*nep++=dirbuf.d_name[i];
*nep++='\0';
fsize(name);
}
close(fd);
*--nbp='\0'; /*restore name*/
}
Если некоторая дыра в справочнике в настоящее время не используется
(потому что файл был удален), то в соответствующее I-узловое число равно нулю,
и эта позиция пропускается. Каждый справочник также содержит запись в самом
себе, называемую ".", и о своем родителе, ".."; они, очевидно, также должны
быть пропущены, а то
Хотя fsize довольно
специализированна, она все же демонстрирует
пару важных идей. во-первых, многие stat.h и dir.h,
и что
В лекции №5 мы написали бесхитростный вариант alloc. Вариант,
который мы напишем теперь, не содержит ограничений: alloc
и free могут перемежаться в любом порядке;
когда это необходимо, alloc
обращается к typedef.
Вместо того, чтобы выделять память из скомпилированного внутри alloc будет по мере
необходимости обращаться за памятью к alloc, не
может быть непрерывной. В силу этого свободная память хранится в виде цепочки
свободных блоков. Каждый блок включает размер,
При поступлении запроса список свободных блоков просматривается до тех
пор, пока не будет найден достаточно большой блок. Если этот блок имеет в
точности требуемый размер, то он отцепляется от списка и передается
пользователю. Если же этот блок слишком велик, то он разделяется, нужное
количество передается пользователю, а остаток возвращается в свободный
список. Если достаточно большого блока найти не удается, то
Освобождение памяти также влечет за собой просмотр свободного списка в
поиске подходящего места для введения освобожденного блока. Если этот
освободившийся блок с какой-либо стороны примыкает к блоку из списка свободных
блоков, то они объединяются в один блок большего размера, так что память не
становится слишком раздробленной. Обнаружить смежные блоки просто, потому что
свободный список содержится в порядке возрастания
Одна из проблем, о которой мы упоминали в лекции №5, заключается в
обеспечении того, чтобы возвращаемая alloc
память была выровнена подходящим образом для тех объектов,
которые будут в ней храниться. Хотя машины
и различаются, для каждой машины существует double ; на int.
Свободный блок содержит
typedef int align; /*forces alignment on pdp-11*/
union header { /*free block header*/
struct {
union header *ptr; /*next free block*/
unsigned size; /*size of this free block*/
} s;
align x; /*force alignment of blocks*/
};
typedef union header header;
alloc округляет требуемый
размер в символах до нужного числа
единиц размера заголовка; фактический блок, который будет выделен, содержит на
одну единицу больше, предназначаемую для самого заголовка, и это и есть
значение, которое записывается в поле size
заголовка. alloc,
указывает на свободное пространство, а не на сам заголовок.
static header base; /*empty list to get started*/
static header *allocp=null; /*last allocated block*/
char *alloc(nbytes)/*general-purpose storage allocator*/
unsigned nbytes;
{
header *morecore();
register header *p, *g;
register int nunits;
nunits=1+(nbytes+sizeof(header)-1)/sizeof(header);
if ((g=allocp)==null) \( /*no free list yet*/
base.s ptr=allocp=g=base;
base.s.size=0;
}
for (p=g>s.ptr; ; g=p, p=p->s.ptr) {
if (p->s.size>=nunits) { /*big enough*/
if (p->s.size==nunits) /*exactly*/
g->s.ptr=p->s.ptr;
else { /*allocate tail end*/
p->s.size-=nunits;
p+=p->s.size;
p->s.size=nunits;
}
allocp=g;
return((char *)(p+1));
}
if(p==allocp) /*wrapped around free list*/
if((p=morecore(nunits))==null)
return(null); /*none left*/
}
}
base используется для
начала работы. Если allocp имеет значение null, как в случае первого обращения к alloc, то создается вырожденный свободный
список: он состоит из свободного блока размера нуль и allocp ),
где был найден последний блок; такая стратегия
помогает сохранить однородность диска. Если найден
слишком большой блок, то пользователю предлагается его хвостовая часть; это
приводит к тому, что в заголовке исходного блока нужно изменить только его
размер. Во всех случаях возвращаемый пользователю alloc
перед возвращением " p " преобразует его
в
morecore получает память от sbrk(n) возвращает n "
дополнительных байтов памяти.( alloc. Поэтому morecore
округляет затребованное число единиц до большего значения; этот больший блок
будет затем разделен так, как необходимо. Масштабирующая величина является
параметром, который может быть подобран в соответствии с необходимостью.
#define nalloc 128 /*#units to allocate at once*/
static header *morecore(nu) /*ask system for memory*/
unsigned nu;
{
char *sbrk();
register char *cp;
register header *up;
register int rnu;
rnu=nalloc*((nu+nalloc-1)/nalloc);
cp=sbrk(rnu*sizeof(header));
if ((int)cp==-1) /*no space at all*/
return(null);
up=(header *)cp;
up->s.size=rnu;
free((char *)(up+1));
return(allocp);
}
Если больше не осталось свободного пространства,
то sbrk возвращает " -1 ", хотя null был бы лучшим выбором. Для надежности сравнения " -1 "
должна быть преобразована к int.
Снова приходится многократно использовать явные преобразования
(перевод)
И последнее - сама free.
Начиная с allocp, она просто
просматривает свободный список в поиске места для введения свободного блока. Это
место находится либо между двумя существующими блоками, либо в одном из концов
списка. В любом случае, если освободившийся блок примыкает к одному из соседних,
смежные блоки объединяются. Следить нужно только затем, чтобы
free(ap) /*put blocke ap in free list*/
char *ap;
{
register header *p, *g;
p=(header*)ap-1; /*point to header*/
for (g=allocp; !(p>g p>g->s.ptr);g=g->s.ptr)
if (g>=g->s.ptr (p>g || p<g->s.ptr))
break; /*at one end or other*/
if (p+p->s.size==g->s.ptr) { /*join to upper nbr*/
p->s.size += g->s.ptr->s.size;
p->s.ptr = g->s.ptr->s.ptr;
} else
p->s.ptr = g->s.ptr;
if (g+g->s.size==p) { /*join to lower nbr*/
g->s.size+=p->s.size;
g->s.ptr=p->s.ptr;
} else
g->s.ptr=p;
allocp = g;
}
Хотя распределение памяти по своей сути зависит от используемой машины,
приведенная выше typedef и union позволяет справиться с
выравниванием (при условии, что sbrk обеспечивает
подходящий
Упражнение 8-6
calloc(n,size) возвращает n "
объектов размера size, причем соответствующая память инициализируется на
нуль. Напишите calloc,
используя alloc либо в качестве
образца, либо как
Упражнение 8-7
alloc принимает затребованный размер, не проверяя его
правдоподобности; free полагает, что тот блок, который она должна
освободить, содержит правильное значение в поле размера. Усовершенствуйте эти
Упражнение 8-8
Напишите bfree(p,n), которая включает произвольный
блок " p " из " n " символов в список свободных блоков,
управляемый alloc и free. С
помощью bfree пользователь может в любое время
добавлять в свободный список статический или внешний
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.