[个人推荐] Linux poll机制分析(基于内核3.10.0)
来源:互联网 发布:淘宝女包品牌排行 编辑:程序博客网 时间:2024/06/05 11:12
原理分析来自网上,代码和数据结构图由本人梳理分析
阅读vhost的时候,发现使用了大量的等待队列和poll,这里温故而知新一下。
注:wait_queue_t是等待在wait_queue_head_t队列中的等待元素
一、内核框架:
1. 对于系统调用poll或select,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。sys_poll函数位于fs/select.c文件中,代码如下:
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs) //就是sys_poll
{
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs) //就是sys_poll
{
/*设定timeout*/
if (timeout_msecs >= 0) {
to = &end_time;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC, NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
//关键实现
ret = do_sys_poll(ufds, nfds, to);
......
return ret;
}
if (timeout_msecs >= 0) {
to = &end_time;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC, NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
//关键实现
ret = do_sys_poll(ufds, nfds, to);
......
return ret;
}
它对超时参数稍作处理后,直接调用do_sys_poll。
2.do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他代码:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec *end_time)
2.do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他代码:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec *end_time)
{
........
/*初始化poll_wqueues变量table,设置添加队列方法为__pollwait*/
poll_initwait(&table);........
/*
* 在do_poll函数中有一个for的死循环,退出条件为count或者timeout为非零。
* count为非零标识do_pollfd函数返回的mask为真,timeout表示定时时间到。
*/
* 在do_poll函数中有一个for的死循环,退出条件为count或者timeout为非零。
* count为非零标识do_pollfd函数返回的mask为真,timeout表示定时时间到。
*/
fdcount = do_poll(nfds, head, &table, end_time);
........
}
poll_initwait函数非常简单,它初始化一个poll_wqueues变量table:注: 参考本图可以很容易理解poll_get_entry
/*
* 从poll_wqueues返回一个空闲的poll_table_entry
*/
static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p)
{
struct poll_table_page *table = p->table;
/*
* 首先试图从内部已分配的inline_entries中返回一个poll_table_entry
*/
if (p->inline_index < N_INLINE_POLL_ENTRIES)
return p->inline_entries + p->inline_index++;
/*
* 如果inline的没有,就分配页面,从页面中的poll_table_page的entries中取得entry
* 在还没有poll_table_page或者poll_table_page中的entry已经用满的情况下,分配新的table
*/
if (!table || POLL_TABLE_FULL(table)) {
struct poll_table_page *new_table;
/*分配新的table*/
new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
if (!new_table) {
p->error = -ENOMEM;
return NULL;
}
/*执行第一个entry,并且将table上链*/
new_table->entry = new_table->entries;
new_table->next = table;
p->table = new_table;
/*更新table的指针*/
table = new_table;
}
/*
* 更新并返回entries中下一个新的entry的地址
* 此时table和entry分别执行最新的table和table中最新的空闲entry
*/
return table->entry++;
}
/*
* 初始化poll_wqueues,设置poll_table的处理函数qproc为__pollwait,
* 这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。
* poll_wait会执行p->_qproc,也就是__pollwait,将当前task加入等待队列后阻塞住
*/
init_poll_funcptr(&pwq->pt, __pollwait);
}
* 在for死循环中,首先轮询整条poll_list。在所有等待poll的操作中寻找是否有已满足条件的操作,有则跳出循环
*/
for (;;) {
for (walk = list; walk != NULL; walk = walk->next) {
for (; pfd != pfd_end; pfd++) {
/*
* 在do_pollfd函数中通过mask = file->f_op->poll来调用驱动中的poll方法,并且获得驱动中操作poll后的mask值。将当前任务加入到等待队列中
* 但是注意此时任务还没有block挂起,直到后面执行poll_schedule_timeout才挂起
*/
if (do_pollfd(pfd, pt, &can_busy_loop, busy_flag)) {
可见,它就是调用我们的驱动程序里注册的poll函数。
二、驱动程序:
驱动程序里与poll相关的地方有两处:
p->qproc就是__pollwait函数,从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列里而已。它的代码如下:
/*
* 此函数就是完成添加队列的工作。
* 把当前进程挂入我们驱动程序里定义的一个队列里
*/
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);
/*取得一个空隙的entry*/
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
/*更新entry的信息,entry的wait元素的方法更新为pollwake*/
entry->filp = get_file(filp);
entry->wait_address = wait_address;
entry->key = p->_key;
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
/*将当前实体wait加入到等待队列wait_address中*/
add_wait_queue(wait_address, &entry->wait);
}
执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的do_sys_poll函数的“poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)”。
poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数加入到等待队列后,再调用poll_schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:poll_schedule_timeout(to),只是休眠to中指定的这段时间。
现在来总结一下poll机制:
1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。用来将当前任务加入等待队列中
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪。
3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间
4. 进程被唤醒的条件有2:
/*
* 从poll_wqueues返回一个空闲的poll_table_entry
*/
static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p)
{
struct poll_table_page *table = p->table;
/*
* 首先试图从内部已分配的inline_entries中返回一个poll_table_entry
*/
if (p->inline_index < N_INLINE_POLL_ENTRIES)
return p->inline_entries + p->inline_index++;
/*
* 如果inline的没有,就分配页面,从页面中的poll_table_page的entries中取得entry
* 在还没有poll_table_page或者poll_table_page中的entry已经用满的情况下,分配新的table
*/
if (!table || POLL_TABLE_FULL(table)) {
struct poll_table_page *new_table;
/*分配新的table*/
new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
if (!new_table) {
p->error = -ENOMEM;
return NULL;
}
/*执行第一个entry,并且将table上链*/
new_table->entry = new_table->entries;
new_table->next = table;
p->table = new_table;
/*更新table的指针*/
table = new_table;
}
/*
* 更新并返回entries中下一个新的entry的地址
* 此时table和entry分别执行最新的table和table中最新的空闲entry
*/
return table->entry++;
}
/*
* 初始化poll_wqueues,设置poll_table的处理函数qproc为__pollwait,
* 这里的__pollwait函数将在驱动的poll方法中通过调用poll_wait函数来执行。
* poll_wait会执行p->_qproc,也就是__pollwait,将当前task加入等待队列后阻塞住
*/
void poll_initwait(struct poll_wqueues *pwq)
{init_poll_funcptr(&pwq->pt, __pollwait);
}
/*初始化poll_table的处理函数_qproc为指定的处理函数,select认为__pollwait*/
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->_qproc = qproc;
pt->_key = ~0UL; /* all events enabled */
}
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->_qproc = qproc;
pt->_key = ~0UL; /* all events enabled */
}
即table->pt->qproc = __pollwait,__pollwait将在驱动的poll函数里用到。(通过驱动的poll方法,调用poll_wait,poll_wait调用qproc方法,也就调用到了__poll_wait)
3.do_poll函数位于fs/select.c文件中,代码如下:
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, struct timespec *end_time)
3.do_poll函数位于fs/select.c文件中,代码如下:
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, struct timespec *end_time)
{
........
/** 在for死循环中,首先轮询整条poll_list。在所有等待poll的操作中寻找是否有已满足条件的操作,有则跳出循环
*/
for (;;) {
for (walk = list; walk != NULL; walk = walk->next) {
for (; pfd != pfd_end; pfd++) {
/*
* 在do_pollfd函数中通过mask = file->f_op->poll来调用驱动中的poll方法,并且获得驱动中操作poll后的mask值。将当前任务加入到等待队列中
* 但是注意此时任务还没有block挂起,直到后面执行poll_schedule_timeout才挂起
*/
if (do_pollfd(pfd, pt, &can_busy_loop, busy_flag)) {
......
}
}
}
}
}
}
if (count || timed_out)
break;
/*
* 执行poll_schedule_timeout(), 执行完此函数后,进程进入休眠,直到被wake_up或者休眠时间到。
*/
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
① 这是个循环,它退出的条件为:
a. 3个条件之一(count非0,超时、有信号等待处理)
count不为0表示do_pollfd至少有一个成功。
b.发生错误
② 重点在do_pollfd函数,后面再分析
③ 让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。
4. do_pollfd函数位于fs/select.c文件中,代码如下:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait, bool *can_busy_poll, unsigned int busy_flag)
{
if (fd >= 0) {
if (f.file) {
if (f.file->f_op && f.file->f_op->poll) {
/*
* 即调用驱动中的poll函数(在写驱动程序时编写poll函数)
* 通过file->f_op->poll来调用驱动中的poll方法,而驱动中的poll方法会调用poll_wait函数
* poll_wait函数调用poll_initwait函数注册的__pollwait函数
* (驱动函数的poll的实现方法中,一般都会调用这个poll_wait,如dvb_audio_poll(), bttv_poll())
break;
/*
* 执行poll_schedule_timeout(), 执行完此函数后,进程进入休眠,直到被wake_up或者休眠时间到。
*/
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return count;
}
分析其中的代码,可以发现,它的作用如下:① 这是个循环,它退出的条件为:
a. 3个条件之一(count非0,超时、有信号等待处理)
count不为0表示do_pollfd至少有一个成功。
b.发生错误
② 重点在do_pollfd函数,后面再分析
③ 让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。
4. do_pollfd函数位于fs/select.c文件中,代码如下:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait, bool *can_busy_poll, unsigned int busy_flag)
{
if (fd >= 0) {
if (f.file) {
if (f.file->f_op && f.file->f_op->poll) {
/*
* 即调用驱动中的poll函数(在写驱动程序时编写poll函数)
* 通过file->f_op->poll来调用驱动中的poll方法,而驱动中的poll方法会调用poll_wait函数
* poll_wait函数调用poll_initwait函数注册的__pollwait函数
* (驱动函数的poll的实现方法中,一般都会调用这个poll_wait,如dvb_audio_poll(), bttv_poll())
* 注意,这里本任务不会休眠,只是加入到了wait_queue中,需要后面调用schedule()才会休眠
*/
mask = f.file->f_op->poll(f.file, pwait);
}
}
}
pollfd->revents = mask;
return mask;
}
*/
mask = f.file->f_op->poll(f.file, pwait);
}
}
}
pollfd->revents = mask;
return mask;
}
二、驱动程序:
驱动程序里与poll相关的地方有两处:
一是构造file_operation结构时,要定义自己的poll函数。
二是通过poll_wait来调用上面说到的__pollwait函数,pollwait的代码如下:
/*
* 通过调用poll_table注册的_qproc函数,来实现将任务加入等待队列,select中为__pollwait
*/
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}
* 通过调用poll_table注册的_qproc函数,来实现将任务加入等待队列,select中为__pollwait
*/
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
p->_qproc(filp, wait_address, p);
}
/*
* 此函数就是完成添加队列的工作。
* 把当前进程挂入我们驱动程序里定义的一个队列里
*/
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);
/*取得一个空隙的entry*/
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
/*更新entry的信息,entry的wait元素的方法更新为pollwake*/
entry->filp = get_file(filp);
entry->wait_address = wait_address;
entry->key = p->_key;
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
/*将当前实体wait加入到等待队列wait_address中*/
add_wait_queue(wait_address, &entry->wait);
}
执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的do_sys_poll函数的“poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)”。
poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数加入到等待队列后,再调用poll_schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:poll_schedule_timeout(to),只是休眠to中指定的这段时间。
现在来总结一下poll机制:
1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。用来将当前任务加入等待队列中
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪。
3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间
4. 进程被唤醒的条件有2:
一是上面说的“一定时间”到了,
二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
5. 如果驱动程序没有去唤醒进程,那么poll_schedule_timeout(to)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。
5. 如果驱动程序没有去唤醒进程,那么poll_schedule_timeout(to)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。
0 0
- [个人推荐] Linux poll机制分析(基于内核3.10.0)
- Linux内核:poll机制
- Linux内核:poll机制
- Linux poll机制分析
- Linux poll机制分析
- Linux poll机制分析
- Linux内核:Poll机制理解
- Linux poll机制精彩分析
- Linux poll机制精彩分析
- linux中的poll机制分析
- Linux poll机制精彩分析
- Linux poll机制精彩分析
- poll机制之内核实现简要分析
- Linux 内核机制--阻塞与非阻塞机制及Poll/Select分析
- linux2.6.38 linux内核poll机制解析
- 深入理解linux内核poll机制
- Linux 内核驱动--阻塞与非阻塞机制及Poll/Select分析if
- Linux 内核驱动--阻塞与非阻塞机制及Poll/Select分析
- 如何有效沟通-1分钟原则
- host_notifier, 虚拟机通过VHOST发包流程(基于kernel3.10.0 && qemu 2.0.0)
- Vhost Architecture(基于kernel3.10.0 & qemu2.0.0)
- linux内核的等待队列使用方法,wait_queue_head_t,进程休眠
- linux poll 和 等待队列休眠的关系(基于kernel 3.10.0)
- [个人推荐] Linux poll机制分析(基于内核3.10.0)
- 安卓优化
- [个人推荐]理解poll_wait
- select(poll)实现代码实现分析(基于kernel 3.10.0)
- 新手对Bash环境变量解析漏洞的理解 http://www.linuxidc.com/Linux/2014-10/107984.htm
- select(poll)实现代码实现分析(基于kernel 3.10.0)
- KVM源代码分析1:基本工作原理
- Linux 新api eventfd
- 令狐冲的SDL(安全开发周期)引进手记
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
吉川爱美中文字在线播放
吉川中文字幕
吉川爱美影片
吉川莲在线播放
吉川爱美在线全部播放高清
吉川爱美中文y一暮
吉川莲在线中文字幕
吉川莲中文字幕
吉川爱美白天是严母
比起老公更爱公公吉川爱美
吉平
吉庆街
武汉吉庆街
吉庆
吉庆热干面
吉庆家园
吉庆临门
吉庆小区
吉庆街小吃攻略
吉庆家园二手房价格
武汉吉庆街酒店
大脚怀了吉庆的小孩
吉庆有鱼 章鱼凤梨
吉庆家园二手房
吉德
吉德洗衣机好不好
吉德洗衣机旗舰店
吉德洗衣机xqb50-5168
吉德洗衣机xqb70-6259
吉德洗衣机怎么用
吉德洗衣机型号
吉德洗衣机e1
吉德洗衣机e2
吉德洗衣机f1
吉德洗衣机离合器
吉德洗衣机好吗
吉德洗衣机说明书
吉德洗衣机电脑板
吉德滚筒洗衣机
吉德滚筒洗衣机价格
吉德洗衣机显示e2