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资料
原文地址:https://www.cnblogs.com/wanjianjun777/p/10483957.html