阻塞
非阻塞
非阻塞I/O,如果I/O事件没有就绪就会立刻返回, 可以根据返回值和 errno 区分各种情况, 返回后我们可以去做其他事. 由于不阻塞, 所以为了完成读/写, 通常循环调用I/O函数, 直到事件发生后, 才去拷贝(从内核的读缓冲区拷贝到用户态或者用户态拷贝进内核的写缓冲区), 拷贝完成后返回
I/O复用
Linux使用select, poll, epoll函数实现I/O复用模型,这些函数也会使进程阻塞,但是和阻塞IO所不同的是 这些函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。直到有文件描述符发生了我们检测的读/写事件时,才真正去读/写;
I/O复用并不是提高并发的, 而是在单进程/线程环境下可以同时阻塞并且检测多个I/O
信号驱动
注册某个信号捕捉函数, 进程可以继续运行, 等到I/O事件就绪后, 进程收到一个信号, 并且去执行预先设置的信号处理函数去处理I/O事件;
因此, 在第一个阶段是异步的, 但真正读/写这个操作仍然是同步的. 但比非阻塞I/O好的一点是: 它提供了消息通知机制, 不需要用户进程不断轮循检查, 减少了系统API的调用次数, 使CPU减少了很多无用的空转
异步
以读为例, Linux提供了 aio_read 函数, 调用它会告诉内核描述符缓冲区的位置、存到哪, 及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再以某种方式通知应用程序, 这才是真正的异步.
struct aiocb { // 存了预先告知内核的信息
int aio_fildes; /* File desriptor. */
int aio_lio_opcode; /* Operation to be performed. */
int aio_reqprio; /* Request priority offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
struct sigevent aio_sigevent; /* Signal number and value. */
/* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /* File offset. */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /* File offset. */
#endif
char __glibc_reserved[32];
};