栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

linux驱动-中断上下部、阻塞非阻塞IO、异步通知

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

linux驱动-中断上下部、阻塞非阻塞IO、异步通知

本文将主要就中断的上下部机制,阻塞IO和非阻塞IO,异步通知这几个点列出一些实验代码,对照代码的注释,可以帮助我们更好的理解这些概念和知识。本文所贴的代码全部经过上板子编译运行,各位看官可放心食用(>.<)。
中断上下部机制:一般中断上半部只是对中断进行登记,将工作推迟到下半部中去执行,中断上半部要求时间尽量快,不可引起阻塞和休眠,也不可以嵌套中断。在中断上半部发起对下半部的调度,下半部会在合适的时机执行(与内核的调度有关),下半部机制有软中断、tasklet和工作队列。
阻塞IO和非阻塞IO、异步通知:主要是为了解决一个问题,应用层如何知道我们的按键已经按下?如果应用层采用轮询的方式去读取按键值,那么可能会造成该进程的CPU使用率特别高,为了解决这个问题,需要在按键没有按下时,将进程休眠,按键按下时,再将进程唤醒去读取按键值,这就会运用到阻塞IO和非阻塞IO的相关知识;或者采用异步通知的方式,当驱动检测到按键按下时,主动发送信号给应用进程,应用在信号处理函数中读取按键值。

一、按键中断与tasklet下半部机制

这个实验我们通过按键中断进入中断上半部,在上半部里调度tasklet,下半部tasklet的任务是打印一行printk(“my_tasklet_func,data is %ldn”,data),将初始化时传递的参数data打印出来,注意下半部tasklet运行在中断上下文中,不能休眠和阻塞。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define IRQNAME "key_irq"
#define GPIONUM 66               //按键的GPIO号,需要根据实际情况修改

struct module_dev{	
	struct tasklet_struct my_tasklet;   //定义一个tasklet
};

struct module_dev mydevice;


//下半部tasklet执行的函数,打印初始化时传递的参数data
void my_tasklet_func(unsigned long data)
{
	printk("my_tasklet_func,data is %ldn",data);
}
//上半部服务函数,发起tasklet的调度。
static irqreturn_t irqhanler(int irq, void *dev_id)
{  
	printk("key irq catched !!n");
	tasklet_schedule(&mydevice.my_tasklet);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	unsigned long data=1;
	printk("moudule init!n");
	
	//tasklet初始化,指定tasklet执行的函数,以及传递给函数的参数为data
	tasklet_init(&mydevice.my_tasklet, my_tasklet_func, data);
	//按键的gpio资源申请,设置为输入
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	//按键的gpio转换为中断号
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发,传递设备结构体参数mydevice
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exit!n");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
二、按键中断和工作队列

本实验通过按键中断进入中断上半部,在中断上半部里调度延时工作队列,添加到工作队列的工作只是简单的打印printk(“my delay work func here!n”)而已,由于工作队列运行休眠和阻塞,我们的延时工作队列在上半部调度queue_delayed_work的时候,指定了一个延时时间,所以可以观察到按键按下1s后才打印printk(“my delay work func here!n”)。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define IRQNAME "key_irq" 
#define GPIONUM 66                      

struct module_dev{
	struct workqueue_struct *task;          //定义一个工作队列
	struct delayed_work my_work;          //定义一个延时工作
};

struct module_dev mydevice;	

//下半部delay_work执行的函数
void my_work_func_t(struct work_struct *work)
{
	printk("my delay work func here!n");
}
//上半部服务函数,发起delay_work的调度,delay_work延时一秒执行
static irqreturn_t irqhanler(int irq, void *dev_id)
{
	printk("key irq catched !!n");
	queue_delayed_work(mydevice.task, &mydevice.my_work, msecs_to_jiffies(1000));
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	printk("moudule initn");
	
	//创建工作队列task,该函数会为cpu创建内核线程
	mydevice.task = create_singlethread_workqueue("test");
	if(IS_ERR(mydevice.task))
    {
         printk("Failed to create workqueuen");
         mydevice.task = NULL;
    }
	//delay_work初始化,指定delay_work要执行的函数
	INIT_DELAYED_WORK(&mydevice.my_work, my_work_func_t);
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exitn");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
}

module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
三、阻塞IO和等待队列

本实验应用层通过while循环读取按键值,在按键没有按下的时候,驱动层在file_operations 里面的read函数里将进程添加到等待队列头进行休眠,如果按键按下,在按键中断里使用wake_up()将进程唤醒,唤醒后再去读取并返回按键值给应用层。(CPU占用率低)。
应用层代码:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"

int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	int data = 0;
	char *filename="/dev/TEST_MODULE";
	
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("Can't open file %srn", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, &data, sizeof(data));
		if (ret < 0) { 
			
		} else {		
			if (data)	
				printf("key value = %#Xrn", data);
		}
		mdelay(100);
	}
	close(fd);
	return ret;
}

驱动层代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE_CNT		1			    
#define DEVICE_NAME		"TEST_MODULE"			

#define IRQNAME "key_irq" 
#define GPIONUM 66                      

struct module_dev{
	dev_t devid;			  	  
	struct cdev cdev;		 	  
	struct class *class;	  		  
	struct device *device;	  	  
	int major;				   
	int minor;	
	atomic_t key_flag;
	wait_queue_head_t r_wait;    //定义一个用于读数据的等待队列头  
	struct device_node *nd;     
};

struct module_dev mydevice;	

//上半部中断服务函数,按键按下时,设置key_flag,并wake_up()唤醒进程             
static irqreturn_t irqhanler(int irq, void *dev_id)
{
	struct module_dev *dev =&mydevice;
	printk("key irq catched !!n");
	atomic_set(&dev->key_flag,1);           
	wake_up_interruptible(&dev->r_wait);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int module_dev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &mydevice;	       
	return 0;
}

static ssize_t module_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret;
	struct module_dev *dev = (struct module_dev *)filp->private_data;
	
	DECLARE_WAITQUEUE(wait, current);         //将当前进程定义为一个等待队列
	if(atomic_read(&dev->key_flag) == 0)      //读flag,flag==0表示按键未按下,不可读数据,需要将进程阻塞休眠。
	{
		add_wait_queue(&dev->r_wait, &wait);  //将等待队列添加到等待队列头
		__set_current_state(TASK_INTERRUPTIBLE); //设置任务状态为TASK_INTERRUPTIBLE,可由wake_up()和信号唤醒,TASK_UNINTERRUPTIBLE只能由wake_up()唤醒
		schedule();		//通知内核重新调度,进程将休眠;直到调用wake_up()或进程接受到信号,唤醒后将从这里开始执行					     
		if(signal_pending(current))	{	//唤醒后首先检查一下是不是信号唤醒的	
			printk("signal wake upn");
			ret = -ERESTARTSYS;
			goto wait_error;
		}
		__set_current_state(TASK_RUNNING);  //唤醒后设置当前进程为运行状态    
	    remove_wait_queue(&dev->r_wait, &wait);   //唤醒后从等待队列头中移除等待队列,解除阻塞
	}
	
				
	//唤醒后读取按键值并返回
if(atomic_read(&dev->key_flag) == 1)
	{
		ret = copy_to_user(buf, &dev->key_flag, sizeof(&dev->key_flag));
		atomic_set(&dev->key_flag,0);                   
	}
	return 0;
	
	wait_error:
	set_current_state(TASK_RUNNING);		
	remove_wait_queue(&dev->r_wait, &wait);	
	return ret;
}

static struct file_operations mydevice_fops = {
	.owner = THIS_MODULE,
	.open = module_dev_open,
	.read = module_dev_read,
};

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	printk("moudule initn");
	if (mydevice.major) {
		mydevice.devid = MKDEV(mydevice.major, 0);
		register_chrdev_region(mydevice.devid, DEVICE_CNT, DEVICE_NAME);
	} else {
		alloc_chrdev_region(&mydevice.devid, 0, DEVICE_CNT, DEVICE_NAME);
		mydevice.major = MAJOR(mydevice.devid);
		mydevice.minor = MINOR(mydevice.devid);
	}
	cdev_init(&mydevice.cdev, &mydevice_fops);
	cdev_add(&mydevice.cdev, mydevice.devid, DEVICE_CNT);

	mydevice.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(mydevice.class)) {
		return PTR_ERR(mydevice.class);
	}
	mydevice.device = device_create(mydevice.class, NULL, mydevice.devid, NULL, DEVICE_NAME);
	if (IS_ERR(mydevice.device)) {
		return PTR_ERR(mydevice.device);
	}
	
	//初始化等待队列头
	init_waitqueue_head(&mydevice.r_wait);  
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exitn");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
	cdev_del(&mydevice.cdev);
	unregister_chrdev_region(mydevice.devid, DEVICE_CNT);
	device_destroy(mydevice.class, mydevice.devid);
	class_destroy(mydevice.class);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");

驱动层也可以使用wait_event_interruptible
驱动层也可以使用wait_event_interruptible()将进程添加到等待队列头进行休眠,但是唤醒进程的条件除了调用wake_up(),还要满足condition的条件为真。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE_CNT		1			    
#define DEVICE_NAME		"TEST_MODULE"			

#define IRQNAME "key_irq" 
#define GPIONUM 66                      

struct module_dev{
	dev_t devid;			  	  
	struct cdev cdev;		 	  
	struct class *class;	  		  
	struct device *device;	  	  
	int major;				   
	int minor;	
	atomic_t key_flag;
	wait_queue_head_t r_wait;    //定义一个用于读数据的等待队列头  
	struct device_node *nd;     
};

struct module_dev mydevice;	

//上半部中断服务函数,按键按下时,设置key_flag,并wake_up()唤醒进程             
static irqreturn_t irqhanler(int irq, void *dev_id)
{
	struct module_dev *dev =&mydevice;
	printk("key irq catched !!n");
//这里如果不设置key_flag,光是wake_up()是唤醒不了进程的。
	atomic_set(&dev->key_flag,1);           
	wake_up_interruptible(&dev->r_wait);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int module_dev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &mydevice;	       
	return 0;
}

static ssize_t module_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret;
	struct module_dev *dev = (struct module_dev *)filp->private_data;
	
	//加入等待队列,等待被唤醒,唤醒的条件是按键中断里调用了wake_up()并且key_flag==1
 	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->key_flag)); 
	
	if(signal_pending(current))	{	//唤醒后首先检查一下是不是信号唤醒的	
			printk("signal wake upn");
			ret = -ERESTARTSYS;
			return ret;
		}
	//唤醒后读取按键值并返回用户层
	if(atomic_read(&dev->key_flag) == 1)
	{
		ret = copy_to_user(buf, &dev->key_flag, sizeof(&dev->key_flag));
		atomic_set(&dev->key_flag,0);                   
	}
	return 0;	
}

static struct file_operations mydevice_fops = {
	.owner = THIS_MODULE,
	.open = module_dev_open,
	.read = module_dev_read,
};

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	printk("moudule initn");
	if (mydevice.major) {
		mydevice.devid = MKDEV(mydevice.major, 0);
		register_chrdev_region(mydevice.devid, DEVICE_CNT, DEVICE_NAME);
	} else {
		alloc_chrdev_region(&mydevice.devid, 0, DEVICE_CNT, DEVICE_NAME);
		mydevice.major = MAJOR(mydevice.devid);
		mydevice.minor = MINOR(mydevice.devid);
	}
	cdev_init(&mydevice.cdev, &mydevice_fops);
	cdev_add(&mydevice.cdev, mydevice.devid, DEVICE_CNT);

	mydevice.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(mydevice.class)) {
		return PTR_ERR(mydevice.class);
	}
	mydevice.device = device_create(mydevice.class, NULL, mydevice.devid, NULL, DEVICE_NAME);
	if (IS_ERR(mydevice.device)) {
		return PTR_ERR(mydevice.device);
	}
	
	//初始化等待队列头
	init_waitqueue_head(&mydevice.r_wait);  
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exitn");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
	cdev_del(&mydevice.cdev);
	unregister_chrdev_region(mydevice.devid, DEVICE_CNT);
	device_destroy(mydevice.class, mydevice.devid);
	class_destroy(mydevice.class);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
四、非阻塞IO和poll机制(select机制)

select机制和poll机制类似,只不过poll机制能够监测的文件描述符数量更多,这里只介绍poll机制。应用通过非阻塞的方式打开驱动文件fd,并通过poll函数的方式监听文件描述符fd,当监听的文件描述符已就绪,就开始read读取。应用的poll函数会对应执行驱动层的poll函数,驱动中的poll函数会将进程添加到等待队列头进行休眠,一旦满足某个条件conditon(这个条件通过按键中断触发),就返回一个mask通知应用层可以读取了,这个时候应用层就监听到了文件描述符的变化,就可以读了(CPU占用率低)。
应用层代码:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"



int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	
	struct pollfd fds;
	unsigned char data;

	fd = open("/dev/TEST_MODULE", O_RDWR | O_NONBLOCK);	//以非阻塞的方式打开文件
	if (fd < 0) {
		printf("Can't open file %srn", "/dev/TEST_MODULE");
		return -1;
	}

#if 1
	
	
	
	fds.fd = fd; 
	fds.events = POLLIN;
		
	while (1) {
		
		ret = poll(&fds, 1, 500);
		if (ret) {	 //监听的文件描述符已就绪,这时可读      
			ret = read(fd, &data, sizeof(data));
			if(ret < 0) {
				printf("read errn");
			} else {
				printf("key value = %d rn", data);
			} 	
		} else if (ret == 0) { 	
			//自定义超时处理 
		} else if (ret < 0) {	
			//自定义错误处理 
		}
	}
	close(fd);
	return ret;
}

驱动层代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE_CNT		1			    
#define DEVICE_NAME		"TEST_MODULE"			

#define IRQNAME "key_irq" 
#define GPIONUM 66                      

struct module_dev{
	dev_t devid;			  	  
	struct cdev cdev;		 	  
	struct class *class;	  		  
	struct device *device;	  	  
	int major;				   
	int minor;	
	atomic_t key_flag;
	wait_queue_head_t r_wait;     
	struct device_node *nd;     
};

struct module_dev mydevice;	

//上半部中断服务函数,按键按下时,设置key_flag==1,这个时候poll函数将进程唤醒,并返回文件描述符准备就绪            
static irqreturn_t irqhanler(int irq, void *dev_id)
{
	struct module_dev *dev =&mydevice;
	printk("key irq catched !!n");
//wake_up()注释掉,只需要key_flag==1唤醒
	atomic_set(&dev->key_flag,1);           
	//wake_up_interruptible(&dev->r_wait);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int module_dev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &mydevice;	       
	return 0;
}

static ssize_t module_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret;
	struct module_dev *dev = (struct module_dev *)filp->private_data;

	
if (filp->f_flags & O_NONBLOCK)	{   //非阻塞访问
	if(atomic_read(&dev->key_flag) == 0)	//没有按键按下,不可读取,返回-EAGAIN 
		return -EAGAIN;
}
else {  //这条分支是将进程阻塞加入等待队列头的方式,与前面一致
 	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->key_flag)); 
	if(signal_pending(current))	{		
			printk("signal wake upn");
			ret = -ERESTARTSYS;
			return ret;
		}	
}
	
	if(atomic_read(&dev->key_flag) == 1)
	{
		ret = copy_to_user(buf, &dev->key_flag, sizeof(&dev->key_flag));
		atomic_set(&dev->key_flag,0);                   
	}
	return 0;	
}

unsigned int module_dev_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	struct module_dev *dev = (struct module_dev *)filp->private_data;
	
	//添加进程到等待队列头进行休眠
	poll_wait(filp, &dev->r_wait, wait);	
	//按键按下时,key_flag==1,满足该条件后return mask,告知应用层准备就绪了,可以读了
	if(atomic_read(&dev->key_flag)) {		
		mask = POLLIN | POLLRDNORM;			
	}
	return mask;
}

static struct file_operations mydevice_fops = {
	.owner = THIS_MODULE,
	.open = module_dev_open,
	.read = module_dev_read,
	.poll = module_dev_poll,
};

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	printk("moudule initn");
	if (mydevice.major) {
		mydevice.devid = MKDEV(mydevice.major, 0);
		register_chrdev_region(mydevice.devid, DEVICE_CNT, DEVICE_NAME);
	} else {
		alloc_chrdev_region(&mydevice.devid, 0, DEVICE_CNT, DEVICE_NAME);
		mydevice.major = MAJOR(mydevice.devid);
		mydevice.minor = MINOR(mydevice.devid);
	}
	cdev_init(&mydevice.cdev, &mydevice_fops);
	cdev_add(&mydevice.cdev, mydevice.devid, DEVICE_CNT);

	mydevice.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(mydevice.class)) {
		return PTR_ERR(mydevice.class);
	}
	mydevice.device = device_create(mydevice.class, NULL, mydevice.devid, NULL, DEVICE_NAME);
	if (IS_ERR(mydevice.device)) {
		return PTR_ERR(mydevice.device);
	}
	
	//初始化等待队列头
	init_waitqueue_head(&mydevice.r_wait);  
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exitn");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
	cdev_del(&mydevice.cdev);
	unregister_chrdev_region(mydevice.devid, DEVICE_CNT);
	device_destroy(mydevice.class, mydevice.devid);
	class_destroy(mydevice.class);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
五、异步通知与信号处理

应用层通过signal()去捕捉内核发送的SIGIO信号,并在信号处理函数里对数据进行读取。应用层需要做几件事:1、signal捕捉内核发送的SIGIO信号。2、fcntl(fd, F_SETOWN, getpid())告诉内核哪个进程的pid去接受内核的信号。3、追加驱动设备文件状态标志FASYNC,表示启用异步通知功能,设置此项,驱动程序 file_operations 操作集中的 fasync 函数就会执行。4、创建信号处理函数,在处理函数里读取数据。

驱动层里面fasync函数对应执行,fasync_helper会初始化异步结构体fasync_struct,在按键中断的服务程序里,kill_fasync会发送信号,应用层捕捉到信号后,就进入信号处理函数读取数据。
应用层代码:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
#include "signal.h"



static int fd = 0;	

//信号处理函数里面读取按键值并打印
static void sigio_signal_func(int signum)
{
	int err = 0;
	unsigned int keyvalue = 0;

	err = read(fd, &keyvalue, sizeof(keyvalue));
	if(err < 0) {
		
	} else {
		printf("sigio signal! key value=%drn", keyvalue);
	}
}

int main(int argc, char *argv[])
{
	int flags = 0;
	fd = open("/dev/TEST_MODULE", O_RDWR);
	if (fd < 0) {
		printf("Can't open file %srn", "/dev/TEST_MODULE");
		return -1;
	}

	signal(SIGIO, sigio_signal_func);   //进程捕捉内核发送的信号为SIGIO,信号处理函数为sigio_signal_func
	
	fcntl(fd, F_SETOWN, getpid());		//getpid获取当前进程的pid号,F_SETOWN表示设置接受SIGIO和SIGURG的pid,发送方为fd。也就是说告诉内核哪个进程接受信号。
	flags = fcntl(fd, F_GETFL);			//获取打开设备文件的文件状态标志
	fcntl(fd, F_SETFL, flags | FASYNC);	//添加 FASYNC 状态标志,启用异步通知功能,设置此项,驱动程序 file_operations 操作集中的 fasync 函数就会执行	。
	while(1) {
		sleep(2);                       //进程睡眠等待内核信号
	}
	close(fd);
	return 0;
}

驱动层代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define DEVICE_CNT		1			    
#define DEVICE_NAME		"TEST_MODULE"			

#define IRQNAME "key_irq" 
#define GPIONUM 66                      

struct module_dev{
	dev_t devid;			  	  
	struct cdev cdev;		 	  
	struct class *class;	  		  
	struct device *device;	  	  
	int major;				   
	int minor;	
	atomic_t key_flag;
	wait_queue_head_t r_wait;
	struct fasync_struct *async_queue;	//异步相关结构体fasync_struct
	struct device_node *nd;     
};



struct module_dev mydevice;	

//上半部中断服务函数,按键按下时,kill_fasync发送信号SIGIO          
static irqreturn_t irqhanler(int irq, void *dev_id)
{
	struct module_dev *dev =&mydevice;
	printk("key irq catched !!n");
	atomic_set(&dev->key_flag,1);    
//kill_fasync用于发送指定信号,第三个参数可读时设置为POLL_IN,可写时设置为POLL_OUT。
	kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int module_dev_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &mydevice;	       
	return 0;
}

static ssize_t module_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int ret;
	struct module_dev *dev = (struct module_dev *)filp->private_data;

	
if (filp->f_flags & O_NONBLOCK)	{   //非阻塞访问
	if(atomic_read(&dev->key_flag) == 0)	//没有按键按下,不可读取,返回-EAGAIN 
		return -EAGAIN;
}
else {  //这条分支是将进程阻塞加入等待队列头的方式,与前面一致
 	ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->key_flag)); 
	if(signal_pending(current))	{		
			printk("signal wake upn");
			ret = -ERESTARTSYS;
			return ret;
		}	
}
	
	
	if(atomic_read(&dev->key_flag) == 1)
	{
		ret = copy_to_user(buf, &dev->key_flag, sizeof(&dev->key_flag));
		atomic_set(&dev->key_flag,0);                   
	}
	return 0;	
	
	
}

unsigned int module_dev_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	struct module_dev *dev = (struct module_dev *)filp->private_data;
	
	//添加进程到等待队列头进行休眠
	poll_wait(filp, &dev->r_wait, wait);	
	//按键按下时,key_flag==1,满足该条件后return mask,告知应用层准备就绪了,可以读了
	if(atomic_read(&dev->key_flag)) {		
		mask = POLLIN | POLLRDNORM;			
	}
	return mask;
}


static int module_dev_fasync(int fd, struct file *filp, int on)
{
	struct module_dev *dev = (struct module_dev *)filp->private_data;
	//fasync_helper 函数来初始化定义的 fasync_struct 结构体指针
	return fasync_helper(fd, filp, on, &dev->async_queue);
}

static int module_dev_release(struct inode *inode, struct file *filp)
{
	
	return module_dev_fasync(-1, filp, 0);
}

static struct file_operations mydevice_fops = {
	.owner = THIS_MODULE,
	.open = module_dev_open,
	.read = module_dev_read,
	.poll = module_dev_poll,
	.fasync = module_dev_fasync,
	.release = module_dev_release,
};

static int __init moudule_test_init(void)
{
	int ret;
	int irqnum;
	printk("moudule initn");
	if (mydevice.major) {
		mydevice.devid = MKDEV(mydevice.major, 0);
		register_chrdev_region(mydevice.devid, DEVICE_CNT, DEVICE_NAME);
	} else {
		alloc_chrdev_region(&mydevice.devid, 0, DEVICE_CNT, DEVICE_NAME);
		mydevice.major = MAJOR(mydevice.devid);
		mydevice.minor = MINOR(mydevice.devid);
	}
	cdev_init(&mydevice.cdev, &mydevice_fops);
	cdev_add(&mydevice.cdev, mydevice.devid, DEVICE_CNT);

	mydevice.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(mydevice.class)) {
		return PTR_ERR(mydevice.class);
	}
	mydevice.device = device_create(mydevice.class, NULL, mydevice.devid, NULL, DEVICE_NAME);
	if (IS_ERR(mydevice.device)) {
		return PTR_ERR(mydevice.device);
	}
	
	
	init_waitqueue_head(&mydevice.r_wait);  
	gpio_request(GPIONUM,"key");
	gpio_direction_input(GPIONUM);
	irqnum=gpio_to_irq(GPIONUM);
	//中断申请,与中断服务函数关联,设置触发模式上升沿触发
	ret = request_irq(irqnum, irqhanler, IRQF_TRIGGER_RISING, IRQNAME, &mydevice);
	if(ret<0)
	{
		printk("irq request failed!n");
	}
	return 0;
}

static void __exit moudule_test_exit(void)
{
	printk("moudule exitn");
	gpio_free(GPIONUM);
	free_irq(gpio_to_irq(GPIONUM), &mydevice);
	cdev_del(&mydevice.cdev);
	unregister_chrdev_region(mydevice.devid, DEVICE_CNT);
	device_destroy(mydevice.class, mydevice.devid);
	class_destroy(mydevice.class);
}
module_init(moudule_test_init);
module_exit(moudule_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Q");
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/599507.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号