poll 从应用层到内核实现解析
poll函数的原型如下所示:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll可以监视多个描述符的属性变化,其参数的意义如下:
参数fds:
指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,具体如下:
struct pollfd{
int fd; //文件描述符
short events; //等待的事件
short revents; //实际发生的事件
};
pollfd结构中的fd为文件描述符,events为待监视的事件,由用户进行设置,可选的监视事件和返回事件如下所示:
revents为具体产生的事件,由内核进行设置,也就是当某一个设备文件描述符产生了具体事件时,内核会设置revents,并最终返回给用户空间。由上图可以看到,events设置的值,都可能由revents返回。
参数nfds:用来指定第一个参数数组中元素的个数
参数timeout:超时值,若为-1,则poll永远等待,若为0,则立即返回,若大于0,则为具体的超时时间,单位是毫秒。
poll函数执行成功时, 返回结构体中 revents 域不为 0 的文件描述符个数;如果在超时前没有任何事件发生,poll()返回 0,如果poll执行失败,返回-1,并设置errno的值,错误值具体如下:
EBADF:一个或多个结构体中指定的文件描述符无效。
EFAULT:fds 指针指向的地址超出进程的地址空间。
EINTR:请求的事件之前产生一个信号,调用可以重新发起。
EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。
ENOMEM:可用内存不足,无法完成请求。
poll的调用路径为sys_poll->do_sys_poll->do_poll->do_pollfd
do_sys_poll将用户空间的pollfd拷贝到内核空间,初始化poll_wqueues table对象。代码如下:
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
struct timespec *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
unsigned long todo = nfds; if (nfds > rlimit(RLIMIT_NOFILE))
return -EINVAL; len = min_t(unsigned int, nfds, N_STACK_PPS);
//这个for循环会进行一些简单的判断。通常一般都会跳出该for循环。
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
//这段代码很关键,将应用程序中通过open函数得到的fd信息,在这里copy给linux内核变量walk->entries。这个时候walk变量就携带有设备文件的fd信息了。
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds; todo -= walk->len;
if (!todo)
break; len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
//进行一些初始化的动作
poll_initwait(&table);
//关键调用函数,会去调用do_poll函数,通过其返回值来判断其可读的个数,同时table指针是poll_wqueues类型的指针
//该poll_wqueues结构体包含有poll_table、poll_table_page、task_struct等非常重要的结构体和指针,例如后面要用到的等待队列项wait_queue_t就存放在
//poll_table_page->poll_table_entry->wait_queue_t中,当然poll_wait函数是可以通过poll_table_struct的地址找到poll_table_entry的。
//看我们这里的第二个参数head,其实就是walk的地址(poll_list指针类型)。
fdcount = do_poll(nfds, head, &table, end_time);
poll_freewait(&table); for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j; for (j = ; j < walk->len; j++, ufds++)
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
} err = fdcount;
out_fds:
walk = head->next;
while (walk) {
struct poll_list *pos = walk;
walk = walk->next;
kfree(pos);
} return err;
}
在do_sys_poll调用do_poll,具体代码如下:
static int do_poll(unsigned int nfds, struct poll_list *list,
struct poll_wqueues *wait, struct timespec *end_time)
{
poll_table* pt = &wait->pt;
ktime_t expire, *to = NULL;
int timed_out = , count = ;
unsigned long slack = ; /* Optimise the no-wait case */
if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
pt = NULL;
timed_out = ;
} if (end_time && !timed_out)
slack = select_estimate_accuracy(end_time); for (;;) {
struct poll_list *walk;
//看这里面的walk首先会指向list。每一个walk都应该代表一个设备文件,因为walk里面的entries数组只有一个元素用来存放fd信息的
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end; pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill the poll_table, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
//调用do_pollfd函数,该函数会调用我们自己编写驱动程序的file_operation->poll函数指针指向的函数
if (do_pollfd(pfd, pt)) {
count++;
pt = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table to them on the next loop iteration.
*/
pt = NULL;
if (!count) {
count = wait->error;
if (signal_pending(current))
count = -EINTR;
}
if (count || timed_out)
break; /*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
} if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) //关键函数,该函数会使进程将当前进程休眠,因为在此前该进程已经被标识在等待队列上了
timed_out = ;
}
return count;
}
list中包含了待监视的fd及其相关信息,对list链表中的每一项都执行了do_pollfd(pfd, pt),并最终调用到驱动程序的poll函数,进一步调用到poll_wait,最终调用到__pollwait。大概完成的工作是为每一个fd分配poll_table_entry并初始化,然后将当前进程封装成一个等待队列项,并将这个等待队列项加入到fd设备的等待队列中。do_pollfd的代码如下:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
unsigned int mask;
int fd; mask = ;
fd = pollfd->fd;
if (fd >= ) {
int fput_needed;
struct file * file; file = fget_light(fd, &fput_needed);
mask = POLLNVAL;
if (file != NULL) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll) {
if (pwait)
pwait->key = pollfd->events |
POLLERR | POLLHUP;
mask = file->f_op->poll(file, pwait); //这句代码很关键,在这里就直接调用驱动程序的poll函数了
}
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP;
fput_light(file, fput_needed);
}
}
pollfd->revents = mask; return mask;
}
调用到__pollwait之后差不多就和select函数的调用殊途同归了,将等待队列项加入到设备等待队列的时候同时查看一下设备的状态,如果就绪了,就将状态写会revevts中,这样查询完所有的fd之后就可以返回了。如果查询完所有的fd之后没有设备就绪,那就根据timeout的值判断一下,我们假设timeout的值是大于0的,因为没有设备准备就绪,所以当前进程进入睡眠。等到超时时间到或者被设备就绪信号唤醒时,会再次调用每个fd对用的poll函数,对它们状态再进行一次查询,查询完所有的设备后,revents中也写好了相应的事件,下一步就返回到用户空间中了。
poll函数相比于select函数,它没有描述符数量的限制,可以监视任意多个设备。每次返回后events不会被破坏,下一次调用poll可以继续使用。
poll函数也需要根据返回值的大小,去数组中依次查询到底哪一个设备描述符准备就绪了以及其返回的事件。
poll 从应用层到内核实现解析的更多相关文章
- select 从应用层到内核实现解析
在一个应用中,如果需要读取多个设备文件,这其中有多种实现方式: 1.使用一个进程,并采用同步查询机制,不停的去轮询每一个设备描述符,当设备描述符不可用时,进程睡眠. 2:使用多个进程或者线程分别读取一 ...
- ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57
转自: ARM内核全解析,从ARM7,ARM9到Cortex-A7,A8,A9,A12,A15到Cortex-A53,A57 前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列 ...
- /proc/sys/ 下内核参数解析
http://blog.itpub.net/15480802/viewspace-753819/ http://blog.itpub.net/15480802/viewspace-753757/ ht ...
- uC/OS-II内核架构解析(1)---嵌入式RTOS(转)
uC/OS-II内核架构解析(1)---嵌入式RTOS 1. 嵌入式系统基本模型 2. RTOS设计原则 采用各种算法和策略,始终保持系统行为的可预测性.即在任何情况下,在系统运行的任何时刻,OS的资 ...
- sysctl内核参数解析
sysctl内核参数解析 kernel.参数 kernel.shmall = 2097152 ## 1> 表示所有内存大小.可以分配的所有共享内存段的总和最大值.(以页为单位) ## 2& ...
- ARM内核全解析
前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列产品,以此来扩大ARM在高性能与低功耗 领域的领先地位,进一步抢占移动终端市场份额.Cortex-A50是继Cortex-A1 ...
- ksar、sar及相关内核知识点解析
关键词:sar.sadc.ksar./proc/stat./proc/cpuinfo./proc/meminfo./proc/diskstats. 在之前有简单介绍过sar/ksar,最近在使用中感觉 ...
- Linux内核(11) - 子系统的初始化之内核选项解析
首先感谢国家.其次感谢上大的钟莉颖,让我知道了大学不仅有校花,还有校鸡,而且很多时候这两者其实没什么差别.最后感谢清华女刘静,让我深刻体会到了素质教育的重要性,让我感到有责任写写子系统的初始化. 各个 ...
- Linux进程的创建函数fork()及其fork内核实现解析【转】
转自:http://www.cnblogs.com/zengyiwen/p/5755193.html 进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进 ...
随机推荐
- dom 绑定数据
一.绑定/修改 .jQuery修改属性值,都是在内存中进行的,并不会修改 DOM 1. 对象绑定 $(selector).data(name) $("#form").da ...
- Codeforces 595D - Max and Bike
595D - Max and Bike 思路:开始和结束时的计时器的高度相同时(也就是关于圆竖着直径对称)时间最少. 证明: 总距离为d. 圆周长为s=2*π*r. 设len=d-floor(d/s) ...
- HDU 6114 Chess
Chess 思路:求C(n,m),除法取余用乘法逆元算. 代码: #include<bits/stdc++.h> using namespace std; #define ll long ...
- OpenGL入门程序四:颜色模式
1.OpenGL支持两种颜色模式: 1>RGBA颜色模式 ,用 glClearColor 指定清空屏幕后的颜色,即“空颜色” . 2>索引颜色模式,用 glClearIndex 指定清空屏 ...
- Short Encoding of Words
2018-07-02 09:48:48 问题描述: 问题求解: 方法一.问题给了规模n = 2000,也就是说用BF在O(n^2)的时间复杂度可以过,因此,第一个方法就是BF,但是需要注意的是这里已经 ...
- 机器学习 Numpy库入门
2017-06-28 13:56:25 Numpy 提供了一个强大的N维数组对象ndarray,提供了线性代数,傅里叶变换和随机数生成等的基本功能,可以说Numpy是Scipy,Pandas等科学计算 ...
- 流氓 2345.com的新动态及解决方法
安装了[电脑公司]的Win7_SP1之后, IE的主页被绑架. 症状是先转到 IE959.com,然后自动跳转到 www.2345.com 网上当然有很多例子了,可是都没有效果. 1. 更改IE设置没 ...
- jsp/post中文乱码问题
在 iso-8859-1,gb2312, utf-8 以及任意一种编码格式下,英文编码格式都是一样的,每个字符占8位,而中文就麻烦了,在gb2312 下一个中文占 16位,两字节,而在utf-8 下一 ...
- [.NET开发] NPOI导出
//导出全部 expertPara = GetExpetPara(); expertPara.BeginIndex = pager.CurrentPageIndex; expertPara.EndIn ...
- 2018焦作网络赛Give Candies
一开始忽略了欧拉定理指数部分是modphi(n-1)没有memset,减法后面没加0: