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

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)

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

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.调用流程

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

(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机制

上一篇:谈谈Linux字符设备驱动的实现


下一篇:bootstrap如何去除自带的样式----导航栏中的菜单实现平滑的过渡到对应的菜单区域-------动态跟换模态框中的内容