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 }
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 }
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 }
驱动程序
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");
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 描述字不是一个打开的文件
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)))
①如果成立就直接跳出,
②如果不成立,就睡眠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 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
参考文章: