select与poll与epoll,kqueue

select与poll与epoll,kqueue都是I/O多路复用的机制。

select

select的调用过程: 1. 首先应用程序调用select,进入内核调用sys_select,做些简单初始化工作。 2. 接着进入core_sys_select,将应用描述符从用户空间复制到内核空间。 3. - 最终进入do_select,调用 poll_initwait,主要工作是注册poll_wait的回调函数为__pollwait。 - __pollwait的主要工作是把当前进程挂载到等待队列里,当等待的事件到来就会唤醒此进程。 - 接着执行for循环,循环里首先遍历每个文件描述符,调用对应描述符的poll回调函数,检测是否就绪 - 遍历完所有描述符之后,只要有描述符处于就绪状态,信号中断,出错或者超时,就退出循环,否则会调用schedule_xxx函数,让当前进程睡眠,一直到超时或者有描述符就绪被唤醒。

select的缺点: 1. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 2. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 3. select支持的文件描述符数量太小了,默认是1024

poll

poll与select类似,但是select使用writefds、readfds、和exceptfds(可读,可写,期望)来表示三个文件描述符,poll使用一个 pollfd的指针实现

从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的资源。 同时,当select被多次唤醒时,内核与用户态之间的多次复制会消耗大量的cpu时间。

epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。

相对于select和poll来说,epoll更加灵活,没有描述符限制。

epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。而不是像select,poll每次调用都要重复拷贝文件描述符。

epoll分为三个函数 epoll_create,epoll_ctl, epoll_wait。

epoll_create创建epoll描述符,用来管理所有添加进去的描述符,epoll_ctl 用来添加,修改或者删除描述符,并且为文件描述符fd指定一个回掉函数。epoll_wait作实际上就是在这个就绪链表中查看有没有就绪的fd

select,poll需要自己不断轮询所有fd集合,直到设备就绪,多次遍历花费了大量的时间。而epoll虽然也需要调用epoll_wait不断检测,但是它只检测一下描述符就绪队列是否为空。设备就绪时,调用回调函数,把就绪fd放入就绪队列中。检测到就绪队列不为空时,就唤醒在epoll_wait中fd对应的进入睡眠的进程

kqueue

与epoll非常相似,在mac平台上。

libevent与libev

Libevent、libev、libuv三个网络库

libevent

libevent 库实际上没有更换 select()、poll() 或其他机制的基础。而是使用对于每个平台最高效的高性能解决方案在实现外加上一个包装器。

创建 libevent 服务器的基本方法是, 注册当发生某一操作(比如接受来自客户端的连接)时应该执行的函数,然后调用主事件循环event_dispatch()。执行过程的控制现在由 libevent 系统处理。注册事件和将调用的函数之后,事件系统开始自治;在应用程序运行时,可以在事件队列中添加(注册)或删除(取消注册)事件。事件注册非常方便,可以通过它添加新事件以处理新打开的连接,从而构建灵活的网络处理系统

libev

与 libevent 一样,libev 系统也是基于事件循环的系统,它在 poll()、select() 等机制的本机实现的基础上提供基于事件的循环。

libev 支持在实现中内置更多事件类型。例如,一种 evstat 实现可以监视多个文件的属性变动