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. python 分数的数学四则运算

    import fractions f1 = fractions.Fraction(, ) f2 = fractions.Fraction(, ) print('{} + {} = {}'.format ...

  2. Redis 安装到linux系统

    下载地址 : http://download.redis.io/releases/redis-3.0.3.tar.gz ). tar -zxvf redis-.tar.gz -C /usr/share ...

  3. 算法笔记--sg函数详解及其模板

    算法笔记 参考资料:https://wenku.baidu.com/view/25540742a8956bec0975e3a8.html sg函数大神详解:http://blog.csdn.net/l ...

  4. Python - PIL-pytesseract-tesseract验证码识别

    N天前实现了简单的验证识别,这玩意以前都觉得是高大上的东西,一直没有去研究,这次花了点时间研究了一下,当然只是一些基础的东西,高深的我也不会,分享一下给大家吧. 关于python验证码识别库,网上主要 ...

  5. Redis之哈希类型命令

    Hash(哈希) Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象. Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿 ...

  6. spojPlay on Words

    题意:给出几个词语,问能不能接龙. 一开始猜只要所有字母连通,并且只有一个字母出现在开头次数为奇,一个字母末尾为奇,其它偶,就行.后来发现全为偶也行.而且条件也不对,比如ac,ac,ac就不行.实际上 ...

  7. android------2018 年初值得关注的 16 个新 Android 库和项目

    1. transitioner Transitioner 是一个为两个拥有嵌入子视图的视图之间提供简便.动态且可调整的动画效果的库.它纯 100% 使用 Kotlin 编写而成,使用 MIT 许可,且 ...

  8. Party CodeForces - 906C (状压)

    大意: 给定n(n<=22)个人, m个关系谁跟谁是朋友, 朋友关系是双向的, 每次操作可以选择一个人, 使他的朋友互相成为朋友, 求最少多少次操作可以使所有人互相认识 这个题挺巧妙的了, 关键 ...

  9. vij 1097 贪心

    合并果子 描述 在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆.多多决定把所有的果子合成一堆. 每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之 ...

  10. Java IO流中的flush()

    通过BufferedOutputStream或BufferedWriter 链接到底层流上来实现.因此,在写 完数据时,flush就显得尤为重要. 例如: 上图中WEB服务器通过输出流向客户端响应了一 ...