struct
poll_wqueues {
poll_table pt;
struct
poll_table_page *table;
struct task_struct *polling_task;
保存当前调用select的用户进程struct task_struct结构体
int triggered;
当前用户进程被唤醒后置成1,以免该进程接着进睡眠
int error;
int inline_index;
数组inline_entries的引用下标
struct
poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};
每一个调用select()系统调用的应用进程都会存在一个
struct poll_weueues结构体,
用来统一辅佐实现这个进程中所有待监测的fd的轮询工作,
后面所有的工作和都这个结构体有关,所以它非常重要
struct
poll_table_page {
struct poll_table_page * next;
struct poll_table_entry * entry;
struct poll_table_entry entries[0];
};
这个表记录了select过程中所有等待队列的节点。
由于select要监视多个fd,并且要把当前进程放入这些fd的等待队列中去,因此要分配等待队列的节点。
这些节点可能如此之多,以至于不可能像通常做的那样,在堆栈中分配它们。
所以,select以动态分配的方式把它保存在poll_table_page中。
保存的方式是单向链表,每个节点以页为单位,分配多个poll_table_entry项。
struct
poll_table_entry {
struct file *filp;
unsigned long key;
wait_queue_t wait; 内嵌了一个等待队列
wait_queue_head_t *wait_address;
};
filp是select要监视的struct file结构体,wait_address是文件操作的等待队列的队首,wait是等待队列的节点。
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt,
__pollwait);
保存回调函数
pwq->polling_task = current;
设置polling_task为当前进程
pwq->triggered = 0;
pwq->error = 0;
pwq->table = NULL;
pwq->inline_index = 0;
}
static inline void
init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->qproc = qproc;
pt->key = ~0UL; /* all events enabled */
}
static unsigned int
sock_poll(struct file *file, poll_table *wait)
{
struct socket *sock;
sock = file->private_data; 得到socket结构,存于file的私有数据区中
这里就从fd转化为对对应socket结构的操作了
return
sock->ops->poll(file, sock, wait);
在前面socket的创建一文中分析过
sock->ops = answer->ops;
以TCP为例 即为
.ops = &inet_stream_ops, .poll = tcp_poll,
以UDP为例 即为
.ops = &
inet_dgram_ops
, .poll = udp_poll,
对应的poll函数就是去查看对应的sock结构中
struct sk_buff_head sk_receive_queue;
struct sk_buff_head sk_write_queue;
这些队列是否可读,可写,以及其他一些状态的判断,具体的不进入分析了
我们只大概看下udp_poll
}
unsigned int
datagram_poll(struct file *file, struct socket *sock,
poll_table *wait)
{
struct sock *sk = sock->sk;
unsigned int mask;
sock_poll_wait(file, sk->sk_sleep, wait); 将wait加入到sock的sk_sleep等待队列头中
mask = 0;
下面对各种可读,可写,异常错误等状态判断,返回mask
/* exceptional events? */
if (sk->sk_err || !skb_queue_empty(&sk->sk_error_queue))
mask |= POLLERR;
if (sk->sk_shutdown & RCV_SHUTDOWN)
mask |= POLLRDHUP;
if (sk->sk_shutdown == SHUTDOWN_MASK)
mask |= POLLHUP;
/* readable? */
if (!skb_queue_empty(&sk->sk_receive_queue) ||
(sk->sk_shutdown & RCV_SHUTDOWN))
mask |= POLLIN | POLLRDNORM;
/* Connection-based need to check for termination and startup */
if (connection_based(sk)) {
if (sk->sk_state == TCP_CLOSE)
mask |= POLLHUP;
/* connection hasn't started yet? */
if (sk->sk_state == TCP_SYN_SENT)
return mask;
}
/* writable? */
if (sock_writeable(sk))
mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
else
set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
return mask;
}
static inline void sock_poll_wait(struct file *filp,
wait_queue_head_t *wait_address, poll_table *p)
{
if (p && wait_address) {
poll_wait(filp, wait_address, p);
smp_mb();
}
}
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); 这里qproc即为
init_poll_funcptr(&pwq->pt,
__pollwait);
保存回调函数
}
static void
__pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
从poll_table结构得到poll_wqueues 结构
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
获得一个poll_table_entry
struct poll_table_entry *
entry = poll_get_entry(pwq);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
保存file结构变量
entry->wait_address = wait_address;
这里wait_address为sk->sk_sleep结构
entry->key = p->key;
初始化等待队列项,pollwake是唤醒该等待队列项时候调用的函数
init_waitqueue_func_entry(&entry->wait, pollwake);
将poll_wqueues作为该等待队列项的私有数据,后面使用
entry->wait.private = pwq;
add_wait_queue(wait_address, &entry->wait);
将该等待队列项添加到从驱动程序中传递过来的等待队列头中去 为
sk->sk_sleep结构
}
该函数首先通过container_of宏来得到结构体poll_wqueues的地址,然后调用poll_get_entry()函数来获得一个poll_table_entry结构体,这个结构体是用来连接驱动和应用进程的关键结构体,其实联系很简单,这个结构体中内嵌了一个等待队列项wait_queue_t,和一个等待队列头 wait_queue_head_t,它就是驱动程序中定义的等待队列头,应用进程就是在这里保存了每一个硬件设备驱动程序中的等待队列头(
当然每一个fd都有一个poll_table_entry结构体)。
我们看下睡眠
poll_schedule_timeout(&table, TASK_INTERRUPTIBLE, to, slack)
int
poll_schedule_timeout(struct poll_wqueues *pwq, int state,
ktime_t *expires, unsigned long slack)
{
int rc = -EINTR;
set_current_state(state);
if (!pwq->triggered) 这个triggered在什么时候被置1的呢?只要有一个fd对应的设备将当前应用进程唤醒后将会把它设置成1
rc = schedule_hrtimeout_range(expires, slack, HRTIMER_MODE_ABS);
__set_current_state(TASK_RUNNING);
set_mb(pwq->triggered, 0);
return rc;
}
看下唤醒过程:
前面介绍了select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。
驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),
然后返回一个bitmask告诉select当前资源哪些可用。
上面poll函数中已经将wait 即当前进程插入到了等待队列中。
唤醒该进程的过程通常是在所监测文件的设备驱动内实现的,驱动程序维护了针对自身资源读写的等待队列。
当设备驱动发现自身资源变为可读写并且有进程睡眠在该资源的等待队列上时,
就会唤醒这个资源等待队列上的进程。
在这里,比如UDP,有udp数据包来了后,挂载到了对应sock的接收队列上时,会查看是否有进程正在睡眠等待,
如果有的话就调用注册的
唤醒函数进行唤醒,我们这里注册的唤醒函数就是上面提到的
pollwake.
static int
pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_table_entry *entry;
entry = container_of(wait, struct poll_table_entry, wait);
从wait得到poll_table_entry 结构
if (key && !((unsigned long)key & entry->key))
判断key,检查是否有错误唤醒
return 0;
return
__pollwake(wait, mode, sync, key);
}
static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
struct poll_wqueues *pwq = wait->private;
DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);
smp_wmb();
pwq->triggered = 1;
return
default_wake_function(&dummy_wait, mode, sync, key);
将等待进程从等待队列上摘下,加入运行进程队列等一系列复杂操作,达到唤醒目的
}
到这里明白了select进程被唤醒的过程。
由于该进程是阻塞在所有监测的文件对应的设备等待队列上的,因此在timeout时间内,只要任意个设备变为可操作,
都会立即唤醒该进程,从而继续往下执行。这就实现了select的当有一个文件描述符可操作时就立即唤醒执行的基本原理。
这篇文章对select(poll)分析的很好,大家共同学习
http://blog.csdn.net/lizhiguo0532/article/details/6568964