poll & epoll的内核实现
1 poll
poll的使用这里不多说。需要注意的是,调用poll时需要传递一个数组,每个fd对应一个struct pollfd,然后在poll返回后需要重新遍历数组以确定哪些fd可读或可写,效率比较低下。试想,在高并发的网络环境下,遍历一遍几十万规模的数组太耗功夫了。
但是,研究poll的内核实现,可以为我们提供一些背景知识。
1.1 驱动的poll与等待队列
每一个打开的文件均对应一个驱动程序,例如普通文件基于磁盘文件系统驱动,socket则对应网络协议栈,设备文件则基于对应的设备驱动。驱动程序最核心的就是通过read/write等操作为用户提供操作该设备的接口。
读写操作并不总是能够立即返回的,因此一般地,驱动程序会为读和写分别维护一个等待队列。当进程执行阻塞型read/write并且不能立即返回时,就会被加入到等待队列(wait_queue
),并且自己陷入睡眠(wait_event_interruptable
)。当条件满足时,驱动程序就会唤醒等待队列里的所有进程(wake_up_interruptable
)。
因此,使用poll代替read/write的目的就是避免阻塞操作。能够对某个fd使用poll的条件是对应的驱动程序必须实现文件操作中的poll函数,具体可以参考struct file_operations
。
首先它会调用poll_wait
,执行(通过形参传递过来的)的回调函数;接着,它需要检查当前是否可读/可写,并返回一个mask值。有了驱动的poll支持,sys_poll就可以实现自己功能了。
例如,socket的poll(sock_poll)的实现如下:
static unsigned int sock_poll(struct file *file, poll_table *wait)
{
struct socket *sock;
sock = file->private_data;
return sock->ops->poll(file, sock, wait);
}
static unsigned int poll(struct file *file, struct socket *sock, poll_table *wait)
{
struct sock *sk = sock->sk;
u32 mask;
poll_wait(file, sk->sk_sleep, wait);
if (!skb_queue_empty(&sk->sk_receive_queue) ||
(sock->state == S