fdcount = do_poll(nfds, head, &table, timeout);
poll_freewait(&table);
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
for (j = 0; j < walk->len; j++, ufds++)
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
}
err = fdcount;
out_fds:
walk = head->next;
while (walk) {
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos);
}
return err;
}
为了加快处理速度和提高系统性能,这里优先使用已经定好的一个栈空间,其大小为POLL_STACK_ALLOC,在我系统上,其值为256,大小为256个字节的栈空间转换为struct poll_list结构,以存储需要被检测的socket描述符,struct poll_list的结构如下:
[cpp]
struct poll_list {
struct poll_list *next;
int len;
struct pollfd entries[0];
};
上面可以看到该结构的entries为一个数组,结构为struct pollfd,这个有点眼熟,没错,它就是存储poll调用中需要被检测的socket描述符。那么前面分配的栈空间能存储多少个struct pollfd呢?这计算如下:
[cpp]
len = min_t(unsigned int, nfds, N_STACK_PPS);
式中的N_STACK_PPS就是计算前面默认的固定栈大小能够存储多少个struct pollfd的
[cpp] view plaincopy
#define N_STACK_PPS ((sizeof(stack_pps) - sizeof(struct poll_list)) / \
sizeof(struct pollfd))
然后就复制len个struct pollfd至内核空间,这里有细心的用户就会发现:如果nfds比N_STACK_PPS大的话,怎么办呢?注意上面的函数,是一个循环,如果nfds比N_STACK_PPS大(事实上,一般都会比这里大),那么会再请求内存,然后接着复制,就是这个代码片段:
[cpp]
len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
POLLFD_PER_PAGE表示一页的内存能够存储多少个struct pollfd,可以计算一下,一页是4K,而struct pollfd的内存占用8个字节,就是一页的内存可以将近存储512个socket描述符。如果在分配一页的内存之后,还不够nfds来用,没关系,循环不会退出的,会再分配一个页,并且所有分配的块都被struct poll_list链接起来,上面可以看到,这个结构有一个next域,就是专门做这个的。
在这之后,就会形成一个以stack_pps存储空间为头,然后一页一页分配的内存为接点的链表,这个链表上就存储了poll调用时传入的所有的socket描述符。
接下来调用一个很重要的部分
[cpp]
poll_initwait(&table);
fdcount = do_poll(nfds, head, &table, timeout);
poll_freewait(&table);
这是最重要的部分,因为接下来的部分比较容易理解,在这之后,做两件事:
将链表上的所有struct pollfd中的revents的状态写入到用户空间(记得之前也从用户空间写入过内核空间,这是因为内核态地址,用户空间应用不能访问),所以需要写入到用户空间中去。
之前调用kmalloc分配了很多内存,现在要释放了,所以要从stack_pps地址处的head开始,顺着next不断的释放内存。
再回到最重要的部分,先看poll_initwait调用,下面是主要相关的数据结构
[cpp]
struct poll_wqueues {
poll_table pt;
struct poll_table_page * table;
int error;
int inline_index;
struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
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;
poll_initwait函数如下:
[cpp]
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);//设置poll_table结构中的qproc函数指针为__pollwait函数,就是pwq->pt->qproc=__pollwait。这个函数是一个回调函数,基本上这种机制的实现,就是依靠回调函数了。
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
所以poll_initwait就是初始化了poll_wqueues table,主要是将其结构中的函数指针设置为__pollwait函数。那么这个函数是做什么的呢?我们先看poll_initwait之后调用的函数,就是do_poll函数,其实现如下:
注意下面函数在调用时的参数,参数有这么几个nfds, head, &table, timeout,参数就容易理解了:nfds表示poll调用时传入的数组中struct pollfd的个数,head其实是表示将poll调用时传入的数组,因为全部都表示为struct poll_list链表了(前面分析的,还记得吧),table是刚刚初始化的一个,里面暂时就只是包含一个回调函数的指针,就是__pollwait函数。timeout表示超时时间。
[cpp]
static int do_poll(unsigned int nfds, struct poll_list *list,