关于这个韦老师给了一个简单的参考文档:

poll机制分析

韦东山 2009.12.10

所有的系统调用,基本都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll。

一、内核框架:

对于系统调用poll或select,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。

  1. sys_poll函数位于fs/select.c文件中,代码如下:

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

long timeout_msecs)

{

s64 timeout_jiffies;

if (timeout_msecs > 0) {

#if HZ > 1000

/* We can only overflow if HZ > 1000 */

if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)

timeout_jiffies = -1;

else

#endif

timeout_jiffies = msecs_to_jiffies(timeout_msecs);

} else {

/* Infinite (< 0) or no (0) timeout */

timeout_jiffies = timeout_msecs;

}

return do_sys_poll(ufds, nfds, &timeout_jiffies);

}

它对超时参数稍作处理后,直接调用do_sys_poll。

  1. do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他代码:

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)

{

……

poll_initwait(&table);

……

fdcount = do_poll(nfds, head, &table, timeout);

……

}

poll_initwait函数非常简单,它初始化一个poll_wqueues变量table:

poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;

即table->pt->qproc = __pollwait,__pollwait将在驱动的poll函数里用到。

  1. do_sys_poll函数位于fs/select.c文件中,代码如下:

static int do_poll(unsigned int nfds,  struct poll_list *list,

struct poll_wqueues *wait, s64 *timeout)

{

01 ……

02   for (;;) {

03 ……

04                  if (do_pollfd(pfd, pt)) {

05                         count++;

06                         pt = NULL;

07                 }

08 ……

09      if (count || !*timeout || signal_pending(current))

10          break;

11      count = wait->error;

12      if (count)

13          break;

14

15      if (*timeout < 0) {

16          /* Wait indefinitely */

17          __timeout = MAX_SCHEDULE_TIMEOUT;

18      } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {

19          /*

20          * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in

21          * a loop

22          */

23          __timeout = MAX_SCHEDULE_TIMEOUT - 1;

24          *timeout -= __timeout;

25      } else {

26          __timeout = *timeout;

27          *timeout = 0;

28      }

29

30      __timeout = schedule_timeout(__timeout);

31      if (*timeout >= 0)

32          *timeout += __timeout;

33  }

34  __set_current_state(TASK_RUNNING);

35  return count;

36 }

分析其中的代码,可以发现,它的作用如下:

①    从02行可以知道,这是个循环,它退出的条件为:

  1. 09行的3个条件之一(count非0,超时、有信号等待处理)

count非0表示04行的do_pollfd至少有一个成功。

  1. 11、12行:发生错误

②    重点在do_pollfd函数,后面再分析

③    第30行,让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。

  1. do_pollfd函数位于fs/select.c文件中,代码如下:

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)

{

……

if (file->f_op && file->f_op->poll)

mask = file->f_op->poll(file, pwait);

……

}

可见,它就是调用我们的驱动程序里注册的poll函数。

二、驱动程序:

驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到的__pollwait函数,pollwait的代码如下:

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

{

if (p && wait_address)

p->qproc(filp, wait_address, p);

}

p->qproc就是__pollwait函数,从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列里而已。它的代码如下:

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,

poll_table *p)

{

struct poll_table_entry *entry = poll_get_entry(p);

if (!entry)

return;

get_file(filp);

entry->filp = filp;

entry->wait_address = wait_address;

init_waitqueue_entry(&entry->wait, current);

add_wait_queue(wait_address, &entry->wait);

}

执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的do_sys_poll函数的30行“__timeout = schedule_timeout(__timeout)”。

poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:chedule_timeout(__timeout),只是要休眠__time_out这段时间。

现在来总结一下poll机制:

1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数

它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;

它还判断一下设备是否就绪。

3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间

4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

5. 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。

还是那句话,现在作为入门阶段,先知道有那么回事,然后会依样画葫芦使用某些函数,最后肯定是需要阅读内核源码的,只有看了源码,才能够知道整个linux内核是怎样运作的,只是一来就分析内核相当于先学跑步再学走路一样难度太大。

那么我们为什么需要poll这种机制和怎么写程序实现这样的机制呢?(这就是我们目前最需要学习的)

之前的按键中断程序中,我们在没有按键按下的时候让系统进程进入了休眠状态,这样就会出现一个,一个任务被一直阻塞,那么阻塞处后面的代码都无法执行。这就像使用freertos时,挂起某个任务时,这个任务就停止了,可我们想要一种超时等待的机制,比如3s没有按键按下,继续执行后面的任务,这在freertos中有对应的事件函数来实现了,在linux驱动开发中,利用poll机制就可以实现这样的效果。

体现在代码上就是:

上面的ev_press在有按键按下的时候为1,即if条件满足,返回的mask代表什么呢?这就要看poll机制中的后面或上的那两个宏是干什么的了。

POLLIN
有数据可读。
POLLRDNORM
有普通数据可读。
POLLRDBAND
有优先数据可读。
POLLPRI
有紧迫数据可读。
POLLOUT
写数据不会导致阻塞。
POLLWRNORM
写普通数据不会导致阻塞。
POLLWRBAND
写优先数据不会导致阻塞。
POLLMSG
SIGPOLL 消息可用。

此外,revents域中还可能返回下列事件:
POLLER
指定的文件描述符发生错误。
POLLHUP
指定的文件描述符挂起事件。
POLLNVAL
指定的文件描述符非法。

DECLARE_WAIT_QUEUE_HEAD是一个宏,

(name) -- 生成一个等待队列头wait_queue_head_t,名字为name
-----------------------------------------------------------------
#define DECLARE_WAIT_QUEUE_HEAD (name)                            /
    wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

#define __WAIT_QUEUE_HEAD_INITIALIZER (name) {                    /
    .lock       = __SPIN_LOCK_UNLOCKED(name.lock),               /
    .task_list = { &(name).task_list, &(name).task_list } }

typedef struct __wait_queue_head wait_queue_head_t ;
struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h> /* forthdrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int ret; struct pollfd fds[]; fd = open("/dev/buttons", O_RDWR);
if (fd < )
{
printf("can't open!\n");
} fds[].fd = fd;
fds[].events = POLLIN;//有数据可读的事件,这里对应有按键按下
while ()
{
ret = poll(fds, , );//超时等待5s,这个调用回阻塞任务,可是有按键按下或者超时时间到了会继续执行后面的代码
if (ret == )
{
printf("time out\n");
}
else
{
read(fd, &key_val, );
printf("key_val = 0x%x\n", key_val);
}
} return ;
}

不按键的时候,5s打印一次超时消息,有按键的时候,立即响应,也几乎不占用cpu资源。

最新文章

  1. H5版俄罗斯方块(1)---需求分析和目标创新
  2. 编译器错误消息: CS0016: 未能写入输出文件“c:/Windows/Microsoft.NET/Framework/v2.0.50727/....dll”--“拒绝访问。
  3. Java--常用类summary
  4. Visual Studio 2013无法打开IIS Express Web的解决办法
  5. Poj 3239 Solution to the n Queens Puzzle
  6. ZendFramework 两种安装方式
  7. Oracle EBS-SQL (PO-15):检查不能审批的PO.sql
  8. C++_enum
  9. Hadoop概论
  10. java爬虫技术
  11. SLAM+语音机器人DIY系列:(二)ROS入门——3.在ubuntu16.04中安装ROS kinetic
  12. QT槽函数处理线程
  13. SSM(Spring+SpringMVC+Mybatis)框架环境搭建(整合步骤)(一)
  14. android webview内存泄露解决方法
  15. CRM 2016 一个IFrame页面,执行另一IFrame页面的函数
  16. CentOS 7 之前好好的,突然一天启动时黑屏,没有登陆界面了(配置 network-scripts 连网)
  17. maven报错集
  18. MySQL工具 Navicat
  19. poj3233 Matrix Power Series(矩阵快速幂)
  20. 如何布局您的PC站和移动站,并表达两者之间内容的对应关系

热门文章

  1. ROS学习(七)—— 理解ROS Topic
  2. Java NIO.2 —— 文件或目录移动操作
  3. 开关电源9v,1A
  4. 有关java调用方法参数传递的分析
  5. MySQL开启federated引擎实现数据库表映射
  6. 在js中嵌套java代码
  7. Gitlab Runner的使用(涵盖gitlab-page)
  8. 如何在JS数组特定索引处指定位置插入元素?
  9. Windows 以管理员运行而不提示
  10. 【Unity】2.6 游戏视图(Game)