三者实现原理对比分析
select, poll, epoll都是IO多路复用的机制,上文中提到的多路复用主要是以select为例,select和poll大同小异,因为select和poll的实现有明显的缺点,所以在Linux2.5.44中引入了
新的处理大批量句柄的API——epoll,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。这里还是要强调,I/O多路复用机制的目的是应对大量的描述符,并不是所有情况下性能都是最好的(相比于直接的blocking、non-blocking、asynchronous),select、poll、epoll本质上都是同步I/O,因为它们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的(上文也得出了这个结论,epoll使用mmap机制省去了内核空间到用户空间的数据拷贝,似乎可以认为是异步的)。
select和poll的实现比较相似,重点还是以select为例,epoll可以说是select和poll的增强版,优化了几个方面的性能。
1. select实现原理
1)使用copy_from_user从用户空间拷贝fd_set到内核空间
2)注册回调函数__pollwait
3)遍历所有fd,调用其对应的poll方法(对于socket这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll、udp_poll或者datagram_poll)
4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数
5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(把进程挂到等待队列中并不代表进程已经睡眠)。在设备收到一条消息或填写完文件数据后,会唤醒设备等待队列上的进程,这时current便被唤醒了
6)poll方法返回时会返回一个描述读写操作是否就绪的掩码,根据这个掩码给fd_set赋值
7)如果遍历完所有的fd,还没有返回一个可读写的掩码,则会调用schedule_timeout使调用select的进程(也就是current)进入睡眠。当设备驱动发现自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定)还是没被唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd
8)把fd_set从内核空间拷贝到用户空间
select 的几个缺点:
1)每次调用select,都需要把fd集合从用户空间拷贝到内核空间,这个开销在fd很多时会很大
2)每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也会很大
3)select支持的文件描述符数量太小了,默认是1024
2. poll的实现