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

1.poll情景描述

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

1 while (1)
2 {
3 read(fd, &key_val, 1);
4 printf("key_val = 0x%x
", key_val);
5 }

  这样做的效果是:如果有按键按下了,调用该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

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

2.2 do_sys_poll

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

 1 int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
 2 {
 3     struct poll_wqueues table;
 4      int fdcount, err;
 5      unsigned int i;
 6     struct poll_list *head;
 7      struct poll_list *walk;
 8     /* Allocate small arguments on the stack to save memory and be
 9        faster - use long to make sure the buffer is aligned properly
10        on 64 bit archs to avoid unaligned access */
11     long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
12     struct poll_list *stack_pp = NULL;
13 
14     /* Do a sanity check on nfds ... */
15     if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
16         return -EINVAL;
17 
18     poll_initwait(&table);
19 
20     head = NULL;
21     walk = NULL;
22     i = nfds;
23     err = -ENOMEM;
24     while(i!=0) {
25         struct poll_list *pp;
26         int num, size;
27         if (stack_pp == NULL)
28             num = N_STACK_PPS;
29         else
30             num = POLLFD_PER_PAGE;
31         if (num > i)
32             num = i;
33         size = sizeof(struct poll_list) + sizeof(struct pollfd)*num;
34         if (!stack_pp)
35             stack_pp = pp = (struct poll_list *)stack_pps;
36         else {
37             pp = kmalloc(size, GFP_KERNEL);
38             if (!pp)
39                 goto out_fds;
40         }
41         pp->next=NULL;
42         pp->len = num;
43         if (head == NULL)
44             head = pp;
45         else
46             walk->next = pp;
47 
48         walk = pp;
49         if (copy_from_user(pp->entries, ufds + nfds-i, 
50                 sizeof(struct pollfd)*num)) {
51             err = -EFAULT;
52             goto out_fds;
53         }
54         i -= pp->len;
55     }
56 
57     fdcount = do_poll(nfds, head, &table, timeout);
58 
59     /* OK, now copy the revents fields back to user space. */
60     walk = head;
61     err = -EFAULT;
62     while(walk != NULL) {
63         struct pollfd *fds = walk->entries;
64         int j;
65 
66         for (j=0; j < walk->len; j++, ufds++) {
67             if(__put_user(fds[j].revents, &ufds->revents))
68                 goto out_fds;
69         }
70         walk = walk->next;
71       }
72     err = fdcount;
73     if (!fdcount && signal_pending(current))
74         err = -EINTR;
75 out_fds:
76     walk = head;
77     while(walk!=NULL) {
78         struct poll_list *pp = walk->next;
79         if (walk != stack_pp)
80             kfree(walk);
81         walk = pp;
82     }
83     poll_freewait(&table);
84     return err;
85 }
do_sys_poll
1 int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
2 {
3   ... ...
4   /*初始化一个poll_wqueues变量table*/
5   poll_initwait(&table);
6   ... ...
7   fdcount = do_poll(nfds, head, &table, timeout);
8   ... ... 
9 }

  2.2.1 进入poll_initwait

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

 1 poll_initwait(&table);
 2 
 3 void poll_initwait(struct poll_wqueues *pwq)
 4 {
 5     init_poll_funcptr(&pwq->pt, __pollwait);
 6     pwq->error = 0;
 7     pwq->table = NULL;
 8     pwq->inline_index = 0;
 9 }
10 
11 static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
12 {
13     pt->qproc = qproc;
14 }
15 
16 也就是最终是
17 
18 table->pt->qproc=__pollwait

  那么_pollwait做了什么

 1 /* Add a new entry */
 2 static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
 3                 poll_table *p)
 4 {
 5     struct poll_table_entry *entry = poll_get_entry(p);
 6     if (!entry)
 7         return;
 8     get_file(filp);
 9     entry->filp = filp;
10     entry->wait_address = wait_address;
11     init_waitqueue_entry(&entry->wait, current);
12     add_wait_queue(wait_address, &entry->wait);  /*将当前进程current挂到队列里面去*/
13 }

   2.2.2 进入 do_poll

 1 static int do_poll(unsigned int nfds,  struct poll_list *list,
 2            struct poll_wqueues *wait, s64 *timeout)
 3 {
 4     int count = 0;
 5     poll_table* pt = &wait->pt;
 6 
 7     /* Optimise the no-wait case */
 8     if (!(*timeout))
 9         pt = NULL;
10  
11     for (;;) {
12         struct poll_list *walk;
13         long __timeout;
14 
15         set_current_state(TASK_INTERRUPTIBLE);
16         for (walk = list; walk != NULL; walk = walk->next) {
17             struct pollfd * pfd, * pfd_end;
18 
19             pfd = walk->entries;
20             pfd_end = pfd + walk->len;
21             for (; pfd != pfd_end; pfd++) {
22                 /*
23                  * Fish for events. If we found one, record it
24                  * and kill the poll_table, so we don't
25                  * needlessly register any other waiters after
26                  * this. They'll get immediately deregistered
27                  * when we break out and return.
28                  */
29                 if (do_pollfd(pfd, pt)) {
30                     count++;
31                     pt = NULL;
32                 }
33             }
34         }
35         /*
36          * All waiters have already been registered, so don't provide
37          * a poll_table to them on the next loop iteration.
38          */
39         pt = NULL;
40         if (count || !*timeout || signal_pending(current))
41             break;
42         count = wait->error;
43         if (count)
44             break;
45 
46         if (*timeout < 0) {
47             /* Wait indefinitely */
48             __timeout = MAX_SCHEDULE_TIMEOUT;
49         } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
50             /*
51              * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
52              * a loop
53              */
54             __timeout = MAX_SCHEDULE_TIMEOUT - 1;
55             *timeout -= __timeout;
56         } else {
57             __timeout = *timeout;
58             *timeout = 0;
59         }
60 
61         __timeout = schedule_timeout(__timeout);
62         if (*timeout >= 0)
63             *timeout += __timeout;
64     }
65     __set_current_state(TASK_RUNNING);
66     return count;
67 }
do_poll
 1 static int do_poll(unsigned int nfds,  struct poll_list *list, struct poll_wqueues *wait,  s64 *timeout)
 2 {
 3   ……
 4        for (;;)
 5    {
 6     ……
 7     set_current_state(TASK_INTERRUPTIBLE);       //设置为等待队列状态
 8     ......
 9        for (; pfd != pfd_end; pfd++) {             //for循环运行多个poll机制
10                    /*将pfd和pt参数代入我们驱动程序里注册的poll函数*/
11                         if (do_pollfd(pfd, pt))     //若返回非0,count++,后面并退出
12               {  count++;
13                            pt = NULL; } }
14 
15     ……
16 
17     /*count非0(.poll函数返回非0),timeout超时计数到0,有信号在等待*/
18 
19        if (count || !*timeout || signal_pending(current))
20                             break;
21     ……
22   
23     /*进入休眠状态,只有当timeout超时计数到0,或者被中断唤醒才退出,*/
24          __timeout = schedule_timeout(__timeout);
25 
26     ……
27 
28    }
29 
30 __set_current_state(TASK_RUNNING);  //开始运行
31 return count;
32 
33 }

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

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

1 static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
2 {
3       ……
4          if (file->f_op && file->f_op->poll)
5          mask = file->f_op->poll(file, pwait);  //f_op 即 file_operation结构体
6       ……
7 
8 return mask;
9 }

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

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

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

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

4.测试·应用程序

 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <stdio.h>
 5 #include <poll.h>
 6 
 7 
 8 /* forthdrvtest 
 9   */
10 int main(int argc, char **argv)
11 {
12     int fd;
13     unsigned char key_val;
14     int ret;
15 
16     struct pollfd fds[1];
17     
18     fd = open("/dev/buttons", O_RDWR);
19     if (fd < 0)
20     {
21         printf("can't open!
");
22     }
23 
24     fds[0].fd     = fd;
25     fds[0].events = POLLIN;
26     while (1)
27     {
28         ret = poll(fds, 1, 5000);
29         if (ret == 0)
30         {
31             printf("time out
");
32         }
33         else
34         {
35             read(fd, &key_val, 1);
36             printf("key_val = 0x%x
", key_val);
37         }
38     }
39     
40     return 0;
41 }
forthdrv_test.c

驱动程序

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/fs.h>
  4 #include <linux/init.h>
  5 #include <linux/delay.h>
  6 #include <linux/irq.h>
  7 #include <asm/uaccess.h>
  8 #include <asm/irq.h>
  9 #include <asm/io.h>
 10 #include <asm/arch/regs-gpio.h>
 11 #include <asm/hardware.h>
 12 #include <linux/poll.h>
 13 
 14 
 15 static struct class *forthdrv_class;
 16 static struct class_device    *forthdrv_class_dev;
 17 
 18 volatile unsigned long *gpfcon;
 19 volatile unsigned long *gpfdat;
 20 
 21 volatile unsigned long *gpgcon;
 22 volatile unsigned long *gpgdat;
 23 
 24 
 25 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
 26 
 27 /* 中断事件标志, 中断服务程序将它置1,forth_drv_read将它清0 */
 28 static volatile int ev_press = 0;
 29 
 30 
 31 struct pin_desc{
 32     unsigned int pin;
 33     unsigned int key_val;
 34 };
 35 
 36 
 37 /* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
 38 /* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
 39 static unsigned char key_val;
 40 
 41 struct pin_desc pins_desc[4] = {
 42     {S3C2410_GPF0, 0x01},
 43     {S3C2410_GPF2, 0x02},
 44     {S3C2410_GPG3, 0x03},
 45     {S3C2410_GPG11, 0x04},
 46 };
 47 
 48 
 49 /*
 50   * 确定按键值
 51   */
 52 static irqreturn_t buttons_irq(int irq, void *dev_id)
 53 {
 54     struct pin_desc * pindesc = (struct pin_desc *)dev_id;
 55     unsigned int pinval;
 56     
 57     pinval = s3c2410_gpio_getpin(pindesc->pin);
 58 
 59     if (pinval)
 60     {
 61         /* 松开 */
 62         key_val = 0x80 | pindesc->key_val;
 63     }
 64     else
 65     {
 66         /* 按下 */
 67         key_val = pindesc->key_val;
 68     }
 69 
 70     ev_press = 1;                  /* 表示中断发生了 */
 71     wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
 72 
 73     
 74     return IRQ_RETVAL(IRQ_HANDLED);
 75 }
 76 
 77 static int forth_drv_open(struct inode *inode, struct file *file)
 78 {
 79     /* 配置GPF0,2为输入引脚 */
 80     /* 配置GPG3,11为输入引脚 */
 81     request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
 82     request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
 83     request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
 84     request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);    
 85 
 86     return 0;
 87 }
 88 
 89 ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 90 {
 91     if (size != 1)
 92         return -EINVAL;
 93 
 94     /* 如果没有按键动作, 休眠 */
 95     wait_event_interruptible(button_waitq, ev_press);
 96 
 97     /* 如果有按键动作, 返回键值 */
 98     copy_to_user(buf, &key_val, 1);
 99     ev_press = 0;
100     
101     return 1;
102 }
103 
104 
105 int forth_drv_close(struct inode *inode, struct file *file)
106 {
107     free_irq(IRQ_EINT0, &pins_desc[0]);
108     free_irq(IRQ_EINT2, &pins_desc[1]);
109     free_irq(IRQ_EINT11, &pins_desc[2]);
110     free_irq(IRQ_EINT19, &pins_desc[3]);
111     return 0;
112 }
113 
114 static unsigned forth_drv_poll(struct file *file, poll_table *wait)
115 {
116     unsigned int mask = 0;
117     poll_wait(file, &button_waitq, wait); // 不会立即休眠
118 
119     if (ev_press)
120         mask |= POLLIN | POLLRDNORM;
121 
122     return mask;
123 }
124 
125 
126 
127 static struct file_operations sencod_drv_fops = {
128     .owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
129     .open    =  forth_drv_open,     
130     .read     =    forth_drv_read,       
131     .release =  forth_drv_close,
132     .poll    =  forth_drv_poll,
133 };
134 
135 
136 int major;
137 static int forth_drv_init(void)
138 {
139     major = register_chrdev(0, "forth_drv", &sencod_drv_fops);
140 
141     forthdrv_class = class_create(THIS_MODULE, "forth_drv");
142 
143     forthdrv_class_dev = class_device_create(forthdrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */
144 
145     gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
146     gpfdat = gpfcon + 1;
147 
148     gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
149     gpgdat = gpgcon + 1;
150 
151     return 0;
152 }
153 
154 static void forth_drv_exit(void)
155 {
156     unregister_chrdev(major, "forth_drv");
157     class_device_unregister(forthdrv_class_dev);
158     class_destroy(forthdrv_class);
159     iounmap(gpfcon);
160     iounmap(gpgcon);
161     return 0;
162 }
163 
164 
165 module_init(forth_drv_init);
166 
167 module_exit(forth_drv_exit);
168 
169 MODULE_LICENSE("GPL");
forth_drv.c

5.相关参数:

POLLIN相关:

 1 常量                说明
 2     
 3 POLLIN            普通或优先级带数据可读
 4 
 5 POLLRDNORM    normal普通数据可读
 6 
 7 POLLRDBAND    优先级带数据可读
 8 
 9 POLLPRI            Priority高优先级数据可读
10 
11 POLLOUT            普通数据可写
12 
13 POLLWRNORM    normal普通数据可写
14 
15 POLLWRBAND     band优先级带数据可写
16 
17 POLLERR            发生错误
18 
19 POLLHUP            发生挂起
20 
21 POLLNVAL            描述字不是一个打开的文件                        
POLLxxx

poll函数相关

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

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

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

  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)))

  ①如果成立就直接跳出,

  ②如果不成立,就睡眠timeoutjiffes这么长的时间(调用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
781 0          1312 S   ./test /dev/xyz0

#top
  PID  PPID USER     STAT   VSZ %MEM %CPU COMMAND
  781   770 0        S     1312   2%   0% ./test /dev/xyz0

参考文章:

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

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

 

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

原文地址:https://www.cnblogs.com/y4247464/p/10107552.html