• poll机制


    更新记录

    version status description date author
    V1.0 C Create Document 2019.1.10 John Wan

    status:
    C―― Create,
    A—— Add,
    M—— Modify,
    D—— Delete。

    注:内核版本 3.0.15

    1、poll概述

      所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll,那么在驱动程序中,也有与之对应的函数处于驱动程序的数据结构中,例如 file_operation 中的 open、read、write、poll。

      那么poll机制的作用是什么呢?以按键事件为例:

      1、查询方法:一直在查询,不断去查询是否有事件发生,响应的快慢取决于查询的频率,如何对实时性要求高,那么查询频率要非常快,整个过程都占用CPU资源,消耗CPU资源非常大。

      2、中断方式:当有事件发生时,才跳转到相应事件去处理,CPU占用时间少。

      3、poll机制:在中断的基础上,添加超时机制,对事件有两种处理方式:1)事件发生,立即跳转处理;2)超时,立即跳转处理。这样的好处是,即使在固定的时间之内没有事件发生,也能得到反馈。

    2、poll机制的内核框架

      对于系统调用 pollselect,它们对应的内核函数是 sys_poll()。分析 sys_poll() 即可理解 poll机制。

    2.1 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.2 sys_poll()函数

      位于 linux/syscalls.h文件中的:

    asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,	long timeout);
    

      位于 fs/select.c文件中的:

    SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long, timeout_msecs)
    {
    	struct timespec end_time, *to = NULL;
    	int ret;
    
    	if (timeout_msecs >= 0) {
    		to = &end_time;
    		poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
    			NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
    	}
    
    	ret = do_sys_poll(ufds, nfds, to);
    
    ......
    	return ret;
    }
    

    注:从 poll/select 到 sys_poll,再到 SYSCALL_DEFINE3 之间是如何实现的,暂时还不清楚。

      作用:1)对超时参数稍作处理;2)调用 do_sys_poll()函数。

    2.3 do_sys_poll()函数

      位于fs/select.c文件中:

    int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
    		struct timespec *end_time)
    {
    	struct poll_wqueues table;
    ......
    	poll_initwait(&table);
    	fdcount = do_poll(nfds, head, &table, end_time);
    ......
    }
    

      作用:

      1) poll_initwait()函数非常简单,它初始化一个 poll_wqueues变量 table”poll_initwait(&table); > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;”,即table->pt->qproc = __pollwait__pollwait 将在驱动的 poll 函数里用到。

      2)调用 do_poll()函数。

    2.4 do_poll()函数

      位于 fs/select.c 文件中:

    static int do_poll(unsigned int nfds,  struct poll_list *list,
    		   struct poll_wqueues *wait, struct timespec *end_time)
    {
    ......
    	for (;;) {
    		struct poll_list *walk;
    
    		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) {
    			count = wait->error;
    			if (signal_pending(current))
    				count = -EINTR;
    		}
    		if (count || timed_out)
    			break;
    
    		/*
    		 * If this is the first loop and we have a timeout
    		 * given, then we convert to ktime_t and set the to
    		 * pointer to the expiry value.
    		 */
    		if (end_time && !to) {
    			expire = timespec_to_ktime(*end_time);
    			to = &expire;
    		}
    
    		if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
    			timed_out = 1;
    	}
    	return count;
    }
    

      分析代码,作用如下:

    ① 这是个循环,它的退出条件为:

    ​ a. 有信号等待处理;

    ​ b. count非0,超时。

    ② 调用do_pollfd()函数,这是重点,后面分析;

    poll_schedule_timeout() 让本进程休眠一段时间,注意:应用程序执行 poll() 调用后,如果 ① ② 的条件不满足,进程就会进入休眠。那么谁来唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒--记住这点,这就是为什么驱动的 poll() 里要调用 poll_wait的原因,后面分析。

    2.5 do_pollfd()函数

      位于 fs/select.c 文件中:

    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);
    ......
    }
    

      作用:这就是调用在驱动程序中注册的 poll函数(file_operation中的poll)。

    3、poll机制的驱动程序

      驱动程序里与 poll 相关的地方有两处:

    1)是构造 file_operation 结构时,要定义自己的 poll 函数;

    2)是通过 poll_wait 来调用上面说到的 __pollwait 函数。

      位于 linux/poll.h 文件中:

    static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    {
    	if (p && wait_address)
    		p->qproc(filp, wait_address, p);
    }
    

      p->qproc 就是 __pollwait() 函数(前面的poll_initwait()函数进行的),从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列中,函数位于 fs/select.c 中代码如下:

    static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
    				poll_table *p)
    {
    	struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    	struct poll_table_entry *entry = poll_get_entry(pwq);
    	if (!entry)
    		return;
    	get_file(filp);
    	entry->filp = filp;
    	entry->wait_address = wait_address;
    	entry->key = p->key;
    	init_waitqueue_func_entry(&entry->wait, pollwake);
    	entry->wait.private = pwq;
    	add_wait_queue(wait_address, &entry->wait);
    }
    

      执行到驱动程序的 poll_wait() 函数时,进程并没有休眠,我们的驱动程序里实现的 poll() 函数是不会引起休眠的。让进程进入休眠的是前面分析 do_sys_poll() 函数中的 poll_schedule_timeout()

      poll_wait() 只是把本进程挂入某个队列,应用程序调用顺序 "poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己写的poll函数后,再调用 poll_schedule_timeout() 进入休眠"。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait 的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用 poll_wait,我们的程序也有机会被唤醒:poll_schedule_timeout() ,只是要休眠设定的那段时间。

    4、总结

      poll 机制:

    1. poll > sys_poll > do_sys_poll > poll_initwaitpoll_initwait() 函数注册一下回调函数 __pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。

    2. 接下来执行do_pollfd()中的file->f_op->poll,即我们驱动程序里自己实现的poll函数。

      它会调用poll_wait()把自己挂入某个队列,这个队列驱动程序自己来定义,指明应该挂哪个队列。

      它还判断一下设备是否就绪。

    3. 如何设备未就绪,do_sys_poll里会让进程休眠一定时间。

    4. 进程被唤醒的条件有两个:1)设定的时间到了,即超时;2)被驱动程序唤醒。驱动程序发现条件就绪时,就把"某个队列"上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

    5. 如果驱动程序没有去唤醒进程,那么poll_schedule_timeout 超时后,会重复2、3的动作,直到应用程序的 poll 调用传入的时间到达。

    poll机制流程

      1. 驱动模块的加载就已经向 file_operation进行了成员.poll的注册。

      2. 应用程序调用 poll()函数,对应的调用内核的sys_poll()函数。然后执行内核当中的框架。

      3. 内核框架的do_poll()函数中会调用驱动中的.poll成员,而驱动中的poll函数会通过调用poll_wait()函数来讲进程挂载到指定的队列用,poll_wait()的实质就是调用在内核中注册的__pollwait()函数。在驱动程序的poll函数中判断设备是否就绪,并将返回值返回到内核的do_poll()函数中。

      4. do_poll()将进行判断处理,将返回值给到应用层的poll()函数。

    5、案例

      驱动代码:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <linux/delay.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/platform_device.h>
    /*Linux中申请GPIO的头文件*/
    #include <linux/gpio.h>
    /*三星平台的GPIO配置函数头文件*/
    /*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
    #include <plat/gpio-cfg.h>
    #include <mach/gpio.h>
    /*三星平台4412平台,GPIO宏定义头文件*/
    #include <mach/gpio-exynos4.h>
    
    #include <linux/irq.h>
    #include <linux/wait.h>
    #include <linux/interrupt.h>
    #include <linux/sched.h>
    #include <linux/poll.h>
    
    
    #define DEVICE_NAME "buttons_irq"
    
    static struct class *buttons_irq_class;
    static struct device *buttons_irq_class_dev;
    
    
    struct pin_desc {
    	unsigned int pin;
    	unsigned int key_val;
    };
    
    struct pin_desc pins_desc[5] = {
    	{EXYNOS4_GPX1(1), 1},
    	{EXYNOS4_GPX1(2), 2},
    	{EXYNOS4_GPX3(3), 3},
    	{EXYNOS4_GPX2(1), 4},
    	{EXYNOS4_GPX2(0), 5},
    };
    
    static unsigned char key_val = 0;
    
    static DECLARE_WAIT_QUEUE_HEAD(button_waitq);	//声明一个队列
    
    /* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
    static volatile int ev_press = 0;
    
    
    static irqreturn_t buttons_irq(int irq, void *dev_id)
    {
    	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
    	unsigned int pinval;
    
    	pinval = gpio_get_value(pindesc->pin);
    
    	if (pinval)
    		key_val = 0x80 | pindesc->key_val;
    	else
    		key_val = pindesc->key_val;
    
    	printk(DEVICE_NAME " key press1");
    
    	ev_press = 1; 	//中断发生
    	wake_up_interruptible(&button_waitq);	/* 唤醒休眠的进程 */
      
    
    	printk(DEVICE_NAME " key press2");
    
    	return IRQ_RETVAL(IRQ_HANDLED);
    }
    
    
    static int buttons_irq_open(struct inode *pinode, struct file *pfile)
    {
    	/* 配置各按键引脚为外部中断 */
    	request_irq(IRQ_EINT(9), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S1_Home", &pins_desc[0]);
    	request_irq(IRQ_EINT(10), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S2_Back", &pins_desc[1]);
    	request_irq(IRQ_EINT(27), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S3_Sleep", &pins_desc[2]);
    	request_irq(IRQ_EINT(17), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S4_Vol+", &pins_desc[3]);
    	request_irq(IRQ_EINT(16), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S5_Vol-", &pins_desc[4]);
    
    
    	printk(DEVICE_NAME " I'm open!
    ");
    
    	return 0;
    }
    
    static ssize_t buttons_irq_read(struct file *pfile, char __user *pbuf,
    									size_t count, loff_t *ploff)
    {
    	if (count != 1)
    		return -EINVAL;
    
    	//如果没有按键动作,休眠
    	wait_event_interruptible(button_waitq, ev_press);
    
    	//如果有按键动作,返回键值
    	copy_to_user(pbuf, &key_val, 1);
    	ev_press = 0;
    
    	printk(DEVICE_NAME " I'm read key_val %d!
    ", key_val);
    
    	return 1;
    }
    
    
    static int buttons_irq_release(struct inode *pinode, struct file *pfile)
    {
    	free_irq(IRQ_EINT(9), &pins_desc[0]);
    	free_irq(IRQ_EINT(10), &pins_desc[1]);
    	free_irq(IRQ_EINT(27), &pins_desc[2]);
    	free_irq(IRQ_EINT(17), &pins_desc[3]);
    	free_irq(IRQ_EINT(16), &pins_desc[4]);
    
    	printk(DEVICE_NAME " I'm release
    ");
    
    	return 0;
    }
    
    static unsigned int buttons_irq_poll(struct file *pfile, struct poll_table_struct *ptable)
    {
    	unsigned int mask = 0;
    	poll_wait(pfile, &button_waitq, ptable); // 不会立即休眠
    
    	if (ev_press)
    		mask |= POLLIN | POLLRDNORM;
    
    	return mask;
    }
    
    
    static struct file_operations buttons_irq_fpos = {
    	.owner = THIS_MODULE,
    	.open = buttons_irq_open,
    	.read = buttons_irq_read,
    	.release = buttons_irq_release,
    	.poll = buttons_irq_poll,
    };
    
    int major;
    static int __init buttons_irq_init(void)
    {
    	/*注册主设备号*/
    	major = register_chrdev(0, "buttons_irq", &buttons_irq_fpos);
    
    	/*注册次设备号*/
    	buttons_irq_class = class_create(THIS_MODULE, "buttons_irq");
    	if (IS_ERR(buttons_irq_class))
    		return PTR_ERR(buttons_irq_class);
    
    	buttons_irq_class_dev = device_create(buttons_irq_class, NULL,
    								MKDEV(major, 0), NULL, "buttons_irq_minor");
    
    	printk(DEVICE_NAME " initialized
    ");
    
    	return 0;
    }
    
    static void __exit buttons_irq_exit(void)
    {
    	unregister_chrdev(major, "buttons_irq");
    
    	device_unregister(buttons_irq_class_dev);
    
    	class_destroy(buttons_irq_class);
    
    	//return 0;
    }
    
    module_init(buttons_irq_init);
    module_exit(buttons_irq_exit);
    
    MODULE_LICENSE("GPL");
    

      测试代码:

    #include <stdio.h>
    
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/ioctl.h>
    #include <poll.h>
    
    
    int main(int argc, char **argv)
    {
    	int fd;
    	unsigned char key_val;
    	int ret;
    
    	struct pollfd fds[1];
    
    	fd = open("/dev/buttons_irq_minor", O_RDWR);
    	if (fd < 0)
    		printf("can't open is!
    ");
    
    	fds[0].fd = fd;
    	fds[0].events = POLLIN;
    
    	while (1) {
    
    		ret = poll(fds, 1, 5000);
    		if (ret == 0) {
    			printf("time out
    ");
    		} else {
    			read(fd, &key_val, sizeof(key_val));
    			printf("key_val = 0x%x
    ", key_val);
    		}
    	}
    
    	return 0;
    }
    

    测试:

    [root@iTOP-4412]# insmod buttons_poll.ko
    [ 1936.603014] buttons_irq initialized
    [root@iTOP-4412]# lsmod
    buttons_poll 2655 0 - Live 0xbf004000
    [root@iTOP-4412]# ./buttons_poll_test
    [ 1948.384684] buttons_irq I'm open!
    time out
    time out
    [ 1961.125175] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 1!
    key_val = 0x1
    [ 1961.336777] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 129!
    key_val = 0x81
    [ 1963.807090] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 2!
    key_val = 0x2
    [ 1964.015423] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 130!
    key_val = 0x82
    [ 1965.903509] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 3!
    key_val = 0x3
    [ 1966.075094] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 131!
    key_val = 0x83
    [ 1967.500492] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 4!
    key_val = 0x4
    [ 1967.677299] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 132!
    key_val = 0x84
    [ 1969.124986] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 5!
    key_val = 0x5
    [ 1969.299227] buttons_irq key press1buttons_irq key press2buttons_irq I'm read key_val 133!
    key_val = 0x85
    time out
    

      超过设定时间没有检测到触发,也会唤醒进程打印 "time out"。

    参考

    1. 韦东山第一期视频,第十二课
    2. 迅为iTop4412资料
  • 相关阅读:
    17-电话号码字母的组合
    16-最接近的三数之和
    牛客网上的java面经,JVM
    15-三数之和
    mybatis 懒加载不生效
    @ControllerAdvice
    异常
    @ExceptionHandler处理异常
    spring aop annotation
    return 与 system.exit(0) 区别
  • 原文地址:https://www.cnblogs.com/wanjianjun777/p/10483957.html
Copyright © 2020-2023  润新知