低速系统调用 —— 可能会使进程永远阻塞的一类系统调用,包括:
如果数据不能立即被上述同类型的文件接受(由于在管道中无空间、网络流控制等),则写操作也会使调用者永远阻塞。
对已经上锁的文件进行读/写。
某些 ioctl
操作。
某些进程间通信函数(见第 15 章)
—
非阻塞 I/O 使我们可以调用 open
、 read
和 write
这样的 I/O 操作,并使这些操作不会永远阻塞。
如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。
—
有两种方法可以让给定的文件描述符使用非阻塞形式:
open
打开文件时,执行 O_NONBLOCK
标志fcntl
函数打开 O_NONBLOCK
标志(见3.14节)Note
POSIX.1 要求,对于一个非阻塞的描述符,如果无数据可读,则 read
返回 -1
,并设置 errno
为 EAGAIN
。
Note
O_NONBLOCK
影响共享同一文件表的所有用户,但与通过其他文件表对同一设备的访问无关(见图 3-1 和图 3-3)。
—
非阻塞 I/O 实例:
/*
* 14-1.c
*/
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
char buf [500000];
int
main(void)
{
int ntowrite, nwrite;
char *ptr;
ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
fprintf(stderr, "read %d bytes\n", ntowrite);
// 设置非阻塞标志
set_fl(STDOUT_FILENO, O_NONBLOCK);
ptr = buf;
while (ntowrite > 0) {
errno = 0;
nwrite = write(STDOUT_FILENO, ptr, ntowrite);
// 每次都将写出字节数和错误号码打印到标准输出
fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);
if (nwrite > 0) {
ptr += nwrite;
ntowrite -= nwrite;
}
}
// 清除非阻塞
clr_fl(STDOUT_FILENO, O_NONBLOCK);
exit(0);
}
输出:
$ ./14-1.out < /bin/bash 2>14-1-err.out
$ head 14-1-err.out
read 500000 bytes
nwrite = 6144, errno = 0 // 成功
nwrite = -1, errno = 11 // 失败 (AGAIN)
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
nwrite = -1, errno = 11
在这个实例中,程序发出了数千个 write
调用,但是只有非常小的一部分额是真正输出数据的,其余的都是出错返回。
这种形式的循环成为轮询,它的缺点是浪费了 CPU 时间。14.5 节将介绍非阻塞描述符的 I/O 多路转接,这是进行这种操作的一种比较有效的方法。
通过多线程,可以避免使用阻塞 I/O :即使一个线程被阻塞了,另一个线程也可以继续工作。不过多线程的使用增加了复杂性,很容易造成得不偿失的结果。