一、应用程序

  1. /*
  2. struct pollfd {
  3. int fd; //文件描述符
  4. short events; //表示请求检测的事件
  5. short revents; //表示检测之后返回的事件
  6. };
  7. */
  8. int fd;
  9. struct pollfd fds[]; // 只用poll函数来检测一个描述符
  10. fd = open("/dev/tiny6410_button", );
  11. fds[].fd = fd; //存放文件描述符
  12. fds[].events = POLLIN; //有数据可以读
  13. while ()
  14. {
  15.   ret = poll(fds, , ); // int poll(struct pollfd fds[], nfds_t nfds, int timeout)
  16.   if (ret == ) // fds[]:存放需要被检测状态的描述符 nfds:fds[]数组的个数 timeout:poll函数阻塞调用的时间
  17. {
  18.     printf("timeout fds[0].revents = %d \n",fds[].revents);
  19. }
  20.   else if (ret>)
  21. {
  22.     printf("fds[0].revents = %d\n",fds[].revents);
  23.     read(fd, press_cnt, sizeof(press_cnt));
  24.     for (i = ; i < sizeof(press_cnt)/sizeof(press_cnt[]); i++)
  25.     {
  26.       if (press_cnt[i])
  27.       printf("K%d has been pressed %d times!\n", i+, press_cnt[i]);
  28.     }
  29. }
  30. else
  31. {
  32.     return ;
  33.   }
  34. }
  35. close(fd);
  36. return ;

poll(...)函数的返回值:>0 表示 fds[]中存放的某些文件描述符的状态发生了变化

          =0 表示 fds[]中存放的文件描述符的状态没有变化,并且调用超时了

          <0 表示有错误发生

看到实验结果:当5秒没有按键按下时,timeout   fds[0].revents = 0

        当有按键立即按下时,   fds[0].revents = 1

        因此可以根据revents的值来判断哪个文件描述符的状态发生了变化

二、 从内核看poll 函数调用

  2.1 找sys_poll(...)函数

  在应用程序调用poll(...)函数时,内核会调用sys_poll(...)函数,因此在内核中寻找sys_poll(...)函数,在linux-2.6.38中,系统

调用函数名称都是用宏定义实现的。所有先找一找sys_poll(...)在哪里,在select.c中有如下函数:

  SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

这是一个宏定义,需要把这个宏定义展开成如下的形式:

  asmlink long sys_poll( struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs)

宏定义的展开过程分析:

在syscalls.h中有一大堆关于系统调用的宏定义

  #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

宏替换后变成了:

  SYSCALL_DEFINEx(3, _poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

  #define SYSCALL_DEFINEx(x, sname, ...)   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

宏替换后变成了:

  __SYSCALL_DEFINEx(3,_poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

  #define __SYSCALL_DEFINEx(x, name, ...)    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

宏替换后变成了:

  asmlinkage long sys_poll(__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs))

对__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)进行展开

    __SC_DECL##x(__VA_ARGS__) 这个也是一个宏定义,仍然需要进行展开:

    #define __SC_DECL1(t1, a1) t1 a1

    #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)

    #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)

    #define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)

    #define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)

    #define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)

就得到了  struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs

故得到了最终的展开函数:

asmlink long sys_poll( struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs)

  2.2 函数调用过程分析

  1. SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
  2. long, timeout_msecs)
  3. {
  4. ....
  5. //1.设置timeout时间
  6. if (timeout_msecs >= ) {
  7. to = &end_time;
  8. poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
  9. NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
  10. }
  11. // 2.完成poll调用的主要任务
  12. ret = do_sys_poll(ufds, nfds, to);
  13. ....
  14. }

  2.2.1这里的核心函数do_sys_poll(...)

  1. int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
  2. struct timespec *end_time)
  3. {
  4. struct poll_wqueues table;
  5. int err = -EFAULT, fdcount, len, size;
  6. // 从这里开始都是分配内存空间,并将用户空间的fds[]拷贝到内核空间
  7. long stack_pps[POLL_STACK_ALLOC/sizeof(long)];// 在栈上分配一个固定空间的
  8. struct poll_list *const head = (struct poll_list *)stack_pps;//强制将上边分配的空间转换为poll_list
  9. struct poll_list *walk = head;
  10. unsigned long todo = nfds;//用户空间的fds[]数组的个数
  11.  
  12. if (nfds > rlimit(RLIMIT_NOFILE))
  13. return -EINVAL;
  14.  
  15. len = min_t(unsigned int, nfds, N_STACK_PPS);
  16. for (;;) {
  17. walk->next = NULL; //将指针先置为NULL
  18. walk->len = len; //长度=len
  19. if (!len)
  20. break;
  21.  
  22. if (copy_from_user(walk->entries, ufds + nfds-todo,
  23. sizeof(struct pollfd) * walk->len))//拷贝的前提应该是不会超过分配内存的大小
  24. goto out_fds; //当用户空间的fds[]超过所分配内存大小时,跳转
  25.  
  26. todo -= walk->len; //nfds-walk->len 求剩下多少个fds结构体组没有拷
  27. if (!todo) //如果不剩即全部拷完了, 则break
  28. break;
  29.  
  30. len = min(todo, POLLFD_PER_PAGE); //若还剩todo个没考, 求下一次需要拷的个数
  31. size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;//求需要分配内存的大小
  32. walk = walk->next = kmalloc(size, GFP_KERNEL);// 在分配size大小的内存,并将这块内存的首地址挂载walk->next上, 这样如果有很多fds[]的话,就可以够成一个一个的poll_list 链表, 链表之间通过poll_list->next 连接
  33. if (!walk) {
  34. err = -ENOMEM;
  35. goto out_fds;
  36. }
  37. }
  38. // 到这里已经分配完内存空间,并将所有的fds[]从用户空间拷贝到内核空间
  39. poll_initwait(&table); // 这个函数就是初始化poll_wqueues类型的变量 table
  40. 初始化了什么,在下边分析
  41. fdcount = do_poll(nfds, head, &table, end_time);// 核心函数
  42. poll_freewait(&table);
  43.  
  44. for (walk = head; walk; walk = walk->next) {
  45. struct pollfd *fds = walk->entries;
  46. int j;
  47.  
  48. for (j = ; j < walk->len; j++, ufds++) // 分析到这里应该就很明朗了,这里是把revents从内核空间拷贝到用户空间,方便用户空间来查询哪些文件描述符的状态发生了变化
  49. if (__put_user(fds[j].revents, &ufds->revents))
  50. goto out_fds;
  51. }
  52. err = fdcount;
  53. out_fds:
  54. walk = head->next;
  55. while (walk) {
  56. struct poll_list *pos = walk;
  57. walk = walk->next;
  58. kfree(pos);
  59. }
  60.  
  61. return err;
  62. }

在do_sys_poll(...)函数中,首先就是分配内存空间,将用户空间的fds[]拷贝到内核空间,具体的拷贝过程已经在注释中大概分析了

            其次初始化table变量 ,在初始化table变量之前有必要看一下table是什么

  1. struct poll_wqueues {
  2. poll_table pt;
  3. struct poll_table_page *table;
  4. struct task_struct *polling_task;
  5. int triggered;
  6. int error;
  7. int inline_index;
  8. struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
  9. }; //这是table是什么 重点是poll_table成员
  10.  
  11. typedef struct poll_table_struct {
  12. poll_queue_proc qproc;
  13. unsigned long key;
  14. } poll_table; // 这是poll_table 是什么
  15. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
  16. poll_table *p)
  17. {
  18. struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
  19. struct poll_table_entry *entry = poll_get_entry(pwq);
  20. if (!entry)
  21. return;
  22. get_file(filp);
  23. entry->filp = filp;
  24. entry->wait_address = wait_address;
  25. entry->key = p->key;
  26. init_waitqueue_func_entry(&entry->wait, pollwake);
  27. entry->wait.private = pwq;
  28. add_wait_queue(wait_address, &entry->wait);
  29. }
  30. void poll_initwait(struct poll_wqueues *pwq)
  31. {
  32. init_poll_funcptr(&pwq->pt, __pollwait); //见下边,这table->pt ->qproc = qproc = _pollwait;
  33. pwq->polling_task = current;
  34. pwq->triggered = ;
  35. pwq->error = ;
  36. pwq->table = NULL;
  37. pwq->inline_index = ;
  38. }
  39. static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
  40. {
  41. pt->qproc = qproc;
  42. pt->key = ~0UL; /* all events enabled */
  43. }

上边的函数和结构体说明了初始化table变量的全过程

在初始化完成table之后: table->polling_task = current;

             table->triggered = 0;

             table->error = 0;

             table->table = NULL;

             table->inline_index = 0;

            重点 table->pt->qproc = __pollwait; 这是给table->pt->qproc挂上了一个函数__pollwait,注意之后会用到,这个函数的具体类容已经在上边分析过了

2.2.2在do_sys_poll中核心函数: do_poll(nfds, head, &table, end_time);

  1. for (;;) { //这里三层循环嵌套,虽然复杂,但是就是遍历从用户空间拷贝的fds[]数组,然后执行do_pollfd(pfd,pt) ,这里的pt就是上边分析的wait->pt
  2. struct poll_list *walk;
  3.  
  4. for (walk = list; walk != NULL; walk = walk->next) {
  5. struct pollfd * pfd, * pfd_end;
  6.  
  7. pfd = walk->entries;
  8. pfd_end = pfd + walk->len;
  9. for (; pfd != pfd_end; pfd++) {
  10. if (do_pollfd(pfd, pt)) {
  11. count++;
  12. pt = NULL;
  13. }
  14. }
  15. }
  16. pt = NULL;
  17. if (!count) {
  18. count = wait->error;
  19. if (signal_pending(current))
  20. count = -EINTR;
  21. }
  22. if (count || timed_out)
  23. break;
  24. if (end_time && !to) {
  25. expire = timespec_to_ktime(*end_time);
  26. to = &expire;
  27. }
  28.  
  29. if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
  30. timed_out = ;
  31. }

(1)发循环退出条件的分析:

      第一个条件:fds[]文件中某些文件描述符的状态发生了变化,即fd[x]->revents != 0

      第二个条件:timeout != 0 也就是超时了

  这里先分析第二个条件即timeout  !=  0的情况

  当timeout != 0时大循环退出   注意在这里边timeout看起来像是一个bool量,程序刚进入do_poll(...)时,timeout为0, 假设所有的文件描述符的状态都没有发生变化, 然后执行休眠函数poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)后,定时休眠成功后timeout变成1,然后再次循环,此时不管fds[]中文件描述符的状态怎么样,timeout都为1,都会退出大循环。

(2) 第一个退出条件分析:count != 0时

  1. static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
  2. {
  3. // 先看传入参数:pollfd :fds[]数组中的某一个注意是拷贝到内核空间的fds[]
  4. // pwait : 这个pwait就是之前初始化的table->pt
  5. // pt下边挂着一个回调函数,在这里派上用处了
  6. unsigned int mask;
  7. int fd;
  8.  
  9. mask = ;
  10. fd = pollfd->fd; // 取出文件描述符
  11. if (fd >= ) {
  12. int fput_needed;
  13. struct file * file;
  14.  
  15. file = fget_light(fd, &fput_needed);
  16. mask = POLLNVAL;
  17. if (file != NULL) {
  18. mask = DEFAULT_POLLMASK;
  19. if (file->f_op && file->f_op->poll) { // 这里就和文件操作对应的结构体中中的内容就比较相似了,
  20. //当我们驱动程序中定义了文件操作结构体,并且定义了poll函数,那么久执行驱动程序的poll函数
  21. if (pwait)
  22. pwait->key = pollfd->events |
  23. POLLERR | POLLHUP;
  24. mask = file->f_op->poll(file, pwait); // 这里执行驱动程序的poll函数, 传入参数有一个回调函数
  25. //这个回调函数就是之前初始化table提到的
  26. // 注意驱动程序poll函数执行完会返回一个值, 正是根据驱动程序中的poll函数的返回值来确定,我们的文件描述符的状态是否发生了变化
  27. }
  28. /* Mask out unneeded events. */
  29. mask &= pollfd->events | POLLERR | POLLHUP;
  30. fput_light(file, fput_needed);
  31. }
  32. }
  33. pollfd->revents = mask;
  34.  
  35. return mask;
  36. }

  这里的do_pollfd(...)函数实际上就是执行驱动函数poll,通过驱动函数来判断文件描述符的状态。

 这里我们结合按键驱动的poll函数来分析这个是怎么判断状态的

  1. unsigned int tiny6410_button_poll (struct file *file, struct poll_table_struct *wait)
  2. {
  3. unsigned int mask = ;
  4. poll_wait(file, &button_waitq, wait);
  5. if (ev_press)
  6. mask |= POLLIN;
  7. return mask;
  8. }

(1)驱动函数中的poll_wait(file, &button_waitq, wait) , 注意wait是table->pt

    poll_wait(file, &button_waitq, wait) ====》table->pt->qproc(file, &button_waitq, table->pt) ===》__pollwait(file,&button_waitq,table->pt)

这个_pollwait(.....)函数之前提过,现在在重复一遍:

  1. static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
  2. poll_table *p)
  3. {
  4. ...
  5. add_wait_queue(wait_address, &entry->wait); // add_wait_queue(&button_waitq, &entry->wait)
  6. }

  这个函数就是将等待对列挂到我们在驱动程序中定义的等待对列:button_waitq中,在这里并不会进入到休眠。

(2)if (ev_press)

    mask |= POLLIN;

   return mask;

  当有按键按下时,产生中断,在中断服务函数中会将ev_press至为1;

  当do_pollfd(...) 中调用驱动程序的poll函数时,会检测ev_press,当ev_press==1 时,说明有中断发生,然后mask |= POLLIN 并返回mask

                                当ev_press == 0, 没有中断发生, mask=0,并返回

  然后在do_pollfd(...)将根据mask设置revents;

  最后 do_poll(nfds, head, &table, end_time)中,根据返回do_pollfd()返回的mask值,来判断count是否++ ,即当有中断产生的时候,count++,

从而退出do_poll(...)的死循环。

linux-2.6.38poll机制简析(以tiny6410按键中断程序为基础)的更多相关文章

  1. Linux内核poll/select机制简析

    0 I/O多路复用机制 I/O多路复用 (I/O multiplexing),提供了同时监测若干个文件描述符是否可以执行IO操作的能力. select/poll/epoll 函数都提供了这样的机制,能 ...

  2. Linux VFS机制简析(二)

    Linux VFS机制简析(二) 接上一篇Linux VFS机制简析(一),本篇继续介绍有关Address space和address operations.file和file operations. ...

  3. Linux VFS机制简析(一)

    Linux VFS机制简析(一) 本文主要基于Linux内核文档,简单分析Linux VFS机制,以期对编写新的内核文件系统(通常是给分布式文件系统编写内核客户端)的场景有所帮助. 个人渊源 切入正文 ...

  4. Linux内存管理机制简析

    Linux内存管理机制简析 本文对Linux内存管理机制做一个简单的分析,试图让你快速理解Linux一些内存管理的概念并有效的利用一些管理方法. NUMA Linux 2.6开始支持NUMA( Non ...

  5. Linux驱动之中断处理体系结构简析

    S3C2440中的中断处理最终是通过IRQ实现的,在Linux驱动之异常处理体系结构简析已经介绍了IRQ异常的处理过程,最终分析到了一个C函数asm_do_IRQ,接下来继续分析asm_do_IRQ, ...

  6. Linux 目录结构学习与简析 Part2

    linux目录结构学习与简析 by:授客 QQ:1033553122 ---------------接Part 1-------------- #1.查看CPU信息 #cat /proc/cpuinf ...

  7. Linux 目录结构学习与简析 Part1

    linux目录结构学习与简析 by:授客 QQ:1033553122 说明: /             linux系统目录树的起点 =============== /bin      User Bi ...

  8. Linux网络性能优化方法简析

    Linux网络性能优化方法简析 2010-12-20 10:56 赵军 IBMDW 字号:T | T 性能问题永远是永恒的主题之一,而Linux在网络性能方面的优势则显而易见,这篇文章是对于Linux ...

  9. Binder机制简析(三)

    注册Service Service组件运行在Server进程中,首先要将Service注册到Service Manager中,再启动一个Binder线程池来等待和处理Client的通信请求. 注册过程 ...

随机推荐

  1. groupby+agg

    一.在处理pandas表格数据时,有时会遇到这样的问题:按照某一列聚合后,判断另一列是否出现唯一值,比如安泰杯--跨境电商比赛中,某个商人的ID如果出现在两个国家(xx和yy),则要剔除这样的数据,这 ...

  2. (转)svn执行clean up命令时报错“Previous operation has not finished; run 'cleanup' if it was interrupted”

    今天碰到了个郁闷的问题,svn执行clean up命令时报错“Previous operation has not finished; run 'cleanup' if it was interrup ...

  3. Dart 语言简述

    Dart是一种“结构化的web编程”语言,Dart编程语言在所有现代浏览器和环境中提供高性能.Dart是谷歌开发的计算机编程语言,后来被ECMA认定为标准. Dart重要的概念: 1.所有的东西都是对 ...

  4. Elasticsearch 追加更新

    追加更新,学名不知道叫啥,我这里指在历史数据的基础上,追加数据更新.比如 价格数据,我在价格字段里面保存了一个每天价格的数组,追加更新的时候在数组的后面直接add,而不是像一般情况那样覆盖. ES追加 ...

  5. 【DSP开发】德州仪器达芬奇五年之路七宗罪,嵌入式处理器架构之争决战2012

    芯片是产业链上游重要的一个环节,一颗小小的芯片具有极高的技术含量和价值,半导体行业每年都会有一个各大厂商营业额的排名,除去2009年,常年盘踞在前三名位置的分别是英特尔,三星半导体和德州仪器,英特尔凭 ...

  6. spring使用注解的方式创建bean ,将组件加入容器中

    第一种使用@Bean的方式 1.创建一个bean package com.springbean; public class Person { private String name; private ...

  7. [Cometoj#3 B]棋盘_状压dp

    棋盘 题目链接:https://cometoj.com/contest/38/problem/B?problem_id=1535 数据范围:略. 题解: 因为行数特别小,所以$dp$的时候可以状压起来 ...

  8. [转帖]关于USB3.0以及type-C

    忘记来源页面了.. 但是昨天晚上 usb 4.0 发布了 跟雷电C 安全一样的标准 双向40gb 的带宽. 而且 以后只有usb type-C的接口了. 我们办公机器上面的 typeC 同事用 ngf ...

  9. 在win7中解决Visual C++ 6.0打开文件时出现停止工作问题

    在使用Visual C++ 6.0打开文件时可能会出现下面的情况 这可能是Vc6.0和win7兼容性问题. 方法: 下载filetool即可 链接:https://pan.baidu.com/s/1X ...

  10. 剑指offer14:输入一个链表,输出该链表中倒数第k个结点。

    1. 题目描述 输入一个链表,输出该链表中倒数第k个结点. 2. 思路和方法 可以用两个指针,一个指针遍历到第k个结点的时候,第二个指针再走到第一个节点,然后两个指针的距离始终保持k-1.这样,当第一 ...