赞
踩
UNIX 操作系统通过一系列“系统调用”来对外提供服务,实际上,系统调用是操作系统内可以被用户程序调用的函数。本章描述怎样在 C 程序中使用一些最重要的系统调用。如果你使用 UNIX,本章应当会有直接的帮助,因为有时需要使用系统调用以得到最大化的效率,或访问库中没有的一些功能。然而,即使你在其他操作系统上使用 C 语言,通过研究这些样例,你也能够对 C 语言编程有更深入的理解;尽管细节不同,但类似的代码在任何系统上都能找到。由于在很多情况下 ANSI C 库是以 UNIX 功能为原型的,这些代码也能帮助你理解库。
本章分为三个主要部分:输入输出、文件系统和内存分配。前两部分假定读者对 UNIX 系统的外部特性有一定的熟悉。
第七章关注的是跨操作系统的统一输入/输出接口。在任何特定的系统上,标准库必须用该主机系统提供的功能来写。下面几节我们将描述 UNIX 的输入输出系统调用,并展示如何使用它们来实现部分的标准库。
在 UNIX 操作系统中,所有输入输出都是通过文件读写来完成的,因为所有的外部设备,即使是键盘和屏幕,也是文件系统中的文件。这意味着一个同构的接口就能处理程序和外设之间的所有通信。
在大多数情况下,在你读写文件之前,必须把你的意图告诉系统,这个过程叫做“打开”文件。如果你想要写文件,可能还有必要创建文件或者丢弃它之前的内容。系统会校验你是否有权限做(文件存在吗?你有权限访问它吗?),如果一切正常,会向程序返回一个称为“文件描述符”的非负小整数。(文件描述符类似标准库使用的文件指针,或是 MS-DOS 的文件句柄。)被打开的文件,其所有相关信息都由系统维护;用户程序只通过文件描述符来引用该文件。
由于涉及键盘和屏幕的输入输出非常普遍,为了使其更方便,系统做了一些特殊的安排。当命令解释器(“shell”)执行程序时,文件描述符分别为 0、1、2 的三个文件被打开,称为标准输入、标准输出和标准错误。如果程序读 0 并且写 1 和 2,则它可以进行输入输出而不用关心如何打开文件。
程序的用户可以使用 < 和 > 将 IO 重定向为来自文件或写到文件,如
prog <infile >outfile
在这种情况下,shell 把文件描述符0和1从默认分配改变为对应的文件。通常情况下,文件描述符 2 还是保持与屏幕的连接,因此错误消息能往屏幕输出。在输入或输出关联到管道的情况下,也能得到类似的观察结果。在所有情况下,文件分配是被 shell 而不是被程序修改的。只要程序使用0作为输入,而1和2作为输出,它就不知道自己的输入来自哪里,而输出去了哪里。
输入和输出使用 read 和 write 系统调用,C 程序可以通过两个叫做 read 和 write 的函数来访问它们。两个函数的第一个参数都是文件描述符。第二个参数是你程序中的一个字符数组,表示数据要输出到哪里,或来自哪里。第三个参数是要传输的字节数量。
- int n_read = read(int fd, char *buf, int n);
- int n_written = write(int fd, char *buf, int n);
每次调用返回已传输的字节数量。在读时,返回的字节数可能比请求的数量少。返回值为 0 字节说明文件结束,而 -1 表示出现了某种错误。在写时,返回值是写入的数量;如果不等于请求写入的数量,则说明发生了错误。
在一次调用中可能返回任意数量的字节。最常见的值是 1,这意味着每次一个字符(“无缓存”),而像 1024 和 4096 这样的数字对应着外部设备的一个物理块的大小。更大的字节数会更高效,因为系统调用的次数会更少。
把这些内容拼凑起来,我们就可以写个简单的程序来将其输入拷贝到输出,等价于在第一章写的文件拷贝程序。这个程序可以从任意的输入拷贝到任意的输出,因为输入和输出可以重定向到任意文件或设备。
- #include "syscalls.h"
-
- main() /* 拷贝输入到输出 */
- {
- char buf[BUFSIZ];
- int n;
-
- while ((n = read(0, buf, BUFSIZ)) > 0)
- write(1, buf, n);
- return 0;
- }
我们将这些系统调用的函数原型集中到一个叫做 syscalls.h 的头文件内,这样就能在本章的程序中包含使用了。不过,这个文件名不是标准。
参数 BUFSIZ 也是定义在 syscalls.h 中的,它的取值与本地系统适配。如果文件大小不是 BUFSIZ 的倍数,某一次 read 就会返回一个较小的字节数,来供 write 写入;对 read 的下一次调用会返回零。
看看如何使用 read 和 write 来构造如 getchar,putchar 等高级例程,是有指导意义的。例如,下面这个版本的 getchar 进行无缓冲的输出,每次从标准输入读取一个字符。
- #include "syscalls.h"
-
- /* getchar:无缓冲的单个字符输入 */
- int getchar(void)
- {
- char c;
-
- return (read(0, &c, 1)) == 1 ? (unsigned char) c : EOF;
- }
c 必须是 char,因为 read 需要字符指针。在返回语句中,把 c 强制转换为 unsigned char,避免了符号扩展带来的问题。
第二个版本的 getchar 采用大块的输入,并每次输出一个字符。
- #include "syscalls.h"
-
- /* getchar:简单的缓冲版本 */
- int getchar(void)
- {
- static char buf[BUFSIZ];
- static char *bufp = buf;
- static int n = 0;
-
- if (n == 0) { /* 缓冲区为空 */
- n = read(0, buf, sizeof buf);
- bufp = buf;
- }
- return (--n >= 0) ? (unsigned char) *bufp++ : EOF;
- }
如果这两个版本的 getchar 在编译时也包含了 <stdion.h> ,那么当标准库的 getchar 是用宏来实现时,我们代码里有必要对 getchar 这个名字做 #undef 。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。