一、应用程序

  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. python 时间类型

  2. Zabbix 3.4.3 使用阿里云短信服务进行报警

    目录 一.阿里云短信服务 1.1.首先开通阿里云短信服务 1.2 创建签名 1.3 创建短信模板 1.4 创建发送脚本 二.Zabbix Web 配置 2.1 增加 Media types 2.2 给 ...

  3. [BAT] SetX 永久设置环境变量

    SetX 有三种使用方式: 语法 1: SETX [/S system [/U [domain\]user [/P [password]]]] var value [/M] 语法 2: SETX [/ ...

  4. Adobe Acrobat 如何通过书签制作多级目录

    废话不多说,直接上官方文档 看不清可 右击 > 在新标签页中打开图片

  5. 31.网络协议介绍tcp/udp

    网络协议 TCP:网络中传输数据的协议,打电话 解决了我可能在网络中找不到别人(数据无法传输到) 保证数据传输的稳定性,可靠性 保证数据的安全性,完整性 对方要有响应 尝试重新发送 UDP:传输数据的 ...

  6. DS博客作业--07查找

    目录 DS博客作业--07查找 1.本周学习总结(0--2分) 1.思维导图 2.谈谈你对查找运算的认识及学习体会. 2.PTA实验作业(6分) 2.1.题目1:6-1 二叉搜索树的操作集 (30 分 ...

  7. Ajax方式上传文件报错"Uncaught TypeError: Illegal invocation"

    今天使用ajax上传文件时,出现了错误.数据传输的方式是通过定义formData完成的,提交的文件对象也设置为dom对象,但是还是不能发送请求.F12看到后台报了个错误:Uncaught TypeEr ...

  8. jsp获取Session中的值

    摘要:这个问题算是老生常谈了,我也是一段时间没弄过了,所以感觉有些忘了,就记录一下. 一.后端通过shiro在session中存储数据: // username是前台传过来的用户名 if (subje ...

  9. Photon Server初识(三) ---ORM映射改进

    一:新建一些管理类, 二.实现每个管理类 (1)NHibernateHelper.cs 类,管理数据库连接 using NHibernate; using NHibernate.Cfg; namesp ...

  10. 基于docker的mongodb安装以及PHP使用

    说明:用docker基于单服务器,虚拟多个服务器的方案, 以下是两个config服务器,两个分片,以及每个分片有一个副本的方案 这里关于给mongodb设置远程密码的问题,我采取了用两个compser ...