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

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

引入

字符设备驱动(五)按键休眠中的App中虽然使用了休眠,但是如果Read没有返回的话会一直死等,类似阻塞,我们期望等待一段时间后自动返回,等待的时候程序依然是睡眠的,这里引入poll机制

应用程序的open/close/write/read都有对应的系统内核的sys_open/sys_close/sys_read/sys_write,同样的,poll对应了系统调用sys_poll

程序分析

sys_poll

sys_poll函数会对超时参数timeout_msecs作简单处理后调用do_sys_poll

//fs/select.c
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,
			long timeout_msecs)
{
	s64 timeout_jiffies;

	if (timeout_msecs > 0) {
#if HZ > 1000
		/* We can only overflow if HZ > 1000 */
		if (timeout_msecs / 1000 > (s64)0x7fffffffffffffffULL / (s64)HZ)
			timeout_jiffies = -1;
		else
#endif
			timeout_jiffies = msecs_to_jiffies(timeout_msecs);
	} else {
		/* Infinite (< 0) or no (0) timeout */
		timeout_jiffies = timeout_msecs;
	}

	return do_sys_poll(ufds, nfds, &timeout_jiffies);
}

do_sys_poll

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

//fs/select.c
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!=0) {
		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=0; 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;
}

poll_initwait

这个初始化函数相当重要,最后赋值table->pt->qproc=__pollwait

poll_initwait(&table);

void poll_initwait(struct poll_wqueues *pwq)
{
	init_poll_funcptr(&pwq->pt, __pollwait);
	pwq->error = 0;
	pwq->table = NULL;
	pwq->inline_index = 0;
}

static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
	pt->qproc = qproc;
}

也就是最终是

table->pt->qproc=__pollwait

do_poll

static int do_poll(unsigned int nfds,  struct poll_list *list,
		   struct poll_wqueues *wait, s64 *timeout)
{
	int count = 0;
	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 < 0) {
			/* Wait indefinitely */
			__timeout = MAX_SCHEDULE_TIMEOUT;
		} else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
			/*
			 * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
			 * a loop
			 */
			__timeout = MAX_SCHEDULE_TIMEOUT - 1;
			*timeout -= __timeout;
		} else {
			__timeout = *timeout;
			*timeout = 0;
		}

		__timeout = schedule_timeout(__timeout);
		if (*timeout >= 0)
			*timeout += __timeout;
	}
	__set_current_state(TASK_RUNNING);
	return count;
}
  • 退出条件:超时,接受到信号,do_pollfd查询到数据了(count>0)

    pt = NULL;
    if (count || !*timeout || signal_pending(current))
        break;
    count = wait->error;
    if (count)
        break;
    
  • 休眠,如富哦上述的退出条件不满则则休眠,唤醒的条件是[指定时间超时或应用程序唤醒]

    __timeout = schedule_timeout(__timeout);
    

do_pollfd

这个函数在do_poll中用来查询具体的数据,这个会调用最终的file->f_op->poll(file, pwait);,也就是具体到file结构也就是我们的驱动程序的poll

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
	unsigned int mask;
	int fd;

	mask = 0;
	fd = pollfd->fd;
	if (fd >= 0) {
		int fput_needed;
		struct file * file;

		file = fget_light(fd, &fput_needed);
		mask = POLLNVAL;
		if (file != NULL) {
			mask = DEFAULT_POLLMASK;
			if (file->f_op && file->f_op->poll)
				mask = file->f_op->poll(file, pwait);
			/* Mask out unneeded events. */
			mask &= pollfd->events | POLLERR | POLLHUP;
			fput_light(file, fput_needed);
		}
	}
	pollfd->revents = mask;

	return mask;
}

__pollwait

我们在poll_initwait中定义了table->pt->qproc=__pollwait,驱动程序的poll函数会调用poll_wait来调用这个p->qproc,也就是table中的__pollwait,这个函数只是把当前进程挂入我们驱动程序里定义的一个队列里而已,用于查询到具体的任务以退出.并不是直接休眠,真正的休眠在do_sys_poll.如果没有poll_wait具体的队列,只能等待超时退出了

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

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

小结

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

  2. 驱动程序的poll会执行__pollwait将自己的进程挂入队列,判断是否就绪

  3. 如果没有就绪,do_sys_poll中进入休眠一段小时间,如果就绪直接退出

  4. 一段小时间过后起来再查询,到步骤3,或者发现超时时间到退出,

mark

程序更改

APP

App修改使用poll查询

flag=poll 挂入查询机制,超时机制

if(flag) 超时退出
	...
else      查询到有效数据
    ...read
    	..read这里还可以休眠,应该是没必要休眠的了

poll查询可以一次查询多个驱动程序,我们只需要查询一个就好了.

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

POLLIN There is data to read.
    
int poll(struct pollfd *fds, nfds_t nfds, int timeout);  //返回0表示超时

其中events=POLLIN表示期待有数据读取.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.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", O_RDWR);
	if (fd < 0)
	{
		printf("can't open!\n");
	}
	fds[0].fd     = fd;
	fds[0].events = POLLIN;
	while (1)
	{
		ret = poll(fds, 1, 5000);
		if (ret == 0)
		{
			printf("time out\n");
		}
		else
		{
			read(fd, &key_val, 1);
			printf("key_val = 0x%x\n", key_val);
		}
	}
	return 0;
}

驱动函数

poll函数的加入

static unsigned drv_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait); // 不会立即休眠

	if (ev_press)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

测试

加载驱动后可以发现超时后会打印超时,按键有输出

# 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

尝试删除read中的等待休眠,cpu占用率依然是低的

删除中断的唤醒
//wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
删除read的休眠
//wait_event_interruptible(button_waitq, flag);

完整的驱动程序

#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>
//#include <linux/interrupt.h>



volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;


static struct class *drv_class;
static struct class_device	*drv_class_dev;

// 定义一个名为`button_waitq`的队列
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
// flag=1 means irq happened and need to update
int flag=0;

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[4] = {
	{S3C2410_GPF0, 0x01},
	{S3C2410_GPF2, 0x02},
	{S3C2410_GPG3, 0x03},
	{S3C2410_GPG11, 0x04},
};


static irqreturn_t buttons_irq(int irq, void *dev_id)
{
	printk("irq%d\r\n",irq);

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

	//wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */
	flag=1;

	return IRQ_RETVAL(IRQ_HANDLED);
}

static unsigned  drv_poll(struct file *file, poll_table *wait)
{
	unsigned int mask = 0;
	poll_wait(file, &button_waitq, wait); // 不会立即休眠

	if (flag)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

static int drv_open(struct inode *inode, struct file *file)
{
	/* 配置GPF0,2为输入引脚 */
	/* 配置GPG3,11为输入引脚 */
	request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
	request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
	request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
	request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);	
	return 0;
}

int drv_close(struct inode *inode, struct file *file)
{
	free_irq(IRQ_EINT0, &pins_desc[0]);
	free_irq(IRQ_EINT2, &pins_desc[1]);
	free_irq(IRQ_EINT11,&pins_desc[2]);
	free_irq(IRQ_EINT19,&pins_desc[3]);
	return 0;
}


static ssize_t drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	//int minor =  MINOR(file->f_dentry->d_inode->i_rdev);
	//printk("drv_write=%d\n",minor);
	return 0;
}

static ssize_t drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	if (size != 1)
	return -EINVAL;

	/* 如果没有按键动作, 休眠 */
	//wait_event_interruptible(button_waitq, flag);

	/* 如果有按键动作, 返回键值 */
	copy_to_user(buf, &key_val, 1);
	flag = 0;
	
	return 1;
}


static struct file_operations drv_fops = {
	.owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   drv_open,     
	.write	=	drv_write,
	.read	=	drv_read,	 
	.release =  drv_close, 
	.poll    =  drv_poll, 
};

static int major;
static int drv_init(void)
{
	int minor=0;
	major=register_chrdev(0, "drv", &drv_fops); // 注册, 告诉内核
	drv_class = class_create(THIS_MODULE, "drv");
	drv_class_dev = class_device_create(drv_class, NULL, MKDEV(major, 0), NULL, "xyz%d", minor);

	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;
	gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
	gpgdat = gpgcon + 1;
	return 0;
}

static void drv_exit(void)
{
	unregister_chrdev(major, "drv"); // 卸载
	class_device_unregister(drv_class_dev);
	class_destroy(drv_class);
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(drv_init);
module_exit(drv_exit);

MODULE_AUTHOR("xxx");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
MODULE_LICENSE("GPL");


posted @ 2018-11-25 17:19  zongzi10010  阅读(378)  评论(0编辑  收藏  举报