一、回顾
在linux poll机制使用(一)写了个实现poll机制的简单例子。在驱动模块中需要实现struct file_operations
的.poll
成员。在驱动模块中xxx_poll函数
的的作用是将当前进程添加到等待队列中;然后判断事件是否发生,发生则返回POLLIN | POLLRDNORM
,否则返回0(可以看看上一章的例子);接下来分析一下 linux 内核中 poll 机制的实现。
二、poll机制分析
1、系统调用
当应用层调用poll函数时,linux发生系统调用(
系统调用入口CALL(sys_poll)
),程序从应用空间切换到内核空间,然后执行sys_poll
函数(sys_poll函数在fs\select.c
文件中)。sys_poll
函数的作用是 根据时间计算滴答频率数,然后调用do_sys_poll
函数。代码块如下:
/* ufds:应用层传递过来的struct pollfd结构体数组 * nfds:应用层传递过来的struct pollfd结构体个数 * timeout_msecs:超时时间*/ asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs) { s64 timeout_jiffies; if (timeout_msecs > 0) { /* 大于0,根据时间计算滴答频率数 */ timeout_jiffies = msecs_to_jiffies(timeout_msecs); } else { /* Infinite (< 0) or no (0) timeout */ timeout_jiffies = timeout_msecs; } /* 然后调用do_sys_poll函数*/ return do_sys_poll(ufds, nfds, &timeout_jiffies); }
2、do_sys_poll函数
do_sys_poll
函数在fs\select.c
文件中;do_sys_poll
函数的主要作用初始化table(table->pt->qproc = __pollwait)
,然后初始化poll链表
(poll链表的作用是存放用户空间的struct pollfd
)接着调用do_poll
函数。下面的代码块省略了部分源代码。代码块如下:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) { struct poll_wqueues table; struct poll_list *head; struct poll_list *walk; .....//省略了部分源代码 if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur) return -EINVAL; /* 初始化 table */ /* table->pt->qproc = __pollwait * __pollwait 主要将当前进程挂到等待队列中,这里只是做初始化而已。 * 在驱动程序里调用的 poll_wait(file, &button_waitq, wait); 函数里面最终 * 调用的就是table->pt->qproc()函数,也就是调用__pollwait()函数。 */ poll_initwait(&table); .....//省略部分源代码(省略的源代码其实就是分配空间和初始化head链表) /* do_poll函数的参数说明: * * nfds: 应用层传递过来的struct pollfd结构体个数 * head: poll链表头,链表里面包含应用层传进来的struct pollfd数组的信息 * table: table->pt->qproc指向__pollwait函数(主要将当前进程挂到等待队列中) * timeout: 等待超时 */ /* 调用do_poll函数 */ fdcount = do_poll(nfds, head, &table, timeout); .....//省略部分源代码(这部分的源代码是将revents(返回的事件) copy 到应用层) return err; }
3、do_sys_poll函数
do_sys_poll
函数在fs\select.c
文件中。do_poll
函数的作用主要是设置当前进程的任务状态,然后遍历poll链表、调用do_pollfd
函数,然后根据实际情况是否将当前进程休眠或者唤醒当前进程,代码块如下:
//参数说明 /*nfds: 应用层传递过来的struct pollfd结构体个数 *list:poll链表头,链表里面包含应用层传进来的struct pollfd数组的信息 *wait:wait->pt->qproc指向__pollwait函数(主要将当前进程挂到等待队列中) *timeout:等待超时时间 */ static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, s64 *timeout) { int count = 0; poll_table* pt = &wait->pt; /* Optimise the no-wait case */ if (!(*timeout)) pt = NULL; /* 执行poll的时候有一个大循环 */ for (;;) { struct poll_list *walk; long __timeout; /* 设置当前的任务状态为可中断休眠 */ set_current_state(TASK_INTERRUPTIBLE); /* 遍历poll链表 */ for (walk = list; walk != NULL; walk = walk->next) { struct pollfd * pfd, * pfd_end; /* 获取头部的地址 */ pfd = walk->entries; /* 获取尾部指针 */ pfd_end = pfd + walk->len; /* 遍历struct pollfd结构体数组 */ for (; pfd != pfd_end; pfd++) { /*------------------------------------------------------------------*/ /* do_pollfd其实就是调用开发者所写的xxx_poll函数了(里面调用的的是struct file_operations .poll函数) * do_pollfd函数下面的代码块分析 */ /* 执行完do_pollfd后,这个进程就被挂到等待队列里面了。 * 这里仅仅是挂到等待队列而已,进程的休眠是在下面 * 执行 schedule_timeout 函数的时候休眠的 */ if (do_pollfd(pfd, pt)) { /* 如果执行驱动程序的poll返回的是非0值 则count++ */ /* 表名设备有数据要返回给应用程序 */ count++; pt = NULL; } /*------------------------------------------------------------------*/ } } pt = NULL; /* 结束大循环的条件有三个: * count: 表示设备有数据返回给应用程序 * timeout: 等待超时时间到 * signal_pending(current): 当前进程接收到信号 */ if (count || !*timeout || signal_pending(current)) break; /* 发生错误退出 */ count = wait->error; if (count) break; if (*timeout < 0) { /* Wait indefinitely */ __timeout = MAX_SCHEDULE_TIMEOUT; } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) { /* * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in * a loop */ __timeout = MAX_SCHEDULE_TIMEOUT - 1; *timeout -= __timeout; } else { __timeout = *timeout; *timeout = 0; } /* 执行到这里之后 timeout 的值已经为零,下一次循环就会超时返回 */ /* 这里让当前进程休眠一会 * 唤醒进程的处理休眠的时间到之外,还可以由驱动程序唤醒 */ __timeout = schedule_timeout(__timeout); if (*timeout >= 0) *timeout += __timeout; } /* 将当前进程设置为就绪状态,等待CPU调度 */ __set_current_state(TASK_RUNNING); return count; }
4、do_pollfd
do_pollfd
函数在fs\select.c
文件中,函数的作用就是,根据文件描述符找到struct file
结构体,然后调用驱动程序的poll函数
//参数说明 /* pollfd: struct pollfd结构体 * pwait:pwait->qproc指向__pollwait函数(主要将当前进程挂到等待队列中) */ static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait) { unsigned int mask; int fd; mask = 0; fd = pollfd->fd; if (fd >= 0) { int fput_needed; struct file * file; /* 根据文件描述符,获取file结构 * file结构是每打开一个文件就会有一个file */ file = fget_light(fd, &fput_needed); mask = POLLNVAL; if (file != NULL) { mask = DEFAULT_POLLMASK; if (file->f_op && file->f_op->poll) /* 调用驱动里面的poll函数,这个函数是驱动开发者添加的poll函数 */ /* 如果驱动有数据让应用程序读的话,就返回非0, 否侧返回0 */ mask = file->f_op->poll(file, pwait); /* Mask out unneeded events. */ /* 根据应用层传递的events,来屏蔽不需要的事件 */ mask &= pollfd->events | POLLERR | POLLHUP; fput_light(file, fput_needed); } } pollfd->revents = mask; /* 设备有数据可读则返回非0 */ /* 否则返回0 */ return mask; }
三、总结
- poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。
- 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪- 如果设备未就绪,do_sys_poll里会让进程休眠一定时间
- 进程被唤醒的条件有两个 :1、“一定时间”到了 ;2、二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。
- 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。
来源:https://www.cnblogs.com/gulan-zmc/p/12241004.html