博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux驱动编写之poll机制
阅读量:6297 次
发布时间:2019-06-22

本文共 4818 字,大约阅读时间需要 16 分钟。

一、概念

1、poll情景描述

      以按键驱动为例进行说明,用阻塞的方式打开按键驱动文件/dev/buttons,应用程序使用read()函数来读取按键的键值。这样做的效果是:如果有按键按下了,调用该read()函数的进程,就成功读取到数据,应用程序得到继续执行;倘若没有按键按下,则要一直处于休眠状态,等待这有按键按下这样的事件发生。

      这种功能在一些场合是适用的,但是并不能满足我们所有的需要,有时我们需要一个时间节点。倘若没有按键按下,那么超过多少时间之后,也要返回超时错误信息,进程能够继续得到执行,而不是没有按键按下,就永远休眠。这种例子其实还有很多,比方说两人相亲,男方等待女方给个确定相处的信,男方不可能因为女方不给信,就永远等待下去,双方需要一个时间节点。这个时间节点,就是说超过这个时间之后,不能再等了,程序还要继续运行,需要采取其他的行动来解决问题。

example:  

      单片机编程,等待IIC设备一个事件的发生,如果在允许的时间内发生了就返回1(SUCCESS),否则返回0(ERROR)。

uint8_t I2C_WaitForEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT,int32_t delay){        while(!I2C_CheckEvent(I2Cx, I2C_EVENT) && (delay-- > 0));        if(delay < 0){        return 0;    }        return 1;}

      此段函数代码可以这样来调用,如下:

int8_t I2C_EE_PageWrite(u8* pBuffer, u16 WriteAddr, u8 NumByteToWrite){      .............      if(I2C_WaitForEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED, 100000) != 1){            return -1;      }      ............}

      这个例子是STM32单片机写i2cflash--AT24C02,可见上述的页写函数调用的等待字节传输完成函数(I2C_EVENT_MASTER_BYTE_TRANSMITTED)

,如果在限定的时间内(CPU将100000减到0),还没有成功写入,那么就将返回超时错误,页写函数也会返回写入失败的错误信息。之后,任务重新得到了运行。

      对于单片机这样通常单任务运行的状况,必须采取这样的措施。如果没有超时限制,那么程序将陷入死机,不能再继续运行。

2、linux应用程序poll的使用

     对于类似的场景,linux系统使用poll功能来解决这样的问题。而且,与上述单片机等待方式不同,linux系统再调用poll()函数时候,如果没有发生需要的事件,那么进程进入休眠。如果在限定的时间内得到需要的事件,那么成功返回,如果没有则返回超时错误信息。

     可见,等待期间将进程休眠,利用事件驱动来唤醒进程,将更能提高CPU的效率。下面,以一个应用例程来说明poll的应用程序使用方法:

#include 
#include
#include
#include
#include
int main(int argc, char **argv){ int i; int ret; int fd; unsigned char keys_val; struct pollfd fds[1]; fd = open("/dev/buttons", 0); // 打开设备 if (fd < 0) { printf("Can't open /dev/buttons\n"); return -1; } fds[0].fd = fd; fds[0].events = POLLIN; while (1) { ret = poll(fds,1, 5000); if(ret == 0) { printf("time out!\n"); } else { read(fd, &keys_val, sizeof(keys_val)); printf("keys_val = 0x%x\n",keys_val); } } close(fd); return 0; }

       例程实现的功能是这样的:用poll()函数监测按键按下的事件,如果按下了就将键值打印出来;如果超过5S,还没有按键按下,就打印出超时信息。

3、poll()函数

      函数原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

     输入参数

fds         可以传递多个结构体,也就是说可以监测多个驱动设备所产生的事件,只要有一个产生了请求事件,就能立即返回

    struct pollfd {

          int fd;                  /* 文件描述符 */
          short events;        /* 请求的事件类型,监视驱动文件的事件掩码 */
          short revents;       /* 驱动文件实际返回的事件 */
    } ;

nfds       监测驱动文件的个数

timeout  超时时间,单位为ms 

     事件类型events 可以为下列值:

POLLIN           有数据可读

POLLRDNORM 有普通数据可读,等效与POLLIN
POLLPRI         有紧迫数据可读
POLLOUT        写数据不会导致阻塞
POLLER          指定的文件描述符发生错误
POLLHUP        指定的文件描述符挂起事件
POLLNVAL      无效的请求,打不开指定的文件描述符

     返回值

有事件发生        返回revents域不为0的文件描述符个数(也就是说事件发生,或者错误报告)

超时                返回0;

失败            返回-1,并设置errno为错误类型

二、驱动实现方法

/* 定义一个等待队列,这个等待队列实际上是由中断驱动的,当中断发生时,会令挂接到这个等待队列的休眠进程唤醒 */ static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static unsigned drivers_poll(struct file *file, poll_table *wait){    unsigned int mask = 0;    poll_wait(file, &button_waitq, wait); /* 将进程挂接到button_waitq等待队列下 */ /* 根据实际情况,标记事件类型 */    if (ev_press)        mask |= POLLIN | POLLRDNORM; /* 如果mask为0,那么证明没有请求事件发生;如果非零说明有时间发生 */    return mask;}

      上述代码展示了一个poll()函数功能,具体对应的底层驱动实现细节。利用这样的框架,我们可以写出类似驱动的poll功能。但是,这个框架很难理解,不知道为什么这样编写?为此,我们需要了解linux系统poll功能实现的机制。

三、linux内核poll实现机制

     从应用程序调用poll()函数开始,一直到调用drivers_poll函数,期间的过程很复杂,捡主要的内容列出来:

app: poll      |drv:sys_poll      |      — do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, struct timespec * end_time)        |           - poll_initwait(&table);  >  实际效果:令函数指针 table.pt.qproc = __pollwait,这个函数指针最终会传递给poll_wait函数调用中的wait->qproc        |        - do_poll(nfds, head, &table, end_time);        |         _ for ( ; ; )          {                for (; pfd != pfd_end; pfd++) {    /* 可以监测多个驱动设备所产生的事件 */                    if (do_pollfd(pfd, pt)) {                        |                       _  mask = file->f_op->poll(file, pwait); > 实际效果:执行我们写的drivers_poll(file,pwait)
|                                  _  poll_wait(file, &button_waitq, wait); > 实际效果:执行__pollwait(file, &button_waitq, wait),也就是将                                                                             进程挂接到button_waitq等待队列下 |        —  mask赋值 ; return mask; /* 返回事件类型 */
pollfd->revents = mask;    /* 将实际事件类型返回 */                          count++; pt = NULL;                      }                  } if (count || timed_out) /* 如果有事件发生,或者超时,则跳出poll */ break; if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 如果没有事件发生,那么陷入休眠状态 */                   timed_out = 1;           }

      由此可见,我们的drivers_poll()函数,是系统在执行sys_poll()过程中的一个调用,调用的目的是“将进程挂接到等待队列下”和“返回事件类型mask”。当已经发生了请求事件,那么通过标记mask非0,if (do_pollfd(pfd, pt))判断为真,令count++,从而可以直接令poll()函数成功返回。如果还没有发生请求的事件,那么mask被标记为0,进程将通过函数poll_schedule_timeout()陷入休眠状态。一旦发生了请求的事件,因为之前已经将进程挂接到等待队列下,所以进程将被唤醒,重新执行drivers_poll(),而显然此时能够成功返回。

备注:分析的源码版本为linux-2.6.30.4。

 

参考资料:韦东山linux教学视频           

             

转载地址:http://eslta.baihongyu.com/

你可能感兴趣的文章
Android的Framework分析---4硬件抽象HAL
查看>>
JS中字符串的相关操作
查看>>
Android版本介绍
查看>>
我的友情链接
查看>>
Exchange Server 2013之CAS服务器NLB负载均衡
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
数据导出之sqlserver-导出脚本
查看>>
【CentOS 7MySQL常用操作2】,连接MySQL#180112
查看>>
以后从这里安家了
查看>>
用Spotlight on windows 实时监控Windows服务器性能
查看>>
3大主流前端框架对比
查看>>
北京汽车、机动车年检(审)规定 时间、地点和资料查询
查看>>
LINUX下查看CPU、主板、硬盘、内存,网卡信息
查看>>
Oracle的连接与会话
查看>>
Ecshop显示商品销售数量
查看>>
Discuz x3论坛使用CDN后获取真实IP的解决方法
查看>>
input框必填星号*垂直居中变大设置
查看>>
烂泥:SQL Server 2005数据库备份与恢复
查看>>
Picasso:开启大前端的未来
查看>>