首先看一下select的大致执行过程:
用户空间select --->内核空间sys_select--->core_sys_select---->do_select---->驱动程序poll
最主要的地方在do_select函数中,该函数中的一个大的调用关系如下:
int do_select(int n, fd_set_bits *fds, s64 *timeout)
{
struct poll_wqueues table;
poll_table *wait;
int retval, i;
......
poll_initwait(&table);
wait = &table.pt;
......
retval = 0;
for (;;)
{
......
set_current_state(TASK_INTERRUPTIBLE);
......
for (i = 0; i < n; ++rinp, ++routp, ++rexp)
{
......
const struct file_operations *f_op = NULL;
struct file *file = NULL;
......
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1)
{
......
mask = (*f_op->poll)(file, retval ? NULL : wait);
......
}
......
}
if( ......)
{
......
break;
}
__timeout = schedule_timeout(__timeout);
}
__set_current_state(TASK_RUNNING);
poll_freewait(&table);
return retval;
}
省略号为删除的一些代码。
对上面的执行过程做一个分析:
table 为poll_wqueues结构体。
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];
};
poll_table也为一个结构体:
typedef struct poll_table_struct
{
poll_queue_proc qproc;
} poll_table;
其中poll_queue_proc为一个函数指针:
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
do_select函数调用poll_initwait函数,__pollwait为一个函数名,既函数指针。
void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
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;
}
到这里,table.pt.qproc = __pollwait。
然后wait = &table.pt; wait为poll_table结构体的指针。
在for( ; ; )循环中,执行set_current_state(TASK_INTERRUPTIBLE);函数将当前进程的状态设置成TASK_INTERRUPTIBLE。
在for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1)循环中执行mask = (*f_op->poll)(file, retval ? NULL : wait);驱动程序中的poll函数,该函数进一步调用 poll_wait函数。
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);
}
其中p->qproc(filp, wait_address, p);是为调用__pollwait函数。
代码如下:
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;
/*对poll_table_entry结构体进行一些列的赋值,初始化*/
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait, current); /*初始化等待队列项*/
add_wait_queue(wait_address, &entry->wait);/*添加至等待队列头*/
}
poll_table_page结构体如下:
struct poll_table_page
{
struct poll_table_page * next;
struct poll_table_entry * entry;
struct poll_table_entry entries[0];
};
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;
}
/*返回table->entry++既struct poll_table_entry entries[0]所在位置的指针*/
return table->entry++;
}
poll_table_entry 结构体
struct poll_table_entry
{
struct file * filp;
wait_queue_t wait;
wait_queue_head_t * wait_address;
};
返回__pollwait然后进行一系列赋值,初始化操作。
至此也了解了为什么使用poll系统调用需要定义等待队列头。由于以上都是在for (i = 0; i < n; ++rinp, ++routp, ++rexp)循环中,故每个传入的文件描述符的文件都通过__pollwait函数穿件了一个poll_table_entry结构体,该结构体内包含等待对立的成员,poll系统调用的实现是依赖与等待队列的。
然后满足一定的条件退出for (i = 0; i < n; ++rinp, ++routp, ++rexp)循环,执行
__timeout = schedule_timeout(__timeout); 使当前进程睡眠。
然后当有进程唤醒该等待队列时,例如设备写入了数据可以被读取,则写系统调用调用wakeup或同类函数唤醒该等待队列。然后程序退出for( ; ; )循环。然后执行__set_current_state(TASK_RUNNING);将当前进程状态改为TASK_RUNNING(不知为何还要将进程改为TASK_RUNNING状态,个人感觉在wakeup函数中就已经改了,毕竟在wait_event函数最后并没有在此修改进程状态)。
最后调用poll_freewait(&table);
void poll_freewait(struct poll_wqueues *pwq)
{
struct poll_table_page * p = pwq->table;
int i;
for (i = 0; i < pwq->inline_index; i++)
free_poll_entry(pwq->inline_entries + i);
while (p)
{
struct poll_table_entry * entry;
struct poll_table_page *old;
entry = p->entry;
do
{
entry--;
free_poll_entry(entry);
} while (entry > p->entries);
old = p;
p = p->next;
free_page((unsigned long) old);
}
}
static void free_poll_entry(struct poll_table_entry *entry)
{
remove_wait_queue(entry->wait_address, &entry->wait);
fput(entry->filp);
}
执行一些移除等待对列项,和释放内存的操作。