linux 内核poll/select/epoll实现剖析(原理经典)_poll 内部实现-程序员宅基地

技术标签: C++  LINUX  网络编程  

转自:https://blog.csdn.net/lishenglong666/article/details/45536611

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实现及相关结构体如下

C代码  收藏代码

  1. struct file {  
  2.     const struct file_operations    *f_op;  
  3.     spinlock_t          f_lock;  
  4.     // 文件内部实现细节  
  5.     void               *private_data;  
  6. #ifdef CONFIG_EPOLL  
  7.     /* Used by fs/eventpoll.c to link all the hooks to this file */  
  8.     struct list_head    f_ep_links;  
  9.     struct list_head    f_tfile_llink;  
  10. #endif /* #ifdef CONFIG_EPOLL */  
  11.     // 其他细节....  
  12. };  
  13.   
  14. // 文件操作  
  15. struct file_operations {  
  16.     // 文件提供给poll/select/epoll  
  17.     // 获取文件当前状态, 以及就绪通知接口函数  
  18.     unsigned int (*poll) (struct file *, struct poll_table_struct *);  
  19.     // 其他方法read/write 等... ...  
  20. };  
  21.   
  22. // 通常的file.f_ops.poll 方法的实现  
  23. unsigned int file_f_op_poll (struct file *filp, struct poll_table_struct *wait)  
  24. {  
  25.     unsigned int mask = 0;  
  26.     wait_queue_head_t * wait_queue;  
  27.   
  28.     //1. 根据事件掩码wait->key_和文件实现filep->private_data 取得事件掩码对应的一个或多个wait queue head  
  29.     some_code();  
  30.   
  31.     // 2. 调用poll_wait 向获得的wait queue head 添加节点  
  32.     poll_wait(filp, wait_queue, wait);  
  33.   
  34.     // 3. 取得当前就绪状态保存到mask  
  35.     some_code();  
  36.   
  37.     return mask;  
  38. }  
  39.   
  40. // select/poll/epoll 向文件注册就绪后回调节点的接口结构  
  41. typedef struct poll_table_struct {  
  42.     // 向wait_queue_head 添加回调节点(wait_queue_t)的接口函数  
  43.     poll_queue_proc _qproc;  
  44.     // 关注的事件掩码, 文件的实现利用此掩码将等待队列传递给_qproc  
  45.     unsigned long   _key;  
  46. } poll_table;  
  47. typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);  
  48.   
  49.   
  50. // 通用的poll_wait 函数, 文件的f_ops->poll 通常会调用此函数  
  51. static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)  
  52. {  
  53.     if (p && p->_qproc && wait_address) {  
  54.         // 调用_qproc 在wait_address 上添加节点和回调函数  
  55.         // 调用 poll_table_struct 上的函数指针向wait_address添加节点, 并设置节点的func  
  56.         // (如果是select或poll 则是 __pollwait, 如果是 epoll 则是 ep_ptable_queue_proc),  
  57.         p->_qproc(filp, wait_address, p);  
  58.     }  
  59. }  
  60.   
  61.   
  62. // wait_queue 头节点  
  63. typedef struct __wait_queue_head wait_queue_head_t;  
  64. struct __wait_queue_head {  
  65.     spinlock_t lock;  
  66.     struct list_head task_list;  
  67. };  
  68.   
  69. // wait_queue 节点  
  70. typedef struct __wait_queue wait_queue_t;  
  71. struct __wait_queue {  
  72.     unsigned int flags;  
  73. #define WQ_FLAG_EXCLUSIVE   0x01  
  74.     void *private;  
  75.     wait_queue_func_t func;  
  76.     struct list_head task_list;  
  77. };  
  78. typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);  
  79.   
  80.   
  81. // 当文件的状态发生改变时, 文件会调用此函数,此函数通过调用wait_queue_t.func通知poll的调用者  
  82. // 其中key是文件当前的事件掩码  
  83. void __wake_up(wait_queue_head_t *q, unsigned int mode,  
  84.                int nr_exclusive, void *key)  
  85. {  
  86.     unsigned long flags;  
  87.   
  88.     spin_lock_irqsave(&q->lock, flags);  
  89.     __wake_up_common(q, mode, nr_exclusive, 0, key);  
  90.     spin_unlock_irqrestore(&q->lock, flags);  
  91. }  
  92. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
  93.                              int nr_exclusive, int wake_flags, void *key)  
  94. {  
  95.     wait_queue_t *curr, *next;  
  96.     // 遍历并调用func 唤醒, 通常func会唤醒调用poll的线程  
  97.     list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
  98.         unsigned flags = curr->flags;  
  99.   
  100.         if (curr->func(curr, mode, wake_flags, key) &&  
  101.                 (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) {  
  102.             break;  
  103.         }  
  104.     }  
  105. }  

 poll 和 select

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 是 select/poll 对poll_table接口的具体化实现,其中的table, inline_index和inline_entries都是为了管理内存。
poll_table_entry 与一个文件相关联,用于管理插入到文件的wait_queue节点。

C代码  收藏代码

  1. // select/poll 对poll_table的具体化实现  
  2. struct poll_wqueues {  
  3.     poll_table pt;  
  4.     struct poll_table_page *table;     // 如果inline_entries 空间不足, 从poll_table_page 中分配  
  5.     struct task_struct *polling_task;  // 调用poll 或select 的进程  
  6.     int triggered;                     // 已触发标记  
  7.     int error;  
  8.     int inline_index;                  // 下一个要分配的inline_entrie 索引  
  9.     struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];//  
  10. };  
  11. // 帮助管理select/poll  申请的内存  
  12. struct poll_table_page {  
  13.     struct poll_table_page  * next;       // 下一个 page  
  14.     struct poll_table_entry * entry;      // 指向第一个entries  
  15.     struct poll_table_entry entries[0];  
  16. };  
  17. // 与一个正在poll /select 的文件相关联,  
  18. struct poll_table_entry {  
  19.     struct file *filp;               // 在poll/select中的文件  
  20.     unsigned long key;  
  21.     wait_queue_t wait;               // 插入到wait_queue_head_t 的节点  
  22.     wait_queue_head_t *wait_address; // 文件上的wait_queue_head_t 地址  
  23. };  

 

公共函数

 下面是poll/select公用的一些函数,这些函数实现了poll和select的核心功能。

poll_initwait 用于初始化poll_wqueues,

__pollwait 实现了向文件中添加回调节点的逻辑,

pollwake 当文件状态发生改变时,由文件调用,用来唤醒线程,

poll_get_entry,free_poll_entry,poll_freewait用来申请释放poll_table_entry 占用的内存,并负责释放文件上的wait_queue节点。

 

C代码  收藏代码

  1. // poll_wqueues 的初始化:  
  2. // 初始化 poll_wqueues , __pollwait会在文件就绪时被调用  
  3. void poll_initwait(struct poll_wqueues *pwq)  
  4. {  
  5.     // 初始化poll_table, 相当于调用基类的构造函数  
  6.     init_poll_funcptr(&pwq->pt, __pollwait);  
  7.     /* 
  8.      * static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) 
  9.      * { 
  10.      *     pt->_qproc = qproc; 
  11.      *     pt->_key   = ~0UL; 
  12.      * } 
  13.      */  
  14.     pwq->polling_task = current;  
  15.     pwq->triggered = 0;  
  16.     pwq->error = 0;  
  17.     pwq->table = NULL;  
  18.     pwq->inline_index = 0;  
  19. }  
  20.   
  21.   
  22. // wait_queue设置函数  
  23. // poll/select 向文件wait_queue中添加节点的方法  
  24. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
  25.                        poll_table *p)  
  26. {  
  27.     struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);  
  28.     struct poll_table_entry *entry = poll_get_entry(pwq);  
  29.     if (!entry) {  
  30.         return;  
  31.     }  
  32.     get_file(filp); //put_file() in free_poll_entry()  
  33.     entry->filp = filp;  
  34.     entry->wait_address = wait_address; // 等待队列头  
  35.     entry->key = p->key;  
  36.     // 设置回调为 pollwake  
  37.     init_waitqueue_func_entry(&entry->wait, pollwake);  
  38.     entry->wait.private = pwq;  
  39.     // 添加到等待队列  
  40.     add_wait_queue(wait_address, &entry->wait);  
  41. }  
  42.   
  43. // 在等待队列(wait_queue_t)上回调函数(func)  
  44. // 文件就绪后被调用,唤醒调用进程,其中key是文件提供的当前状态掩码  
  45. static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  46. {  
  47.     struct poll_table_entry *entry;  
  48.     // 取得文件对应的poll_table_entry  
  49.     entry = container_of(wait, struct poll_table_entry, wait);  
  50.     // 过滤不关注的事件  
  51.     if (key && !((unsigned long)key & entry->key)) {  
  52.         return 0;  
  53.     }  
  54.     // 唤醒  
  55.     return __pollwake(wait, mode, sync, key);  
  56. }  
  57. static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
  58. {  
  59.     struct poll_wqueues *pwq = wait->private;  
  60.     // 将调用进程 pwq->polling_task 关联到 dummy_wait  
  61.     DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);  
  62.     smp_wmb();  
  63.     pwq->triggered = 1;// 标记为已触发  
  64.     // 唤醒调用进程  
  65.     return default_wake_function(&dummy_wait, mode, sync, key);  
  66. }  
  67.   
  68. // 默认的唤醒函数,poll/select 设置的回调函数会调用此函数唤醒  
  69. // 直接唤醒等待队列上的线程,即将线程移到运行队列(rq)  
  70. int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,  
  71.                           void *key)  
  72. {  
  73.     // 这个函数比较复杂, 这里就不具体分析了  
  74.     return try_to_wake_up(curr->private, mode, wake_flags);  
  75. }  

 

poll,select对poll_table_entry的申请和释放采用的是类似内存池的管理方式,先使用预分配的空间,预分配的空间不足时,分配一个内存页,使用内存页上的空间。

 

C代码  收藏代码

  1. // 分配或使用已先前申请的 poll_table_entry,  
  2. static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p) {  
  3.     struct poll_table_page *table = p->table;  
  4.   
  5.     if (p->inline_index < N_INLINE_POLL_ENTRIES) {  
  6.         return p->inline_entries + p->inline_index++;  
  7.     }  
  8.   
  9.     if (!table || POLL_TABLE_FULL(table)) {  
  10.         struct poll_table_page *new_table;  
  11.         new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);  
  12.         if (!new_table) {  
  13.             p->error = -ENOMEM;  
  14.             return NULL;  
  15.         }  
  16.         new_table->entry = new_table->entries;  
  17.         new_table->next = table;  
  18.         p->table = new_table;  
  19.         table = new_table;  
  20.     }  
  21.     return table->entry++;  
  22. }  
  23.   
  24. // 清理poll_wqueues 占用的资源  
  25. void poll_freewait(struct poll_wqueues *pwq)  
  26. {  
  27.     struct poll_table_page * p = pwq->table;  
  28.     // 遍历所有已分配的inline poll_table_entry  
  29.     int i;  
  30.     for (i = 0; i < pwq->inline_index; i++) {  
  31.         free_poll_entry(pwq->inline_entries + i);  
  32.     }  
  33.     // 遍历在poll_table_page上分配的inline poll_table_entry  
  34.     // 并释放poll_table_page  
  35.     while (p) {  
  36.         struct poll_table_entry * entry;  
  37.         struct poll_table_page *old;  
  38.         entry = p->entry;  
  39.         do {  
  40.             entry--;  
  41.             free_poll_entry(entry);  
  42.         } while (entry > p->entries);  
  43.         old = p;  
  44.         p = p->next;  
  45.         free_page((unsigned long) old);  
  46.     }  
  47. }  
  48. static void free_poll_entry(struct poll_table_entry *entry)  
  49. {  
  50.     // 从等待队列中删除, 释放文件引用计数  
  51.     remove_wait_queue(entry->wait_address, &entry->wait);  
  52.     fput(entry->filp);  
  53. }  

 

 poll/select核心结构关系

下图是 poll/select 实现公共部分的关系图,包含了与文件直接的关系,以及函数之间的依赖。


 

 poll的实现

 

C代码  收藏代码

  1. // poll 使用的结构体  
  2. struct pollfd {  
  3.     int fd;        // 描述符  
  4.     short events;  // 关注的事件掩码  
  5.     short revents; // 返回的事件掩码  
  6. };  
  7. // long sys_poll(struct pollfd *ufds, unsigned int nfds, long timeout_msecs)  
  8. SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,  
  9.                 long, timeout_msecs)  
  10. {  
  11.     struct timespec end_time, *to = NULL;  
  12.     int ret;  
  13.     if (timeout_msecs >= 0) {  
  14.         to = &end_time;  
  15.         // 将相对超时时间msec 转化为绝对时间  
  16.         poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,  
  17.                                 NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));  
  18.     }  
  19.     // do sys poll  
  20.     ret = do_sys_poll(ufds, nfds, to);  
  21.     // do_sys_poll 被信号中断, 重新调用, 对使用者来说 poll 是不会被信号中断的.  
  22.     if (ret == -EINTR) {  
  23.         struct restart_block *restart_block;  
  24.         restart_block = &current_thread_info()->restart_block;  
  25.         restart_block->fn = do_restart_poll; // 设置重启的函数  
  26.         restart_block->poll.ufds = ufds;  
  27.         restart_block->poll.nfds = nfds;  
  28.         if (timeout_msecs >= 0) {  
  29.             restart_block->poll.tv_sec = end_time.tv_sec;  
  30.             restart_block->poll.tv_nsec = end_time.tv_nsec;  
  31.             restart_block->poll.has_timeout = 1;  
  32.         } else {  
  33.             restart_block->poll.has_timeout = 0;  
  34.         }  
  35.         // ERESTART_RESTARTBLOCK 不会返回给用户进程,  
  36.         // 而是会被系统捕获, 然后调用 do_restart_poll,  
  37.         ret = -ERESTART_RESTARTBLOCK;  
  38.     }  
  39.     return ret;  
  40. }  
  41. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
  42.                 struct timespec *end_time)  
  43. {  
  44.     struct poll_wqueues table;  
  45.     int err = -EFAULT, fdcount, len, size;  
  46.     /* 首先使用栈上的空间,节约内存,加速访问 */  
  47.     long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
  48.     struct poll_list *const head = (struct poll_list *)stack_pps;  
  49.     struct poll_list *walk = head;  
  50.     unsigned long todo = nfds;  
  51.     if (nfds > rlimit(RLIMIT_NOFILE)) {  
  52.         // 文件描述符数量超过当前进程限制  
  53.         return -EINVAL;  
  54.     }  
  55.     // 复制用户空间数据到内核  
  56.     len = min_t(unsigned int, nfds, N_STACK_PPS);  
  57.     for (;;) {  
  58.         walk->next = NULL;  
  59.         walk->len = len;  
  60.         if (!len) {  
  61.             break;  
  62.         }  
  63.         // 复制到当前的 entries  
  64.         if (copy_from_user(walk->entries, ufds + nfds-todo,  
  65.                            sizeof(struct pollfd) * walk->len)) {  
  66.             goto out_fds;  
  67.         }  
  68.         todo -= walk->len;  
  69.         if (!todo) {  
  70.             break;  
  71.         }  
  72.         // 栈上空间不足,在堆上申请剩余部分  
  73.         len = min(todo, POLLFD_PER_PAGE);  
  74.         size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;  
  75.         walk = walk->next = kmalloc(size, GFP_KERNEL);  
  76.         if (!walk) {  
  77.             err = -ENOMEM;  
  78.             goto out_fds;  
  79.         }  
  80.     }  
  81.     // 初始化 poll_wqueues 结构, 设置函数指针_qproc  为__pollwait  
  82.     poll_initwait(&table);  
  83.     // poll  
  84.     fdcount = do_poll(nfds, head, &table, end_time);  
  85.     // 从文件wait queue 中移除对应的节点, 释放entry.  
  86.     poll_freewait(&table);  
  87.     // 复制结果到用户空间  
  88.     for (walk = head; walk; walk = walk->next) {  
  89.         struct pollfd *fds = walk->entries;  
  90.         int j;  
  91.         for (j = 0; j < len; j++, ufds++)  
  92.             if (__put_user(fds[j].revents, &ufds->revents)) {  
  93.                 goto out_fds;  
  94.             }  
  95.     }  
  96.     err = fdcount;  
  97. out_fds:  
  98.     // 释放申请的内存  
  99.     walk = head->next;  
  100.     while (walk) {  
  101.         struct poll_list *pos = walk;  
  102.         walk = walk->next;  
  103.         kfree(pos);  
  104.     }  
  105.     return err;  
  106. }  
  107. // 真正的处理函数  
  108. static int do_poll(unsigned int nfds,  struct poll_list *list,  
  109.                    struct poll_wqueues *wait, struct timespec *end_time)  
  110. {  
  111.     poll_table* pt = &wait->pt;  
  112.     ktime_t expire, *to = NULL;  
  113.     int timed_out = 0, count = 0;  
  114.     unsigned long slack = 0;  
  115.     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  116.         // 已经超时,直接遍历所有文件描述符, 然后返回  
  117.         pt = NULL;  
  118.         timed_out = 1;  
  119.     }  
  120.     if (end_time && !timed_out) {  
  121.         // 估计进程等待时间,纳秒  
  122.         slack = select_estimate_accuracy(end_time);  
  123.     }  
  124.     // 遍历文件,为每个文件的等待队列添加唤醒函数(pollwake)  
  125.     for (;;) {  
  126.         struct poll_list *walk;  
  127.         for (walk = list; walk != NULL; walk = walk->next) {  
  128.             struct pollfd * pfd, * pfd_end;  
  129.             pfd = walk->entries;  
  130.             pfd_end = pfd + walk->len;  
  131.             for (; pfd != pfd_end; pfd++) {  
  132.                 // do_pollfd 会向文件对应的wait queue 中添加节点  
  133.                 // 和回调函数(如果 pt 不为空)  
  134.                 // 并检查当前文件状态并设置返回的掩码  
  135.                 if (do_pollfd(pfd, pt)) {  
  136.                     // 该文件已经准备好了.  
  137.                     // 不需要向后面文件的wait queue 中添加唤醒函数了.  
  138.                     count++;  
  139.                     pt = NULL;  
  140.                 }  
  141.             }  
  142.         }  
  143.         // 下次循环的时候不需要向文件的wait queue 中添加节点,  
  144.         // 因为前面的循环已经把该添加的都添加了  
  145.         pt = NULL;  
  146.   
  147.         // 第一次遍历没有发现ready的文件  
  148.         if (!count) {  
  149.             count = wait->error;  
  150.             // 有信号产生  
  151.             if (signal_pending(current)) {  
  152.                 count = -EINTR;  
  153.             }  
  154.         }  
  155.   
  156.         // 有ready的文件或已经超时  
  157.         if (count || timed_out) {  
  158.             break;  
  159.         }  
  160.         // 转换为内核时间  
  161.         if (end_time && !to) {  
  162.             expire = timespec_to_ktime(*end_time);  
  163.             to = &expire;  
  164.         }  
  165.         // 等待事件就绪, 如果有事件发生或超时,就再循  
  166.         // 环一遍,取得事件状态掩码并计数,  
  167.         // 注意此次循环中, 文件 wait queue 中的节点依然存在  
  168.         if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) {  
  169.             timed_out = 1;  
  170.         }  
  171.     }  
  172.     return count;  
  173. }  
  174.   
  175.   
  176. static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)  
  177. {  
  178.     unsigned int mask;  
  179.     int fd;  
  180.     mask = 0;  
  181.     fd = pollfd->fd;  
  182.     if (fd >= 0) {  
  183.         int fput_needed;  
  184.         struct file * file;  
  185.         // 取得fd对应的文件结构体  
  186.         file = fget_light(fd, &fput_needed);  
  187.         mask = POLLNVAL;  
  188.         if (file != NULL) {  
  189.             // 如果没有 f_op 或 f_op->poll 则认为文件始终处于就绪状态.  
  190.             mask = DEFAULT_POLLMASK;  
  191.             if (file->f_op && file->f_op->poll) {  
  192.                 if (pwait) {  
  193.                     // 设置关注的事件掩码  
  194.                     pwait->key = pollfd->events | POLLERR | POLLHUP;  
  195.                 }  
  196.                 // 注册回调函数,并返回当前就绪状态,就绪后会调用pollwake  
  197.                 mask = file->f_op->poll(file, pwait);  
  198.             }  
  199.             mask &= pollfd->events | POLLERR | POLLHUP; // 移除不需要的状态掩码  
  200.             fput_light(file, fput_needed);// 释放文件  
  201.         }  
  202.     }  
  203.     pollfd->revents = mask; // 更新事件状态  
  204.     return mask;  
  205. }  
  206.   
  207.   
  208. static long do_restart_poll(struct restart_block *restart_block)  
  209. {  
  210.     struct pollfd __user *ufds = restart_block->poll.ufds;  
  211.     int nfds = restart_block->poll.nfds;  
  212.     struct timespec *to = NULL, end_time;  
  213.     int ret;  
  214.     if (restart_block->poll.has_timeout) {  
  215.         // 获取先前的超时时间  
  216.         end_time.tv_sec = restart_block->poll.tv_sec;  
  217.         end_time.tv_nsec = restart_block->poll.tv_nsec;  
  218.         to = &end_time;  
  219.     }  
  220.     ret = do_sys_poll(ufds, nfds, to); // 重新调用 do_sys_poll  
  221.     if (ret == -EINTR) {  
  222.         // 又被信号中断了, 再次重启  
  223.         restart_block->fn = do_restart_poll;  
  224.         ret = -ERESTART_RESTARTBLOCK;  
  225.     }  
  226.     return ret;  
  227. }  

 

 

 

select 实现

 

C代码  收藏代码

  1. typedef struct {  
  2.     unsigned long *in, *out, *ex;  
  3.     unsigned long *res_in, *res_out, *res_ex;  
  4. } fd_set_bits;  
  5. //  long sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)  
  6. SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,  
  7.                 fd_set __user *, exp, struct timeval __user *, tvp)  
  8. {  
  9.     struct timespec end_time, *to = NULL;  
  10.     struct timeval tv;  
  11.     int ret;  
  12.     if (tvp) {  
  13.         if (copy_from_user(&tv, tvp, sizeof(tv))) {  
  14.             return -EFAULT;  
  15.         }  
  16.         // 计算超时时间  
  17.         to = &end_time;  
  18.         if (poll_select_set_timeout(to,  
  19.                                     tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),  
  20.                                     (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) {  
  21.             return -EINVAL;  
  22.         }  
  23.     }  
  24.     ret = core_sys_select(n, inp, outp, exp, to);  
  25.     // 复制剩余时间到用户空间  
  26.     ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);  
  27.     return ret;  
  28. }  
  29.   
  30. int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,  
  31.                     fd_set __user *exp, struct timespec *end_time)  
  32. {  
  33.     fd_set_bits fds;  
  34.     void *bits;  
  35.     int ret, max_fds;  
  36.     unsigned int size;  
  37.     struct fdtable *fdt;  
  38.     //小对象使用栈上的空间,节约内存, 加快访问速度  
  39.     long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  
  40.   
  41.     ret = -EINVAL;  
  42.     if (n < 0) {  
  43.         goto out_nofds;  
  44.     }  
  45.   
  46.     rcu_read_lock();  
  47.     // 取得进程对应的 fdtable  
  48.     fdt = files_fdtable(current->files);  
  49.     max_fds = fdt->max_fds;  
  50.     rcu_read_unlock();  
  51.     if (n > max_fds) {  
  52.         n = max_fds;  
  53.     }  
  54.   
  55.     size = FDS_BYTES(n);  
  56.     bits = stack_fds;  
  57.     if (size > sizeof(stack_fds) / 6) {  
  58.         // 栈上的空间不够, 申请内存, 全部使用堆上的空间  
  59.         ret = -ENOMEM;  
  60.         bits = kmalloc(6 * size, GFP_KERNEL);  
  61.         if (!bits) {  
  62.             goto out_nofds;  
  63.         }  
  64.     }  
  65.     fds.in     = bits;  
  66.     fds.out    = bits +   size;  
  67.     fds.ex     = bits + 2*size;  
  68.     fds.res_in  = bits + 3*size;  
  69.     fds.res_out = bits + 4*size;  
  70.     fds.res_ex  = bits + 5*size;  
  71.   
  72.     // 复制用户空间到内核  
  73.     if ((ret = get_fd_set(n, inp, fds.in)) ||  
  74.             (ret = get_fd_set(n, outp, fds.out)) ||  
  75.             (ret = get_fd_set(n, exp, fds.ex))) {  
  76.         goto out;  
  77.     }  
  78.     // 初始化fd set  
  79.     zero_fd_set(n, fds.res_in);  
  80.     zero_fd_set(n, fds.res_out);  
  81.     zero_fd_set(n, fds.res_ex);  
  82.   
  83.     ret = do_select(n, &fds, end_time);  
  84.   
  85.     if (ret < 0) {  
  86.         goto out;  
  87.     }  
  88.     if (!ret) {  
  89.         // 该返回值会被系统捕获, 并以同样的参数重新调用sys_select()  
  90.         ret = -ERESTARTNOHAND;  
  91.         if (signal_pending(current)) {  
  92.             goto out;  
  93.         }  
  94.         ret = 0;  
  95.     }  
  96.   
  97.     // 复制到用户空间  
  98.     if (set_fd_set(n, inp, fds.res_in) ||  
  99.             set_fd_set(n, outp, fds.res_out) ||  
  100.             set_fd_set(n, exp, fds.res_ex)) {  
  101.         ret = -EFAULT;  
  102.     }  
  103.   
  104. out:  
  105.     if (bits != stack_fds) {  
  106.         kfree(bits);  
  107.     }  
  108. out_nofds:  
  109.     return ret;  
  110. }  
  111.   
  112. int do_select(int n, fd_set_bits *fds, struct timespec *end_time)  
  113. {  
  114.     ktime_t expire, *to = NULL;  
  115.     struct poll_wqueues table;  
  116.     poll_table *wait;  
  117.     int retval, i, timed_out = 0;  
  118.     unsigned long slack = 0;  
  119.   
  120.     rcu_read_lock();  
  121.     // 检查fds中fd的有效性, 并获取当前最大的fd  
  122.     retval = max_select_fd(n, fds);  
  123.     rcu_read_unlock();  
  124.   
  125.     if (retval < 0) {  
  126.         return retval;  
  127.     }  
  128.     n = retval;  
  129.   
  130.     // 初始化 poll_wqueues 结构, 设置函数指针_qproc    为__pollwait  
  131.     poll_initwait(&table);  
  132.     wait = &table.pt;  
  133.     if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
  134.         wait = NULL;  
  135.         timed_out = 1;  
  136.     }  
  137.   
  138.     if (end_time && !timed_out) {  
  139.         // 估计需要等待的时间.  
  140.         slack = select_estimate_accuracy(end_time);  
  141.     }  
  142.   
  143.     retval = 0;  
  144.     for (;;) {  
  145.         unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;  
  146.   
  147.         inp = fds->in;  
  148.         outp = fds->out;  
  149.         exp = fds->ex;  
  150.         rinp = fds->res_in;  
  151.         routp = fds->res_out;  
  152.         rexp = fds->res_ex;  
  153.         // 遍历所有的描述符, i 文件描述符  
  154.         for (i = 0; i < n; ++rinp, ++routp, ++rexp) {  
  155.             unsigned long in, out, ex, all_bits, bit = 1, mask, j;  
  156.             unsigned long res_in = 0, res_out = 0, res_ex = 0;  
  157.             const struct file_operations *f_op = NULL;  
  158.             struct file *file = NULL;  
  159.             // 检查当前的 slot 中的描述符  
  160.             in = *inp++;  
  161.             out = *outp++;  
  162.             ex = *exp++;  
  163.             all_bits = in | out | ex;  
  164.             if (all_bits == 0) { // 没有需要监听的描述符, 下一个slot  
  165.                 i += __NFDBITS;  
  166.                 continue;  
  167.             }  
  168.   
  169.             for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {  
  170.                 int fput_needed;  
  171.                 if (i >= n) {  
  172.                     break;  
  173.                 }  
  174.                 // 不需要监听描述符 i  
  175.                 if (!(bit & all_bits)) {  
  176.                     continue;  
  177.                 }  
  178.                 // 取得文件结构  
  179.                 file = fget_light(i, &fput_needed);  
  180.                 if (file) {  
  181.                     f_op = file->f_op;  
  182.                     // 没有 f_op 的话就认为一直处于就绪状态  
  183.                     mask = DEFAULT_POLLMASK;  
  184.                     if (f_op && f_op->poll) {  
  185.                         // 设置等待事件的掩码  
  186.                         wait_key_set(wait, in, out, bit);  
  187.                         /* 
  188.                         static inline void wait_key_set(poll_table *wait, unsigned long in, 
  189.                         unsigned long out, unsigned long bit) 
  190.                         { 
  191.                         wait->_key = POLLEX_SET;// (POLLPRI) 
  192.                         if (in & bit) 
  193.                         wait->_key |= POLLIN_SET;//(POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR) 
  194.                         if (out & bit) 
  195.                         wait->_key |= POLLOUT_SET;//POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) 
  196.                         } 
  197.                         */  
  198.                         // 获取当前的就绪状态, 并添加到文件的对应等待队列中  
  199.                         mask = (*f_op->poll)(file, wait);  
  200.                         // 和poll完全一样  
  201.                     }  
  202.                     fput_light(file, fput_needed);  
  203.                     // 释放文件  
  204.                     // 检查文件 i 是否已有事件就绪,  
  205.                     if ((mask & POLLIN_SET) && (in & bit)) {  
  206.                         res_in |= bit;  
  207.                         retval++;  
  208.                         // 如果已有就绪事件就不再向其他文件的  
  209.                         // 等待队列中添加回调函数  
  210.                         wait = NULL;  
  211.                     }  
  212.                     if ((mask & POLLOUT_SET) && (out & bit)) {  
  213.                         res_out |= bit;  
  214.                         retval++;  
  215.                         wait = NULL;  
  216.                     }  
  217.                     if ((mask & POLLEX_SET) && (ex & bit)) {  
  218.                         res_ex |= bit;  
  219.                         retval++;  
  220.                         wait = NULL;  
  221.                     }  
  222.                 }  
  223.             }  
  224.             if (res_in) {  
  225.                 *rinp = res_in;  
  226.             }  
  227.             if (res_out) {  
  228.                 *routp = res_out;  
  229.             }  
  230.             if (res_ex) {  
  231.                 *rexp = res_ex;  
  232.             }  
  233.             cond_resched();  
  234.         }  
  235.         wait = NULL; // 该添加回调函数的都已经添加了  
  236.         if (retval || timed_out || signal_pending(current)) {  
  237.             break;   // 信号发生,监听事件就绪或超时  
  238.         }  
  239.         if (table.error) {  
  240.             retval = table.error; // 产生错误了  
  241.             break;  
  242.         }  
  243.         // 转换到内核时间  
  244.         if (end_time && !to) {  
  245.             expire = timespec_to_ktime(*end_time);  
  246.             to = &expire;  
  247.         }  
  248.         // 等待直到超时, 或由回调函数唤醒, 超时后会再次遍历文件描述符  
  249.         if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,  
  250.                                    to, slack)) {  
  251.             timed_out = 1;  
  252.         }  
  253.     }  
  254.   
  255.     poll_freewait(&table);  
  256.   
  257.     return retval;  
  258. }  

 

 epoll实现

 

epoll 的实现比poll/select 复杂一些,这是因为:
1.  epoll_wait, epoll_ctl 的调用完全独立开来,内核需要锁机制对这些操作进行保护,并且需要持久的维护添加到epoll的文件
2.  epoll本身也是文件,也可以被poll/select/epoll监视,这可能导致epoll之间循环唤醒的问题
3.  单个文件的状态改变可能唤醒过多监听在其上的epoll,产生唤醒风暴

epoll各个功能的实现要非常小心面对这些问题,使得复杂度大大增加。

 

epoll的核心数据结构

 

C代码  收藏代码

  1. // epoll的核心实现对应于一个epoll描述符  
  2. struct eventpoll {  
  3.     spinlock_t lock;  
  4.     struct mutex mtx;  
  5.     wait_queue_head_t wq; // sys_epoll_wait() 等待在这里  
  6.     // f_op->poll()  使用的, 被其他事件通知机制利用的wait_address  
  7.     wait_queue_head_t poll_wait;  
  8.     /* 已就绪的需要检查的epitem 列表 */  
  9.     struct list_head rdllist;  
  10.     /* 保存所有加入到当前epoll的文件对应的epitem*/  
  11.     struct rb_root rbr;  
  12.     // 当正在向用户空间复制数据时, 产生的可用文件  
  13.     struct epitem *ovflist;  
  14.     /* The user that created the eventpoll descriptor */  
  15.     struct user_struct *user;  
  16.     struct file *file;  
  17.     /*优化循环检查,避免循环检查中重复的遍历 */  
  18.     int visited;  
  19.     struct list_head visited_list_link;  
  20. }  
  21.   
  22. // 对应于一个加入到epoll的文件  
  23. struct epitem {  
  24.     // 挂载到eventpoll 的红黑树节点  
  25.     struct rb_node rbn;  
  26.     // 挂载到eventpoll.rdllist 的节点  
  27.     struct list_head rdllink;  
  28.     // 连接到ovflist 的指针  
  29.     struct epitem *next;  
  30.     /* 文件描述符信息fd + file, 红黑树的key */  
  31.     struct epoll_filefd ffd;  
  32.     /* Number of active wait queue attached to poll operations */  
  33.     int nwait;  
  34.     // 当前文件的等待队列(eppoll_entry)列表  
  35.     // 同一个文件上可能会监视多种事件,  
  36.     // 这些事件可能属于不同的wait_queue中  
  37.     // (取决于对应文件类型的实现),  
  38.     // 所以需要使用链表  
  39.     struct list_head pwqlist;  
  40.     // 当前epitem 的所有者  
  41.     struct eventpoll *ep;  
  42.     /* List header used to link this item to the &quot;struct file&quot; items list */  
  43.     struct list_head fllink;  
  44.     /* epoll_ctl 传入的用户数据 */  
  45.     struct epoll_event event;  
  46. };  
  47.   
  48. struct epoll_filefd {  
  49.     struct file *file;  
  50.     int fd;  
  51. };  
  52.   
  53. // 与一个文件上的一个wait_queue_head 相关联,因为同一文件可能有多个等待的事件,这些事件可能使用不同的等待队列  
  54. struct eppoll_entry {  
  55.     // List struct epitem.pwqlist  
  56.     struct list_head llink;  
  57.     // 所有者  
  58.     struct epitem *base;  
  59.     // 添加到wait_queue 中的节点  
  60.     wait_queue_t wait;  
  61.     // 文件wait_queue 头  
  62.     wait_queue_head_t *whead;  
  63. };  
  64.   
  65. // 用户使用的epoll_event  
  66. struct epoll_event {  
  67.     __u32 events;  
  68.     __u64 data;  
  69. } EPOLL_PACKED;  

 

 

文件系统初始化和epoll_create

 

C代码  收藏代码

  1. // epoll 文件系统的相关实现  
  2. // epoll 文件系统初始化, 在系统启动时会调用  
  3.   
  4. static int __init eventpoll_init(void)  
  5. {  
  6.     struct sysinfo si;  
  7.   
  8.     si_meminfo(&si);  
  9.     // 限制可添加到epoll的最多的描述符数量  
  10.   
  11.     max_user_watches = (((si.totalram - si.totalhigh) / 25) << PAGE_SHIFT) /  
  12.                        EP_ITEM_COST;  
  13.     BUG_ON(max_user_watches < 0);  
  14.   
  15.     // 初始化递归检查队列  
  16.    ep_nested_calls_init(&poll_loop_ncalls);  
  17.     ep_nested_calls_init(&poll_safewake_ncalls);  
  18.     ep_nested_calls_init(&poll_readywalk_ncalls);  
  19.     // epoll 使用的slab分配器分别用来分配epitem和eppoll_entry  
  20.     epi_cache = kmem_cache_create("eventpoll_epi", sizeof(struct epitem),  
  21.                                   0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);  
  22.     pwq_cache = kmem_cache_create("eventpoll_pwq",  
  23.                                   sizeof(struct eppoll_entry), 0, SLAB_PANIC, NULL);  
  24.   
  25.     return 0;  
  26. }  
  27.   
  28.   
  29. SYSCALL_DEFINE1(epoll_create, int, size)  
  30. {  
  31.     if (size <= 0) {  
  32.         return -EINVAL;  
  33.     }  
  34.   
  35.     return sys_epoll_create1(0);  
  36. }  
  37.   
  38. SYSCALL_DEFINE1(epoll_create1, int, flags)  
  39. {  
  40.     int error, fd;  
  41.     struct eventpoll *ep = NULL;  
  42.     struct file *file;  
  43.   
  44.     /* Check the EPOLL_* constant for consistency.  */  
  45.     BUILD_BUG_ON(EPOLL_CLOEXEC != O_CLOEXEC);  
  46.   
  47.     if (flags & ~EPOLL_CLOEXEC) {  
  48.         return -EINVAL;  
  49.     }  
  50.     /* 
  51.      * Create the internal data structure ("struct eventpoll"). 
  52.      */  
  53.     error = ep_alloc(&ep);  
  54.     if (error < 0) {  
  55.         return error;  
  56.     }  
  57.     /* 
  58.      * Creates all the items needed to setup an eventpoll file. That is, 
  59.      * a file structure and a free file descriptor. 
  60.      */  
  61.     fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));  
  62.     if (fd < 0) {  
  63.          error = fd;  
  64.          goto out_free_ep;  
  65.       }  
  66.       // 设置epfd的相关操作,由于epoll也是文件也提供了poll操作  
  67.     file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,  
  68.                               O_RDWR | (flags & O_CLOEXEC));  
  69.     if (IS_ERR(file)) {  
  70.         error = PTR_ERR(file);  
  71.         goto out_free_fd;  
  72.     }  
  73.     fd_install(fd, file);  
  74.     ep->file = file;  
  75.     return fd;  
  76.   
  77. out_free_fd:  
  78.     put_unused_fd(fd);  
  79. out_free_ep:  
  80.     ep_free(ep);  
  81.     return error;  
  82. }  

 

 

 

epoll中的递归死循环和深度检查

 

递归深度检测(ep_call_nested)

epoll本身也是文件,也可以被poll/select/epoll监视,如果epoll之间互相监视就有可能导致死循环。epoll的实现中,所有可能产生递归调用的函数都由函函数ep_call_nested进行包裹,递归调用过程中出现死循环或递归过深就会打破死循环和递归调用直接返回。该函数的实现依赖于一个外部的全局链表nested_call_node(不同的函数调用使用不同的节点),每次调用可能发生递归的函数(nproc)就向链表中添加一个包含当前函数调用上下文ctx(进程,CPU,或epoll文件)和处理的对象标识cookie的节点,通过检测是否有相同的节点就可以知道是否发生了死循环,检查链表中同一上下文包含的节点个数就可以知道递归的深度。以下就是这一过程的源码。

C代码  收藏代码

  1. struct nested_call_node {  
  2.     struct list_head llink;  
  3.     void *cookie;   // 函数运行标识, 任务标志  
  4.     void *ctx;      // 运行环境标识  
  5. };  
  6. struct nested_calls {  
  7.     struct list_head tasks_call_list;  
  8.     spinlock_t lock;  
  9. };  
  10.   
  11. // 全局的不同调用使用的链表  
  12. // 死循环检查和唤醒风暴检查链表  
  13. static nested_call_node poll_loop_ncalls;  
  14. // 唤醒时使用的检查链表  
  15. static nested_call_node poll_safewake_ncalls;  
  16. // 扫描readylist 时使用的链表  
  17. static nested_call_node poll_readywalk_ncalls;  
  18.   
  19.   
  20. // 限制epoll 中直接或间接递归调用的深度并防止死循环  
  21. // ctx: 任务运行上下文(进程, CPU 等)  
  22. // cookie: 每个任务的标识  
  23. // priv: 任务运行需要的私有数据  
  24. // 如果用面向对象语言实现应该就会是一个wapper类  
  25. static int ep_call_nested(struct nested_calls *ncalls, int max_nests,  
  26.                           int (*nproc)(void *, void *, int), void *priv,  
  27.                           void *cookie, void *ctx)  
  28. {  
  29.     int error, call_nests = 0;  
  30.     unsigned long flags;  
  31.     struct list_head *lsthead = &ncalls->tasks_call_list;  
  32.     struct nested_call_node *tncur;  
  33.     struct nested_call_node tnode;  
  34.     spin_lock_irqsave(&ncalls->lock, flags);  
  35.     // 检查原有的嵌套调用链表ncalls, 查看是否有深度超过限制的情况  
  36.     list_for_each_entry(tncur, lsthead, llink) {  
  37.         // 同一上下文中(ctx)有相同的任务(cookie)说明产生了死循环  
  38.         // 同一上下文的递归深度call_nests 超过限制  
  39.         if (tncur->ctx == ctx &&  
  40.                 (tncur->cookie == cookie || ++call_nests > max_nests)) {  
  41.             error = -1;  
  42.         }  
  43.         goto out_unlock;  
  44.     }  
  45.     /* 将当前的任务请求添加到调用列表*/  
  46.     tnode.ctx = ctx;  
  47.     tnode.cookie = cookie;  
  48.     list_add(&tnode.llink, lsthead);  
  49.     spin_unlock_irqrestore(&ncalls->lock, flags);  
  50.     /* nproc 可能会导致递归调用(直接或间接)ep_call_nested 
  51.          * 如果发生递归调用, 那么在此函数返回之前, 
  52.          * ncalls 又会被加入额外的节点, 
  53.          * 这样通过前面的检测就可以知道递归调用的深度 
  54.       */  
  55.     error = (*nproc)(priv, cookie, call_nests);  
  56.     /* 从链表中删除当前任务*/  
  57.     spin_lock_irqsave(&ncalls->lock, flags);  
  58.     list_del(&tnode.llink);  
  59. out_unlock:  
  60.     spin_unlock_irqrestore(&ncalls->lock, flags);  
  61.     return error;  
  62. }  

 

循环检测(ep_loop_check)

循环检查(ep_loop_check),该函数递归调用ep_loop_check_proc利用ep_call_nested来实现epoll之间相互监视的死循环。因为ep_call_nested中已经对死循环和过深的递归做了检查,实际的ep_loop_check_proc的实现只是递归调用自己。其中的visited_list和visited标记完全是为了优化处理速度,如果没有visited_list和visited标记函数也是能够工作的。该函数中得上下文就是当前的进程,cookie就是正在遍历的epoll结构。

 

C代码  收藏代码

  1. static LIST_HEAD(visited_list);  
  2. // 检查 file (epoll)和ep 之间是否有循环  
  3. static int ep_loop_check(struct eventpoll *ep, struct file *file)  
  4. {  
  5.     int ret;  
  6.     struct eventpoll *ep_cur, *ep_next;  
  7.   
  8.     ret = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  9.                          ep_loop_check_proc, file, ep, current);  
  10.     /* 清除链表和标志 */  
  11.     list_for_each_entry_safe(ep_cur, ep_next, &visited_list,  
  12.                              visited_list_link) {  
  13.         ep_cur->visited = 0;  
  14.         list_del(&ep_cur->visited_list_link);  
  15.     }  
  16.     return ret;  
  17. }  
  18.   
  19. static int ep_loop_check_proc(void *priv, void *cookie, int call_nests)  
  20. {  
  21.     int error = 0;  
  22.     struct file *file = priv;  
  23.     struct eventpoll *ep = file->private_data;  
  24.     struct eventpoll *ep_tovisit;  
  25.     struct rb_node *rbp;  
  26.     struct epitem *epi;  
  27.   
  28.     mutex_lock_nested(&ep->mtx, call_nests + 1);  
  29.     // 标记当前为已遍历  
  30.     ep->visited = 1;  
  31.     list_add(&ep->visited_list_link, &visited_list);  
  32.     // 遍历所有ep 监视的文件  
  33.     for (rbp = rb_first(&ep->rbr); rbp; rbp = rb_next(rbp)) {  
  34.         epi = rb_entry(rbp, struct epitem, rbn);  
  35.         if (unlikely(is_file_epoll(epi->ffd.file))) {  
  36.             ep_tovisit = epi->ffd.file->private_data;  
  37.             // 跳过先前已遍历的, 避免循环检查  
  38.             if (ep_tovisit->visited) {  
  39.                 continue;  
  40.             }  
  41.             // 所有ep监视的未遍历的epoll  
  42.             error = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  43.                                    ep_loop_check_proc, epi->ffd.file,  
  44.                                    ep_tovisit, current);  
  45.             if (error != 0) {  
  46.                 break;  
  47.             }  
  48.         } else {  
  49.             // 文件不在tfile_check_list 中, 添加  
  50.             // 最外层的epoll 需要检查子epoll监视的文件  
  51.             if (list_empty(&epi->ffd.file->f_tfile_llink))  
  52.                 list_add(&epi->ffd.file->f_tfile_llink,  
  53.                          &tfile_check_list);  
  54.         }  
  55.     }  
  56.     mutex_unlock(&ep->mtx);  
  57.   
  58.     return error;  
  59. }  

 

 

 

 唤醒风暴检测(reverse_path_check)

 当文件状态发生改变时,会唤醒监听在其上的epoll文件,而这个epoll文件还可能唤醒其他的epoll文件,这种连续的唤醒就形成了一个唤醒路径,所有的唤醒路径就形成了一个有向图。如果文件对应的epoll唤醒有向图的节点过多,那么文件状态的改变就会唤醒所有的这些epoll(可能会唤醒很多进程,这样的开销是很大的),而实际上一个文件经过少数epoll处理以后就可能从就绪转到未就绪,剩余的epoll虽然认为文件已就绪而实际上经过某些处理后已不可用。epoll的实现中考虑到了此问题,在每次添加新文件到epoll中时,就会首先检查是否会出现这样的唤醒风暴。

该函数的实现逻辑是这样的,递归调用reverse_path_check_proc遍历监听在当前文件上的epoll文件,在reverse_pach_check_proc中统计并检查不同路径深度上epoll的个数,从而避免产生唤醒风暴。

 

C代码  收藏代码

  1. #define PATH_ARR_SIZE 5  
  2. // 在EPOLL_CTL_ADD 时, 检查是否有可能产生唤醒风暴  
  3. // epoll 允许的单个文件的唤醒深度小于5, 例如  
  4. // 一个文件最多允许唤醒1000个深度为1的epoll描述符,  
  5. //允许所有被单个文件直接唤醒的epoll描述符再次唤醒的epoll描述符总数是500  
  6. //  
  7.   
  8. // 深度限制  
  9. static const int path_limits[PATH_ARR_SIZE] = { 1000, 500, 100, 50, 10 };  
  10. // 计算出来的深度  
  11. static int path_count[PATH_ARR_SIZE];  
  12.   
  13. static int path_count_inc(int nests)  
  14. {  
  15.     /* Allow an arbitrary number of depth 1 paths */  
  16.     if (nests == 0) {  
  17.         return 0;  
  18.     }  
  19.   
  20.     if (++path_count[nests] > path_limits[nests]) {  
  21.         return -1;  
  22.     }  
  23.     return 0;  
  24. }  
  25.   
  26. static void path_count_init(void)  
  27. {  
  28.     int i;  
  29.   
  30.     for (i = 0; i < PATH_ARR_SIZE; i++) {  
  31.         path_count[i] = 0;  
  32.     }  
  33. }  
  34.   
  35. // 唤醒风暴检查函数  
  36. static int reverse_path_check(void)  
  37. {  
  38.     int error = 0;  
  39.     struct file *current_file;  
  40.   
  41.     /* let's call this for all tfiles */  
  42.     // 遍历全局tfile_check_list 中的文件, 第一级  
  43.     list_for_each_entry(current_file, &tfile_check_list, f_tfile_llink) {  
  44.         // 初始化  
  45.         path_count_init();  
  46.         // 限制递归的深度, 并检查每个深度上唤醒的epoll 数量  
  47.         error = ep_call_nested(&poll_loop_ncalls, EP_MAX_NESTS,  
  48.                                reverse_path_check_proc, current_file,  
  49.                                current_file, current);  
  50.         if (error) {  
  51.             break;  
  52.         }  
  53.     }  
  54.     return error;  
  55. }  
  56. static int reverse_path_check_proc(void *priv, void *cookie, int call_nests)  
  57. {  
  58.     int error = 0;  
  59.     struct file *file = priv;  
  60.     struct file *child_file;  
  61.     struct epitem *epi;  
  62.   
  63.     list_for_each_entry(epi, &file->f_ep_links, fllink) {  
  64.         // 遍历监视file 的epoll  
  65.         child_file = epi->ep->file;  
  66.         if (is_file_epoll(child_file)) {  
  67.             if (list_empty(&child_file->f_ep_links)) {  
  68.                 // 没有其他的epoll监视当前的这个epoll,  
  69.                 // 已经是叶子了  
  70.                 if (path_count_inc(call_nests)) {  
  71.                     error = -1;  
  72.                     break;  
  73.                 }  
  74.             } else {  
  75.                 // 遍历监视这个epoll 文件的epoll,  
  76.                 // 递归调用  
  77.                 error = ep_call_nested(&poll_loop_ncalls,  
  78.                                        EP_MAX_NESTS,  
  79.                                        reverse_path_check_proc,  
  80.                                        child_file, child_file,  
  81.                                        current);  
  82.             }  
  83.             if (error != 0) {  
  84.                 break;  
  85.             }  
  86.         } else {  
  87.             // 不是epoll , 不可能吧?  
  88.             printk(KERN_ERR "reverse_path_check_proc: "  
  89.                    "file is not an ep!\n");  
  90.         }  
  91.     }  
  92.     return error;  
  93. }  

 

 epoll 的唤醒过程

 

 

C代码  收藏代码

  1. static void ep_poll_safewake(wait_queue_head_t *wq)  
  2. {  
  3.     int this_cpu = get_cpu();  
  4.   
  5.     ep_call_nested(&poll_safewake_ncalls, EP_MAX_NESTS,  
  6.                    ep_poll_wakeup_proc, NULL, wq, (void *) (long) this_cpu);  
  7.   
  8.     put_cpu();  
  9. }  
  10.   
  11. static int ep_poll_wakeup_proc(void *priv, void *cookie, int call_nests)  
  12. {  
  13.     ep_wake_up_nested((wait_queue_head_t *) cookie, POLLIN,  
  14.                       1 + call_nests);  
  15.     return 0;  
  16. }  
  17.   
  18. static inline void ep_wake_up_nested(wait_queue_head_t *wqueue,  
  19.                                      unsigned long events, int subclass)  
  20. {  
  21.     // 这回唤醒所有正在等待此epfd 的select/epoll/poll 等  
  22.     // 如果唤醒的是epoll 就可能唤醒其他的epoll, 产生连锁反应  
  23.     // 这个很可能在中断上下文中被调用  
  24.     wake_up_poll(wqueue, events);  
  25. }  

 

 

 epoll_ctl

 

C代码  收藏代码

  1. // long epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
  2.   
  3. SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,  
  4.                 struct epoll_event __user *, event)  
  5. {  
  6.     int error;  
  7.     int did_lock_epmutex = 0;  
  8.     struct file *file, *tfile;  
  9.     struct eventpoll *ep;  
  10.     struct epitem *epi;  
  11.     struct epoll_event epds;  
  12.   
  13.     error = -EFAULT;  
  14.     if (ep_op_has_event(op) &&  
  15.             // 复制用户空间数据到内核  
  16.             copy_from_user(&epds, event, sizeof(struct epoll_event))) {  
  17.         goto error_return;  
  18.     }  
  19.   
  20.     // 取得 epfd 对应的文件  
  21.     error = -EBADF;  
  22.     file = fget(epfd);  
  23.     if (!file) {  
  24.         goto error_return;  
  25.     }  
  26.   
  27.     // 取得目标文件  
  28.     tfile = fget(fd);  
  29.     if (!tfile) {  
  30.         goto error_fput;  
  31.     }  
  32.   
  33.     // 目标文件必须提供 poll 操作  
  34.     error = -EPERM;  
  35.     if (!tfile->f_op || !tfile->f_op->poll) {  
  36.         goto error_tgt_fput;  
  37.     }  
  38.   
  39.     // 添加自身或epfd 不是epoll 句柄  
  40.     error = -EINVAL;  
  41.     if (file == tfile || !is_file_epoll(file)) {  
  42.         goto error_tgt_fput;  
  43.     }  
  44.   
  45.     // 取得内部结构eventpoll  
  46.     ep = file->private_data;  
  47.   
  48.     // EPOLL_CTL_MOD 不需要加全局锁 epmutex  
  49.     if (op == EPOLL_CTL_ADD || op == EPOLL_CTL_DEL) {  
  50.         mutex_lock(&epmutex);  
  51.         did_lock_epmutex = 1;  
  52.     }  
  53.     if (op == EPOLL_CTL_ADD) {  
  54.         if (is_file_epoll(tfile)) {  
  55.             error = -ELOOP;  
  56.             // 目标文件也是epoll 检测是否有循环包含的问题  
  57.             if (ep_loop_check(ep, tfile) != 0) {  
  58.                 goto error_tgt_fput;  
  59.             }  
  60.         } else  
  61.         {  
  62.             // 将目标文件添加到 epoll 全局的tfile_check_list 中  
  63.             list_add(&tfile->f_tfile_llink, &tfile_check_list);  
  64.         }  
  65.     }  
  66.   
  67.     mutex_lock_nested(&ep->mtx, 0);  
  68.   
  69.     // 以tfile 和fd 为key 在rbtree 中查找文件对应的epitem  
  70.     epi = ep_find(ep, tfile, fd);  
  71.   
  72.     error = -EINVAL;  
  73.     switch (op) {  
  74.     case EPOLL_CTL_ADD:  
  75.         if (!epi) {  
  76.             // 没找到, 添加额外添加ERR HUP 事件  
  77.             epds.events |= POLLERR | POLLHUP;  
  78.             error = ep_insert(ep, &epds, tfile, fd);  
  79.         } else {  
  80.             error = -EEXIST;  
  81.         }  
  82.         // 清空文件检查列表  
  83.         clear_tfile_check_list();  
  84.         break;  
  85.     case EPOLL_CTL_DEL:  
  86.         if (epi) {  
  87.             error = ep_remove(ep, epi);  
  88.         } else {  
  89.             error = -ENOENT;  
  90.         }  
  91.         break;  
  92.     case EPOLL_CTL_MOD:  
  93.         if (epi) {  
  94.             epds.events |= POLLERR | POLLHUP;  
  95.             error = ep_modify(ep, epi, &epds);  
  96.         } else {  
  97.             error = -ENOENT;  
  98.         }  
  99.         break;  
  100.     }  
  101.     mutex_unlock(&ep->mtx);  
  102.   
  103. error_tgt_fput:  
  104.     if (did_lock_epmutex) {  
  105.         mutex_unlock(&epmutex);  
  106.     }  
  107.   
  108.     fput(tfile);  
  109. error_fput:  
  110.     fput(file);  
  111. error_return:  
  112.   
  113.     return error;  
  114. }  

 

 

EPOLL_CTL_ADD 实现

 

C代码  收藏代码

  1. // EPOLL_CTL_ADD  
  2. static int ep_insert(struct eventpoll *ep, struct epoll_event *event,  
  3.                      struct file *tfile, int fd)  
  4. {  
  5.     int error, revents, pwake = 0;  
  6.     unsigned long flags;  
  7.     long user_watches;  
  8.     struct epitem *epi;  
  9.     struct ep_pqueue epq;  
  10.     /* 
  11.     struct ep_pqueue { 
  12.         poll_table pt; 
  13.         struct epitem *epi; 
  14.     }; 
  15.     */  
  16.   
  17.     // 增加监视文件数  
  18.     user_watches = atomic_long_read(&ep->user->epoll_watches);  
  19.     if (unlikely(user_watches >= max_user_watches)) {  
  20.         return -ENOSPC;  
  21.     }  
  22.   
  23.     // 分配初始化 epi  
  24.     if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL))) {  
  25.         return -ENOMEM;  
  26.     }  
  27.   
  28.     INIT_LIST_HEAD(&epi->rdllink);  
  29.     INIT_LIST_HEAD(&epi->fllink);  
  30.     INIT_LIST_HEAD(&epi->pwqlist);  
  31.     epi->ep = ep;  
  32.     // 初始化红黑树中的key  
  33.     ep_set_ffd(&epi->ffd, tfile, fd);  
  34.     // 直接复制用户结构  
  35.     epi->event = *event;  
  36.     epi->nwait = 0;  
  37.     epi->next = EP_UNACTIVE_PTR;  
  38.   
  39.     // 初始化临时的 epq  
  40.     epq.epi = epi;  
  41.     init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);  
  42.     // 设置事件掩码  
  43.     epq.pt._key = event->events;  
  44.     //  内部会调用ep_ptable_queue_proc, 在文件对应的wait queue head 上  
  45.     // 注册回调函数, 并返回当前文件的状态  
  46.     revents = tfile->f_op->poll(tfile, &epq.pt);  
  47.   
  48.     // 检查错误  
  49.     error = -ENOMEM;  
  50.     if (epi->nwait < 0) { // f_op->poll 过程出错  
  51.         goto error_unregister;  
  52.     }  
  53.     // 添加当前的epitem 到文件的f_ep_links 链表  
  54.     spin_lock(&tfile->f_lock);  
  55.     list_add_tail(&epi->fllink, &tfile->f_ep_links);  
  56.     spin_unlock(&tfile->f_lock);  
  57.   
  58.     // 插入epi 到rbtree  
  59.     ep_rbtree_insert(ep, epi);  
  60.   
  61.     /* now check if we've created too many backpaths */  
  62.     error = -EINVAL;  
  63.     if (reverse_path_check()) {  
  64.         goto error_remove_epi;  
  65.     }  
  66.   
  67.     spin_lock_irqsave(&ep->lock, flags);  
  68.   
  69.     /* 文件已经就绪插入到就绪链表rdllist */  
  70.     if ((revents & event->events) && !ep_is_linked(&epi->rdllink)) {  
  71.         list_add_tail(&epi->rdllink, &ep->rdllist);  
  72.   
  73.   
  74.         if (waitqueue_active(&ep->wq))  
  75.             // 通知sys_epoll_wait , 调用回调函数唤醒sys_epoll_wait 进程  
  76.         {  
  77.             wake_up_locked(&ep->wq);  
  78.         }  
  79.         // 先不通知调用eventpoll_poll 的进程  
  80.         if (waitqueue_active(&ep->poll_wait)) {  
  81.             pwake++;  
  82.         }  
  83.     }  
  84.   
  85.     spin_unlock_irqrestore(&ep->lock, flags);  
  86.   
  87.     atomic_long_inc(&ep->user->epoll_watches);  
  88.   
  89.     if (pwake)  
  90.         // 安全通知调用eventpoll_poll 的进程  
  91.     {  
  92.         ep_poll_safewake(&ep->poll_wait);  
  93.     }  
  94.   
  95.     return 0;  
  96.   
  97. error_remove_epi:  
  98.     spin_lock(&tfile->f_lock);  
  99.     // 删除文件上的 epi  
  100.     if (ep_is_linked(&epi->fllink)) {  
  101.         list_del_init(&epi->fllink);  
  102.     }  
  103.     spin_unlock(&tfile->f_lock);  
  104.   
  105.     // 从红黑树中删除  
  106.     rb_erase(&epi->rbn, &ep->rbr);  
  107.   
  108. error_unregister:  
  109.     // 从文件的wait_queue 中删除, 释放epitem 关联的所有eppoll_entry  
  110.     ep_unregister_pollwait(ep, epi);  
  111.   
  112.     /* 
  113.      * We need to do this because an event could have been arrived on some 
  114.      * allocated wait queue. Note that we don't care about the ep->ovflist 
  115.      * list, since that is used/cleaned only inside a section bound by "mtx". 
  116.      * And ep_insert() is called with "mtx" held. 
  117.      */  
  118.     // TODO:  
  119.     spin_lock_irqsave(&ep->lock, flags);  
  120.     if (ep_is_linked(&epi->rdllink)) {  
  121.         list_del_init(&epi->rdllink);  
  122.     }  
  123.     spin_unlock_irqrestore(&ep->lock, flags);  
  124.   
  125.     // 释放epi  
  126.     kmem_cache_free(epi_cache, epi);  
  127.   
  128.     return error;  
  129. }  

 

 EPOLL_CTL_DEL

EPOLL_CTL_DEL 的实现调用的是 ep_remove 函数,函数只是清除ADD时, 添加的各种结构,EPOLL_CTL_MOD 的实现调用的是ep_modify,在ep_modify中用新的事件掩码调用f_ops->poll,检测事件是否已可用,如果可用就直接唤醒epoll,这两个的实现与EPOLL_CTL_ADD 类似,代码上比较清晰,这里就不具体分析了。

 

 

C代码  收藏代码

  1. static int ep_remove(struct eventpoll *ep, struct epitem *epi)  
  2. {  
  3.     unsigned long flags;  
  4.     struct file *file = epi->ffd.file;  
  5.   
  6.     /* 
  7.      * Removes poll wait queue hooks. We _have_ to do this without holding 
  8.      * the "ep->lock" otherwise a deadlock might occur. This because of the 
  9.      * sequence of the lock acquisition. Here we do "ep->lock" then the wait 
  10.      * queue head lock when unregistering the wait queue. The wakeup callback 
  11.      * will run by holding the wait queue head lock and will call our callback 
  12.      * that will try to get "ep->lock". 
  13.      */  
  14.     ep_unregister_pollwait(ep, epi);  
  15.   
  16.     /* Remove the current item from the list of epoll hooks */  
  17.     spin_lock(&file->f_lock);  
  18.     if (ep_is_linked(&epi->fllink))  
  19.         list_del_init(&epi->fllink);  
  20.     spin_unlock(&file->f_lock);  
  21.   
  22.     rb_erase(&epi->rbn, &ep->rbr);  
  23.   
  24.     spin_lock_irqsave(&ep->lock, flags);  
  25.     if (ep_is_linked(&epi->rdllink))  
  26.         list_del_init(&epi->rdllink);  
  27.     spin_unlock_irqrestore(&ep->lock, flags);  
  28.   
  29.     /* At this point it is safe to free the eventpoll item */  
  30.     kmem_cache_free(epi_cache, epi);  
  31.   
  32.     atomic_long_dec(&ep->user->epoll_watches);  
  33.   
  34.     return 0;  
  35. }  

 

 

 

 

 

C代码  收藏代码

  1. /* 
  2.  * Modify the interest event mask by dropping an event if the new mask 
  3.  * has a match in the current file status. Must be called with "mtx" held. 
  4.  */  
  5. static int ep_modify(struct eventpoll *ep, struct epitem *epi, struct epoll_event *event)  
  6. {  
  7.     int pwake = 0;  
  8.     unsigned int revents;  
  9.     poll_table pt;  
  10.   
  11.     init_poll_funcptr(&pt, NULL);  
  12.   
  13.     /* 
  14.      * Set the new event interest mask before calling f_op->poll(); 
  15.      * otherwise we might miss an event that happens between the 
  16.      * f_op->poll() call and the new event set registering. 
  17.      */  
  18.     epi->event.events = event->events;  
  19.     pt._key = event->events;  
  20.     epi->event.data = event->data; /* protected by mtx */  
  21.   
  22.     /* 
  23.      * Get current event bits. We can safely use the file* here because 
  24.      * its usage count has been increased by the caller of this function. 
  25.      */  
  26.     revents = epi->ffd.file->f_op->poll(epi->ffd.file, &pt);  
  27.   
  28.     /* 
  29.      * If the item is "hot" and it is not registered inside the ready 
  30.      * list, push it inside. 
  31.      */  
  32.     if (revents & event->events) {  
  33.         spin_lock_irq(&ep->lock);  
  34.         if (!ep_is_linked(&epi->rdllink)) {  
  35.             list_add_tail(&epi->rdllink, &ep->rdllist);  
  36.   
  37.             /* Notify waiting tasks that events are available */  
  38.             if (waitqueue_active(&ep->wq))  
  39.                 wake_up_locked(&ep->wq);  
  40.             if (waitqueue_active(&ep->poll_wait))  
  41.                 pwake++;  
  42.         }  
  43.         spin_unlock_irq(&ep->lock);  
  44.     }  
  45.   
  46.     /* We have to call this outside the lock */  
  47.     if (pwake)  
  48.         ep_poll_safewake(&ep->poll_wait);  
  49.   
  50.     return 0;  
  51. }  

 

 

 

 

epoll_wait

 

 

C代码  收藏代码

  1. /* 
  2. epoll_wait实现 
  3. */  
  4.   
  5. SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,  
  6.                 int, maxevents, int, timeout)  
  7. {  
  8.     int error;  
  9.     struct file *file;  
  10.     struct eventpoll *ep;  
  11.   
  12.     // 检查输入数据有效性  
  13.     if (maxevents <= 0 || maxevents > EP_MAX_EVENTS) {  
  14.         return -EINVAL;  
  15.     }  
  16.   
  17.     if (!access_ok(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))) {  
  18.         error = -EFAULT;  
  19.         goto error_return;  
  20.     }  
  21.   
  22.     /* Get the "struct file *" for the eventpoll file */  
  23.     error = -EBADF;  
  24.     file = fget(epfd);  
  25.     if (!file) {  
  26.         goto error_return;  
  27.     }  
  28.   
  29.     error = -EINVAL;  
  30.     if (!is_file_epoll(file)) {  
  31.         goto error_fput;  
  32.     }  
  33.     // 取得ep 结构  
  34.     ep = file->private_data;  
  35.   
  36.     // 等待事件  
  37.     error = ep_poll(ep, events, maxevents, timeout);  
  38.   
  39. error_fput:  
  40.     fput(file);  
  41. error_return:  
  42.   
  43.     return error;  
  44. }  
  45.   
  46. static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,  
  47.                    int maxevents, long timeout)  
  48. {  
  49.     int res = 0, eavail, timed_out = 0;  
  50.     unsigned long flags;  
  51.     long slack = 0;  
  52.     wait_queue_t wait;  
  53.     ktime_t expires, *to = NULL;  
  54.   
  55.     if (timeout > 0) {  
  56.         // 转换为内核时间  
  57.         struct timespec end_time = ep_set_mstimeout(timeout);  
  58.   
  59.         slack = select_estimate_accuracy(&end_time);  
  60.         to = &expires;  
  61.         *to = timespec_to_ktime(end_time);  
  62.     } else if (timeout == 0) {  
  63.         // 已经超时直接检查readylist  
  64.         timed_out = 1;  
  65.         spin_lock_irqsave(&ep->lock, flags);  
  66.         goto check_events;  
  67.     }  
  68.   
  69. fetch_events:  
  70.     spin_lock_irqsave(&ep->lock, flags);  
  71.   
  72.     // 没有可用的事件,ready list 和ovflist 都为空  
  73.     if (!ep_events_available(ep)) {  
  74.   
  75.         // 添加当前进程的唤醒函数  
  76.         init_waitqueue_entry(&wait, current);  
  77.         __add_wait_queue_exclusive(&ep->wq, &wait);  
  78.   
  79.         for (;;) {  
  80.             /* 
  81.              * We don't want to sleep if the ep_poll_callback() sends us 
  82.              * a wakeup in between. That's why we set the task state 
  83.              * to TASK_INTERRUPTIBLE before doing the checks. 
  84.              */  
  85.             set_current_state(TASK_INTERRUPTIBLE);  
  86.             if (ep_events_available(ep) || timed_out) {  
  87.                 break;  
  88.             }  
  89.             if (signal_pending(current)) {  
  90.                 res = -EINTR;  
  91.                 break;  
  92.             }  
  93.   
  94.             spin_unlock_irqrestore(&ep->lock, flags);  
  95.             // 挂起当前进程,等待唤醒或超时  
  96.             if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) {  
  97.                 timed_out = 1;  
  98.             }  
  99.   
  100.             spin_lock_irqsave(&ep->lock, flags);  
  101.         }  
  102.       
  103.         __remove_wait_queue(&ep->wq, &wait);  
  104.   
  105.         set_current_state(TASK_RUNNING);  
  106.     }  
  107. check_events:  
  108.     // 再次检查是否有可用事件  
  109.     eavail = ep_events_available(ep);  
  110.   
  111.     spin_unlock_irqrestore(&ep->lock, flags);  
  112.   
  113.     /* 
  114.      * Try to transfer events to user space. In case we get 0 events and 
  115.      * there's still timeout left over, we go trying again in search of 
  116.      * more luck. 
  117.      */  
  118.     if (!res && eavail   
  119.             && !(res = ep_send_events(ep, events, maxevents)) // 复制事件到用户空间  
  120.             && !timed_out) // 复制事件失败并且没有超时,重新等待。  
  121.             {  
  122.         goto fetch_events;  
  123.     }  
  124.   
  125.     return res;  
  126. }  
  127.   
  128.   
  129. static inline int ep_events_available(struct eventpoll *ep)  
  130. {  
  131.     return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;  
  132. }  
  133.   
  134. struct ep_send_events_data {  
  135.     int maxevents;  
  136.     struct epoll_event __user *events;  
  137. };  
  138.   
  139. static int ep_send_events(struct eventpoll *ep,  
  140.                           struct epoll_event __user *events, int maxevents)  
  141. {  
  142.     struct ep_send_events_data esed;  
  143.   
  144.     esed.maxevents = maxevents;  
  145.     esed.events = events;  
  146.   
  147.     return ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0);  
  148. }  
  149.   
  150. static int ep_send_events_proc(struct eventpoll *ep, struct list_head *head,  
  151.                                void *priv)  
  152. {  
  153.     struct ep_send_events_data *esed = priv;  
  154.     int eventcnt;  
  155.     unsigned int revents;  
  156.     struct epitem *epi;  
  157.     struct epoll_event __user *uevent;  
  158.   
  159.     // 遍历已就绪链表  
  160.     for (eventcnt = 0, uevent = esed->events;  
  161.             !list_empty(head) && eventcnt < esed->maxevents;) {  
  162.         epi = list_first_entry(head, struct epitem, rdllink);  
  163.   
  164.         list_del_init(&epi->rdllink);  
  165.         // 获取ready 事件掩码  
  166.         revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL) &  
  167.                   epi->event.events;  
  168.   
  169.         /* 
  170.          * If the event mask intersect the caller-requested one, 
  171.          * deliver the event to userspace. Again, ep_scan_ready_list() 
  172.          * is holding "mtx", so no operations coming from userspace 
  173.          * can change the item. 
  174.          */  
  175.         if (revents) {  
  176.             // 事件就绪, 复制到用户空间  
  177.             if (__put_user(revents, &uevent->events) ||  
  178.                     __put_user(epi->event.data, &uevent->data)) {  
  179.                 list_add(&epi->rdllink, head);  
  180.                 return eventcnt ? eventcnt : -EFAULT;  
  181.             }  
  182.             eventcnt++;  
  183.             uevent++;  
  184.             if (epi->event.events & EPOLLONESHOT) {  
  185.                 epi->event.events &= EP_PRIVATE_BITS;  
  186.             } else if (!(epi->event.events & EPOLLET)) {  
  187.                 // 不是边缘模式, 再次添加到ready list,  
  188.                 // 下次epoll_wait 时直接进入此函数检查ready list是否仍然继续  
  189.                 list_add_tail(&epi->rdllink, &ep->rdllist);  
  190.             }  
  191.             // 如果是边缘模式, 只有当文件状态发生改变时,  
  192.             // 才文件会再次触发wait_address 上wait_queue的回调函数,  
  193.         }  
  194.     }  
  195.   
  196.     return eventcnt;  
  197. }  

 

 

 

 

eventpoll_poll

 由于epoll自身也是文件系统,其描述符也可以被poll/select/epoll监视,因此需要实现poll方法。

C代码  收藏代码

  1. static const struct file_operations eventpoll_fops = {  
  2.     .release = ep_eventpoll_release,  
  3.     .poll    = ep_eventpoll_poll,  
  4.     .llseek  = noop_llseek,  
  5. };  
  6.   
  7. static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait)  
  8. {  
  9.     int pollflags;  
  10.     struct eventpoll *ep = file->private_data;  
  11.     // 插入到wait_queue  
  12.     poll_wait(file, &ep->poll_wait, wait);  
  13.     // 扫描就绪的文件列表, 调用每个文件上的poll 检测是否真的就绪,  
  14.     // 然后复制到用户空间  
  15.     // 文件列表中有可能有epoll文件, 调用poll的时候有可能会产生递归,  
  16.     // 调用所以用ep_call_nested 包装一下, 防止死循环和过深的调用  
  17.     pollflags = ep_call_nested(&poll_readywalk_ncalls, EP_MAX_NESTS,  
  18.                                ep_poll_readyevents_proc, ep, ep, current);  
  19.     // static struct nested_calls poll_readywalk_ncalls;  
  20.     return pollflags != -1 ? pollflags : 0;  
  21. }  
  22.   
  23. static int ep_poll_readyevents_proc(void *priv, void *cookie, int call_nests)  
  24. {  
  25.     return ep_scan_ready_list(priv, ep_read_events_proc, NULL, call_nests + 1);  
  26. }  
  27.   
  28. static int ep_scan_ready_list(struct eventpoll *ep,  
  29.                               int (*sproc)(struct eventpoll *,  
  30.                                       struct list_head *, void *),  
  31.                               void *priv,  
  32.                               int depth)  
  33. {  
  34.     int error, pwake = 0;  
  35.     unsigned long flags;  
  36.     struct epitem *epi, *nepi;  
  37.     LIST_HEAD(txlist);  
  38.   
  39.     /* 
  40.      * We need to lock this because we could be hit by 
  41.      * eventpoll_release_file() and epoll_ctl(). 
  42.      */  
  43.     mutex_lock_nested(&ep->mtx, depth);  
  44.   
  45.     spin_lock_irqsave(&ep->lock, flags);  
  46.     // 移动rdllist 到新的链表txlist  
  47.     list_splice_init(&ep->rdllist, &txlist);  
  48.     // 改变ovflist 的状态, 如果ep->ovflist != EP_UNACTIVE_PTR,  
  49.     // 当文件激活wait_queue时,就会将对应的epitem加入到ep->ovflist  
  50.     // 否则将文件直接加入到ep->rdllist,  
  51.     // 这样做的目的是避免丢失事件  
  52.     // 这里不需要检查ep->ovflist 的状态,因为ep->mtx的存在保证此处的ep->ovflist  
  53.     // 一定是EP_UNACTIVE_PTR  
  54.     ep->ovflist = NULL;  
  55.     spin_unlock_irqrestore(&ep->lock, flags);  
  56.   
  57.     // 调用扫描函数处理txlist  
  58.     error = (*sproc)(ep, &txlist, priv);  
  59.   
  60.     spin_lock_irqsave(&ep->lock, flags);  
  61.   
  62.     // 调用 sproc 时可能有新的事件,遍历这些新的事件将其插入到ready list  
  63.     for (nepi = ep->ovflist; (epi = nepi) != NULL;  
  64.             nepi = epi->next, epi->next = EP_UNACTIVE_PTR) {  
  65.         // #define EP_UNACTIVE_PTR (void *) -1  
  66.         // epi 不在rdllist, 插入  
  67.         if (!ep_is_linked(&epi->rdllink)) {  
  68.             list_add_tail(&epi->rdllink, &ep->rdllist);  
  69.         }  
  70.     }  
  71.     // 还原ep->ovflist的状态  
  72.     ep->ovflist = EP_UNACTIVE_PTR;  
  73.   
  74.     // 将处理后的 txlist 链接到 rdllist  
  75.     list_splice(&txlist, &ep->rdllist);  
  76.   
  77.     if (!list_empty(&ep->rdllist)) {  
  78.         // 唤醒epoll_wait  
  79.         if (waitqueue_active(&ep->wq)) {  
  80.             wake_up_locked(&ep->wq);  
  81.         }  
  82.         // 当前的ep有其他的事件通知机制监控  
  83.         if (waitqueue_active(&ep->poll_wait)) {  
  84.             pwake++;  
  85.         }  
  86.     }  
  87.     spin_unlock_irqrestore(&ep->lock, flags);  
  88.   
  89.     mutex_unlock(&ep->mtx);  
  90.   
  91.     if (pwake) {  
  92.         // 安全唤醒外部的事件通知机制  
  93.         ep_poll_safewake(&ep->poll_wait);  
  94.     }  
  95.   
  96.     return error;  
  97. }  
  98.   
  99. static int ep_read_events_proc(struct eventpoll *ep, struct list_head *head,  
  100.                                void *priv)  
  101. {  
  102.     struct epitem *epi, *tmp;  
  103.     poll_table pt;  
  104.     init_poll_funcptr(&pt, NULL);  
  105.     list_for_each_entry_safe(epi, tmp, head, rdllink) {  
  106.         pt._key = epi->event.events;  
  107.         if (epi->ffd.file->f_op->poll(epi->ffd.file, &pt) &  
  108.                 epi->event.events) {  
  109.             return POLLIN | POLLRDNORM;  
  110.         } else {  
  111.              // 这个事件虽然在就绪列表中,  
  112.              // 但是实际上并没有就绪, 将他移除  
  113.          // 这有可能是水平触发模式中没有将文件从就绪列表中移除  
  114.          // 也可能是事件插入到就绪列表后有其他的线程对文件进行了操作  
  115.             list_del_init(&epi->rdllink);  
  116.         }  
  117.     }  
  118.     return 0;  
  119. }  

 

 epoll全景

以下是epoll使用的全部数据结构之间的关系图,采用的是一种类UML图,希望对理解epoll的内部实现有所帮助。

 

 

 

 

 

 

 poll/select/epoll 对比

通过以上的分析可以看出,poll和select的实现基本是一致,只是用户到内核传递的数据格式有所不同,

 

 

select和poll即使只有一个描述符就绪,也要遍历整个集合。如果集合中活跃的描述符很少,遍历过程的开销就会变得很大,而如果集合中大部分的描述符都是活跃的,遍历过程的开销又可以忽略。

epoll的实现中每次只遍历活跃的描述符(如果是水平触发,也会遍历先前活跃的描述符),在活跃描述符较少的情况下就会很有优势,在代码的分析过程中可以看到epoll的实现过于复杂并且其实现过程中需要同步处理(锁),如果大部分描述符都是活跃的,epoll的效率可能不如select或poll。(参见epoll 和poll的性能测试 http://jacquesmattheij.com/Poll+vs+Epoll+once+again)

select能够处理的最大fd无法超出FDSETSIZE。

select会复写传入的fd_set 指针,而poll对每个fd返回一个掩码,不更改原来的掩码,从而可以对同一个集合多次调用poll,而无需调整。

select对每个文件描述符最多使用3个bit,而poll采用的pollfd需要使用64个bit,epoll采用的 epoll_event则需要96个bit

如果事件需要循环处理select, poll 每一次的处理都要将全部的数据复制到内核,而epoll的实现中,内核将持久维护加入的描述符,减少了内核和用户复制数据的开销。

--------------------- 本文来自 李生龙 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/lishenglong666/article/details/45536611?utm_source=copy

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xcw_1987/article/details/82888981

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法