主题:linux驱动之异步通知

最近今天是2018-12-31日,即将跨年,希望在2018年最后的几个小时内对今天的东西进行一次总结。

编译系统   :ubuntu 16.04

内       核   :linux-2.6.22.6

硬件平台   :jz2240

交叉编译器:arm-linux-gcc 3.4.5

我们知道,读取一个按键状态有几种方法。

  1. 轮询方式,用一个死循环一直读按键状态。
  2. 采样中断方式,有按键就触发中断,可以读按键状态。在没有按键被按下的情况下代表没有数据可以读,除非选择非阻塞状态,否则会使用户程序处于休眠状态,当有按键中断发生,就会唤醒用户程序。
  3. 采样poll或者select机制,定时扫描按键状态,如果还没到扫描时间有按键被按下就会通知select、poll。
  4. 采样异步通知,驱动主动通知用户程序。

有人可能会问,方式2和4看样子没啥区别呀!其实有很多的区别,因为方式2在没有按键按下的情况下会让用户进程处于休眠状态。而方式4不会,其用户程序可以一直处理,当有按键中断来临时,才会跳转到具体的处理函数。方式4很像单片机的裸机程序,没有中断,处理正常的事物,有中断就去处理中断事物。

方式1,是个循环模式,太占cpu资源,是不切实际的。也有场合需要这种。在没有按键被按下,不能进行任何处理。

方式3,select/poll是查询文件状态,当在一次扫描时间之内被改变时,就返回一直状态。当在设置的时间被耗尽后任然没有改变,返回另外一种状态。根据返回状态可以选择执行不同的代码功能。

下面将详细介绍方式4,异步通知机制

先上代码,驱动代码如下

#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 <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/signal.h>

/* 这是一个按键驱动
 * 如果没有按键按下,就进行休眠
 * 为什么要休眠,因为应用程序会一直在读或者等到按键被按下来,
 * 如果说一直没有按下按键,那么久需要让用户空间进行休眠,不然会一直处于读阻塞
 */

static struct class  *g_buttons_class;
static struct class_device *g_my_buttons_classdevice;

/* -------------------------------------------------------------------------  */
/* ################## button 进行配置 ################## */

struct buttondesc
 {
 	unsigned int key_gpio; //引脚
	unsigned int irq ;	  //中断号
	unsigned int flag;	  //触发条件
	const char   *name;	  //名字
	char         keyval;//自定义键值
 };

static struct buttondesc g_buttons[4] = 
{
	{S3C2410_GPF0,  IRQ_EINT0,  IRQT_FALLING, "S2",0x11},
	{S3C2410_GPF2,  IRQ_EINT2,  IRQT_FALLING, "S3",0x12},
	{S3C2410_GPG3,  IRQ_EINT11, IRQT_FALLING, "S4",0x13},
	{S3C2410_GPG11, IRQ_EINT19, IRQT_FALLING, "S5",0x14},
};

static char g_keyval;//用于返回给用户空间

/* -------------------------------------------------------------------------  */
/* ################## 休眠相关变量 ################## */
#define      SLEEP      1
#define      NO_SLEEP   0 
static DECLARE_WAIT_QUEUE_HEAD(g_buttons_waitq);
static char g_ev_press = SLEEP;//用于标志中断休眠

/* -------------------------------------------------------------------------  */
/* ################## 原子操作相关变量 ################## */
static atomic_t g_atomic = ATOMIC_INIT(1);//定义一个原子,并且初始化为1

/* -------------------------------------------------------------------------  */
/* ################## 异步相关变量 ################## */
struct fasync_struct *fasync;


/* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
//实际的中断处理函数
static irq_handler_t buttons_handler(int irq, void *dev_id)
{
	struct buttondesc *temp_desc = (struct buttondesc *)dev_id;//根据传递参数可以获得对应中断号数组
	unsigned int temp_key  = s3c2410_gpio_getpin(temp_desc->key_gpio);//读取gpio引脚状态

	if(!temp_key)//如果有按键按下就是0
		g_keyval = 0x80 | temp_desc->keyval;
	else
		g_keyval = temp_desc->keyval;

	//访问全局变量这里要进行原子操作
	if(!atomic_dec_and_test(&g_atomic))//测试其是否为0,为 0 则返回 true,否则返回 false 
	{
		//已经打开,不能再次打开
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = NO_SLEEP;//唤醒休眠
	atomic_inc(&g_atomic);//恢复原子值
	wake_up_interruptible(&g_buttons_waitq);//唤醒挂在 g_buttons_waitq 上的进程
	kill_fasync(&fasync,SIGIO,POLL_IN);//发送异步信号 POLL_IN可读
	
	return 0;
}

//驱动读函数
static ssize_t buttons_read(struct file *fp, char __user *buf, size_t size, loff_t *fops)
{
	if(g_ev_press == SLEEP)//处于睡眠状态,此时没有数据
	{	
		if(fp->f_flags & O_NONBLOCK)//处于阻塞读取
		{
			return -1;
		}
		else
		{
			wait_event_interruptible(g_buttons_waitq, (g_ev_press == NO_SLEEP));//当g_ev_press == NO_SLEEP条件符合是 会被唤醒,继续执行
		}
	}				
	
	//执行到这里说明被中断唤醒了
	if(copy_to_user(buf,&g_keyval, 1))//返回到用户空间
		printk("copy_to_user not complete\n");
	//访问全局变量这里要进行原子操作
	if(!atomic_dec_and_test(&g_atomic))//测试其是否为0,为 0 则返回 true,否则返回 false 
	{
		//已经打开,不能再次打开
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = SLEEP;//重新进行休眠
	atomic_inc(&g_atomic);//恢复原子值
	return 0;
}


//驱动poll,供应用程序selec和poll使用
static unsigned int buttons_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;

	poll_wait(filp, &g_buttons_waitq, wait);//并不是表示阻塞,而是代表g_buttons_waitq唤醒可唤醒select

	if (g_ev_press == NO_SLEEP)/* 可读 */
		mask |= POLLIN | POLLRDNORM;//POLLIN表示可以无阻塞读

	return mask;
}

static int button_fasync(int fd, struct file *filp, int on)
{
	printk(KERN_INFO "--------- button_fasync --------- \n");
	fasync_helper(fd,filp,on,&fasync);

	return 0;
}


//驱动打开函数
static int buttons_open(struct inode *inodep, struct file *fp)
{
	int i;
	//注册中断
	for(i = 0; i < sizeof(g_buttons)/sizeof(struct buttondesc); i++)
		request_irq(g_buttons[i].irq,buttons_handler,g_buttons[i].flag,g_buttons[i].name,(void *)(&g_buttons[i]));
	//访问全局变量这里要进行原子操作
	if(!atomic_dec_and_test(&g_atomic))//测试其是否为0,为 0 则返回 true,否则返回 false 
	{
		//已经打开,不能再次打开
		atomic_inc(&g_atomic);
		return -1;
	}
	g_ev_press = SLEEP;//进行休眠
	atomic_inc(&g_atomic);//恢复原子值
	printk(KERN_INFO "open buttons success\n");
	
	return 0;
}

//关闭
static int buttons_close(struct inode *inodep, struct file *fp)
{
	int i;
	for(i = 0; i < sizeof(g_buttons)/sizeof(struct buttondesc); i++)
			free_irq(g_buttons[i].irq,&g_buttons[i]);

	button_fasync(-1,fp,0);//释放异步,将文件从异步通知列表中取消
	return 0;
}


//操作函数集
static struct file_operations g_buttons_op = 
{
	.owner   = THIS_MODULE,
	.open    = buttons_open,
	.read    = buttons_read,
	.release = buttons_close,
	.poll    = buttons_poll,
	.fasync  = button_fasync,
};

int g_buttons_major;
static int __init buttons_init(void)
{	
	g_buttons_major = register_chrdev(0, "my_buttons", &g_buttons_op);	    //注册设备 
	g_buttons_class = class_create(THIS_MODULE, "HBUT_class");	   //创建类
	g_my_buttons_classdevice = class_device_create(g_buttons_class, NULL, MKDEV(g_buttons_major, 0), NULL, "buttons");	// /dev/led %d

	return 0;
}

static void __exit buttons_exit(void)
{
	unregister_chrdev(g_buttons_major,"my_buttons");

	class_device_destroy(g_buttons_class,MKDEV(g_buttons_major, 0));	

	class_destroy(g_buttons_class);
}

MODULE_AUTHOR("大白菜");
MODULE_DESCRIPTION("buttons drive with sleep and interrupt");
MODULE_LICENSE("GPL");
module_init(buttons_init);
module_exit(buttons_exit);






用户程序代码如下:

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <fcntl.h>
/* 在用户程序中,select和poll也是与阻塞和非阻塞访问息息相关,使用非阻塞的应用程序通常
 * 会使用select和poll系统调用用于查询是否对设备进行无阻赛访问
 * select 和 poll最终都会调用驱动中的poll
 *
 * 以按键驱动为例进行说明,用阻塞的方式打开按键驱动文件/dev/buttons,
 * 应用程序使用read()函数来读取按键的键值。这样做的效果是:如果有按键按下了,调用该read()函数的进程,就成功读取到数据,应用程序得到继续执行;倘若没有按键按下,则要一直处于休眠状态,等待这有按键按下这样的事件发生。
 * 这种功能在一些场合是适用的,但是并不能满足我们所有的需要,有时我们需要一个时间节点。倘若没有按键按下,那么超过多少时间之后,也要返回超时错误信息,进程能够继续得到执行,而不是没有按键按下,就永远休眠。
 */
 
/*
	ifconfig eth0 192.168.0.11
	mount -t nfs -o nolock,vers=2 192.168.0.104:/home/book/wangruo /mnt
	cd /mnt/
*/

int g_fd;//文件描述符

void my_handler(int sign_num)
{
	int readval;//read函数读返回值
	char keyval = 0;//按键值
	static int count=0;//记录发生多少次按键
	
	readval = read(g_fd,&keyval,sizeof(char));//没有中断时,休眠
	if(readval < 0)
	{			
		printf("no data to read\n");
		exit(-1);
	}
	printf("keyval = 0x%x count = %d\n",keyval,++count);

}

int main(int argc,int **argv)
{
	int get_flag;//fcntl读取的文件状态标志

	//一般来说使用select或者poll机制时,都是使用非阻塞访问
	g_fd = open("/dev/buttons",O_RDONLY|O_NONBLOCK);
	if(g_fd < 0)
	{
		perror("open /dev/buttons");
		return -1;
	}
	
	signal(SIGIO,my_handler);//接收到信号
	fcntl(g_fd,F_SETOWN,getpid());//通过命令将设置设备文件的拥有者为本进程
	get_flag = fcntl(g_fd,F_GETFD);
	fcntl(g_fd,F_SETFD,get_flag|FASYNC);//通过命令将设置设备文件支持FASYNC,即异步机制
	
	for(;;)
	{
		sleep(1000*10);
	}

	close(g_fd);		
	return 0;
}

用户程序中,首先把通过fcntl吧fd和进程绑定,即fcntl(g_fd,F_SETOWN,getpid)

然后,获取当前的fd标志。get_flag = fcntl(g_fd,F_GETFL);

再把fd设置成支持FASYNC,即异步模式 fcntl(g_fd,F_SETFL,get_flag|FASYNC);

在此之前,需要用信号捕获函数接收SIGIO(一般来说驱动程序和用户程序之间通过SIGIO信号传输,应该也可以使用其他信号,有兴趣可以试试)。signal(SIGIO,my_handler)

代码运行如下:可以看出key进程仅仅占据cpu的0.0%,而且是处于休眠状态,但是随机按下按键都会有反应。达到目标。


具体代码就不分析了,因为驱动程序中的注释写的很清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值