关于阻塞I/O 非阻塞I/O 同步I/O 异步I/O epoll select的学习

之前参考了这篇文章,下面写一写笔记

阻塞IO 非阻塞IO

1.blocking IO 

linux中默认的socket都是阻塞的

用户进程发出系统调用后,数据未准备好,进程阻塞。。之后内核中有两个阶段:准备数据,复制数据到用户空间,数据在复制好了之后,内核将会唤醒该进程

特点:在数据准备与数据拷贝两个阶段进程都是阻塞的

2.non blocking IO

linux下,可以通过设置socket使其变为non-blocking

当用户发出系统调用后,如果数据没准备好,用户进程并不会被立即阻塞,而是返回一个错误。用户进程判断结果是一个error后,会再次发送read操作,一旦kernel中的数据准备好了,并且再次接收到了用户进程的系统调用后,那么用户进程直到知道数据拷贝完成。

特点:在数据准备的时候不阻塞,而是轮询。。

3.IO multiplexing

在数据准备阶段,如果数据没有准备好,用户进程被阻塞,但是可以通过调用select/epoll监听socket.当任何一个socket中的数据准备好了,select将会返回并唤醒用户进程。

关于阻塞I/O 非阻塞I/O 同步I/O 异步I/O epoll select的学习_第1张图片

与blocking IO的区别:blocking I/O只有一个一个系统调用,但是这个有两个。。同时一个select函数能处理多个连接,同时监视多个socket.

这个时候,socket是被设置为非阻塞的

4.signal driven IO

利用sigaction捕获SIGIO信号,内核在数据到达的时候发送SIGIO信号,应用进程在收到之后在信号处理程序中调用

recvfrom从内核中获取数据

5.Asynchronus IO

用的极少

用户进程发出一个read后,啥都不用管就可以直接走了。。 数据的准备与拷贝全部是内核负责的,当完成后,kernel发送给用户一个signal,之后可以通过回调函数等机制处理数据

同步IO 异步IO

其实,blocking IO , non blocking IO, IO multiplexing都属于同步IO ,为什么呢?

Stevens给出的定义(其实是POSIX的定义)是这样子的:
    A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
    An asynchronous I/O operation does not cause the requesting process to be blocked; 
两者的区别在于在执行recvfrom这个系统调用的过程中,时候用户进程是否是阻塞的。。。

select、poll、epoll

1.select的问题:

单个进程能够监视的文件描述符的数量存在最大限制。。linux上默认是最大为1024

对socket的扫描是采用线性扫描,需要维护一个存放文件描述符的数据结构

每次调用select,都需要把文件描述符从用户态拷贝到内核态,开销比较大

2.poll相对于select的改进

利用链表存储文件描述符。。没有最大限制

支持水平触发,支持再次提交已经上次遍历已经就绪但是用户进程未处理的文件描述符

但是和select一样,大量的fd数组要被拷贝进内核态

3.epoll

epoll有EPOLLLTEPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据没读完,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。

还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

epoll的优点:

1.每个fd在复制的过程中只拷贝一次

2.epoll为每个fd指定一个回调函数,当设备就绪唤醒等待队列上的消费者时,会调用这个回调函数

3.epoll所支持的文件描述符的上限是最大的可打开文件的数目,这个数目和系统内存有关

为什么要有EPOLLET

边沿触发把如何处理数据的控制权完全交给了开发者,提供了巨大的灵活性。比如,读取一个http的请求,开发者可以决定只读取http中的headers数据就停下来,然后根据业务逻辑判断是否要继续读(比如需要调用另外一个服务来决定是否继续读)。而不是次次被socket尚有数据的状态烦扰;写入数据时也是如此。比如希望将一个资源A写入到socket。当socket的buffer充足时,epoll_wait会返回这个fd是准备好的。但是资源A此时不一定准备好。如果使用水平触发,每次经过epoll_wait也总会被打扰。在边沿触发下,开发者有机会更精细的定制这里的控制逻辑。

但不好的一面时,边沿触发也大大的提高了编程的难度。一不留神,可能就会miss掉处理部分socket数据的机会。如果没有很好的根据EAGAIN来“重置”一个fd,就会造成此fd永远没有新事件产生,进而导致饿死相关的处理代码。

你可能感兴趣的:(Linux之美,epoll,阻塞IO/非阻塞IO)