select中的timeout机制
在do_select函数内部有一个遍历fd的轮询,对其中的每一个调用对应的poll驱动,调用完成之后就会立刻返回,部分代码如下:
- for(j = 0;j < __NFDBITS;++j,++i,bit <<=1) {
- ….
- file = fget_light(i,&fput_needed);
- …
- //调用之后就会立刻返回
- (*f_op->poll)(file,wait);
- //判断之后,开始下一个轮询
- ….
- }
- //当轮询完成后,开始进行,检查是否有抢占进程
- …...
- cond_resched();
- //接着开始判断获取的可读写事件情况,超时等情况,决定是否进行超时睡眠等待
- …..
- if(retval || time_out || signal_pending(current))
- break;
- …..
- if(!poll_schedule_timeout(&table,TASK_INTERRUPTIBLE,to,slack))
- time_out = 1;
- }
- ….....
这样就完成了超时机制的实现,实际上也就是轮询一遍,发现是否有就绪事件来决定是否睡眠或返回.
2.poll驱动中的wait参数作用
在编写设备驱动实现poll时,基本原型如下:
点击(此处)折叠或打开
- static unsigned int dev_poll(struct file *filp,poll_table *wait)
- {
- …...
- poll_wait(filp,&dev->inq,wait);
- poll_wait(filp,&dev->outq,wait);
- //判断是否有就绪事件
- if(condition)
- mask |= POLLIN | POLLRDNORM;
- if(condition)
- mask |= POLLOUT | POLLWRNORM;
- …..
- }
这里就会让对应的进程在驱动定义的wait_queue_head_t 的等待队列中睡眠,这其中最关键的函数之一就是poll_wait,原型如下:
void poll_wait(struct file *filp,wait_queue_head_t *wait_address,poll_table *p)
poll_table定义如下:
- typedef struct poll_table_struct {
- poll_queue_proc qproc;
- unsigned long key;
- }poll_table;
而具体poll_wait函数原型如下:
- static void __pollwait(struct file *filp,wait_queue_head_t *wait_address,poll_table *p)
- {
- struct poll_wqueues *pwq = container_of(p,struct poll_wqueues,pt);
- //在对应调用select 实现中添加一项等待该filp的数据结构
- struct poll_table_entry *entry = poll_get_entry(pwq);
- if(!entry)
- return;
- get_file(filp);
- entry->filp = filp;
- entry->wait_address = wait_address;
- entry->key = p->key;
- //当事件到来时,就会调用pollwake唤醒进程,也就是读写驱动中可读写事件到来时的唤醒
- init_waitqueue_func_entry(&entry->wait,pollwake);
- entry->wait.private = pwq;
- //放到等待队列上,还是没有被抢占,通过调用schedule函数显示让出CPU
- add_wait_queue(wait_address,&entry->wait);
- }
加入到等待队列的主要目的是让驱动中的读写函数在处理阻塞式操作时,解决阻塞等待的问题.
这里的poll_wqueue定义如下:
- struct poll_wqueue {
- poll_table pt;
- struct poll_table_page *table;
- struct task_struct *polling_task;
- int triggered;
- int error;
- int inline_index;
- //N_INLINE_POLL_ENTRIES 18(32位系统)
- struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
- }
在调用poll_get_entry函数时,获取一项时,方法有些巧妙,具体实现如下:
- struct poll_table_page {
- struct poll_table_page *next;
- struct poll_table_entry *entry;
- struct poll_table_entry entries[0];//此为gcc扩展
- }
- static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p)
- {
- 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;
- return NULL;
- }
- //关键是这里两者指针地址相同,entry,entries都是用来标示new_table的数据起始地址
- new_table->entry = new_table->entries;
- new_table->next = table;
- p->table = new_table;
- table = new_table;
- }
-
return table->entry++;
}
}
释放poll_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);
- }
- }
select 的echo和设备驱动示例见这里:
参考资料:
linux设备驱动
深入理解linux内核