IO模型
输入操作包括两个阶段
- 内核等待数据到达
- 应用进程将数据从内核拷贝数据
Unix有5种IO模型
- 阻塞IO
- 非阻塞IO
- I/O复用(select/poll/epoll)
- 信号驱动 I/O(SIGIO)
- 异步IO
阻塞IO(BIO)
应用进程被阻塞数据从内核缓冲区复制到应用进程缓冲区返回。
调用阻塞IO的应用进程,并不影响其他进程执行,因此这种模型的CPU利用率比较高。
结合下面这张图来深入看下阻塞IO经历的过程。
当用户进程recvfrom这个系统调用时候,kernel就开始IO的第一个阶段:准备数据。对于network io来说,很多时候数据还没有到达(比如没有收到完整的UDP包,kernel要等到收到足够的数据。而在用户进程这边,整个进程都会被阻塞。当kernel把数据准备好,它会将数据从kernel拷贝到用户内存中,然后kernal返回结果,用户进程解除阻塞状态。
BIO在执行IO的两个阶段(等待数据和拷贝数据)都被block了。
这里可以看下进程的几个状态转换,于是就和操作系统里面的知识联系起来了。
其次,系统调用作为中断的一种(soft intrrrupt) 注意read(),recvfrom() 等区别。
read()函数原型如下,只能向已经建立好连接的socket读取数据
/**
* @fd 文件描述符
* @buf 读出数据缓冲区
* @count 请求读出字节数
* 成功返回读取的字节数,0表示读到文件尾,失败返回-1
*/
ssize_t read(int fd, void *buf, size_t count);
recvfrom()函数原型如下.如果用在已经建立连接的socket上,需要忽略其地址和地址长度参数,即地址指针设置为NULL,地址长度设置为0.并且flag参数设置为0,就和read()的功能一样。manual 手册解释的很清楚,下面是摘录
The only difference between recv() and read(2) is the presence of flags. With a zero flags argument, recv() is generally equivalent to read(2) (but see NOTES). Also, the following call recv(sockfd, buf, len, flags);is equivalent to recvfrom(sockfd, buf, len, flags, NULL, NULL); All three calls return the length of the message on successful completion.
/**
* @sockfd socket文件描述符
* @buf 读取数据的缓冲区
* @len 请求读取数据长度
* @flags 标志位
* @src_addr 源地址
* @addrlen 地址长度
* 成功返回读取字节数 失败返回-1
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
非阻塞IO
应用进程执行系统调用之后,内核返回一个错误码(errorno,全局变量)。应用进程可以继续执行,但是需要不断重复执行系统调用来确定I/O 是否完成,这种方式称为轮询(polling)
在这种工作模式下,由于应用进程需要不停的进行系统调用,加大了系统的开销,导致CPU的利用效率不高。为什么系统调用开销大?
下图清楚的说明了非阻塞调用在第一阶段不断的询问内核,第二阶段阻塞等待数据从内核拷贝到进程空间
IO复用
复用顾名思义就是一个应用进程同时监听多个套接字连接。
具体来看通过使用 select 或者 poll 等待数据,等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个客户端发起Socket连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
由下图可知第一阶段select()阻塞等待数据到来,第二阶段recvfrom()阻塞等待数据拷贝到进程空间缓冲区。这与阻塞IO模型的区别主要在于:阻塞IO调用recvfrom() 阻塞阶段从等数据到数据拷贝到用户缓冲区,需要注意其中的区别。
信号驱动IO
本质是进程之间的通信,在内核接接受到数据之后通过信号来通知应用进程。
可以借机复习下CSAPP里面的信号部分。
应用进程注册sigalaction信号,内核立刻返回,应用进程在等待数据阶段非阻塞。当数据到达时,内核向应用程序发送SIGIO信号,应用进程在收到该信号之后,在信号处理函数中调用recvfrom() 将数据从内核复制到应用进程中。相比较非阻塞IO,信号驱动下的CPU利用率高。
异步IO
应用进程执行aio_read()
系统调用立即返回,应用程序在整个IO操作不会阻塞,内核在完成所有操作之后向应用进程发送信号。
AIO和信号驱动IO区别:
AIO的信号通知应用IO完成,信号驱动IO通知进程开始IO.
小结
- 同步IO:将数据从内核缓冲区复制到应用进程缓冲区,应用进程会阻塞
- 异步IO:上述阶段不会阻塞
下面这张图很好总结呢五大IO模型