第八章
第八章 UNIX系统接口
8.1 文件描述符
1 //将输入复制到输出 2 3 4 #include "syscall.h" 5 #include <stdio.h> 6 //#define BUFESIZE 100 7 8 main() //copy input to output 9 { 10 char buf[BUFSIZ]; 11 int n; 12 13 while ((n = read(0, buf, BUFSIZ)) > 0) 14 write(1, buf, n); 15 return 0; 16 } 17 /* 函数原型集中放在一个头文件syscalls.h, 18 如果文件大小是BUFSIZ的倍数,则对read的某次调用会返回一个较小的字节数,write再按这个字节数写,此后在调用read将返回0 19 20 */ 21 22 23 24 //下列getchar函数的一个版本,通过每次从标准输入度如一个字符来实现无缓冲输入。 25 26 //getchar: unbuffered single character input 27 28 int getchar(void) 29 { 30 char c; 31 32 return (read(0, &c, 1) == 1) ? (unsigned char) c : EOF; 33 //其中c必须是一个char类型的变量,因为read函数需要一个字符指针类型的参数(&c)。在返回语句中将c转换为unsigend 34 //char 类型可以消除符号扩展问题。 35 } 36 37 38 //第二个版本一次度如一组字符,但每次只输出一个字符。 39 40 41 //getchar: simple buffered version 42 int getchar2(void) 43 { 44 static char buf[BUFSIZ]; 45 static char *bufp = buf; 46 static int n = 0; 47 48 if (n == 0) { //buffer is empty 49 n = read(0, buf, sizeof buf); 50 bufp = buf; 51 } 52 return (--n >= 0) ? (unsigned char) *bufp++ : EOF; 53 } 54 55 //如果在包含头文件<stdio.h>的情况下编译这些版本的getchar函数,有必要使用#undef 预处理指令取消名字getchar的 56 //宏定义,因为在头文件中,该函数是以宏方式实现的。
8.3 open、creat、close和unlink
1 //除了默认的标准输入、输出和错误外,其他文件都必须在读写之前显示打开。系统调用open和creat用于实现该功能。 2 3 4 //open与fopen相似,不同的是,前者返回一个文件描述符,它仅仅只是一个int类型的值。而后者返回一个文件指针。 5 //如果发生错我upen返回-1. 6 7 #include <fcntl.h> 8 9 int fd; 10 int open(char *name, int flags, int perms); 11 main(){ 12 char name; 13 int flags,perms; 14 fd = open(name, flags, perms); 15 } 16 17 /*与fopen一样,参数name是一个包含文件名的字符串。第二个参数flags是一个Int类型的值,说明以何种方式打开 18 O_RDONLY 只读方式打开文件 19 O_WRONLY 只写方式 20 O_RDWR 读写方式 21 22 如果用open打开一个不存在的文件,则将导致错误。可以使用creat系统调用创建新文件或覆盖已有的旧文件。 23 int creat(char *name, int perms); 24 fd = creat(name, perms); 25 如果creat成功创建文件,将返回一个文件描述符,否则返回-1.如果此文件已存在,creat将把该文件的长度截断为0, 26 从而丢弃原先的内容。使用creat创建一个存在的文件不会导致错误。 27 如果要创建的文件不存在,则creat用参数perms指定的权限创建文件。在UNIX文件系统中,每个文件对应一个9比特的特权 28 信息,他们分别控制文件的所有者、所有者组和其他成员对文件的读写和执行访问。因此,通过一个3位的八进制数就可以方便 29 的说明不同的权限,例如,0755说明文件的所有者对它进行读写和执行操作,,而所有者组和其他成员只能进行读和执行。 30 */ 31 32 //简化版creat,程序将一个文件复制到另一个文件。我们编写的这个版本仅仅只能复制一个文件,不允许目录作为第二个参数 33 //并且,目标文件的权限是不通过复制获得的,而是重新定义的。 34 35 #include <stdio.h> 36 #include "syscall.h" 37 #define PERMS 0666 //RW for owner, group, others 38 39 void error(char *, ...); 40 41 //cp: copy f1 to f2 42 43 int main(int argc, char *argv[]) 44 { 45 int f1, f2, n; 46 char buf[BUFSIZ]; 47 48 if (argc != 3) 49 error("Usage: cp from to"); 50 if ((f1 = open(argv[1], O_RDONLY, 0)) == -1) 51 error("cp: can't open %s",argv[1]); 52 if ((f2 = creat(argv[2], PERMS)) == -1) 53 error("cp: can't create %s, mode %03o",argv[2], PERMS); 54 while ((n = read(f1, buf, BUFSIZ)) > 0) 55 if (write(f2, buf, n) != n) 56 error("cp: write error on file %s", argv[2]); 57 return 0; 58 } 59 //程序创建固定权限0666 60 61 //标准库函数vprintf函数与printf函数类似,它用一个参数取代了变长参数表,且次参数通过调用va_start宏进行初始化 62 //同样,vfprintf和vsprintf函数分别与fprintf和sprintf函数类似 63 64 65 #include <stdarg.h> 66 67 //error: printf an error message and die 68 69 void error(char *fmt, ...) 70 { 71 va_list args; 72 73 va_start(args, fmt); 74 fprintf(stderr, "error: "); 75 vprintf(stderr, fmt); 76 fprintf(stderr, "\n"); 77 va_end(args); 78 exit(1); 79 } 80 81 82 /* 一个程序同时打开的文件数时有限制的(通常20)。相应的,如果一个程序需要同时处理许多文件,那么它必须重用 83 文件描述符。函数close(int fd)用来断开文件描述符和已打开文件之间的连接,并释放此文件描述符,以供其它文件使用。 84 close函数与标准库中的fclose函数相对应,它不需要清洗(flush)缓冲区。如果程序通过exit函数退出或从主程序中返回, 85 所有打开的文件将被关闭。 86 函数unlink(char *name)将文件name从文件系统中删除,它对应于标准库函数remove。 87 88 */
1 // 练习8-1 用read、write、open和cclose系统调用代替标准库中功能等价的函数,重写第7章的cat程序, 2 //并通过试验比较两个版本的相对执行速度。 3 4 5 6 #include <stdio.h> 7 #include <fcntl.h> 8 #include "syscall.h" 9 10 void error(char *fmt, ...); 11 12 //cat: concatenate files - read /write/open/close 13 int main(int argc, char const *argv[]) 14 { 15 int fd; 16 void filecopy(int ifd, int ofd); 17 18 if (argc == 1) //no args; copy standard input 19 filecopy(0, 1); 20 else 21 while(--argc > 0) 22 if ((fd = open(*++argv, O_RDONLY)) == -1) 23 error("cat: can't open %s", *argv); 24 else{ 25 filecopy(fd, 1); 26 close(fd); 27 } 28 return 0; 29 } 30 31 //filecopy: copy file ifd to file ofd 32 33 void filecopy(int ifd, int ofd) 34 { 35 int n; 36 char buf[BUFSIZ]; 37 38 while ((n = read(ifd, buf, BUFSIZ)) > 0) 39 if (write(ofd, buf, n) != n) 40 error("cat: write error"); 41 } 42 43 44 45 /* 语句 if ((fd = open(*++argv, D_RDONLY)) == -1) 以只读方式打开一个文件并返回一个文件描述符(一个整数); 46 如果发生错误,则返回-1. 47 filecopy函数的功能时使用文件描述符ifd读入BUFSIZ个字符。read函数的返回值时它实际上读入的字符的字节数;这个字节 48 计数值大于0,通常表示没有出错;如果它等于0,表示读到文件尾了如果它等于-1,就表明读操作出错。write函数的功能时写 49 出n个字节,如果实际写出的字节(write的返回值)与要求它写出的字节(这里时N)不符,就说明写操作出现了错误。 50 */
8.4 随机访问--lseek
输入/输出通常时顺序进行的:每次调用read和write进行读写的位置紧跟在前一次操作的位置之后。但是,有时候需要以任意顺序访问文件,系统调用lseek可以在文件中任意移动位置而不实际读写任何数据:
1 // 2 3 4 long lseek(int fd, long offset, int origin); 5 6 /* 将文件描述符为fd的文件的当前位置设置为offset,其中,offset是相对于orgin指定的位置而言的。随后进行的读写操作 7 将从此位置开始,origin的值可以为0、1或2,分别用于指定offset文件开始、从当前位置或从文件结束开始算起。例如向 8 文件尾写入 9 lseek(fd, 0L, 2); 10 11 若要返回文件的开始出(既反绕),则可以使用下列的调用: 12 13 lseek(fd, 0L, 0); 14 15 请注意, 参数0L也可以写为(long)0,或仅仅写为0,系统调用lseek的声明必须保持一致。使用lseek系统调用时, 16 将文件视为一个大数组,访问速度会慢。 17 18 */ 19 20 #include "syscall.h" 21 22 //get read n bytes from position pos 23 int get(int fd, long pos, char *buf, int n) 24 { 25 if (lseek(fd, pos, 0) >= 0) //get to pos 返给系统一个long类型的值,表示文件的新位置,错误返回-1. 26 //标准库Fseek的第一个参数时FILE *类型,错误返回0 27 return read(fd, buf, n); 28 else 29 return -1; 30 }
8.5 实例--fopen和getc函数的实现
标准库的文件不是通过文件描述符描述的,而是使用文件指针描述的。文件指针是一个指向包含文件各种信息的结构的指针,一个指向缓冲区的指针,通过它一次读入文件的一大块内容;一个记录缓冲区中剩余的字符数的计数器;一个指向缓冲区中下一个字符的指针;文件描述符;描述都/写模式的标志;描述错误状态的标志等。
1 //用字段代替显示的按位操作,重新函数fopen和_fillbuf。比较代码长度和速度 2 #include "/mnt/c/Users/wang/Desktop/C-program_book_test/chapter_8/08.h" 3 //#include <fcntl.h> 4 //#include "unistd.h" 5 //#include <stdio.h> 6 //#include "syscall.h" 7 #define PERMS 0666 8 9 10 FILE *fopen(char *name, char *mode); 11 12 main(int argc, char *argv[]) 13 { 14 fopen(argv[1],argv[2]); 15 return 0; 16 } 17 18 19 //fopen: open file, return file ptr 20 FILE *fopen(char *name, char *mode) 21 { 22 int fd; 23 FILE *fp; 24 25 if (*mode != 'r' && *mode != 'w' && *mode != 'a') 26 return NULL; 27 for (fp = _iob; fp < _iob + OPEN_MAX; fp++) 28 if (fp->flag.is_read == 0 && fp->flag.is_write == 0) 29 break; 30 if (fp >= _iob + OPEN_MAX) 31 return NULL; //no free slots 32 33 if (*mode == 'w') 34 fd = creat(name, PERMS); 35 else if (*mode == 'a') { 36 if ((fd = open(name, O_WRONLY, 0)) == -1) 37 fd = creat(name, PERMS); 38 lseek(fd, 0L, 2); 39 } else 40 fd = open(name, O_RDONLY, 0); 41 if (fd == -1) //couldn't access name 42 return NULL; 43 fp->fd = fd; 44 fp->cnt = 0; 45 fp->base = NULL; 46 fp->flag.is_unbuf = 0; 47 fp->flag.is_buf = 1; 48 fp->flag.is_eof = 0; 49 fp->flag.is_err = 0; 50 if (*mode == 'r') { 51 fp->flag.is_read = 1; 52 fp->flag.is_write = 0; 53 } else { 54 fp->flag.is_read = 0; 55 fp->flag.is_write = 1; 56 } 57 pwrite("%s",fp->ptr); 58 return fp; 59 } 60 61 62 //fillbuf: allocate and fill input buffer 63 int _fillbuf(FILE *fp) 64 { 65 int bufsize; 66 67 if (fp->flag.is_read == 0 || fp->flag.is_eof == 1 || fp->flag.is_err == 1) 68 return EOF; 69 bufsize = (fp->flag.is_unbuf == 1) ? 1 : BUFSIZ; 70 if (fp->base == NULL) //no buffer yet 71 if ((fp->base = (char *) malloc(bufsize)) == NULL) 72 return EOF; //can't get buffer 73 fp->ptr = fp->base; 74 fp->cnt = read(fp->fd, fp->ptr, bufsize); 75 if (--fp->cnt < 0) { 76 if (fp->cnt == -1) 77 fp->flag.is_eof = 1; 78 else 79 fp->flag.is_err = 1; 80 fp->cnt = 0; 81 return EOF; 82 } 83 return (unsigned char) *fp->ptr++; 84 } 85 86 //_flushbuf: allocate and flush output buffer 87 int _flushbuf(int x, FILE *fp) 88 { 89 unsigned nc; //# of chars to flush 90 int bufsize; //size if buffer alloc 91 //如果文件不是位了写操作而打开或发生错误时,函数返回EOF 92 if (fp < _iob || fp >= _iob + OPEN_MAX) 93 return EOF; 94 if ((fp->flag & (_WRITE | _ERR)) != _WRITE) 95 return EOF; 96 bufsize = (fp->flag & _UNBUF) ? 1 : BUFSIZ; 97 if (fp->base == NULL){ //no buffer yet 98 if ((fp->base = (char *) malloc(bufsize)) == NULL){ 99 fp->flag |= _ERR; 100 return EOF; //can't get buffer 101 } 102 } else { //buffer already exists 103 nc = fp->ptr - fp->base; 104 if (write(fp->fd, fp->base, nc) != nc) { 105 fp->flag |= _ERR; 106 return EOF; //error: return EOF 107 } 108 } 109 fp->ptr = fp->base; //beginning of buffer 110 *fp->ptr++ = (char) x; //save current char 111 fp->cnt = bufsize -1; 112 return x 113 } 114 115 //fclose: close file 116 int fclose(FILE *fp) 117 { 118 int rc; //return code 119 120 if ((rc = fflush(fp)) != EOF) { //anything to flush 121 free(fp->base); 122 fp->ptr = NULL; 123 fp->cnt = 0; 124 fp->base = NULL; 125 fp->flag &= (_READ | _WRITE); 126 } 127 return rc; 128 } 129 130 //fflush: flush bufer associated with file fp 131 int fflush(FILE *fp) 132 { 133 int rc = 0; 134 135 if (fp < _iob || fp >= _iob + OPEN_MAX) 136 return EOF; //error: invalid pointer 137 if (fp->flag & _WRITE) 138 rc = _flushbuf(0, fp); 139 fp->ptr = fp->base; 140 fp->cnt = (fp->flag & _UNBUF) ? 1 : BUFSIZ; 141 return rc; 142 } 143 144 /* 练习8-4 类似函数lseek,所不同的时,该函数中的fp是一个文件指针不是文件 145 描述符,且返回值是一个int类型的状态而非位置值。编写函数fseek,并确保该函数 146 与库中其他函数使用的缓冲能够协同工作。 147 */ 148 149 //fseek: seek with a file pointer 150 int fseek(FILE *fp, long offset, int oigin) 151 { 152 unsigned nc; //# of chars to flush 153 154 long rc = 0; //return code 155 156 if (fp->flag & _READ) { 157 if (origin == 1) //from current position ? 158 offset -= fp->cnt; //remember chars in buffer 159 rc = lseek(fp->fd, offest, origin); 160 fp->cnt = 0; 161 } else if (fp->flag & _WRITE) { 162 if ((nc = fp->ptr - fp->base) > 0) 163 if (write(fp->fd, fp->base, nc) != nc) 164 rc = -1; 165 if (rc != -1) 166 rc = lseek(fp->fd, offset, origin); 167 } 168 return (rc == -1) ? -1 : 0; 169 }
8.6 实例--目录列表
我们常常还需要对文件系统执行另一种操作,以获得文件的有关信息,而不是读取文件的具体内容。目录列表程序便是其中一个例子,比如UNIX命令的ls,打印一个目录中文件名和其他一些可选信息,如文件长度、访问权限等等。MS-DOS操作系统中的dir命令也有类似的功能。
fsize程序是ls命令的一个特殊形式,打印命令行参数表中指定的所有文件的长度。如果其中一个文件是目录,则fsize程序将对此目录递归调用自身。如果命令行中没有任何参数,则fsize程序处理当前目录。
在UNIX系统中,目录就是文件,它包含了一个文件名列表和一些指示文件位置的信息。“位置”是一个指向其他表的索引。文件的i节点是存放除文件名意外的所有信息的地方。目录项通常仅包含两个条目:文件名和i节点编号。
/* 结构Dirent包含i节点编号和文件名。文件名的最大长度由NAMZ_MAX设定,NAME_MAX的值由系统决定。 opendir返回一个指向称为DIR结构的指针,该结构与结构FILE类似,它将被readdir和closedir使用。 这些信息存放在头文件dirent.h中 */ #include <sys/stat.h> #include <sys/types.h> //类型定义 #include <stdio.h> #include <string.h> #include "unistd.h" #include <fcntl.h> //读写标志 #include "dirent.h" /* 函数fsize打印文件的长度。但是,如果此文件是一个目录,则fsize首先调用dirwalk函数处理它所包含的 所有文件。注意如何使用文件<sys/stat.h>中的标志名 S_IFMT 和 S_IFDIR 来判定文件是不是一个目录。 括号必须有,因为&运算符的优先级低于==运算符的优先级*/ void fsize(char *); void dirwalk(char *dir, void (*fcn) (char *)); //print file name main(int argc, char *argv[]) { if (argc == 1) //default: current directory fsize("."); else { while (--argc > 0) { fsize(*++argv); } } return 0; } //fsize: print the name if 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); } /* 函数dirwalk是一个通用函数,它对目录中的每一个文件都调用函数fcn一次。它首先打开目录,循环遍历其中 的每一个文件,并对每个文件用该函数,然后关闭目录返回。因为fsize函数对每个目录都要调用dirwalk函数, 索引这两个函数是相互递归调用的。*/ #define MAX_PATH 1024 //dirwalk: apply fcn to all files in dir 对dir中的所有文件应用函数fcn 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; }/* 每次调用readdir都将返回一个指针, */ 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); } /* 每次调用readdir都将返回一个指针,它指向下一个文件的信息。如果目录中已没有待处理的文件,该函数将返回NULL 每个 目录都包含自身" ."和父目录" .."的项目,在处理时必须跳过它们,否则将会导致无限循环。 */ /* opendir函数首先打开目录,验证此文件时一个目录(调用系统调用fstat,它与stat类似,但它以文件描述符作为参数, 然后分配一个目录结构,并保存信息*/ int fstat(int fd, struct stat *); //opendir open a directory for readdir calls 打开目录供函数readdir使用 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函数用于关闭目录文件并释放内存空间 //closedir: close directory opened by opendir void closedir(DIR *dp) { if (dp) { close(dp->fd); free(dp); } } /* 函数readdir使用read系统调用读取每个目录项。如果某个目录位置当前没有使用(因为删除一个文件),则它的i节点 编号为0,并跳过该位置。否则,将i节点编号和目录名放在一个static类型的结构中,并给用户返回一个自画像此结构的指针。 每次调用readdir函数将覆盖前一次调用获得的信息。*/ //#ifdef DIRSIZ #define DIRSIZ 14 //#endif //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; } /* 尽管fsize程序非常特殊,但是它的却说明了一些重要的思想。首先,许多程序并不是"系统程序",他们仅仅使用由操作系统 维护的信息。对于这样的程序,很重要的一点时,信息的表示仅出现在标准头文件中,使用他们的程序只需要在文件中包含 这些文件即可,不需要包含相应的声明。其次有可能为与系统相关的对象创建一个与系统无关的结构。*/
1 //练习8-5 修改fsize程序,打印i节点项中包含的其他信息。 2 3 #include <stdio.h> 4 #include <string.h> 5 #include <fcntl.h> //flags for read and write 6 #include <sys/types.h> //typedefs 7 #include <sys/stat.h> //structure returned by stat 8 #include "dirent.h" 9 10 11 int stat(char *, struct stat *); 12 void dirwalk(char *, void (*fcn)(char *)); 13 14 //fsize: print inode #, mode, links, size of file "name" 15 void fsize(char *name) 16 { 17 struct stat stbuf; 18 19 if (stat(name, &stbuf) == -1) { 20 fprintf(stderr, "fsize: can't access %s\n", name); 21 return; 22 } 23 if ((stbuf.st_mode & S_IFMT) == S_IFDIR) 24 dirwalk(name, fsize); 25 printf("%5u %6o %3u %8ld %s\n", stbuf.st_ino, stbuf.st_mode,stbuf.st_nlink,stbuf.st_size, name); 26 }
8.7 实例--存储分配程序
本节将要并编写可以任意次序调用malloc和free。malloc在必要时调用操作系统以获取更多的存储空间。这程序说明了通过一种与系统无关的方式编写与系统有关的代码时应该考虑的问题,同时也展示了结构、联合和typedef的实际应用。
malloc并不是从一个编译时就确定的固定大小的数组中分配存储空间,而是在需要时向操作系统申请空间。因为程序中的某谢谢地方可能不通过malloc调用申请空间(通过其他方式申请空间),所以,malloc管理空间不一定时连续的。这样,空闲存储空间以空闲块链表的方式组织,每个块包含一个长度、一个指向下一个块的指针以及一个指向自身存储空间的指针。这些块按照存储地址的升序组织,最后一块(最高地址)指向第一块
当有申请请求时,malloc将扫描空闲块链表,只到找到一个足够大的块为止。该算法为“首次适应”(firs fit);与之相对的算法是"最佳适应“(nest fit),它寻找满足条件的最小块。如果该块恰好与请求的大小符合,则将它从链表中移走并返回给用户。如果该块太大,则将它分成两部分:大小合适的返回给用户,剩下的部分留在空闲块链表中。如果找不到足够大的块,则向操作系统申请一个大块并加入到空闲块链表中。
释放过程也是首先搜索空闲块链表,以找到可以插入被释放块的合适位置。如果与被释放块相邻的任一边是一个空闲块,这样存储空间不会太多的碎片。因为空闲块链表是以地址的递增顺序连接在一起的,所以很容易判断相邻的块是否空闲。
空闲块包含一个指向链表中下一个块的指针、一个块大小的记录和一个指向空闲空间本身的指针。位于块开始处的控制信息称为”头部“。为了简化块的对齐,所有块的大小必须是头部大小的整数倍,且头部已正确对齐。这是通过一个联合实现的,该联合包含所需的头部结构以及一个对齐要求最受限制的类型的实例,
// 下面这段程序中,我们假定long类型为最受限的类型 typedef long Align; //for alignment to long boundary 以long类型的边界对齐 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 if blocks 强制块的对齐 }; // 该联合中,Align字段永远不会被使用,它仅仅用于强制每个头部在最坏的情况下满足对齐要求。 typedef union header Header; /* 在malloc函数中,请求的长度(以字符为单位)将被舍入,以保证它是头部大小的整数倍。实际分配的块多包含一个单元 ,用于头部本身。实际分配的块的大小将被记录在头部的size字段中。malloc函数返回的指引将指向空闲空间,而不是块的头部。 用户可对获得的存储空间进行任何操作,但是,如果在分配的存储空间之外写入数据,则可能会破坏块链表。 */ /* 变量BASE表示空闲块链表的头部。每一次调用malloc函数时,freep为NULL,系统将创建一个退还的空闲块链表,它只 包含一个大小为0的块,且该块指向它自己。任何情况下,当请求空闲空间时,都将搜索空闲块链表。搜索从上一次找到空闲 块的地方(FREEP)开始。该策略可以保证链表时均匀的。如果找到的块太大,则将其尾部返回给用户,这样,初始块的头部 只需要修改size字段即可。在任何情况下,返回给用户的指针都指向块内的空闲存储空间,即此指向头部的指针大一个单元*/ #include <stddef.h> 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 = freep = prevp = &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 = moreroce(nunits)) == NULL) return NULL; //none left 没有剩余空间 } } /* 函数morecore用于向操作系统请求存储空间,其实现细节因系统的不同而不同。因为向系统请求存储空间时一个开销很大 的操作,因此,我们不希望每次调用malloc函数时都在执行该操作,基于这个考虑,morecore函数请求这至少NALLOC个单元。 这个较大的块将根据需要分成较小的块。在设置完成SIZE字段之后,morecore函数调用free函数把多余的存储空间插入到空闲 区域中。 UNIX系统调用sbrk(n)返回一个指针,该指针指向n个字节的存储空间。如果没有空闲空间,尽管返回NULL可能更好一些, 但sbrk调用返回-1,必须将-1强制转换为char *类型,以便于返回值进行比较。而且,强制类型转换使得该函数不会受不同机器 中指针表示不同影响。但是,这里仍然假定,有sbrk调用返回的指向不同块的多个指针之间可以进行有意义的比较。ANSI标准 并没有保证这一点,它只允许指向同一个数组的指针间的比较。因此,只有在一般指针间的比较操作由意义的机器上,该版本 的malloc函数才能移植。 */ #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函数。它从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 块freed在列表的开始或末尾 if (bp + bp->s.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->s.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; } /* 存储分配本质上与机器相关的,以上代码说明了如何控制与具体机器相关的部分,并将这部分程序控制到最少量。 typedef和union的使用解决了地址的对齐(假定sbrk返回的是合适的指针)问题。类型的强制转换使得指针的转换是显示 进行的,这样做甚至可以处理设计不够好的系统结构问题。 */
1 /* 标准库函数calloc(n, size)返回一个指针,指向n个长度为size的对象,且所有分配的存储空间都被初始化为0, 2 通过调用或修改malloc函数来实现calloc函数。 3 */ 4 5 #include <unistd.h> 6 7 //calloc: allocate n objeccts of size size 分配对象大小 8 void *calloc(unsigned n, unsigned size) 9 { 10 unsigned i, nb; 11 char *p, *q; 12 13 nb = n * size; 14 if ((p = q = malloc(nb)) != NULL) 15 for (i = 0; i < nb; i++) 16 *p++ = 0; 17 return q; 18 } 19 20 /* 函数calloc为长度是size的n个对象分配存储空间。我们把需要分配的字节总数计算并保存在变量nb中,malloc将返回 21 一个指针,该指针指向一个长度为nb个字节的内存块。这个内存块的起始位置将被保存在指针p和q中。如果分配成功,我们 22 就用下面的语句把分配到的nb个字节全部初始化为0 23 for (i = 0; i < nb; i++) 24 *p++ = 0; 25 calloc函数将返回一个指针,指针指向它申请到的内存块的起始位置,同时把这块内存区域全部初始化为0*/
1 // malloc接受对存储空间的请求时,并不检查请求长度的合理性;而free则认为被释放的块包含一个有效的长度字段。改进函数,使具有错误检查的功能 2 #include <stddef.h> 3 #include <stdio.h> 4 #define MAXBYTES (unsigned) 10240 5 typedef long Align; //for alignment to long boundary 以long类型的边界对齐 6 union header{ //block header 块头部 7 struct { 8 union header *ptr; //next block if on free list 空闲块列表中的下一个块 9 unsigned size; //size of this block 当前块的大小 10 } s; 11 Align x; //force alignment if blocks 强制块的对齐 12 }; 13 14 // 该联合中,Align字段永远不会被使用,它仅仅用于强制每个头部在最坏的情况下满足对齐要求。 15 16 17 typedef union header Header; 18 19 20 static unsigned maxalloc; //max number of units allocated 分配最大单元数 21 static Header base; //empty list to get started 空列表获得开始 22 static Header *freep = NULL; //start of free list 23 static Header *morecore(unsigned); 24 //malloc: general-purpose storage allocator 25 void *malloc(unsigned nbytes) 26 { 27 Header *p, *prevp; 28 unsigned nunits; 29 30 if (nbytes > MAXBYTES) { //not more than MAXBYTES 31 fprintf(stderr, "alloc:can't allocate more than %u bytes\n", MAXBYTES); 32 return NULL; 33 } 34 nunits = (nbytes + sizeof(Header) - 1) / sizeof(Header) + 1; 35 } 36 37 #define NALLOC 1024 //minimum #units to request 最少请求n个单位 38 39 //morecore: ask system for more memory 40 static Header *morecore(unsigned nu) 41 { 42 char *cp, *sbrk(int); 43 Header *up; 44 45 if (nu < NALLOC) 46 nu = NALLOC; 47 cp = sbrk(nu * sizeof(Header)); 48 if (cp == (char *) -1) //no space at all 没有空间 49 return NULL; 50 up = (Header *)cp; 51 up->s.size = nu; 52 maxalloc = (up->s.size > maxalloc) ? up->s.size : maxalloc; 53 free((void *)(up+1)); 54 return freep; 55 } 56 57 58 //free: put block ap in free list 把块放入自由列表 59 void free(void *ap) 60 { 61 Header *bp, *p; 62 63 bp = (Header *)ap - 1; //point to block header 64 if (bp->s.size == 0 || bp->s.size > maxalloc) { 65 fprintf(stderr, "free: can't free %u units\n", bp->s.size); 66 bp->s.size; 67 return; 68 } 69 for (p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr) 70 /*...*/; 71 } 72 73 74 /* 练习8-8 编写函数bfree(p,n),释放一个包含n个字符的任意块p,并将它放入有malloc和free维护的空闲块链表中。 75 通过使用bfree,用户可以在任意时刻向空闲块链表中添加一个静态或外部数组。*/ 76 77 //bfree: free an arbitrary block p of n chars 78 unsigned bfree(char *p, unsigned n) 79 { 80 Header *hp; 81 82 if (n < sizeof(Header)) 83 return 0; //too small to be useful 84 hp = (Header *)p; 85 hp->s.size = n / sizeof(Header); 86 free((void *)(hp+1)); 87 return hp->s.size; 88 } 89 90 /* bfree函数有两个参数:一个指向其实位置的指针p和一个给出字符个数的数值n,如果向调用bfree把某个内存块释放 91 到空闲区列表中,则这个内存块的传唱度至少要等于sizeof(Header),否则,返回值为0. 92 先把指针p转换为Header类型,在把它赋值给hp:hp = (Header *)p; 以sizeof(Header)为单位计算的内存块长度 93 是: hp->s.size = n / sizeof(Header); 94 最后,调用函数free来释放那个内存块。free函数的输入参数是一个指针,而它指向的位置要求刚好越过内存块的头部区域, 95 所以我们必须使用(hp+1)--转换为void *类型,如果内存块的长度太小,bfree函数将返回0;否则,它将返回一个以sizeof 96 (Header)为单位计算的内存块长度值*/

浙公网安备 33010602011771号