8.2 低级 I/O(read 和 write)
输入与输出是通过 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
,即每次读出或写入 1
个字符(无缓冲),或是类似于 1024 ~ 4096
这样的与外围设备的物理块大小相应的值
用更大的值调用该函数可以获得更高的效率,因为系统调用的次数减少了
结合以上的讨论,我们可以编写一个简单的程序,将输入复制到输出,这与第 1 章中的复制程序在功能上相同
程序可以将任意输入复制到任意输出,因为输入/输出可以重定向到任何文件或设备
#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
中
因此,本章中的程序都将包含该头文件,不过,该文件的名字不是标准的
参数 BUFSIZ
也已经在 syscalls.h
头文件中定义
对于所使用的操作系统来说,该值是一个较合适的数值
如果文件大小不是 BUFSIZ
的倍数,则对 read
的某次调用会返回一个较小的字节数
write
再按这个字节数写,此后再调用 read
将返回 0
为了更好地掌握有关概念,下面来说明如何用 read
和 write
构造类似于 getchar
、putchar
等的高级函数
例如,以下是 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
必须是一个 char
类型的变量,因为 read
函数需要一个字符指针类型的参数(&c
)
在返回语句中将 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;
}
如果要在包含头文件 <stdio.h>
的情况下编译这些版本的 getchar
函数
就有必要用 #undef
预处理指令取消名字 getchar
的宏定义,因为在头文件中,getchar
是以宏方式实现的