将多路复用器用epoll_creat()、 epoll_ctl()、 epoll_wait() 三个系统函数实现,最牛逼的版本
过程描述
上面的程序是server端
- server端通过epoll_creat() = fd0 创建一个负责连接的fd0
- fd0在用户进程只是一个地址,在内核空间开辟了一块空间用fd0表示
- 用户进程没有开辟空间来维护fd集合,这里就是共享内存的优化
- server端通过epoll_ctl(fd0...fn) 将fd0负责连接的,注册到内核空间fd0表示的这块空间中
- server端通过epoll_ctl(fd0...fn) 将fd1...fdn负责数据读写的,注册到内核空间中
- server端通过epoll_wait(timeout) 获取IO准备就绪的fd列表
- 如果返回结果为空,非阻塞式的循环获取,如果返回的fd集合不为空,fd集合中的所有IO都是数据已经到达的
内核怎么监听fd列表
a.当fdx的磁盘io准备就绪后通过DMA总线将磁盘数据复制到共享内存dma区,这个是异步完成
b.磁盘产生一个数据就绪的中断
c1.磁盘数据就绪中断产生一个事件
c2.该中断通知CPU处理IO事件
d.OS通过事件到中断向量表中中查找内核注册的回调函数
e.cpu执行回调函数,将就绪的fdx从内核fd0空间中取出来,然后将fdx挂在一棵红黑树上,整棵树表示fx就绪的事件,连接事件,读取数据事件
epoll_wait()不断非阻塞遍历这个棵数,将就绪fd返回给用户进程,用户进程拿到就绪的fdx,此时fdx的数据已经提前复制到了dma区,不必将数据从内核态再复制到用户态
核心总结
epoll
- 突破了操作系统监听fd的数量限制
- 避免了fd数据从用户态到内核态的复制
- 非阻塞式的获取就绪fd列表
- 非阻塞式且带有超时时间的获取就绪fd列表
- 获取就绪列表效率高,每次只返回就绪,且避免空调用
- 就绪fd事件通过红黑树管理,提高查询效率,联想一下HashMap的一个hash桶如果节点数超过8个,则将链表转换成红黑树;这里的设计类似。数据结构太强大了,无处不在!!!
整个IO的进化历史显示,IO只有阻塞和非阻塞的区分,IO都是同步的,因为read的时候还是需要复制数据;只有计算区分同步和异步线程处理