1.poll情景描述

  以之前的按键驱动为例进行说明,用阻塞的方式打开按键驱动文件/dev/buttons,应用程序使用read()函数来读取按键的键值。

 while ()
{
read(fd, &key_val, );
printf("key_val = 0x%x\n", key_val);
}

  这样做的效果是:如果有按键按下了,调用该read()函数的进程,就成功读取到数据,应用程序得到继续执行;倘若没有按键按下,则要一直处于休眠状态,等待这有按键按下这样的事件发生。

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

  poll机制作用:相当于定时器,设置一定时间使进程等待资源,如果时间到了中断还处于睡眠状态(等待队列),poll机制就会唤醒中断,获取一次资源

2.poll机制内核框架

  在用户层上,使用poll或select函数时,和open、read那些函数一样,也要进入内核sys_poll函数里,接下来我们分析sys_poll函数来了解poll机制(位于/fs/select.c)

2.1 sys_poll:

  sys_poll函数会对超时参数timeout_msecs作简单处理后调用do_sys_poll

 asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
{
if (timeout_msecs > ) //参数timeout>0
    {
   timeout_jiffies = msecs_to_jiffies(timeout_msecs); //通过频率来计算timeout时间需要多少计数值
    }
    else
    {
timeout_jiffies = timeout_msecs; //如果timeout时间为0,直接赋值
}
return do_sys_poll(ufds, nfds, &timeout_jiffies); //调用do_sys_poll。
}

2.2 do_sys_poll

函数do_sys_poll会调用poll_initwait来初始化一个struct poll_wqueues table,也就是table->pt->qproc = __pollwait__pollwait将在驱动的poll函数里用到,接着调用do_poll

 int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
struct poll_wqueues table;
int fdcount, err;
unsigned int i;
struct poll_list *head;
struct poll_list *walk;
/* Allocate small arguments on the stack to save memory and be
faster - use long to make sure the buffer is aligned properly
on 64 bit archs to avoid unaligned access */
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *stack_pp = NULL; /* Do a sanity check on nfds ... */
if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
return -EINVAL; poll_initwait(&table); head = NULL;
walk = NULL;
i = nfds;
err = -ENOMEM;
while(i!=) {
struct poll_list *pp;
int num, size;
if (stack_pp == NULL)
num = N_STACK_PPS;
else
num = POLLFD_PER_PAGE;
if (num > i)
num = i;
size = sizeof(struct poll_list) + sizeof(struct pollfd)*num;
if (!stack_pp)
stack_pp = pp = (struct poll_list *)stack_pps;
else {
pp = kmalloc(size, GFP_KERNEL);
if (!pp)
goto out_fds;
}
pp->next=NULL;
pp->len = num;
if (head == NULL)
head = pp;
else
walk->next = pp; walk = pp;
if (copy_from_user(pp->entries, ufds + nfds-i,
sizeof(struct pollfd)*num)) {
err = -EFAULT;
goto out_fds;
}
i -= pp->len;
} fdcount = do_poll(nfds, head, &table, timeout); /* OK, now copy the revents fields back to user space. */
walk = head;
err = -EFAULT;
while(walk != NULL) {
struct pollfd *fds = walk->entries;
int j; for (j=; j < walk->len; j++, ufds++) {
if(__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
}
walk = walk->next;
}
err = fdcount;
if (!fdcount && signal_pending(current))
err = -EINTR;
out_fds:
walk = head;
while(walk!=NULL) {
struct poll_list *pp = walk->next;
if (walk != stack_pp)
kfree(walk);
walk = pp;
}
poll_freewait(&table);
return err;
}

do_sys_poll

 int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
  ... ...
  /*初始化一个poll_wqueues变量table*/
  poll_initwait(&table);
  ... ...
  fdcount = do_poll(nfds, head, &table, timeout);
  ... ...
}

  2.2.1 进入poll_initwait

    table ->pt-> qproc=__pollwait;     //__pollwait将在驱动的poll函数里的poll_wait函数用到

 poll_initwait(&table);

 void poll_initwait(struct poll_wqueues *pwq)
{
init_poll_funcptr(&pwq->pt, __pollwait);
pwq->error = ;
pwq->table = NULL;
pwq->inline_index = ;
} static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
pt->qproc = qproc;
} 也就是最终是 table->pt->qproc=__pollwait

  那么_pollwait做了什么

 /* Add a new entry */
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);  /*将当前进程current挂到队列里面去*/
}

   2.2.2 进入 do_poll

 static int do_poll(unsigned int nfds,  struct poll_list *list,
struct poll_wqueues *wait, s64 *timeout)
{
int count = ;
poll_table* pt = &wait->pt; /* Optimise the no-wait case */
if (!(*timeout))
pt = NULL; for (;;) {
struct poll_list *walk;
long __timeout; set_current_state(TASK_INTERRUPTIBLE);
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end; pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
/*
* Fish for events. If we found one, record it
* and kill the poll_table, so we don't
* needlessly register any other waiters after
* this. They'll get immediately deregistered
* when we break out and return.
*/
if (do_pollfd(pfd, pt)) {
count++;
pt = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table to them on the next loop iteration.
*/
pt = NULL;
if (count || !*timeout || signal_pending(current))
break;
count = wait->error;
if (count)
break; if (*timeout < ) {
/* Wait indefinitely */
__timeout = MAX_SCHEDULE_TIMEOUT;
} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-)) {
/*
* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
* a loop
*/
__timeout = MAX_SCHEDULE_TIMEOUT - ;
*timeout -= __timeout;
} else {
__timeout = *timeout;
*timeout = ;
} __timeout = schedule_timeout(__timeout);
if (*timeout >= )
*timeout += __timeout;
}
__set_current_state(TASK_RUNNING);
return count;
}

do_poll

 static int do_poll(unsigned int nfds,  struct poll_list *list, struct poll_wqueues *wait,  s64 *timeout)
{
  ……
for (;;)
   {
    ……
    set_current_state(TASK_INTERRUPTIBLE); //设置为等待队列状态
    ......
   for (; pfd != pfd_end; pfd++) { //for循环运行多个poll机制
      /*将pfd和pt参数代入我们驱动程序里注册的poll函数*/
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面并退出
              { count++;
pt = NULL; } }     ……     /*count非0(.poll函数返回非0),timeout超时计数到0,有信号在等待*/   if (count || !*timeout || signal_pending(current))
   break;
    ……
  
    /*进入休眠状态,只有当timeout超时计数到0,或者被中断唤醒才退出,*/
     __timeout = schedule_timeout(__timeout);     ……    } __set_current_state(TASK_RUNNING); //开始运行
return count; }

     2.2.2.1 do_poll中的do_pollfd函数到底是怎么将pfd和pt参数代入的?

  if (do_pollfd(pfd, pt))  -->>>mask = file->f_op->poll(file, pwait);   return mask;

 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); //f_op 即 file_operation结构体
      …… return mask;
}

  当poll进入休眠状态后,又是谁来唤醒它?这就要分析我们的驱动程序.poll函数

3.写驱动程序.poll函数,并分析.poll函数

  在上一节驱动程序里添加以下代码:

 static unsigned int forth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = ;
poll_wait(file, &button_waitq, wait); //将进程挂到队列里,不会立即休眠 if (ev_press) //中断事件标志, 1:退出休眠状态 0:进入休眠状态
mask |= POLLIN | POLLRDNORM; //普通数据可读|优先级带数据可读
return mask; //当超时,就返给应用层为0 ,被唤醒了就返回POLLIN | POLLRDNORM
} static struct file_operations forth_drv_fops = {
.owner = THIS_MODULE,
.open = forth_drv_open,
.read = forth_drv_read,
.release = forth_drv_close,
.poll = forth_drv_poll, //创建.poll函数
};

4.测试·应用程序

 #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, , );
if (ret == )
{
printf("time out\n");
}
else
{
read(fd, &key_val, );
printf("key_val = 0x%x\n", key_val);
}
} return ;
}

forthdrv_test.c

驱动程序

 #include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h> static struct class *forthdrv_class;
static struct class_device *forthdrv_class_dev; volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat; volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); /* 中断事件标志, 中断服务程序将它置1,forth_drv_read将它清0 */
static volatile int ev_press = ; struct pin_desc{
unsigned int pin;
unsigned int key_val;
}; /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val; struct pin_desc pins_desc[] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
}; /*
* 确定按键值
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval; pinval = s3c2410_gpio_getpin(pindesc->pin); if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
} ev_press = ; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */ return IRQ_RETVAL(IRQ_HANDLED);
} static int forth_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[]); return ;
} ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != )
return -EINVAL; /* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press); /* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, );
ev_press = ; return ;
} int forth_drv_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0, &pins_desc[]);
free_irq(IRQ_EINT2, &pins_desc[]);
free_irq(IRQ_EINT11, &pins_desc[]);
free_irq(IRQ_EINT19, &pins_desc[]);
return ;
} static unsigned forth_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = ;
poll_wait(file, &button_waitq, wait); // 不会立即休眠 if (ev_press)
mask |= POLLIN | POLLRDNORM; return mask;
} static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = forth_drv_open,
.read = forth_drv_read,
.release = forth_drv_close,
.poll = forth_drv_poll,
}; int major;
static int forth_drv_init(void)
{
major = register_chrdev(, "forth_drv", &sencod_drv_fops); forthdrv_class = class_create(THIS_MODULE, "forth_drv"); forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, ), NULL, "buttons"); /* /dev/buttons */ gpfcon = (volatile unsigned long *)ioremap(0x56000050, );
gpfdat = gpfcon + ; gpgcon = (volatile unsigned long *)ioremap(0x56000060, );
gpgdat = gpgcon + ; return ;
} static void forth_drv_exit(void)
{
unregister_chrdev(major, "forth_drv");
class_device_unregister(forthdrv_class_dev);
class_destroy(forthdrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return ;
} module_init(forth_drv_init); module_exit(forth_drv_exit); MODULE_LICENSE("GPL");

forth_drv.c

5.相关参数:

POLLIN相关:

 常量                说明

 POLLIN            普通或优先级带数据可读

 POLLRDNORM    normal普通数据可读

 POLLRDBAND    优先级带数据可读

 POLLPRI            Priority高优先级数据可读

 POLLOUT            普通数据可写

 POLLWRNORM    normal普通数据可写

 POLLWRBAND     band优先级带数据可写

 POLLERR            发生错误

 POLLHUP            发生挂起

 POLLNVAL            描述字不是一个打开的文件                        

POLLxxx

poll函数相关

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

  1) *fds:是一个poll描述符结构体数组(可以处理多个poll),结构体pollfd如下:

   struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 请求的事件 --其中events=POLLIN表示期待有数据读取.*/
short revents; /* returned events 返回的事件(函数返回值)*/
};

  2) nfds:表示多少个poll,如果1个,就填入1

  3) timeout:定时多少ms

6.调用流程

(1)当应用程序调用poll函数的时候,会调用到系统调用sys_poll函数,该函数最终调用do_poll函数

(2)do_poll函数中有一个死循环,在里面又会利用do_pollfd函数去调用驱动中的poll函数(fds中每个成员的字符驱动程序都会被扫描到),

(3)驱动程序中的Poll函数的工作 有两个

  ①调用poll_wait 函数,把进程挂到等待队列中去(这个是必须的,你要睡眠,必须要在一个等待队列上面,否则到哪里去唤醒你呢??),

  ②另一个是确定相关的fd是否有内容可读,如果可读,就返回1,否则返回0,如果返回1 ,do_poll函数中的count++

(4)  do_poll函数中判断三个条件(if (count ||!timeout || signal_pending(current)))

  ①如果成立就直接跳出,

  ②如果不成立,就睡眠timeout个jiffes这么长的时间(调用schedule_timeout实现睡眠),

  如果在这段时间内没有其他进程去唤醒它,那么第二次执行判断的时候就会跳出死循环。(????????不懂)

  如果在这段时间内有其他进程唤醒它,那么也可以跳出死循环返回

   (例如我们可以利用中断处理函数去唤醒它,这样的话一有数据可读,就可以让它立即返回)。

7.测试

加载驱动后可以发现超时后会打印超时,按键有输出

# insmod dri.ko
# ./test /dev/xyz0
time out
irq55
key_val = 0x3
irq55
key_val = 0x83

ps查询是休眠状态,top查询cpu也比较低

#ps
PID Uid VSZ Stat Command
S ./test /dev/xyz0 #top
PID PPID USER STAT VSZ %MEM %CPU COMMAND
S % % ./test /dev/xyz0

参考文章:

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

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

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

3.字符设备驱动------Poll机制的更多相关文章

  1. Linux高级字符设备驱动 poll方法(select多路监控原理与实现)

    1.什么是Poll方法,功能是什么? 2.Select系统调用(功能)      Select系统调用用于多路监控,当没有一个文件满足要求时,select将阻塞调用进程.      int selec ...

  2. 字符设备之poll机制

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

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

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

  4. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  5. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

  6. Linux字符设备驱动

    一.字符设备基础 字符设备 二.字符设备驱动与用户空间访问该设备的程序三者之间的关系 三.字符设备模型 1.Linux内核中,使用 struct cdev 来描述一个字符设备 动态申请(构造)cdev ...

  7. 《linux设备驱动开发详解》笔记——6字符设备驱动

    6.1 字符设备驱动结构 先看看字符设备驱动的架构: 6.1.1 cdev cdev结构体是字符设备的核心数据结构,用于描述一个字符设备,cdev定义如下: #include <linux/cd ...

  8. 浅析Linux字符设备驱动程序内核机制

    前段时间在学习linux设备驱动的时候,看了陈学松著的<深入Linux设备驱动程序内核机制>一书. 说实话.这是一本非常好的书,作者不但给出了在设备驱动程序开发过程中的所须要的知识点(如对 ...

  9. (57)Linux驱动开发之三Linux字符设备驱动

    1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是: ...

随机推荐

  1. MFC的执行过程分析

    MFC程序的执行细节剖析 MFC程序也是Windows程序,所以它应该也有一个WinMain.可是在程序中看不到它的踪影.事实上在程序进入点之前.另一个(并且仅有一个)全局对象(theApp).这就是 ...

  2. 关于es6中对象的扩展

    1.Object.is() es5比较两个值是否相等,只有两个运算符,相等(==) 和 严格相等(===),他们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0 等于 -0.es6提 ...

  3. 项目:rbac 基于角色的权限管理系统;

    - 简单示意流程图 - RBAC分析: - 基于角色的权限管理: - 权限等于用户可以访问的URL: - 通过限制URL来限制权限: - RBAC表结构组成: from django.db impor ...

  4. BZOJ 4144 Dijkstra+Kruskal+倍增LCA

    思路: 先把所有的加油站 push进按weight排序的优先队列里 对于每个不是加油站的点 找到到它的点的最短路以及它来源的加油站 如果x和y有边 且x和y加油站的来源不一样 则它可以连边 跑一边Kr ...

  5. Python(四) 分支、循环、条件与枚举

    一.什么是表达式 表达式(Expression)是运算符(operator)和操作数(operand)所构成的序列 二.表达式的优先级 三.表达式优先级练习 优先级同级 从左往右计算 1 or 2 a ...

  6. Nginx 代理以及HTTPS (二)

    一.HTTPS解析 https 加密 私钥 公钥 http 的握手 是确认网络是连通的. https 的握手 是一个加密的过程 加密图 二. 使用Nginx 部署HTTPS 服务 1.证书生成命令(h ...

  7. NET Native

    起因源自于微软在 MSDN 博客上宣布了 .NET Native 的开发者预览版..NET Native 可以将 C# 代码编译成本地机器码.有了它,开发者将不仅能享受 C# 的高生产力,而且能拥有 ...

  8. C#中如何获得两个日期之间的天数差

    DateTime d1; DateTime d2; //自己去赋值吧 int days = (d1 - d2).Days;//天数差 label1.Text = "2012-1-1 15:3 ...

  9. 温习 socket http tcp

    Socket是一个接口,可以实现TCP或者UDP的传输HTTP是协议 资料: 1.TCP/IP连接 手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接. ...

  10. PXE无人值守部署centos7.4操作系统

    1.基础环境: 镜像ISO文件名为:CentOS-7-x86_64-DVD-1804.iso 2.安装需要的软件包 yum install dhcp xinetd syslinux httpd tft ...