linux驱动的中断函数,嵌入式Linux驱动开发(四)——字符设备驱动之中断方式以及中断方式获取按键值...
之前我们完成了关于通过查询的方式获取按键键值的驱动程序,可以参考:嵌入式Linux开发——裸板程序之中断控制器。
虽然读取键值没有什么问题,但是测试程序占用CPU过高,一直在不断的查询,资源消耗过大,这个问题非常严重,我们必须要来解决一下。但是在解决这个问题之前,我们先来思考一个问题,除了不断的这样 read,是不是还有其他的方法可以获取按键的键值呢?自然是有的,这个方式就是通过终端的方式来获取键值。
如果举一个例子,小明在家等小华给他送东西,有什么方式不会错过呢?他可以不断的去门口查看,其实这就是我们之前的方式,不断的让应用程序去read,这时候自然小明大量的精力都消耗在了不断的查看过程中,他想做别的事的时间和精力也会被不断的查看而占用。
为了这个解决这个问题,方法很多,我们先来说其中一个普通的,就是小明家安装一个门铃,小华到了,按下门铃,这时候小明去开门就可以了。这时候,小明在家做其他事,也就不会被打扰了,节省了大量的资源。
其实上面的故事很平常,但是实际上,第一种方式就是不断查询的方式,虽然不会错过小华,但是消耗了大量资源,一定是不可以采用的。而第二种方式有点类似于今天要说的——中断模式。
当发生了某件事,这件事作为一个中断,处理完了这个中断后,在恢复去执行另外一件事。
简单总结一下:
单片机程序处理中断的过程
按键按下,中断发生
CPU发生中断,跳转到异常向量入口进行执
a. 保存被中断的现场(寄存器的值)
b. 判断中断的来源,执行中断处理函数,清中断
c. 恢复被中断的现场,继续执行
那么对于Linux嵌入式来说,处理中断是不是相同呢?
对于Linux来说,处理流程也是相同的,肯定需要先设置异常向量,不然都不知道该调用谁来处理。同样也需要分辨 异常的来源,毕竟按下每个键都应该执行不同的任务。同样,不能因为按下按键,恢复以后之前的程序就不继续执行了(reset除外)。所以,裸板程序中处理异常事件的流程在Linux下都是成立的,只是这些代码都是由系统帮我们实现的,不需要我们自己来实现。那么我们就需要使用好。那么,我们可以来思考一下,对于以上流程,哪一个对于系统来说没办法帮我们代劳,必须我们自己来完成呢?那么这个过程对我们来说肯定是需要关注的了。
其实,不难想到,以上的处理流程里面,分析中断源、中断处理函数这些部分一定是系统没办法帮我们代劳的。不但这些内容和硬件相关性比较高,同时,我们想要如何处理,也都不一定相同。
ARM架构下的Linux的异常处理原理如下图:
按下按键
CPU进入异常模式
b vector_irq + stubs_offset,vector_irq是使用宏来定义的。在这个宏中调用__irq_xxxx的列表,其中会做对现场进行相应的保存的工作
最终会调用到asm_do_IRQ
其中调用desc_handle_irq(irq, desc)
调用desc->handle_irq(irq, desc)以中断号为下标取出handle_irq,在其中调用irq_desc[irq] -> handle_irq,irq_desc实际是一个结构体数组
handle_irq 就等于handle_edge_irq`
其中主要做了desc0>chip->ack(irq)清中断
调用handle_IRQ_event来处理中断,
取出action链表中的成员
执行action->handler
那么如何才能将自己的中断处理加入到终端框架中
注册中断::request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char * devname, void * dev_id):
irq为中断号,handler为处理函数,irqflags为上升沿触发、下降沿触发、边缘触发等标志,devname为设备名,dev_id为设备号
实际背后做了以下工作:
分配irq_action结构,以上成员都指向传入的参数
调用setut_irq(irq, action),传入中断号和irqaction结构
找到irq_desc[irq],并在irq_desc[irq] -> action链表中,加入之前构造的action结构体
desc->chip->set_type(irq, new->flags & IRQF_TRIGGER_MASK):将对应的引脚设置为中断引脚
desc->chip->startup或desc->chip->enable:使能中断
free_irq(irq, dev_id):来解除中断(出链,禁止中断)
说了这么多,还是老规矩,先来看看一个按键的中断框架是如何,然后我们再来考虑该如区分不同的中断
#include
#include
#include
#include
#include
#include
#include
#include
#include
static const char* dev_name = "first_eint";
static unsigned int major = 0;
static struct class* first_class;
static struct class_device* first_class_device;
//中断的处理函数
static irqreturn_t irq_handler(int irq, void *dev_id)
{
printk("Interrupted!\n");
return 0;
}
static int first_open (struct inode *inode, struct file *file)
{
//int request_irq(unsigned int irq, irq_handler_t handler,
// unsigned long irqflags, const char *devname, void *dev_id)
//IRQ_EINT0:中断号,定义在\include\asm-arm\arch\irqs.h中
// IRQ_TYPE_EDGE_BOTH:双边缘触发,定义在\include\linux\irq.h中
request_irq(IRQ_EINT0, irq_handler, IRQ_TYPE_EDGE_BOTH, dev_name, major);
return 0;
}
static int first_release (struct inode *inode, struct file *file)
{
printk("release\n");
//void free_irq(unsigned int irq, void *dev_id)
free_irq(IRQ_EINT0, major);
return 0;
}
static struct file_operations first_fops =
{
.owner = THIS_MODULE,
.open = first_open,
.release= first_release,
};
static int __init first_init(void)
{
major = register_chrdev(major, dev_name, &first_fops);
first_class = class_create(THIS_MODULE, dev_name);
first_class_device = class_device_create(first_class, NULL, MKDEV(major, 0), NULL, dev_name);
return 0;
}
static void __exit first_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(first_class_device);
class_destroy(first_class);
}
module_init(first_init);
module_exit(first_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
这个驱动还是很简单的,主要用到了我们之前讲的驱动框架,在open的时候会注册中断,使用了request_irq,在release中,调用了free_irq来释放了注册的中断。
Makefile
KERN_DIR=/code/LinuxDev/Lab/KernelOfLinux/linux-2.6.22.6 #内核目录
all:
make -C $(KERN_DIR) M=`pwd` modules #M=`pwd`表示,生成的目标放在pwd命令的目录下 # -C代表使用目录中的Makefile来进行编译
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -f modules.order
obj-m += first.o #加载到module的编译链中,内核会编译生成出来ko文件,作为一个模块
最后就是测试程序了
#include
#include
#include
#include
int main()
{
int cnt = 15;
int fd = open("/dev/first_eint", O_RDWR);
if(fd < 0)
{
printf("open error\n");
return -1;
}
while(cnt--){ //十五秒后如果自动结束程序
if(!(cnt % 5)) printf("count = %d\n", cnt);
sleep(1);
}
printf("time out\n");
close(fd);
return 0;
}
执行结果:
执行结果
后台执行,占用资源的情况
以上已经实现了中断程序的框架结构,主要用到的了request_irq和free_irq两个函数。只要设备文件被打开,当按钮被按下,就会触发中断函数的调用。当时对于上面的代码来说,还不够完善,因为它只能够处理一个按键的信息,我们希望开发板上的按键都可以注册在驱动程序中统一管理。并且前端可以通过read函数来获取键值。现在我们的程序来说,虽然资源占用降低了,但是实际测试程序并没有进行read操作,仅仅是sleep。如果测试程序不断的去读取键值,那么,占用资源的情况依然很难得到缓解。
为了解决以上的问题,可以让前端的程序不断的read的驱动程序所提供的键值,那么,这时候可以有这样一个思路来解决。
read函数的大致调用流程如下:
当测试程序调用了read函数->通过sys_read等系统调用->调用驱动函数中的read函数。
我们采用一种阻塞式的方法来讲驱动函数中的read函数进行阻塞处理,如果没有发生中断就让程序睡眠。如果中断发生,那么,一定会调用中断处理函数,在中断处理函数中讲程序唤醒。以此来解决测试程序read的时候占用资源较多的情况。既然测试程序已经可以read数据,那么,多个键值的问题其实也就引刃而解了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static const char* dev_name = "third_eint";
static volatile unsigned int major = 0;
static struct class* third_class;
static struct class_device* third_class_device;
//定义一个结构体,用来描述引脚的状态,方便讲引脚的值管理起来
struct pin_desc
{
unsigned int pin; //引脚名称
unsigned int value; //键值
};
static struct pin_desc btn_pins[4] =
{
{S3C2410_GPF0, 0x1},
//引脚名和预设的键值,未被按下为1,按下位0x81
//引脚名定义在:\include\asm\arch-s3c2410\regs-gpio.h
{S3C2410_GPF2, 0x2},
{S3C2410_GPG3, 0x3},
{S3C2410_GPG11, 0x4},
};
static int eints[4] = {IRQ_EINT0, IRQ_EINT3, IRQ_EINT9, IRQ_EINT11,};
unsigned int status = 0;//用于存放引脚值信息
unsigned int condition = 0; //用于描述是否需要睡眠,如果非零表示可以睡眠了,0表示不需要睡眠
unsigned char value = 0; //记录键值的内容,以便判断是否被按下
static DECLARE_WAIT_QUEUE_HEAD(wait_queue);
/*
这是一个宏,用来生成存放睡眠信息的结构体。定义在:\include\linux\wait.h头文件中。传入的参数就是结构体的名称,可以直接使用传入的信息来获取结构体。
睡眠的原理:在内核中维护一个睡眠的数组,需要睡眠的程序就挂在这个数组中;如果需要唤醒,内核会从这个数组里面将对应的结构体取出来,并唤醒
*/
/*
在中断函数的框架中,有一个非常重要的参数,就是void* dev_id
这个实际上是由request_irq时传入的变量,最终传递给注册的中断处理函数
方便传递任何类型的信息。
在这里我们传递的是关于引脚描述的信息
*/
static irqreturn_t irq_handler(int irq, void *dev_id)
{
struct pin_desc* desc = (struct pin_desc*) dev_id; //强制类型转换
status = s3c2410_gpio_getpin(desc->pin); //根据我们发送的引脚名称,从中获取到引脚值,和我们之前按位获取没有什么区别,只是一个系统封装好的函数
if(status) //如果被按下了,那么将value变量改为0x8x,比如第一个按键未被按下就是0x81,被按下则是0x01。以此来判断到底是哪个按键被按下了。
value = desc->value | 0x80;
else
value = desc->value;
wake_up_interruptible(&wait_queue); //从睡眠的队列中唤醒
condition = 1; //中断处理完毕,表示可以进入睡眠状态,再次,read的时候,会将此变量传递给系统
return 0;
}
static ssize_t third_read (struct file *file, char __user *buff, size_t size, loff_t *ppos)
{
printk(".........read\n");
wait_event_interruptible(wait_queue, condition);//进入睡眠状态,阻塞于此
condition = 0;//此时值为0,表示不需要再进入睡眠了
printk("read.........\n");
copy_to_user(buff, &value, 1);//将按键数据发送给用户空间
return 0;
}
static int third_open (struct inode *inode, struct file *file)
{
int i = 0;//循环将所有的按键引脚都请求设置为中断
for(; i < EINT_PIN_COUNT; ++i){
request_irq(eints[i], irq_handler, IRQT_BOTHEDGE, dev_name, &btn_pins[i]);
}
return 0;
}
static int third_release (struct inode *inode, struct file *file)
{
//void free_irq(unsigned int irq, void *dev_id)
int i = 0;
for(;i < EINT_PIN_COUNT; ++i){
free_irq(eints[i], &btn_pins[i]);
}
printk("button released\n");
return 0;
}
struct file_operations third_fops =
{
.owner = THIS_MODULE,
.open = third_open,
.read = third_read,
.poll = third_poll,
.release = third_release,
};
static int __init third_init(void)
{
major = register_chrdev(major, dev_name, &third_fops);
third_class = class_create(THIS_MODULE, dev_name);
third_class_device = class_device_create(third_class, NULL, MKDEV(major, 0), NULL, dev_name);
printk("init\n");
return 0;
}
static void __exit third_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(third_class_device);
class_destroy(third_class);
printk("exit\n");
}
module_init(third_init);
module_exit(third_exit);
MODULE_AUTHOR("Ethan Lee <4128127@qq.com>");
MODULE_LICENSE("GPL");
测试代码:
#include
#include
#include
#include
int main()
{
int fd = open("/dev/third_eint", O_RDWR);
if(fd < 0)
{
printf("open error\n");
return -1;
}
while(1){
unsigned char value = 0;
read(fd, &value, 1);
printf("0x%02x\n", value);
}
return 0;
}
测试结果:
测试结果
后台运行,资源占用情况
根据以上的测试结果可以看出,可以通过中断来正确的识别不同按键的键值。同时genuine打印信息可以看出wait_event_interruptible(wait_queue, condition);实际的阻塞点位于此处。
目前,测试程序为死循环read,但实际资源占用率并不高,解决了我们之前所提出的问题。
但是是不是就完美解决了呢?实际并没有,比如,测试程序不希望被阻塞在这里,而有其他的逻辑需要执行,那么,这里通过阻塞的方式来获取按键值,显然不是我们希望所看到的。想要知道如何解决,那么且听下回分解。
linux驱动的中断函数,嵌入式Linux驱动开发(四)——字符设备驱动之中断方式以及中断方式获取按键值...相关推荐
- Linux驱动开发:字符设备驱动开发实战
Linux驱动开发:字符设备驱动开发实战 一.工程创建 VSCode 创建工程,设置 C/C++ 配置,导入 linux kernel 源码目录,方便 vscode 写代码自动补全,vscode 配置 ...
- linux注册函数机制,Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动...
原标题:Linux可信计算机制模块详细分析之函数实现机制(1)字符设备驱动 2.3 函数实现机制 2.3.1 Linux 字符设备驱动 在linux 3.5.4中,用结构体cdev描述字符设备,cde ...
- 嵌入式linux应用层中断函数,嵌入式LINUX驱动开发(中断处理函数)
嵌入式LINUX驱动开发(中断处理函数) 2020年08月11日 | 萬仟网网络运营 | 我要评论 嵌入式LINUX驱动学习之7中断相关(一)中断处理函数一.函数.头文件及说明二.编译举例:一.函数. ...
- linux open函数_Linux驱动开发 / 字符设备驱动内幕 (1)
哈喽,我是老吴,继续记录我的学习心得. 一.保持专注的几个技巧 将最重要的事放在早上做. 待在无干扰环境下,比如图书馆. 意识到刚坐下开始投入工作前,有点负面小情绪是特别正常的现象. 让"开 ...
- 【正点原子MP157连载】第二十章 字符设备驱动开发-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...
- linux字符设备驱动在哪里设置,从点一个灯开始学写Linux字符设备驱动!
原标题:从点一个灯开始学写Linux字符设备驱动! [导读] 前一篇文章,介绍了如何将一个hello word模块编译进内核或者编译为动态加载内核模块,本篇来介绍一下如何利用Linux驱动模型来完成一 ...
- Linux驱动之字符设备驱动
系列文章目录 第一章 Linux入门之驱动框架 第二章 Linux驱动之字符设备驱动 文章目录 系列文章目录 前言 一.认识字符设备驱动 1.基本概念 2.基本概念 二.字符设备旧框架 1.注册和注销 ...
- 第六讲 Linux字符设备驱动1
六.字符设备驱动 6.1.基本概念 Linux操作系统思想:一切皆文件 对设备的操作,也是将设备抽象成设备文件,应用层通过文件IO对设备文件进行操作,其最终结 果,是在操作设备. Linux操作系统, ...
- 字符设备驱动(四)按键中断
目录 字符设备驱动(四)按键中断 硬件IO 程序设计 中断配置 中断关闭 中断函数 共享中断号 测试 完整的程序 title: 字符设备驱动(四)按键中断 tags: linux date: 2018 ...
最新文章
- Matlab数据的可视化 -- 三维网格图形
- 优化javaScript代码,提高执行效率
- java的String构造对象的几种方法以及内存运行过程
- Leetcode 234. 回文链表 解题思路及C++实现
- 10个Bug环环相扣,你能解开几个?
- 扫地机器人返充原理_扫地机器人全解析
- 转载:JAVA日期处理
- 用计算机和用纸的区别,英语四级机考与传统纸考的主要区别
- Servlet的生命周期和工作原理
- word插入图片被文字挡住问题
- matlab 信号去直流,Speex中的近端信号去直流和预加重操作
- qtcpsocket断开_Qt QTcpSocket 对连接服务器中断的不同情况进行判定
- matlab 呼吸灯,一种控制呼吸灯呼吸效果的方法与流程
- 机器学习速成课程_关于获得一流客户推荐的速成课程
- 微信小程序自动回复用户消息
- 使用模型压缩库Distiller遇到的问题
- 王者荣耀英雄的原型故事
- jvm垃圾回收策略之标记清除
- 禁止网站开启F12 禁止调试代码方法
- python怎么画蛇_Python turtle 画蛇
热门文章
- mysql5.7.17完全卸载_MySQL5.7完全卸载步骤详解
- python以列表的形式输出_简单介绍python输出列表元素的所有排列形式
- python的重点和难点_Python - Python的基础知识结构,学习方法、难点和重点
- mysql 查看某数据库中所有表的行数,information_schema.tables不准确。count(*)拼接准确。
- plsql口令过期_Oracle 11g中密码过期问题详解
- 注解形式控制器配置(5) 数据绑定2
- Androidstudio查不出具体哪行报错解决办法
- mysql 数据库日志管理工具_mysql mysqlbinlog日志管理工具使用教程
- 基于JAVA+SpringMVC+Mybatis+MYSQL的家乡美管理系统
- 作业帮:最长连续序列(头部插入)