epoll高效机制其实就是内核回调,我们知道linux把socket也是当成file处理的,只是底层驱动不一样。
先来看看内核文件结构:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ..
可以看到结构体中有一个 poll 函数,这部分由驱动实现,其核心函数是:
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) { if (p && wait_address) p->qproc(filp, wait_address, p); }
polltable 其实不是表,是一个结构体:
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *); typedef struct poll_table_struct { poll_queue_proc qproc; } poll_table;
包含一个回调函数,当驱动有事件的时候,会调用这个回调。
由此可以将epoll 内核实现分成两层:业务层和驱动层。
业务层实现epoll的各个函数,并将新增文件加到驱动层中,驱动层实现有事件时通知业务层。
业务层有两个函数与驱动层交互:
设置队列函数A,异步回调函数B
poll_table pt; pt.qproc = A;
当新增一个文件 sockt1 时,会调用
tfile->f_op->poll(socket1, &pt );
A立即调用,A要负责创建一个队列元素,该队列元素包含异步回调函数B,并将该元素插入到驱动层队列中。
当驱动层有事件时,会回调B,B再将对应socket1 插入到通知列表中,由epoll_wait 取走。
这样,epoll 的内核实现就清晰了,希望对大家有所帮助。