首先看一下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; waitpoll_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);

}

执行一些移除等待对列项,和释放内存的操作。