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 从应用层到内核实现解析的更多相关文章

  1. select 从应用层到内核实现解析

    在一个应用中,如果需要读取多个设备文件,这其中有多种实现方式: 1.使用一个进程,并采用同步查询机制,不停的去轮询每一个设备描述符,当设备描述符不可用时,进程睡眠. 2:使用多个进程或者线程分别读取一 ...

  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处理器系列 ...

  3. /proc/sys/ 下内核参数解析

    http://blog.itpub.net/15480802/viewspace-753819/ http://blog.itpub.net/15480802/viewspace-753757/ ht ...

  4. uC/OS-II内核架构解析(1)---嵌入式RTOS(转)

    uC/OS-II内核架构解析(1)---嵌入式RTOS 1. 嵌入式系统基本模型 2. RTOS设计原则 采用各种算法和策略,始终保持系统行为的可预测性.即在任何情况下,在系统运行的任何时刻,OS的资 ...

  5. sysctl内核参数解析

    sysctl内核参数解析 kernel.参数 kernel.shmall = 2097152    ## 1> 表示所有内存大小.可以分配的所有共享内存段的总和最大值.(以页为单位) ## 2& ...

  6. ARM内核全解析

    前不久ARM正式宣布推出新款ARMv8架构的Cortex-A50处理器系列产品,以此来扩大ARM在高性能与低功耗 领域的领先地位,进一步抢占移动终端市场份额.Cortex-A50是继Cortex-A1 ...

  7. ksar、sar及相关内核知识点解析

    关键词:sar.sadc.ksar./proc/stat./proc/cpuinfo./proc/meminfo./proc/diskstats. 在之前有简单介绍过sar/ksar,最近在使用中感觉 ...

  8. Linux内核(11) - 子系统的初始化之内核选项解析

    首先感谢国家.其次感谢上大的钟莉颖,让我知道了大学不仅有校花,还有校鸡,而且很多时候这两者其实没什么差别.最后感谢清华女刘静,让我深刻体会到了素质教育的重要性,让我感到有责任写写子系统的初始化. 各个 ...

  9. Linux进程的创建函数fork()及其fork内核实现解析【转】

    转自:http://www.cnblogs.com/zengyiwen/p/5755193.html 进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进 ...

随机推荐

  1. Ubuntu16.04 安装 Django

    pip2 install django==1.11 或者手动安装: 链接:https://pan.baidu.com/s/1uQJD-pON7gELoCC2TwYnEw 提取码:flgg cd Dja ...

  2. [ios]自定义UI

    参考:http://blog.sina.com.cn/s/blog_7b9d64af0101edqf.html 回忆一下,这么个场景. 我们在一个界面上,要排列多个相同的元素.你马上就可以想到: 1. ...

  3. 《剑指offer》第三十二题(分行从上到下打印二叉树)

    // 面试题32(二):分行从上到下打印二叉树 // 题目:从上到下按层打印二叉树,同一层的结点按从左到右的顺序打印,每一层 // 打印到一行. #include <cstdio> #in ...

  4. Spring AMQP 源码分析 08 - XML 配置

    ### 准备 ## 目标 通过 XML 配置文件使用 Spring AMQP ## 前置知识 <Spring AMQP 源码分析 07 - MessageListenerAdapter> ...

  5. nodejs 监听文件夹变化的模块

    使用Node.JS监听文件夹变化 fs.watch 其中Node.JS的文件系统也可侦听某个目录的改变, 如fs.watch   其中fs.watch的最大缺点就是不支持子文件夹的侦听,并且在很多情况 ...

  6. 在linux环境下编译C++ 程序

    单个源文件生成可执行程序 下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码: 单个源文件生成可执行程序 /* helloworld.cpp */ #include ...

  7. 12月8日 周五 image_tag.

    Overview of helpers provided by Action View 6.1 AssetTagHelper:用于generate html语言 image_tag ,return a ...

  8. Dajngo的CBV和FBV

    CBV: class. base. view 路由: url(r'students/', views.StudentsView.as_view()) 视图: from django.views imp ...

  9. 『科学计算』图像检测微型demo

    这里是课上老师给出的一个示例程序,演示图像检测的过程,本来以为是传统的滑窗检测,但实际上引入了selectivesearch来选择候选窗,所以看思路应该是RCNN的范畴,蛮有意思的,由于老师的注释写的 ...

  10. UVA-208 Firetruck (回溯)

    题目大意:给一张无向图,节点编号从1到n(n<=20),按字典序输出所有从1到n的路径. 题目分析:先判断从1是否能到n,然后再回溯. 注意:这道题有坑,按样例输出会PE. 代码如下: # in ...