1、概念linux
一、poll情景描述编程
以按键驱动为例进行说明,用阻塞的方式打开按键驱动文件/dev/buttons,应用程序使用read()函数来读取按键的键值。这样作的效果是:若是有按键按下了,调用该read()函数的进程,就成功读取到数据,应用程序获得继续执行;假若没有按键按下,则要一直处于休眠状态,等待这有按键按下这样的事件发生。app
这种功能在一些场合是适用的,可是并不能知足咱们全部的须要,有时咱们须要一个时间节点。假若没有按键按下,那么超过多少时间以后,也要返回超时错误信息,进程可以继续获得执行,而不是没有按键按下,就永远休眠。这种例子其实还有不少,比方说两人相亲,男方等待女方给个肯定相处的信,男方不可能由于女方不给信,就永远等待下去,双方须要一个时间节点。这个时间节点,就是说超过这个时间以后,不能再等了,程序还要继续运行,须要采起其余的行动来解决问题。框架
example: 函数
单片机编程,等待IIC设备一个事件的发生,若是在容许的时间内发生了就返回1(SUCCESS),不然返回0(ERROR)。ui
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; }
此段函数代码能够这样来调用,以下:spa
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).net
,若是在限定的时间内(CPU将100000减到0),尚未成功写入,那么就将返回超时错误,页写函数也会返回写入失败的错误信息。以后,任务从新获得了运行。pwa
对于单片机这样一般单任务运行的情况,必须采起这样的措施。若是没有超时限制,那么程序将陷入死机,不能再继续运行。指针
二、linux应用程序poll的使用
对于相似的场景,linux系统使用poll功能来解决这样的问题。并且,与上述单片机等待方式不一样,linux系统再调用poll()函数时候,若是没有发生须要的事件,那么进程进入休眠。若是在限定的时间内获得须要的事件,那么成功返回,若是没有则返回超时错误信息。
可见,等待期间将进程休眠,利用事件驱动来唤醒进程,将更能提升CPU的效率。下面,以一个应用例程来讲明poll的应用程序使用方法:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <poll.h> 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,尚未按键按下,就打印出超时信息。
三、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为错误类型
2、驱动实现方法
/* 定义一个等待队列,这个等待队列其实是由中断驱动的,当中断发生时,会令挂接到这个等待队列的休眠进程唤醒 */
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功能实现的机制。
3、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教学视频