poll调用深入解析(六)

2014-11-24 09:41:27 · 作者: · 浏览: 4
he flags are set,
* IO signal will be lost.
*/
if (sk_stream_wspace(sk) >= sk_stream_min_wspace(sk))
mask |= POLLOUT | POLLWRNORM;
}
}

if (tp->urg_data & TCP_URG_VALID)
mask |= POLLPRI;
}
return mask;
}
上面的tcp_poll看上去很长,但核心的的调用是:
[cpp]
poll_wait(file, sk->sk_sleep, wait);
这个函数的file和wait是我们在poll调用过程中传入的参数,sk->sk_sleep是什么呢?这里解释一下
sk的值是
[cpp]
struct sock *sk = sock->sk;
struct sock是socket连接的内核表示,sk->sk_sleep是struct wait_queue_head_t结构类型,这表示的是socket的等待队列,每一个socket都有自己的一个等待队列,由内核结构struct sock来维护。
其实大多数驱动实现的时候,此时都调用这个函数,这个函数也很简单,实现如下:
[cpp]
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && wait_address)
p->qproc(filp, wait_address, p);
}
现在一个转折点出现了,前面我们说过初始化table的函数指针为__pollwait,那么此时调用的就是__pollwait(filp,wait_address,p),这里的参数分别表示为进程表示文件结构struct file,socket或设备的等待队列wait_queue_head_t,和poll_table。
再回顾一下,到此为止,从我们调用poll函数开始,然后复制数据至内核、将struct pollfd表示为内核的struct poll_list链表、初始化poll_table变量、然后调用do_pollfd函数等过程,其实都是为了检查poll传递的每个struct pollfd是否有状态变化,也就是调用VFS的file->f_op->poll函数,这就到了__pollwait函数这里来了,这个函数会往等待队列上添加一个新的结点。
__pollwait的实现
[cpp]
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_table_entry *entry = poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait, current);
add_wait_queue(wait_address, &entry->wait);
}
我们现在来分析一下,__pollwait调用完成之后,内核做了什么?先看一下poll_get_entry(p);
[cpp]
static struct poll_table_entry *poll_get_entry(poll_table *_p)
{
struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
struct poll_table_page *table = p->table;

if (p->inline_index < N_INLINE_POLL_ENTRIES)
return p->inline_entries + p->inline_index++;

if (!table || POLL_TABLE_FULL(table)) {
struct poll_table_page *new_table;

new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
if (!new_table) {
p->error = -ENOMEM;
__set_current_state(TASK_RUNNING);
return NULL;
}
new_table->entry = new_table->entries;
new_table->next = table;
p->table = new_table;
table = new_table;
}

return table->entry++;
}
这个函数会根据情况创建struct poll_table_page结构,因为__pollwait在系统中是会被多次调用的,所以可能会有多个struct poll_table_page结构,这个结构是对struct poll_table_entry的一个封装,其结构如下所示:
[cpp]
struct poll_table_page {
struct poll_table_page * next;
struct poll_table_entry * entry;
struct poll_table_entry entries[0];
};
struct poll_table_entry {
struct file * filp;
wait_queue_t wait;
wait_queue_head_t * wait_address;
};
所以在调用poll_get_entry之后,会返回一个新的poll_table_entry,这也是每次调用__pollwait都会产生的。接下来调用init_waitqueue_entry函数将这个新建的struct poll_table_entry和当前的进程绑定起来,再将struct poll_table_entry加入到socket的等待队列。这样就将当前进程和socket的等待队列联系,说白了,就是把current挂到等待队列上。
因为一旦有数据就绪,就会叫醒等待队列上的进程。可以看代码
[cpp]
static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
q->flags = 0;
q->private = p;
q->func = default_wake_func