2024年4月25日发(作者:)
作者:董昊 (要转载的同学帮忙把名字和博客链接/uii/带上,多谢了!)
poll和epoll的使用应该不用再多说了。当fd很多时,使用epoll比poll效率更高。我们通过内核源码分析来
看看到底是为什么。
poll剖析
poll系统调用:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
内核2.6.9对应的实现代码为:
[fs/select.c -->sys_poll]
456 asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout)
457 {
458struct poll_wqueues table;
459int fdcount, err;
460unsigned int i;
461struct poll_list *head;
462struct poll_list *walk;
463
464/* Do a sanity check on nfds ... */ /* 用户给的nfds数不可以超过一个struct file结构支持
的最大fd数(默认是256)*/
465if (nfds > current->files->max_fdset && nfds > OPEN_MAX)
466return -EINVAL;
467
468if (timeout) {
469/* Careful about overflow in the intermediate values */
470if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
471timeout = (unsigned long)(timeout*HZ+999)/1000+1;
472else /* Negative or overflow */
473timeout = MAX_SCHEDULE_TIMEOUT;
474}
475
476poll_initwait(&table);
其中poll_initwait较为关键,从字面上看,应该是初始化变量table,注意此处table在整个执行poll的过
程中是很关键的变量。
而struct poll_table其实就只包含了一个函数指针:
[fs/poll.h]
16 /*
17* structures and helpers for f_op->poll implementations
18*/
19 typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct
poll_table_struct *);
20
21 typedef struct poll_table_struct {
22poll_queue_proc qproc;
23 } poll_table;
现在我们来看看poll_initwait到底在做些什么
[fs/select.c]
57 void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);
58
59 void poll_initwait(struct poll_wqueues *pwq)
60 {
61&(pwq->pt)->qproc = __pollwait; /*此行已经被我“翻译”了,方便观看*/
62pwq->error = 0;
63pwq->table = NULL;
64 }
很明显,poll_initwait的主要动作就是把table变量的成员poll_table对应的回调函数置为__pollwait。这
个__pollwait不仅是poll系统调用需要,select系统调用也一样是用这个__pollwait,说白了,这是个操
作系统的异步操作的“御用”回调函数。当然了,epoll没有用这个,它另外新增了一个回调函数,以达到其
高效运转的目的,这是后话,暂且不表。
我们先不讨论__pollwait的具体实现,还是继续看sys_poll:
[fs/select.c -->sys_poll]
478head = NULL;
479walk = NULL;
480i = nfds;
481err = -ENOMEM;
482while(i!=0) {
483struct poll_list *pp;
484pp = kmalloc(sizeof(struct poll_list)+
485sizeof(struct pollfd)*
486(i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i),
487GFP_KERNEL);
488if(pp==NULL)
489goto out_fds;
490pp->next=NULL;
491pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);
492if (head == NULL)
493head = pp;
494else
495walk->next = pp;
496
497walk = pp;
498if (copy_from_user(pp->entries, ufds + nfds-i,
499sizeof(struct pollfd)*pp->len)) {
500err = -EFAULT;
501goto out_fds;
502}
503i -= pp->len;
504}
505fdcount = do_poll(nfds, head, &table, timeout);
这一大堆代码就是建立一个链表,每个链表的节点是一个page大小(通常是4k),这链表节点由一个指向
struct poll_list的指针掌控,而众多的struct pollfd就通过struct_list的entries成员访问。上面的循环就
是把用户态的struct pollfd拷进这些entries里。通常用户程序的poll调用就监控几个fd,所以上面这个链
表通常也就只需要一个节点,即操作系统的一页。但是,当用户传入的fd很多时,由于poll系统调用每次都
要把所有struct pollfd拷进内核,所以参数传递和页分配此时就成了poll系统调用的性能瓶颈。
最后一句do_poll,我们跟进去:
[fs/select.c-->sys_poll()-->do_poll()]
395 static void do_pollfd(unsigned int num, struct pollfd * fdpage,
396poll_table ** pwait, int *count)
397 {
398int i;
发布评论