日期:2014-05-16 浏览次数:20827 次
poll/select/epoll的实现都是基于文件提供的poll方法(f_op->poll),
该方法利用poll_table提供的_qproc方法向文件内部事件掩码_key对应的的一个或多个等待队列(wait_queue_head_t)上添加包含唤醒函数(wait_queue_t.func)的节点(wait_queue_t),并检查文件当前就绪的状态返回给poll的调用者(依赖于文件的实现)。
当文件的状态发生改变时(例如网络数据包到达),文件就会遍历事件对应的等待队列并调用回调函数(wait_queue_t.func)唤醒等待线程。
通常的file.f_ops.poll实现及相关结构体如下
struct file { const struct file_operations *f_op; spinlock_t f_lock; // 文件内部实现细节 void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ // 其他细节.... }; // 文件操作 struct file_operations { // 文件提供给poll/select/epoll // 获取文件当前状态, 以及就绪通知接口函数 unsigned int (*poll) (struct file *, struct poll_table_struct *); // 其他方法read/write 等... ... }; // 通常的file.f_ops.poll 方法的实现 unsigned int file_f_op_poll (struct file *filp, struct poll_table_struct *wait) { unsigned int mask = 0; wait_queue_head_t * wait_queue; //1. 根据事件掩码wait->key_和文件实现filep->private_data 取得事件掩码对应的一个或多个wait queue head some_code(); // 2. 调用poll_wait 向获得的wait queue head 添加节点 poll_wait(filp, wait_queue, wait); // 3. 取得当前就绪状态保存到mask some_code(); return mask; } // select/poll/epoll 向文件注册就绪后回调节点的接口结构 typedef struct poll_table_struct { // 向wait_queue_head 添加回调节点(wait_queue_t)的接口函数 poll_queue_proc _qproc; // 关注的事件掩码, 文件的实现利用此掩码将等待队列传递给_qproc unsigned long _key; } poll_table; typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *); // 通用的poll_wait 函数, 文件的f_ops->poll 通常会调用此函数 static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) { if (p && p->_qproc && wait_address) { // 调用_qproc 在wait_address 上添加节点和回调函数 // 调用 poll_table_struct 上的函数指针向wait_address添加节点, 并设置节点的func // (如果是select或poll 则是 __pollwait, 如果是 epoll 则是 ep_ptable_queue_proc), p->_qproc(filp, wait_address, p); } } // wait_queue 头节点 typedef struct __wait_queue_head wait_queue_head_t; struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; // wait_queue 节点 typedef struct __wait_queue wait_queue_t; struct __wait_queue { unsigned int flags; #define WQ_FLAG_EXCLUSIVE 0x01 void *private; wait_queue_func_t func; struct list_head task_list; }; typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key); // 当文件的状态发生改变时, 文件会调用此函数,此函数通过调用wait_queue_t.func通知poll的调用者 // 其中key是文件当前的事件掩码 void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, void *key) { unsigned long flags; spin_lock_irqsave(&q->lock, flags); __wake_up_common(q, mode, nr_exclusive, 0, key); spin_unlock_irqrestore(&q->lock, flags); } static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive, int wake_flags, void *key) { wait_queue_t *curr, *next; // 遍历并调用func 唤醒, 通常func会唤醒调用poll的线程 list_for_each_entry_safe(curr, next, &q->task_list, task_list) { unsigned flags = curr->flags; if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) { break; } } }
poll和select的实现基本上是一致的,只是传递参数有所不同,他们的基本流程如下:
1. 复制用户数据到内核空间
2. 估计超时时间
3. 遍历每个文件并调用f_op->poll 取得文件当前就绪状态, 如果前面遍历的文件都没有就绪,向文件插入wait_queue节点
4. 遍历完成后检查状态:
??????? a). 如果已经有就绪的文件转到5;
??????? b). 如果有信号产生,重启poll或select(转到 1或3);
??????? c). 否则挂起进程等待超时或唤醒,超时或被唤醒后再次遍历所有文件取得每个文件的就绪状态
5. 将所有文件的就绪状态复制到用户空间
6. 清理申请的资源
?
下面是poll/select共用的结构体及其相关功能:
poll_wqueues 是?