1、一般的IO过程
- 等待数据准备好,比如socket,等待数据到达内核(TCP协议栈是内核管理的啊!)
- 将数据从内核缓冲区拷贝到应用进程缓冲区。
读写事件:
- 有数据了,可以读了,是读事件。
- 数据区满了就没法写了,当数据区有空间了,可以写了,是写事件。
IO种类:内存IO,磁盘IO,缓存IO,网络IO。
一般是网络IO才需要等待,才需要多路复用;
2、IO模型
同步(synchronous)IO:在IO事件就绪后自己负责进行IO,也就是说这个IO过程是阻塞的,直到IO完成; 阻塞、非阻塞、IO复用都是同步IO;
异步(asynchronous)IO:启动IO时,如果数据还没准备好则直接返回,当数据准备好时,内核将数据copy到应用进程,一切搞定后,再通知应用进程read操作完成了;
阻塞(blocking)IO:数据没有准备好就一直阻塞等待;
非阻塞(non-blocking)IO:数据没准备好时,会返回错误给你,不会阻塞你。所以你需要不断轮询,看数据是否准备好了;(浪费CPU) 当数据准备好了,调用system call将数据从内核copy到应用进程时,这时进程会被阻塞。可以看出,非阻塞不是异步,异步是完全不会被阻塞的;
IO多路复用(multiplexing):也叫event driven IO。单个进程监听多个描述符,当某个描述符数据准备好了,就通知应用进程;也是阻塞的,当一个或多个描述符数据准备好了,才被唤醒。 另一个表述:监听多个事件,当一个或多个事件发生时,才唤醒进程;
什么叫复用?重复使用,单个进程管理多个IO,也就是多个IO重复使用一个进程;
BIO(同步阻塞) NIO(同步非阻塞) AIO(异步非阻塞)
另一个表述:监听多个事件,当一个或多个事件发生时,才唤醒进程;

3、IO多路复用的优势
- I/O多路复用的优势并不是对于单个连接能处理的更快,而是在于可以在单个线程/进程中处理更多的连接。 那多个连接要并发,还是需要多线程啊?
- 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,自然也就不需要线程切换了,从而大大减小了系统的开销。
4、select poll epoll的比较
这三个都是系统调用,内核帮你监听;
(1)select
原型:int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
fd_set
就是fd数组;返回值是就绪fd的个数;(fd_set虽然是指针,但还是会发生内存拷贝,用户态-->内核态)
内核不能信任任何用户空间的指针。必须对用户空间的指针指向的数据进行验证。如果只做验证不做拷贝的话,那么在随后的运行中要随时受到其它进/线程可能修改用户空间数据的威胁。所以必须做拷贝。
运行机制: 将fd_set拷贝进内核,内核帮你监听和遍历,遍历结束后,当有一个或多个fd就绪,内核就将 fd_set 从内核空间拷贝到用户空间,并通知你有fd就绪了,然后你自己再去轮询找哪个是就绪的。
缺点:
- fd采用数组结构,数量有限制,最多是1024;
- 每次调用select需要将所有fd从用户态copy到内核态,开销巨大;
- 某个fd就绪了,会通知你,但是不会告诉你是那个就绪了,你需要遍历所有fd;
(2)poll
原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
运行机制: 和select一样,只是fd采用链表结构,数量不限制了;其他缺点都存在;
(3)epoll
运行机制: 监听一组fd,当有一个或多个fd就绪时,会通知你,并告诉你哪些fd是就绪的;
优点:
- fd数量不限制,上限是进程的最大可以打开文件数;
- 每个fd定义一个类似回调的函数,来实现只有就绪的fd才会执行回调函数。那么应用进程就不需要遍历所有fd了;
- 省去不必要的内存拷贝:epoll 通过内核与用户空间 mmap 同一块内存实现。
for true{
[就绪fd] = epoll_wait(epoll_fd) //阻塞
//有fd就绪
for i in [就绪fd]{
读写操作
}
}
(4)触发机制
水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件
边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)。
select/poll只有水平触发,epoll两者都有;