当前位置:   article > 正文

C语言K&R圣经笔记 8.1文件描述符 8.2低级IO

C语言K&R圣经笔记 8.1文件描述符 8.2低级IO

第八章 UNIX 系统接口

UNIX 操作系统通过一系列“系统调用”来对外提供服务,实际上,系统调用是操作系统内可以被用户程序调用的函数。本章描述怎样在 C 程序中使用一些最重要的系统调用。如果你使用 UNIX,本章应当会有直接的帮助,因为有时需要使用系统调用以得到最大化的效率,或访问库中没有的一些功能。然而,即使你在其他操作系统上使用 C 语言,通过研究这些样例,你也能够对 C 语言编程有更深入的理解;尽管细节不同,但类似的代码在任何系统上都能找到。由于在很多情况下 ANSI C 库是以 UNIX 功能为原型的,这些代码也能帮助你理解库。

本章分为三个主要部分:输入输出、文件系统和内存分配。前两部分假定读者对 UNIX 系统的外部特性有一定的熟悉。

第七章关注的是跨操作系统的统一输入/输出接口。在任何特定的系统上,标准库必须用该主机系统提供的功能来写。下面几节我们将描述 UNIX 的输入输出系统调用,并展示如何使用它们来实现部分的标准库。

8.1 文件描述符


在 UNIX 操作系统中,所有输入输出都是通过文件读写来完成的,因为所有的外部设备,即使是键盘和屏幕,也是文件系统中的文件。这意味着一个同构的接口就能处理程序和外设之间的所有通信。

在大多数情况下,在你读写文件之前,必须把你的意图告诉系统,这个过程叫做“打开”文件。如果你想要写文件,可能还有必要创建文件或者丢弃它之前的内容。系统会校验你是否有权限做(文件存在吗?你有权限访问它吗?),如果一切正常,会向程序返回一个称为“文件描述符”的非负小整数。(文件描述符类似标准库使用的文件指针,或是 MS-DOS 的文件句柄。)被打开的文件,其所有相关信息都由系统维护;用户程序只通过文件描述符来引用该文件。

由于涉及键盘和屏幕的输入输出非常普遍,为了使其更方便,系统做了一些特殊的安排。当命令解释器(“shell”)执行程序时,文件描述符分别为 0、1、2 的三个文件被打开,称为标准输入、标准输出和标准错误。如果程序读 0 并且写 1 和 2,则它可以进行输入输出而不用关心如何打开文件。

程序的用户可以使用 < 和 > 将 IO 重定向为来自文件或写到文件,如

prog <infile >outfile


在这种情况下,shell 把文件描述符0和1从默认分配改变为对应的文件。通常情况下,文件描述符 2 还是保持与屏幕的连接,因此错误消息能往屏幕输出。在输入或输出关联到管道的情况下,也能得到类似的观察结果。在所有情况下,文件分配是被 shell 而不是被程序修改的。只要程序使用0作为输入,而1和2作为输出,它就不知道自己的输入来自哪里,而输出去了哪里。

8.2 低级IO - read和write


输入和输出使用 read 和 write 系统调用,C 程序可以通过两个叫做 read 和 write 的函数来访问它们。两个函数的第一个参数都是文件描述符。第二个参数是你程序中的一个字符数组,表示数据要输出到哪里,或来自哪里。第三个参数是要传输的字节数量。

  1. int n_read = read(int fd, char *buf, int n);
  2. int n_written = write(int fd, char *buf, int n);


每次调用返回已传输的字节数量。在读时,返回的字节数可能比请求的数量少。返回值为 0 字节说明文件结束,而 -1 表示出现了某种错误。在写时,返回值是写入的数量;如果不等于请求写入的数量,则说明发生了错误。

在一次调用中可能返回任意数量的字节。最常见的值是 1,这意味着每次一个字符(“无缓存”),而像 1024 和 4096 这样的数字对应着外部设备的一个物理块的大小。更大的字节数会更高效,因为系统调用的次数会更少。

把这些内容拼凑起来,我们就可以写个简单的程序来将其输入拷贝到输出,等价于在第一章写的文件拷贝程序。这个程序可以从任意的输入拷贝到任意的输出,因为输入和输出可以重定向到任意文件或设备。

  1. #include "syscalls.h"
  2. main()    /* 拷贝输入到输出 */
  3. {
  4.     char buf[BUFSIZ];
  5.     int n;
  6.     
  7.     while ((n = read(0, buf, BUFSIZ)) > 0)
  8.         write(1, buf, n);
  9.     return 0;
  10. }

我们将这些系统调用的函数原型集中到一个叫做 syscalls.h 的头文件内,这样就能在本章的程序中包含使用了。不过,这个文件名不是标准。

参数 BUFSIZ 也是定义在 syscalls.h 中的,它的取值与本地系统适配。如果文件大小不是 BUFSIZ 的倍数,某一次 read 就会返回一个较小的字节数,来供 write 写入;对 read 的下一次调用会返回零。

看看如何使用 read 和 write 来构造如 getchar,putchar 等高级例程,是有指导意义的。例如,下面这个版本的 getchar 进行无缓冲的输出,每次从标准输入读取一个字符。

  1. #include "syscalls.h"
  2. /* getchar:无缓冲的单个字符输入 */
  3. int getchar(void)
  4. {
  5.     char c;
  6.     return (read(0, &c, 1)) == 1 ? (unsigned char) c : EOF;
  7. }

c 必须是 char,因为 read 需要字符指针。在返回语句中,把 c 强制转换为 unsigned char,避免了符号扩展带来的问题。

第二个版本的 getchar 采用大块的输入,并每次输出一个字符。

  1. #include "syscalls.h"
  2. /* getchar:简单的缓冲版本 */
  3. int getchar(void)
  4. {
  5.     static char buf[BUFSIZ];
  6.     static char *bufp = buf;
  7.     static int n = 0;
  8.     if (n == 0) { /* 缓冲区为空 */
  9.         n = read(0, buf, sizeof buf);
  10.         bufp = buf;
  11.     }
  12.     return (--n >= 0) ? (unsigned char) *bufp++ : EOF;
  13. }

如果这两个版本的 getchar 在编译时也包含了 <stdion.h> ,那么当标准库的 getchar 是用宏来实现时,我们代码里有必要对 getchar 这个名字做 #undef 。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/131054
推荐阅读
相关标签
  

闽ICP备14008679号