5种IO模型
-
阻塞IO
调用者调用某个函数,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作
-
非阻塞IO
非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事
-
信号驱动IO
linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件(将数据从内核态复制到用户态,这部分是阻塞的)
-
异步IO
调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后(相比信号驱动更加彻底,数据已经从内核态切换到用户态),再通知应用程序
-
IO复用/多路转接IO
linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数
IO多路复用
- I/O多路复用和阻塞I/O不同点在于可以同时处理多个connection。
- 因为使用两个system call (select 和 recvfrom),连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好。
IO复用优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接
IO multiplexing Model中,对于每一个socket一般为non-blocking。process是被select这个函数block,而不是被socket IO给block。
IO多路复用为什么比多线程效率高
本质:
IO多路复用还是需要多线程,但是所需要的线程数相对单纯多线程大大减少了,只需要相对少量的线程处理就绪的网络IO
结果:
需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销
IO多路复用阻塞
- 只在调用select、poll、epoll这些调用的时候才会阻塞
- 收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来
select
用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。
轮询方式查询每个fd对应的设备状态,没有发现就绪设备,则挂起进程,直到设备就绪或者超时,唤醒后又再次遍历轮询。
select机制
- copy_from_user从用户空间拷贝fd_set到内核空间
- 遍历所有fd,调用对应的poll方法,核心是__pollwait回调函数
- __pollwait把当前进程挂到设备的等待队列中,在设备就绪后(收到数据或者写完数据),会唤醒等待队列上睡眠的当前进程
- poll方法返回时会返回一个代表是否就绪的mask掩码,根据mask掩码给fd_set复制
- 如果遍历完所有fd,没有返回一个可读写mask掩码,则select进入睡眠
- 当驱动设备就绪后会唤醒等待队列上睡眠的进程
- 如果timeout后还没人唤醒,那么调用select的进程重新获得cpu,重新遍历fd
- 将fd_set从内核空间拷贝到用户空间
select缺点
-
select的最大文件数受限与FD_SIZE;
-
每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;
-
轮询(内核态一次、用户态一次)排查当文件描述符个数很多时,效率很低。
poll
poll本质上和select没有区别,基于链表存储保存描述符的结构体,解决了select文件描述符数量受限问题。
- 数组遍历轮询排查未解决
- 描述符集在用户态和内核态之间拷贝未解决
- 水平触发
epoll
epoll机制
- 调用epoll_create方法,内核创建eventpoll结构体
- eventpoll结构体
- 红黑树根节点,保存epitem结构体
- 每一个epitem结构体对应着一个监控事件。
- 红黑树有着良好的插入删除查询性能logn,便于对监控事件的添加删除修改
- 双向链表节点,保存已经满足条件的就绪事件
- epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据,有就返回,没有就sleep阻塞,直到timeout返回
- 红黑树根节点,保存epitem结构体
- 所有添加到epoll的事件都会和设备驱动程序建立回调关系,响应事件发生时,会把事件放到双向链表
调用方式
- epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
- 调用epoll_ctl向epoll对象中添加这100万个连接的套接字
- 调用epoll_wait收集发生的事件的连接
epoll优点
* 只返回状态发生变化的文件描述符,避免了内核态到用户态大量文件描述符拷贝,避免了用户态轮询方式查询所有就绪事件
* epoll中的事件都会与设备(如网卡)驱动程序**建立回调关系**,不需要每次去轮询是否就绪
* epoll没有最大连接数限制,而select有
* epoll性能不会随着fd数量的增减导致性能线性下降,而slect和poll会下降
* epoll利用mmap()文件映射内存,实现内核和用户空间共享一块内存减少了复制开销。而select和poll需要在内核、用户之间来回拷贝
- LT模式
- 缺省工作模式
- 同时支持block和no-block socket.
- 只要有数据都会触发,缓冲区剩余未读尽的数据会导致epoll_wait返回。
- ET模式
- 高速工作方式
- 只支持no-block socket
- 只有数据到来才触发,不管缓存区中是否还有数据,缓冲区剩余未读尽数据不会导致epoll返回
- ET使用场景
- LT模式下,如果大量不需要读写的就绪文件描述符,每次调用epoll_wait都会被返回,降低程序检索关心的就绪文件描述符的效率
- ET模式可以避免这种情况