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;