驱动中断编程——IO模型

	上一章讲到怎么申请和触发一次中断并且和应用层进行数据的交互。但是我们看到这个中断就跟假的中断一样。

申请中断,并设置为双边沿的触发方式。触发了之后我们的中断处理程序就会执行,把值放到我们的数据缓冲区。
那我应用层并不知道你什么按啊,我得一直去读你的数据缓冲区的值才能
读到的传过来的数据啊。这就很不好,不仅仅让应用程序什么都做不了,而且还一直占着CPU的资源。

那么为了处理这种事件我们在驱动中引入了文件IO模型。
文件IO模型有四种:
1)阻塞
2)非阻塞
3)多路复用
4)异步信号

一、阻塞模型
在这里插入图片描述
阻塞模型的概念:就是假设应用程序在通过文件操作结构体的读数据的时候我数据缓冲区没有数据,我就不一直读了,我就进行休眠。进入中止态。并将(READ这个进程)放入到一个等待队列里面阻塞,等待唤醒信号。
当我们触发一次中断之后,我们在中断处理程序里面加入唤醒程序。这样我们就可以实现阻塞模型,并大量减小CPU的损耗。
1)实现步骤。
1,等待队列头
wait_queue_head_t
并初始化等待队列头:init_waitqueue_head(wait_queue_head_t *q);
2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(wait_queue_head_t wq, condition) // 内部会构建一个等待队列项/节点wait_queue_t
(内部会实现三个步骤1,将当前进程加入到等待队列头中
将当前进程状态设置成TASK_INTERRUPTIBLE set_current_state(TASK_INTERRUPTIBLE)
让出调度–休眠schedule(void))
参数1: 等待队列头
参数2: 条件,如果是为假,就会等待,如果为真,就不会等待
可以用一标志位,来表示是否有数据
3,在一个合适的时候(有数据),会将进程唤醒
wake_up_interruptible(wait_queue_head_t *q
用法:
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;

二、非阻塞模型(NONBLOCK)
在这里插入图片描述
非阻塞模型的概念:非阻塞模型就是我应用层要读的时候我就读,如果没有数据我就走,并且返回一个EAGEN,让用户判读是否哟啊再读一次。,如果有数据我就拿了数据就走。
实现:
1、在应用层程序open 状态设置成非阻塞模式,如果不设置默认是阻塞模式(open函数,的权限后面还可以加上模式的选择)。
open("/dev/key0", O_RDWR|O_NONBLOCK);
2、在驱动程序中去判断,当前的模式 ,如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
return -EAGAIN;
filp->f_flags & O_NONBLOCK :这句的意思是文件流指针中的F_FLAGS位中如果==O_NONBLOCK 那么这个等式就不为0。
!key_dev->key_state:如果KEY的状态位等于0(按键被按下)那么再取反后就是等式成立。
两个语句相&&,表示如果flag是非阻塞模式,并且状态位没有数据我就直接返回,一个-EAGEN给用户。
如果不是就继续执行下面的程序。
在这里插入图片描述
这两段代码连接有一些精巧之处。
第一句:如果为非阻塞且没有数据那么就直接返回。
如果为非阻塞但是有数据,那么IF语句不成立(是非阻塞状态)。则继续执行下面的语句。但是有数据的时候阻塞和我非阻塞状态都是一样的。
如果不为非阻塞状态那么直接执行阻塞状态下的程序。
也就是说,不管你要达到什么状态。这两条语句都可以顺利执行并达到模拟想要的状态。

三、多路复用 select/poll/epoll
在这里插入图片描述多路复用: 用于当你在单进程单线程中(就是一个程序且不创建线程)中需要处理很多来自不同信号源的的信息的时候,而且没有优先级。而且你还不知道信号什么时候来。这时候就可以采用多路复用。相当于我用了一个闹铃。当我哪一个闹铃响了我就去干哪一件事。在这里就是监控我们的一些设备,当那个设备有信号的时候我就去处理涉笔传过来的信号。
我们采用POLL这个函数:但是多路复用有三个函数SELECT 和 EPOLL要区分这三个函数有什么区别。
步骤:
1、在应用程序中打开要交互信号的一些设备,并产生相应的文件描述符
2、用POLL来监控这些文件
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1: 表示多个文件描述符集合
struct pollfd { 描述的是文件描述符的结构体
int fd; //文件描述符
short events; //希望监控fd的什么事件:读(POLLIN),写(POLLOUT),出错(POLLERR),
short revents; //结果描述,表示当前的fd是否有读(POLLIN),写(POLLOUT),出错(POLLERR),
//用于判断,是内核自动赋值
};
参数2:被监控到fd的个数
参数3: 监控的时间:
正: 表示监控多少ms
负数: 无限的时间去监控
0: 等待0ms,类似于非阻赛
返回值: 负数:出错
大于0,表示fd中有数据
等于0: 时间到超时
3、利用 short revents来判断是否产生了这些操作(信号是否发生)。
4、在驱动程序中,添加POLL的文件操作结构体
5、实现poll对应的函数
// 调用poll_wait,将当前到等待队列注册系统中
poll_wait(filp, &key_dev->wq_head, pts); // 调用poll_wait,将当前到等待队列注册系统中
if(!key_dev->key_state)
mask = 0; // 1,当没有数据到时候返回一个0
if(key_dev->key_state) // 2,有数据返回一个POLLIN
mask |= POLLIN
——————————————————应用层代码

	
#define KEY_ENTER 28

/*  这个结构体在poll.h的头文件中有,直接用,我这里拿出来为了更好看
truct pollfd {
	int fd; 		//文件描述符
	short events; 	//检测事件  Pollin检测是否输入     pollout pollerr
	short revents;	//返回检测事件
};
*/

struct key_event{
	int code; // 表示按键的类型:  home, esc, Q,W,E,R,T, ENTER
	int value; // 表示按下还是抬起 1 / 0
};
static char in_buf[64];
int main(int argc , char *argv[]){
	int fd0;
	int ret;
	
	struct key_event event;
	struct pollfd pfd[2];
	//打开设备文件
	fd0 = open("/dev/key0", O_RDWR);
	if(fd0 < 0){
		perror("open /dev/dey0");
		exit(0);
	}
	// 0----标准输入,1-----标准输出,2------标准出错

	pfd[0].fd = fd0; 	//检测设备文件
	pfd[0].events = POLLIN;

	pfd[1].fd = 0; 	//检测标准输入键盘,而且内核一运行的时候文件就是打开的
	pfd[1].events = POLLIN;	
	
	poll(pfd, 2,-1);

	while(1){
		ret = poll(pfd, 2,-1);
		if(ret > 0){
			read(pfd[0].fd ,&event,sizeof(struct key_event));
			if(pfd[0].revents & POLLIN ){    //按键被按下
				if(event.code == "KEY_ENTER"){
					if(event.value){
						printf("key3 is purss");
					}else{
						printf("key3 is down");}
					}
				}
			if(pfd[0].revents & POLLIN){ 		//键盘输入信息
			fgets(in_buf, 64, stdin);
			printf("in_buf = %s\n",in_buf);}
			
	
		}else{
				perror("poll");
				exit(0);
			}
		}

——————————————————————驱动部分代码

unsigned int key_drv_poll (struct file *filp , struct poll_table_struct *pts){

// 返回一个mask值
		unsigned int mask;
		// 调用poll_wait,将当前到等待队列放到内核管理系统中
		poll_wait(filp, &key_dev->wq_head, pts);
		
		// 1,当没有数据到时候返回一个0
		if(!key_dev->key_state)
			mask = 0;

		// 2,有数据返回一个POLLIN
		if(key_dev->key_state)
			mask |= POLLIN;

		return mask;

}

————————————————————————————————————————————————————
四、异步通信
在这里插入图片描述
异步通信就是我应用程序正常的的运行,但你什么时候要发数据给我的时候,你就给我一个信号,我去处理你给我的信号。前面都是应用程序主动的等待数据的到来,而异步通信则是类似于信号中断的概念。
设置异步信号的步骤:
1,设置信号处理方法
signal(SIGIO,signale_headle); 参数一:SIGIO 这里用作信号传送 参数二:信号的处理方法(函数)
当SIGIO有信号的时候,就会执行signale_headle这个处理函数
2,将当前进程设置成SIGIO的属主进程
fcntl(fd, F_SETOWN, getpid());
参数一,当前打开的文件描述符 参数二:设置OWn就是设置自己 参数三:获取当前进程PID号。
将当前进程设置成SIGIO的属主进程:这样SIGNO的信号就会发到这个进程中来,要不然乱发的去
3,将io模式设置成异步模式
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC );
参数一:打开的文件描述符,就是驱动设备 参数二:设置标志 参数三要设置的标志。
这样就完成了异步通信的设置。再完成中断处理函数,让信号来的时候,你要做就做什么。

驱动程序
b,驱动–发送信号
1,需要和进程进行关联–记录信号该发送给谁
实现一个fasync的接口和READ,OPEN等文件操作一样这是一个文件操作接口。操作它就可以实现异步通知
int key_drv_fasync(int fd, struct file *filp, int on)
{
//只需要调用一个函数记录信号该发送给谁
return fasync_helper(fd, filp, on, &key_dev->faysnc);
}
2,在某个特定的时候去发送信号,在有数据的时候
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
—————————————应用程序—————————————————

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
 #include <signal.h>

struct key_event{
	int code; // 表示按键的类型:  home, esc, Q,W,E,R,T, ENTER
	int value; // 表示按下还是抬起 1 / 0
};

#define KEY_ENTER		28

static int fd;
static struct key_event event;

 void catch_signale(int signo)
{
	if(signo == SIGIO)
	{
		printf("we got sigal SIGIO\n");
		// 读取数据
		read(fd, &event, sizeof(struct key_event));
		if(event.code == KEY_ENTER)
		{
			if(event.value)
			{
				printf("APP__ key enter pressed\n");
			}else
			{
				printf("APP__ key enter up\n");
			}
		}
	}

}

int main(int argc, char *argv[])
{
	int ret;
	
	fd = open("/dev/key0", O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}

	// 1,设置信号处理方法
	signal(SIGIO,catch_signale);
	// 2,将当前进程设置成SIGIO的属主进程
	fcntl(fd, F_SETOWN, getpid());

	// 3,将io模式设置成异步模式
	int flags  = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, flags | FASYNC );


	while(1)
	{
		// 可以做其他的事情
		printf("I am waiting......\n");
		sleep(1);
	}

	close(fd);


	return 0;

}

—————————————————驱动程序—————————————————————

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include <asm/io.h>
#include <asm/uaccess.h>

#define GPXCON_REG  0x11000C20
#define KEY_ENTER		28

// 设计一个描述按键的数据的对象
struct key_event{
	int code; // 表示按键的类型:  home, esc, Q,W,E,R,T, ENTER
	int value; // 表示按下还是抬起 1 / 0
};

//设计一个全局设备对象--描述按键信息
struct key_desc{
	unsigned int dev_major;
	struct class *cls;
	struct device *dev;
	int irqno;
	void *reg_base;
	struct key_event event;
	wait_queue_head_t  wq_head;
	int key_state; //表示是否有数据
	struct fasync_struct *faysnc;
	
};

struct key_desc *key_dev;


irqreturn_t key_irq_handler(int irqno, void *devid)
{
	printk("-------%s-------------\n", __FUNCTION__);

	//读取数据寄存器
	int value = readl(key_dev->reg_base + 4) & (1<<2);

	if(value){ // 抬起
		printk("key3 up\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 0;

	}else{//按下
		printk("key3 pressed\n");
		key_dev->event.code = KEY_ENTER;
		key_dev->event.value = 1;
	}
	// 表示有数据,需要去唤醒整个进程/等待队列
	wake_up_interruptible(&key_dev->wq_head);
	//同时设置标志位
	key_dev->key_state  = 1;

	//发送信号
	kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
	
	return IRQ_HANDLED;
}

int get_irqno_from_node(void)
{
	// 获取到设备树中到节点
	struct device_node *np = of_find_node_by_path("/key_int_node");
	if(np){
		printk("find node ok\n");
	}else{
		printk("find node failed\n");
	}

	// 通过节点去获取到中断号码
	int irqno = irq_of_parse_and_map(np, 0);
	printk("irqno = %d\n", irqno);
	
	return irqno;
}

int key_drv_open(struct inode *inode, struct file *filp)
{

	printk("-------%s-------------\n", __FUNCTION__);
	return 0;
}

ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	//printk("-------%s-------------\n", __FUNCTION__);

	//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
	if(filp->f_flags & O_NONBLOCK && !key_dev->key_state)
		return -EAGAIN;
	
	int ret;
	//2,在需要等待(没有数据)的时候,进行休眠
	wait_event_interruptible(key_dev->wq_head, key_dev->key_state);
	
	// 表示有数据
	ret = copy_to_user(buf, &key_dev->event,  count);
	if(ret > 0)
	{
		printk("copy_to_user error\n");
		return -EFAULT;
	}

	// 传递给用户数据之后,将数据清除掉
	memset(&key_dev->event, 0,  sizeof(key_dev->event));
	key_dev->key_state = 0;
	
	return count;
	
}
ssize_t key_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	printk("-------%s-------------\n", __FUNCTION__);
	return 0;

}

int key_drv_close (struct inode *inode, struct file *filp)
{
	printk("-------%s-------------\n", __FUNCTION__);
	return 0;

}

unsigned int key_drv_poll(struct file *filp, struct poll_table_struct *pts)
{
	// 返回一个mask值
	unsigned int mask;
	// 调用poll_wait,将当前到等待队列注册系统中
	poll_wait(filp, &key_dev->wq_head, pts);
	
	// 1,当没有数据到时候返回一个0
	if(!key_dev->key_state)
		mask = 0;

	// 2,有数据返回一个POLLIN
	if(key_dev->key_state)
		mask |= POLLIN;

	return mask;
}int key_drv_fasync(int fd, struct file *filp, int on)
{
	//只需要调用一个函数记录信号该发送给谁
	return fasync_helper(fd, filp, on,  &key_dev->faysnc);

}

const struct file_operations key_fops = {
	.open = key_drv_open,
	.read = key_drv_read,
	.write = key_drv_write,
	.release = key_drv_close,
	.poll = key_drv_poll,
	.fasync = key_drv_fasync,
	
};

static int __init key_drv_init(void)
{
	//演示如何获取到中断号
	int ret;

	// 1,设定一个全局的设备对象
	key_dev = kzalloc(sizeof(struct key_desc),  GFP_KERNEL);
	
	// 2,申请主设备号
	key_dev->dev_major = register_chrdev(0, "key_drv", &key_fops);

	// 3,创建设备节点文件
	key_dev->cls = class_create(THIS_MODULE, "key_cls");
	key_dev->dev = device_create(key_dev->cls, NULL, 
									MKDEV(key_dev->dev_major,0), NULL, "key0");


	// 4,硬件的初始化--地址映射或者中断申请
	key_dev->irqno = get_irqno_from_node();

	ret = request_irq(key_dev->irqno, key_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, 
					"key3_eint10", NULL);
	if(ret != 0)
	{
		printk("request_irq error\n");
		return ret;
	}

	// a,硬件如何获取数据--gpx1
	key_dev->reg_base  = ioremap(GPXCON_REG, 8);

	// 初始化等待队列头
	init_waitqueue_head(&key_dev->wq_head);
	
	
	return 0;
}


static void __exit key_drv_exit(void)
{
	iounmap(key_dev->reg_base);
	free_irq(key_dev->irqno, NULL);
	device_destroy(key_dev->cls, MKDEV(key_dev->dev_major,0));
	class_destroy(key_dev->cls);
	unregister_chrdev(key_dev->dev_major, "key_drv");
	kfree(key_dev);

}

module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

—————————————————————————————————————————————
这个驱动程序实现了多种IO模型的接口,
阻塞:在read接口中有 wait_event_interruptible :应用程序如果不设置非阻塞就是阻塞模式。
非阻塞:在READ接口中有:判断fp的flag,如果为阻塞模式就执行非阻塞模式
多路复用:应用层调用了poll接口之后,就可以实现多路复用
异步通信:应用层调用了fasync接口之后,就可以实现多路复用
————————————————————————————————————————————
四种IO模型的总结:什么时候用

————————————————————————————————————————end

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值