回顾用户态和内核态的交互
- 用户态应用程序调用系统调用(系统调用相当于内核态的一组接口,满足用户态应用程序调用内核的使用)
- 用户态的应用程序也可以调用系统函数库的函数,函数库是在系统调用的基础上做了封装
IO底层的实现
- IO通信是在内核态中实现的,需要用户态调用系统调用实现
- 这里的系统调用接口其实就是select()方法,select返回需要的IO数据
select方法参数以及返回值
select的作用:使用socket套接字时,socket的读写需要调用系统调用select方法实现,select实现多路复用,每次socket调用select只需要注册,socket在fd_set集合中,一个select可以负责多个IO操作
- select原型
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- fd_set是C语言的一个结构体,结构体内包含一个数组,底层实现是一个bitMap也就是位图结构,数组大小是1024,所以socket的最大读写文件数是1024,也就是socket发送缓冲区的大小。
- readfds表示socket感兴趣的可读状态的fd文件集合
- writefds表示socket感兴趣的可写状态的fd文件集合
- exceptfds表示特殊事件的文件集合,比如出现错误等
- timeout表示超时时间返回select
- 用户态应用程序循环系统调用select查看是否有感兴趣的事件发生,每一个bit都对应一个fd,往集合了添加n时只需将第n个bit位置1,移除n时只需将第n个比特置为0,移除所有数据时,只需将所有bit置为0,并且在fd_set数组的事件的对应位置填写1,否则填写0。返回select。
返回值负数,0,正数
- 当返回为-1时,所有描述符集清0。
- 当返回为0时,表示超时。
- 当返回为正数时,表示已经准备好的描述符数。
- 返回正数时,开发者可以根据传入的socket循环查询是否有文件可读或者可写,时间复杂度是O(n),流太多的话会很消耗cpu
fd_set的方法
FD_CLR( s, *set) //从set中删除句柄s;
FD_ISSET( s, *set) //检查句柄s是否存在与set中;
FD_SET( s, *set ) //把句柄s添加到set中;
FD_ZERO( *set ) //把set队列初始为空
特点:
1.select/poll每次都需要重复传递全部的监听fd进来,涉及用户空间和内核直接的数据拷贝。
2.fd事件回调函数是pollwake,只是将本进程唤醒,本进程需要重新遍历全部的fd检查事件,然后保存事件,拷贝到用户空间,函数返回。
3.每次循环都是对全部的监测的fd进行轮询检测,可能发生事件的fd很少,这样效率很低。
4.当有事件发生,需要返回时,也需要将全部fd的事件进行返回,而其中可能只有很少的fd有事件发生。
5.select/poll返回时,会将该进程从全部监听的fd的等待队列里移除掉,这样就需要select/poll每次都要重新传入全部监听的fd,然后重新将本进程挂载到全部的监测fd的等待队列,大量重复劳动,效率很低。
epoll的实现原理
epoll的四大三大重要函数
epoll_create() //创建epoll对象,同时创建epoll使用到的fd_set集合,底层使用的是红黑树(方便查询,增删fd)
epoll_ctl() //操作fd_set,有EPPOLL_ADD和EPOLL_DEL操作,执行fd_set的增删
epoll_wait() //查看就绪队列是否有准备就绪的fd时间,唤醒等待进程,返回就绪队列
特点:
1.每次累加添加,不需要每次传入全部的监测fd。
2.每个fd只将本进程挂载到自己的等待队列一次,直到该fd被从epoll移除,不需要重复挂载。
3.fd事件回调函数是ep_epoll_callback,该函数将发生事件的fd加入到epoll专门的就绪队列rdllist中,同时唤醒本进程。
4.本进程不需要遍历每一个fd去监测事件是否发生,而只需要判断epoll中的就绪队列rdllist是否为空即可。
5.epoll返回时,只返回就绪队列rdllist中的项,避免了无关项的操作,应用层也就不需要再次重复遍历。
6.epoll内部使用红黑树存储监测fd,支持大量fd的快速查询、修改和删除操作。
总结
一. epoll与select/poll机制的相同点:
-
主要监测流程是一样的,都需要将当前进程挂载到对应fd的队列中去。如果fd有事件发生,调用挂载的回调函数,该回调函数基本的作用是唤醒本进程。
-
主事件检测循环是一样的,循环检测是否有事件发生,有则处理事件后返回;没有则调用schedule_timeout睡眠一会。不同的是,select/poll直接检测每个fd,而epoll只需检测就绪队列rdllist是否有数据即可。
二. epoll针对select/poll的痛点进行的修改,也就是高效之处总结:
-
select/poll把fd的监听列表放在用户空间,由用户空间管理,导致在用户空间和内核空间之间频繁重复拷贝大量fd;epoll在内核建立fd监听列表(实际是红黑树),每次通过epoll_ctl增删改即可。
-
select/poll每当有fd内核事件时,都唤醒当前进程,然后遍历监听列表全部fd,检查所有就绪fd并返回;epoll在有fd内核事件时,通过回调把该fd放到就绪队列中,只需返回该就绪队列即可,不需要每次遍历全部监听fd。
|