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

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资源。

字符驱动程序之——poll机制的更多相关文章

  1. 3.字符设备驱动------Poll机制

    1.poll情景描述 以之前的按键驱动为例进行说明,用阻塞的方式打开按键驱动文件/dev/buttons,应用程序使用read()函数来读取按键的键值. ) { read(fd, &key_v ...

  2. 韦东山驱动视频笔记——3.字符设备驱动程序之poll机制

    linux内核版本:linux-2.6.30.4 目的:我们在中断方式的按键应用程序中,如果没有按键按下,read就会永远在那等待,所以如果在这个程序里还想做其他事就不可能了.因此我们这次改进它,让它 ...

  3. 字符设备驱动程序之poll机制(韦大仙)

    明确为什么要引用poll机制? while(1) { read(fd,&key_val,1);//如果没有按键按下,它会一直在等待.现在想做这么一件事情:如果5s后,没有按键按下,它就会返回. ...

  4. 8.中断按键驱动程序之poll机制

    本节继续在上一节中断按键程序里改进,添加poll机制. 那么我们为什么还需要poll机制呢.之前的测试程序是这样: ) { read(fd, &key_val, ); printf(" ...

  5. 8.中断按键驱动程序之poll机制(详解)

    本节继续在上一节中断按键程序里改进,添加poll机制. 那么我们为什么还需要poll机制呢.之前的测试程序是这样: ) { read(fd, &key_val, ); printf(" ...

  6. linux字符驱动之poll机制按键驱动

    在上一节中,我们讲解了如何自动创建设备节点,实现一个中断方式的按键驱动.虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的:在实际的应用场所里,有没有那么一种情 ...

  7. 字符设备之poll机制

    poll机制作用:相当于一个定时器.时间到了还没有资源就唤醒进程. 主要用途就是:进程设置一段时间用来等待资源,假设时间到了资源还没有到来,进程就立马从睡眠状态唤醒不再等待.当然这仅仅是使用于这段时间 ...

  8. 字符设备驱动(六)按键poll机制

    title: 字符设备驱动(六)按键poll机制 tags: linux date: 2018-11-23 18:57:40 toc: true --- 字符设备驱动(六)按键poll机制 引入 在字 ...

  9. 字符设备驱动笔记——poll机制分析(七)

    poll机制分析 所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数.比如系统调用open.read.write.poll,与之对应的内核函数为:sys_open. ...

随机推荐

  1. SPI、I2C、UART三种串行总线协议的区别和SPI接口介绍(转)

    SPI.I2C.UART三种串行总线协议的区别 第一个区别当然是名字: SPI(Serial Peripheral Interface:串行外设接口); I2C(INTER IC BUS) UART( ...

  2. 如何在windows下安装JDK

    1:卸载 A:一定要删除注册表中的 HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft 项 B:最好用安装工具卸载JDK,如果没有的话就删除JDK文件夹然后用Wise Regis ...

  3. 如何解决 yum安装出现This system is not registered with RHN

    [root@localhost ~]# yum install libtool Loaded plugins: rhnplugin, security This system is not regis ...

  4. 我的Android之旅——学习、项目、心态

    本文作者: 伯乐在线 - 唐韧 .未经许可,禁止转载!欢迎分享原创到伯乐头条. 来源:唐韧 学习Android也一年多了,项目做了五六个,有大有小,有难有易.一直以来都没有好好总结过,今天周六休息,就 ...

  5. 【Android】详解Android动画

    目录结构: contents structure [+] 补间动画 使用java代码实现Alpha.Rotate.Scale.Translate动画 通过xml文件实现Alpha.Rotate.Sca ...

  6. 【Oracle】详解ORACLE中的trigger(触发器)

    本篇主要内容如下: 8.1 触发器类型 8.1.1 DML触发器 8.1.2 替代触发器 8.1.3 系统触发器 8.2 创建触发器 8.2.1 触发器触发次序 8.2.2 创建DML触发器 8.2. ...

  7. Android的API版本和名称对应关系

    Android版本名和API Level关系全称 Android的版本 Android版本名称Code name Android的API level Android 1.0 (API level 1) ...

  8. golang学习笔记 ---面向并发的内存模型

    Go语言是基于消息并发模型的集大成者,它将基于CSP模型的并发编程内置到了语言中,通过一个go关键字就可以轻易地启动一个Goroutine,与Erlang不同的是Go语言的Goroutine之间是共享 ...

  9. 转 RabbitMQ 基础概念及 Spring 的配置和使用 推荐好文 举例讲解

    从不知道到了解—RabbitMQ 基础概念及 Spring 的配置和使用 原理同上 请求地址:http://localhost:8080/home?type=3&routing_key=myO ...

  10. 关于jquery 取值,赋值常用控件的问题

    一.文本框 对于单个文本框: 1.获取文本框: $("#id").val() 2.赋值: $("#id").val("赋值"); 或 (清空 ...