第 14 章:高级 I/O

14.2 非阻塞 I/O

低速系统调用 —— 可能会使进程永远阻塞的一类系统调用,包括:

  • 如果某类文件类型(例如管道、终端设备和网络设备)的数据并不存在,则读操作会使调用者永远阻塞。
    [huangz:一般可以通过设置最长等待时间(timeout)来解决这类问题。]
  • 如果数据不能立即被上述同类型的文件接受(由于在管道中无空间、网络流控制等),则写操作也会使调用者永远阻塞。

  • 在某种条件发生之前,打开某些类型的文件会被阻塞。
    例如打开一个终端设备可能需要到与之连接的调制解调器应答;
    又例如在没有其他进程已用读模式打开该 FIFO 时若以只写模式打开 FIFO ,那么也要等待。
  • 对已经上锁的文件进行读/写。

  • 某些 ioctl 操作。

  • 某些进程间通信函数(见第 15 章)

非阻塞 I/O 使我们可以调用 openreadwrite 这样的 I/O 操作,并使这些操作不会永远阻塞。 如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。

有两种方法可以让给定的文件描述符使用非阻塞形式:

  1. 在调用 open 打开文件时,执行 O_NONBLOCK 标志
  2. 如果描述符已经被打开,那么使用 fcntl 函数打开 O_NONBLOCK 标志(见3.14节)

Note

POSIX.1 要求,对于一个非阻塞的描述符,如果无数据可读,则 read 返回 -1 ,并设置 errnoEAGAIN

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 :即使一个线程被阻塞了,另一个线程也可以继续工作。不过多线程的使用增加了复杂性,很容易造成得不偿失的结果。

留言

comments powered by Disqus

Table Of Contents

Previous topic

第 11 章:线程

Next topic

《Unix 网络编程》笔记