《The C Programming Language, 2nd Edition》K&R Chapter 8 - The UNIX System Interface 笔记
文件描述符
UNIX中所有外围设备(包括键盘和显示器)都被看作文件。因此,所有输入\输出都通过读写文件完成。单一接口处理所有通信。
读写文件之前,需要先通知操作系统,这个过程称为打开文件。
如果文件存在,并拥有访问它的权限,操作系统返回一个小的非负整数,称为文件描述符。
文件描述符类似于文件指针或MS-DOS中的文件句柄,是文件的标识。(文件的标识不是文件名,可能重名)
UNIX中,当shell运行一个程序,它将打开3个文件,文件描述符分别为0、1、2,依次表示标准输入、标准输出和标准错误。程序可以从文件0中读,向1和2中写。
使用<>可以重定向I/O:prog <输入文件名>输出文件名
。这种情况下shell会把文件描述符0和1的默认赋值变为指定文件。
低级I/O——read和write
输入和输出是通过read和write系统调用实现的,read和write函数访问这两个系统调用。
int n_read = read(int fd, char *buf, int n);
int n_written = write(int fd, char *buf, int n);
fd是文件描述符,buf是字符串,n是传输的字节数。返回实际传输的字节数。
读文件时,返回0表示到达文件末尾,返回-1表示发生错误。
写文件时,返回与请求写入的字节数不相等说明发生错误。
一次调用传输的字节数为任意,越大越效率,因系统调用次数减少。
open、creat、close和unlink
open用于打开文件。打开成功返回文件描述符,失败返回-1。
open打开一个不存在的文件,将报错。
open声明为int open(char *name, int flags, int perms);
。其中name为文件名,flag是模式,perms在本章始终为0。
flag的值可选以下常量。System V UNIX中这些常量在<fcnt1.h>中定义,BSD则在<sys/file.h>中定义。
- O_RDONLY open for reading only
- O_WRONLY open for writing only
- O_RDWR open for both reading and writing
creat创建一个新文件或覆盖已有的文件。创建成功返回文件描述符,失败返回-1。
creat创建的文件如果已存在,则将长度截断为0。
creat的声明为int creat(char *name, int perms);
。其中perms为一个9比特的权限信息。
一个程序同时打开的文件数限制通常为20。
close(int fd)函数用来断开文件描述符与已打开的文件之间的连接,释放此描述符。但close不清洗(flush)缓冲区。
如果程序通过exit函数退出或从主程序返回,所有打开的文件将被关闭。
unlink(char* name)将文件name从文件系统中删除,对应标准库函数remove。
随机访问——lseek
long lseek(int fd, long offset, int origin);
,将文件描述符为fd的文件的当前位置设置为offset。调用成功返回当前位置,失败返回-1。
其中offset相对比orgin。orgin可选0、1或2,分别对应文件开始、当前位置、文件末尾(最后一个字符的下一个位置)。
FILE
<stdio.h>中定义了FILE的结构,包含:
- 缓冲区剩余字符计数。
- 指向缓冲区下一个字符的指针。
- 文件描述符。
- 描述读写的标志。
- 描述错误状态的标志。
#define NULL 0
#define EOF (-1)
#define BUFSIZ 1024
#define OPEN_MAX 20 /* max #files open at once */
typedef struct _iobuf {
int cnt; /* characters left */
char *ptr; /* next character position */
char *base; /* location of buffer */
int flag; /* mode of file access */
int fd; /* file descriptor */
} FILE;
extern FILE _iob[OPEN_MAX];
#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])
enum _flags {
_READ = 01, /* file open for reading */
_WRITE = 02, /* file open for writing */
_UNBUF = 04, /* file is unbuffered */
_EOF = 010, /* EOF has occurred on this file */
_ERR = 020 /* error occurred on this file */
};
int _fillbuf(FILE *);
int _flushbuf(int, FILE *);
#define feof(p) ((p)->flag & _EOF) != 0)
#define ferror(p) ((p)->flag & _ERR) != 0)
#define fileno(p) ((p)->fd)
#define getc(p) (--(p)->cnt >= 0 \
? (unsigned char) *(p)->ptr++ : _fillbuf(p))
#define putc(x,p) (--(p)->cnt >= 0 \
? *(p)->ptr++ = (x) : _flushbuf((x),p))
#define getchar() getc(stdin)
#define putcher(x) putc((x), stdout)
ls
在UNIX中命令ls打印目录下的文件名和其他文件信息,MS-DOS中dir类似。
UNIX中的目录是文件,包含文件名列表和i结点表索引。文件的i结点存放文件除名称以外的信息。
因此UNIX读目录文件,就能获取所有文件名,但获取文件的其它信息需要系统调用。
MS-DOS中获取目录中的文件名也需要系统调用。
系统调用int stat(char *, struct stat *);
以文件名为参数,返回文件i结点中的所有信息,出错返回-1。
<sys/stat.h>中声明了struct stat:
struct stat /* inode information 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; /* owners user id */
short st_gid; /* owners 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 */
};
其中dev_t和ino_t在<sys/types.h>中typedef定义。所有的“系统”类型都可以在<sys/types.h>中找到。
st_mode包含了描述文件的一系列标志:
#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 0010000 /* regular */
/* ... */
使用S_IFMT和S_IFDIR能够判断一个文件是不是一个目录。如(stbuf.st_mode & S_IFMT) == S_IFDIR
。
Version 7和system V UNIX的目录文件结构在<sys/dir.h>中声明:
#ifndef DIRSIZ
#define DIRSIZ 14
#endif
struct direct { /* directory entry */
ino_t d_ino; /* inode number */
char d_name[DIRSIZ]; /* long name does not have '\0' */
};
ino_t通常是unsigned short类型,用于描述i结点表的索引。
如果i结点编号为0,说明该文件被删除。
每个目录都会包含.和..表示当前目录和上级目录。
malloc和free
malloc维护的空闲块链表是一个循环链表,按地址递增的顺序排列。
。
malloc分配内存时有两种算法:
- 扫描空闲块链表,找到足够大的块为止。称为“首次适应”(first fit)。
- 找到满足条件的最小的块。称为“最佳适应”(best fit)。
malloc找到满足条件块之后,或找不到时:
- 如果块与请求相等,将它从链表中移走,返回给用户。
- 如果块太大,分成两部分,将大小合适的块返回给用户,剩下的留在空闲链表中。
- 如果找不到足够大的块,向系统申请一个大的块放到空闲块链表中。
free释放内存时,搜索空闲块链表,以地址递增的顺序,查找插入位置。如果释放的块的位置与空闲块相邻,将两个块合成一个更大的块。
malloc返回的内存满足保存的对象的对齐要求。每个特定的机器都一个最受限的类型,如果最受限的类型可以保存在特定的地址,那么其他的类型也可以放在这个地址。一些机器中最受限的类型是double,另一些机器中是int或long。以下代码,保证以long对齐:
typedef long Align; /* for alignment to long boundary */
union header { /* block header */
struct {
union header *ptr; /* next block if on free list */
unsigned size; /* size of this block */
} s;
Align x; /* force alignment of blocks */
};
每个空闲块包含指向下一个块的指针,一个块大小的记录,一个指向空闲内存本身的指针
malloc返回的块将多包含一个单元用于头部本身,实际分配的块大小被记录在size字段中,返回的指针指向空闲内存,而不是块头部。
每次请求空闲空间时,都从上一次找到空闲块的地方开始搜索,保证链表均匀。
UNIX系统调用sbrk(n)返回一个指针,指向n字节的内存。如果没有空闲内存返回-1。(注意不是0)
ANSI标准只允许同一数组内的指针能够比较,但UNIX下一般指针的比较也有意义。
其他
在<stdio.h>中getchar是宏定义,可以使用#undef取消。
vprintf是使用形参替代变长参数表的printf。vfprintf和vsprintf与fprintf和sprintf的关系类似。
UNIX中要向文件追加内容使用重定向>>,或在系统调用fopen中使用参数"a"。
例程
复制输入到输出
#include "syscalls.h"
main() /* copy input to output */
{
char buf[BUFSIZ];
int n;
while ((n = read(0, buf, BUFSIZ)) > 0)
write(1, buf, n);
return 0;
}
- "syscalls.h"是一个自定义头文件,包含了read和write的声明,BUFSIZ的声明。
getchar函数
#include "syscalls.h"
/* getchar: unbuffered single character input */
int getchar(void)
{
char c;
return (read(0, &c, 1) == 1) ? (unsigned char) c : EOF;
}
- 将c转换为unsigned char消除了符号扩展问题。
带缓冲区的getchar函数
#include "syscalls.h"
/* getchar: simple buffered version */
int getchar(void)
{
static char buf[BUFSIZ];
static char *bufp = buf;
static int n = 0;
if (n == 0) { /* buffer is empty */
n = read(0, buf, sizeof buf);
bufp = buf;
}
return (--n >= 0) ? (unsigned char) *bufp++ : EOF;
}
文件复制
#include <stdio.h>
#include <fcntl.h>
#include "syscalls.h"
#define PERMS 0666 /* RW for owner, group, others */
void error(char *, ...);
/* cp: copy f1 to f2 */
main(int argc, char *argv[])
{
int f1, f2, n;
char buf[BUFSIZ];
if (argc != 3)
error("Usage: cp from to");
if ((f1 = open(argv[1], O_RDONLY, 0)) == -1)
error("cp: can't open %s", argv[1]);
if ((f2 = creat(argv[2], PERMS)) == -1)
error("cp: can't create %s, mode %03o",
argv[2], PERMS);
while ((n = read(f1, buf, BUFSIZ)) > 0)
if (write(f2, buf, n) != n)
error("cp: write error on file %s", argv[2]);
return 0;
}
#include <stdio.h>
#include <stdarg.h>
/* error: print an error message and die */
void error(char *fmt, ...)
{
va_list args;
va_start(args, fmt);
fprintf(stderr, "error: ");
vprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
exit(1);
}
在文件任意位置读n个字节
#include "syscalls.h"
/*get: read n bytes from position pos */
int get(int fd, long pos, char *buf, int n)
{
if (lseek(fd, pos, 0) >= 0) /* get to pos */
return read(fd, buf, n);
else
return -1;
}
fopen函数
#include <fcntl.h>
#include "syscalls.h"
#define PERMS 0666 /* RW for owner, group, others */
FILE *fopen(char *name, char *mode)
{
int fd;
FILE *fp;
if (*mode != 'r' && *mode != 'w' && *mode != 'a')
return NULL;
for (fp = _iob; fp < _iob + OPEN_MAX; fp++)
if ((fp->flag & (_READ | _WRITE)) == 0)
break; /* found free slot */
if (fp >= _iob + OPEN_MAX) /* no free slots */
return NULL;
if (*mode == 'w')
fd = creat(name, PERMS);
else if (*mode == 'a') {
if ((fd = open(name, O_WRONLY, 0)) == -1)
fd = creat(name, PERMS);
lseek(fd, 0L, 2);
} else
fd = open(name, O_RDONLY, 0);
if (fd == -1) /* couldn't access name */
return NULL;
fp->fd = fd;
fp->cnt = 0;
fp->base = NULL;
fp->flag = (*mode == 'r') ? _READ : _WRITE;
return fp;
}
#include "syscalls.h"
/* _fillbuf: allocate and fill input buffer */
int _fillbuf(FILE *fp)
{
int bufsize;
if ((fp->flag&(_READ|_EOF_ERR)) != _READ)
return EOF;
bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZ;
if (fp->base == NULL) /* no buffer yet */
if ((fp->base = (char *) malloc(bufsize)) == NULL)
return EOF; /* can't get buffer */
fp->ptr = fp->base;
fp->cnt = read(fp->fd, fp->ptr, bufsize);
if (--fp->cnt < 0) {
if (fp->cnt == -1)
fp->flag |= _EOF;
else
fp->flag |= _ERR;
fp->cnt = 0;
return EOF;
}
return (unsigned char) *fp->ptr++;
}
FILE _iob[OPEN_MAX] = { /* stdin, stdout, stderr */
{ 0, (char *) 0, (char *) 0, _READ, 0 },
{ 0, (char *) 0, (char *) 0, _WRITE, 1 },
{ 0, (char *) 0, (char *) 0, _WRITE, | _UNBUF, 2 }
};
递归打印给定目录下的文件名和文件长度
#define NAME_MAX 14 /* longest filename component; */
/* system-dependent */
typedef struct { /* portable directory entry */
long ino; /* inode number */
char name[NAME_MAX+1]; /* name + '\0' terminator */
} Dirent;
typedef struct { /* minimal DIR: no buffering, etc. */
int fd; /* file descriptor for the directory */
Dirent d; /* the directory entry */
} DIR;
DIR *opendir(char *dirname);
Dirent *readdir(DIR *dfd);
void closedir(DIR *dfd);
#include <stdio.h>
#include <string.h>
#include "syscalls.h"
#include <fcntl.h> /* flags for read and write */
#include <sys/types.h> /* typedefs */
#include <sys/stat.h> /* structure returned by stat */
#include "dirent.h"
void fsize(char *)
/* print file name */
main(int argc, char **argv)
{
if (argc == 1) /* default: current directory */
fsize(".");
else
while (--argc > 0)
fsize(*++argv);
return 0;
}
int stat(char *, struct stat *);
void dirwalk(char *, void (*fcn)(char *));
/* fsize: print the name of file "name" */
void fsize(char *name)
{
struct stat stbuf;
if (stat(name, &stbuf) == -1) {
fprintf(stderr, "fsize: can't access %s\n", name);
return;
}
if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
dirwalk(name, fsize);
printf("%8ld %s\n", stbuf.st_size, name);
}
#define MAX_PATH 1024
/* dirwalk: apply fcn to all files in dir */
void dirwalk(char *dir, void (*fcn)(char *))
{
char name[MAX_PATH];
Dirent *dp;
DIR *dfd;
if ((dfd = opendir(dir)) == NULL) {
fprintf(stderr, "dirwalk: can't open %s\n", dir);
return;
}
while ((dp = readdir(dfd)) != NULL) {
if (strcmp(dp->name, ".") == 0
|| strcmp(dp->name, ".."))
continue; /* skip self and parent */
if (strlen(dir)+strlen(dp->name)+2 > sizeof(name))
fprintf(stderr, "dirwalk: name %s %s too long\n",
dir, dp->name);
else {
sprintf(name, "%s/%s", dir, dp->name);
(*fcn)(name);
}
}
closedir(dfd);
}
int fstat(int fd, struct stat *);
/* opendir: open a directory for readdir calls */
DIR *opendir(char *dirname)
{
int fd;
struct stat stbuf;
DIR *dp;
if ((fd = open(dirname, O_RDONLY, 0)) == -1
|| fstat(fd, &stbuf) == -1
|| (stbuf.st_mode & S_IFMT) != S_IFDIR
|| (dp = (DIR *) malloc(sizeof(DIR))) == NULL)
return NULL;
dp->fd = fd;
return dp;
}
/* closedir: close directory opened by opendir */
void closedir(DIR *dp)
{
if (dp) {
close(dp->fd);
free(dp);
}
}
#include <sys/dir.h> /* local directory structure */
/* readdir: read directory entries in sequence */
Dirent *readdir(DIR *dp)
{
struct direct dirbuf; /* local directory structure */
static Dirent d; /* return: portable structure */
while (read(dp->fd, (char *) &dirbuf, sizeof(dirbuf))
== sizeof(dirbuf)) {
if (dirbuf.d_ino == 0) /* slot not in use */
continue;
d.ino = dirbuf.d_ino;
strncpy(d.name, dirbuf.d_name, DIRSIZ);
d.name[DIRSIZ] = '\0'; /* ensure termination */
return &d;
}
return NULL;
}
malloc和free函数(以long对齐)
typedef long Align; /* for alignment to long boundary */
union header { /* block header */
struct {
union header *ptr; /* next block if on free list */
unsigned size; /* size of this block */
} s;
Align x; /* force alignment of blocks */
};
typedef union header Header;
static Header base; /* empty list to get started */
static Header *freep = NULL; /* start of free list */
/* malloc: general-purpose storage allocator */
void *malloc(unsigned nbytes)
{
Header *p, *prevp;
Header *moreroce(unsigned);
unsigned nunits;
nunits = (nbytes+sizeof(Header)-1)/sizeof(header) + 1;
if ((prevp = freep) == NULL) { /* no free list yet */
base.s.ptr = freeptr = prevptr = &base;
base.s.size = 0;
}
for (p = prevp->s.ptr; ; prevp = p, p = p->s.ptr) {
if (p->s.size >= nunits) { /* big enough */
if (p->s.size == nunits) /* exactly */
prevp->s.ptr = p->s.ptr;
else { /* allocate tail end */
p->s.size -= nunits;
p += p->s.size;
p->s.size = nunits;
}
freep = prevp;
return (void *)(p+1);
}
if (p == freep) /* wrapped around free list */
if ((p = morecore(nunits)) == NULL)
return NULL; /* none left */
}
}
#define NALLOC 1024 /* minimum #units to request */
/* morecore: ask system for more memory */
static Header *morecore(unsigned nu)
{
char *cp, *sbrk(int);
Header *up;
if (nu < NALLOC)
nu = NALLOC;
cp = sbrk(nu * sizeof(Header));
if (cp == (char *) -1) /* no space at all */
return NULL;
up = (Header *) cp;
up->s.size = nu;
free((void *)(up+1));
return freep;
}
/* free: put block ap in free list */
void free(void *ap)
{
Header *bp, *p;
bp = (Header *)ap - 1; /* point to block header */
for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr)
if (p >= p->s.ptr && (bp > p || bp < p->s.ptr))
break; /* freed block at start or end of arena */
if (bp + bp->size == p->s.ptr) { /* join to upper nbr */
bp->s.size += p->s.ptr->s.size;
bp->s.ptr = p->s.ptr->s.ptr;
} else
bp->s.ptr = p->s.ptr;
if (p + p->size == bp) { /* join to lower nbr */
p->s.size += bp->s.size;
p->s.ptr = bp->s.ptr;
} else
p->s.ptr = bp;
freep = p;
}