第一、二期衔接——4.5 字符驱动设备之按键驱动—poll机制

编写按键中断驱动——poll机制

  • 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3)
  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
  • 参考资料:《嵌入式Linux应用开发手册》、《嵌入式Linux应用开发手册第2版》、https://blog.csdn.net/juS3Ve/article/details/81437432
  • 开发环境:Linux 2.6.22.6 内核、arm-linux-gcc-3.4.5-glibc-2.3.6工具链


一、前言

  问题:在我们的应用程序中的,存在如下状况,在执行read()函数时,假如按键一直没有按下,则该进程会一直处于阻塞状态,此时应用不可以执行其他进程,导致程序卡死
  假设:那么我们是否可以像上一个程序一样,让进程在处于休眠,在一定的条件下才可以唤醒
  答案:是可以的,这个时候就要引用poll机制使阻塞型函数超时返回,避免一直等待造成的阻塞。

二、简述Linux中的poll机制

1、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()函数

2、do_sys_poll()函数

源码位于fs/select.c

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
/*……*/
	poll_initwait(&table);
/*……*/
	fdcount = do_poll(nfds, head, &table, timeout);
/*……*/
}

比较关键的函数有两个:

2.1 poll_initwait()函数
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;
}

poll_initwait() ---------> init_poll_funcptr(&pwq->pt, __pollwait) --------->pt->qproc = qproc
最终这个qproc就是init_poll_funcptr(&pwq->pt, __pollwait);中的__pollwait

2.1.1__pollwait()函数

源码:它只是把当前进程挂入我们驱动程序里定义的一个队列里而已

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);
}
2.2 do_poll()函数大致框架
static int do_poll(unsigned int nfds,  struct poll_list *list,
		   struct poll_wqueues *wait, s64 *timeout)
{
	/*........*/
	
	for (;;) {
		/*........*/
			for (; pfd != pfd_end; pfd++) {
				
				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;

	/*........*/

		__timeout = schedule_timeout(__timeout);
		/*........*/
	__set_current_state(TASK_RUNNING);
	return count;
}

分析其中的代码,可以发现,它的作用如下:
for(; ; )这是个循环,它退出的条件为:

  • ①.a. if (count || !*timeout || signal_pending(current))——(count非0,超时、有信号等待处理)
    count非0表示do_pollfd()至少有一个成功。
    do_pollfd()函数大致框架:
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
	mask = 0;
	fd = pollfd->fd;
	if (fd >= 0) {
		/*........*/
		if (file != NULL) {
			mask = DEFAULT_POLLMASK;
			if (file->f_op && file->f_op->poll)
				mask = file->f_op->poll(file, pwait);
			mask &= pollfd->events | POLLERR | POLLHUP;
			fput_light(file, fput_needed);
		}
	}
	pollfd->revents = mask;

	return mask;
}

可以看到:

  • mask = file->f_op->poll(file, pwait);:当在file_operation中定义了poll,则执行我们在驱动中编写的poll,驱动中poll的返回值给mask

  • mask &= pollfd->events | POLLERR | POLLHUP:mask和应用程序中poll()传入的events| POLLERR | POLLHUP进行与运算并返回。

  • ①.b. count = wait->error; if (count) break;:发生错误

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

3、总结

在这里插入图片描述

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

  2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
    它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;

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

  4. 进程被唤醒的条件有:一是上面说的“一定时间”到了二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。

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

三、代码编写

1、驱动程序修改

file_operations结构体中需要添加.poll

static struct file_operations button_drv_fops = {
	.owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  button_drv_open,     
	.read    =	button_drv_read,
	.release =  button_drv_close,
	.poll    =  buton_drv_poll,
};

编写buton_drv_poll()函数

unsigned int buton_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;	//返回给do_poll函数中的mask
}

2、完整驱动程序

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

int major;
static struct class *buttondrv_class;
static struct class_device	*buttondrv_class_dev;

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

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 
 * 键值: 松开时, 0x81, 0x82, 0x83, 0x84 
 */
static unsigned char key_val;

/* 按键信息 */
struct pin_desc{
	unsigned int pin;
	unsigned int key_val;
};

/* 存储4个按键的信息 */
struct pin_desc pins_desc[4] = {
 	{S3C2410_GPF0,  0x01},
 	{S3C2410_GPF2,  0x02},
 	{S3C2410_GPG3,  0x03},
	{S3C2410_GPG11, 0x04},
};

/* 中断注册信息 */
struct buttonirq_decs{
	unsigned int irq;
	unsigned long flags;
	const char *devname;
	void *dev_id;
};

struct buttonirq_decs buttonirqs_decs[4] = {
	{IRQ_EINT0,	 IRQT_BOTHEDGE, "S2", &pins_desc[0]},
	{IRQ_EINT2,	 IRQT_BOTHEDGE, "S3", &pins_desc[1]},
	{IRQ_EINT11, IRQT_BOTHEDGE, "S4", &pins_desc[2]},
	{IRQ_EINT19, IRQT_BOTHEDGE, "S5", &pins_desc[3]},
};

/* 生成一个等待队列的队头,名字为button_waitq */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,button_drv_read将它清0 */
static volatile int ev_press = 0;

/* 中断服务函数 */
static irqreturn_t button_irq(int irq, void *dev_id)
{
	struct pin_desc *pindesc = (struct pin_desc *)dev_id;
	unsigned int pinval;

	pinval = s3c2410_gpio_getpin(pindesc->pin);	//读取IO口电平

	if(pinval){
		/* 松开 */
		key_val = 0x80 | pindesc->key_val;
	}else{
		/* 按下 */
		key_val = pindesc->key_val;
	}

	ev_press = 1;	//发生中断
	wake_up_interruptible(&button_waitq);   // 唤醒休眠的进程
	
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* open驱动函数 */
static int button_drv_open(struct inode *inode, struct file *file)
{
	unsigned int i;
	int err;

	/* 注册中断 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++){
		err = request_irq(buttonirqs_decs[i].irq, button_irq, buttonirqs_decs[i].flags,
					buttonirqs_decs[i].devname, buttonirqs_decs[i].dev_id);
		if(err){
			printk("func button_drv_open err: request_irq num:%d\n", i);
			break;
		}
	}

	/* 出现错误,释放已经注册的中断 */
	if(err){
        i--;
        for (; i >= 0; i--)
            free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);
        return -EBUSY;
    }	
	return 0;
}

/* read驱动函数 */
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
	
	unsigned long ret = 0;
	int err;

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

	if (err){
			printk("func button_drv_read() err: wait_event_interruptible\n");
		return err;
	}
	

	/* 如果有按键动作, 返回键值 */
	ret = copy_to_user(buf, &key_val, 1);
	ev_press = 0;	//清中断标志
	
	if(ret < 0){
		printk("func button_drv_read() err: copy_to_user\n");
		return -EFAULT;
	}
	
 	return sizeof(key_val);
}

/* close驱动函数 */
int button_drv_close (struct inode *inode, struct file *file)
{
	int i;

	/* 取消中断 */
	for(i = 0; i < (sizeof(buttonirqs_decs)/sizeof(buttonirqs_decs[0])); i++)
		free_irq(buttonirqs_decs[i].irq, buttonirqs_decs[i].dev_id);

	return 0;
}

unsigned int buton_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;	//返回给do_poll函数中的mask
}

static struct file_operations button_drv_fops = {
	.owner   =  THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open    =  button_drv_open,     
	.read    =	button_drv_read,
	.release =  button_drv_close,
	.poll    =  buton_drv_poll,
};

/* 入口函数 
 * 执行”insmod button_drv.ko”命令时就会调用这个函数
 */
static int button_drv_init(void)
{
	//注册一个字符设备,名字为button_drv
	major = register_chrdev(0, "button_drv", &button_drv_fops);	
	
	//创建一个类,名字为buttond_rv(/class/button_drv)
	buttondrv_class = class_create(THIS_MODULE, "button_drv");	
	
	//创建一个设备节点,名为button(/dev/button)
	buttondrv_class_dev = class_device_create(buttondrv_class, NULL, MKDEV(major, 0), NULL, "button"); 

	/* 映射物理地址 */
	gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
	gpfdat = gpfcon + 1;

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

/* 出口函数 
 * 执行”rmmod button_drv.ko”命令时就会调用这个函数
 */
static void button_drv_exit(void)
{
	unregister_chrdev(major, "button_drv"); // 卸载字符

	class_device_unregister(buttondrv_class_dev);	//删除设备节点
	class_destroy(buttondrv_class);	//销毁类
	
	/* 取消映射 */
	iounmap(gpfcon);
	iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");

3、测试程序修改

要使用poll机制,需要在测试程序调用poll()函数,函数原型如下:

int poll(struct pollfd fd[], nfds_t nfds, int timeout);

   /* 第一个参数fd:一个结构数组,struct pollfd结构如下:
  	struct pollfd{
 			int fd;              //文件描述符
  		short events;    //请求的事件
  		short revents;   //返回的事件
  		};
    第二个参数nfds:要监视的描述符的数目
    第三个参数timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间
  */

4、完整测试程序代码

#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;
	int ret = 0;
	unsigned char key_val;
	int count = 0;

	struct pollfd fds[1];
	
	fd = open("/dev/button", O_RDWR);
	if (fd < 0){
		printf("can't open!\n");
		return -1;
	}
	
	fds[0].fd     = fd;
	fds[0].events = POLLIN;
	
	while (1)
	{
		ret = poll(fds, 1, 5000);
		if (ret == 0)
		{
			printf("time out\n");
		}else{
			ret = read(fd, &key_val, 1);
			if(ret < 0)
				printf(" func read() err\n");
			else
				printf("key_val = 0x%x\n", key_val);
		}
	}	
	return ret;
}

有关于测试程序如何与内核合作,完成poll机制的调用,可以参考这个篇博客https://www.cnblogs.com/andyfly/p/9480434.html

四、实际运行

在这里插入图片描述
可以看到,当我们运行测试程序时,不按下按键,测试测试程序不会一直等待read()函数有返回值,而是休眠5秒,在这5秒内,你可以采用中断唤醒进程,或称5秒过后,唤醒进程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX设备驱动第三版_ 前言 第一设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值