热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

linux驱动的中断函数,嵌入式Linux驱动开发(四)——字符设备驱动之中断方式以及中断方式获取按键值...

之前我们完成了关于通过查询的方式获取按键键值的驱动程序,可以参考:嵌入式Linux开发——裸板程序之中断控制器。虽然读取键值没有什么问题,

之前我们完成了关于通过查询的方式获取按键键值的驱动程序,可以参考:嵌入式Linux开发——裸板程序之中断控制器。

虽然读取键值没有什么问题,但是测试程序占用CPU过高,一直在不断的查询,资源消耗过大,这个问题非常严重,我们必须要来解决一下。但是在解决这个问题之前,我们先来思考一个问题,除了不断的这样 read,是不是还有其他的方法可以获取按键的键值呢?自然是有的,这个方式就是通过终端的方式来获取键值。

如果举一个例子,小明在家等小华给他送东西,有什么方式不会错过呢?他可以不断的去门口查看,其实这就是我们之前的方式,不断的让应用程序去read,这时候自然小明大量的精力都消耗在了不断的查看过程中,他想做别的事的时间和精力也会被不断的查看而占用。

为了这个解决这个问题,方法很多,我们先来说其中一个普通的,就是小明家安装一个门铃,小华到了,按下门铃,这时候小明去开门就可以了。这时候,小明在家做其他事,也就不会被打扰了,节省了大量的资源。

其实上面的故事很平常,但是实际上,第一种方式就是不断查询的方式,虽然不会错过小华,但是消耗了大量资源,一定是不可以采用的。而第二种方式有点类似于今天要说的——中断模式。

当发生了某件事,这件事作为一个中断,处理完了这个中断后,在恢复去执行另外一件事。

简单总结一下:

单片机程序处理中断的过程

按键按下,中断发生

CPU发生中断,跳转到异常向量入口进行执

a. 保存被中断的现场(寄存器的值)

b. 判断中断的来源,执行中断处理函数,清中断

c. 恢复被中断的现场,继续执行

那么对于Linux嵌入式来说,处理中断是不是相同呢?

对于Linux来说,处理流程也是相同的,肯定需要先设置异常向量,不然都不知道该调用谁来处理。同样也需要分辨 异常的来源,毕竟按下每个键都应该执行不同的任务。同样,不能因为按下按键,恢复以后之前的程序就不继续执行了(reset除外)。所以,裸板程序中处理异常事件的流程在Linux下都是成立的,只是这些代码都是由系统帮我们实现的,不需要我们自己来实现。那么我们就需要使用好。那么,我们可以来思考一下,对于以上流程,哪一个对于系统来说没办法帮我们代劳,必须我们自己来完成呢?那么这个过程对我们来说肯定是需要关注的了。

其实,不难想到,以上的处理流程里面,分析中断源、中断处理函数这些部分一定是系统没办法帮我们代劳的。不但这些内容和硬件相关性比较高,同时,我们想要如何处理,也都不一定相同。

ARM架构下的Linux的异常处理原理如下图:

69d24da53a54

69d24da53a54

按下按键

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&#64;qq.com>");

这个驱动还是很简单的&#xff0c;主要用到了我们之前讲的驱动框架&#xff0c;在open的时候会注册中断&#xff0c;使用了request_irq&#xff0c;在release中&#xff0c;调用了free_irq来释放了注册的中断。

Makefile

KERN_DIR&#61;/code/LinuxDev/Lab/KernelOfLinux/linux-2.6.22.6 #内核目录

all:

make -C $(KERN_DIR) M&#61;&#96;pwd&#96; modules #M&#61;&#96;pwd&#96;表示&#xff0c;生成的目标放在pwd命令的目录下 # -C代表使用目录中的Makefile来进行编译

clean:

make -C $(KERN_DIR) M&#61;&#96;pwd&#96; modules clean

rm -f modules.order

obj-m &#43;&#61; first.o #加载到module的编译链中&#xff0c;内核会编译生成出来ko文件&#xff0c;作为一个模块

最后就是测试程序了

#include

#include

#include

#include

int main()

{

int cnt &#61; 15;

int fd &#61; open("/dev/first_eint", O_RDWR);

if(fd <0)

{

printf("open error\n");

return -1;

}

while(cnt--){ //十五秒后如果自动结束程序

if(!(cnt % 5)) printf("count &#61; %d\n", cnt);

sleep(1);

}

printf("time out\n");

close(fd);

return 0;

}

执行结果&#xff1a;

69d24da53a54

执行结果

69d24da53a54

后台执行&#xff0c;占用资源的情况

以上已经实现了中断程序的框架结构&#xff0c;主要用到的了request_irq和free_irq两个函数。只要设备文件被打开&#xff0c;当按钮被按下&#xff0c;就会触发中断函数的调用。当时对于上面的代码来说&#xff0c;还不够完善&#xff0c;因为它只能够处理一个按键的信息&#xff0c;我们希望开发板上的按键都可以注册在驱动程序中统一管理。并且前端可以通过read函数来获取键值。现在我们的程序来说&#xff0c;虽然资源占用降低了&#xff0c;但是实际测试程序并没有进行read操作&#xff0c;仅仅是sleep。如果测试程序不断的去读取键值&#xff0c;那么&#xff0c;占用资源的情况依然很难得到缓解。

为了解决以上的问题&#xff0c;可以让前端的程序不断的read的驱动程序所提供的键值&#xff0c;那么&#xff0c;这时候可以有这样一个思路来解决。

read函数的大致调用流程如下&#xff1a;

当测试程序调用了read函数->通过sys_read等系统调用->调用驱动函数中的read函数。

我们采用一种阻塞式的方法来讲驱动函数中的read函数进行阻塞处理&#xff0c;如果没有发生中断就让程序睡眠。如果中断发生&#xff0c;那么&#xff0c;一定会调用中断处理函数&#xff0c;在中断处理函数中讲程序唤醒。以此来解决测试程序read的时候占用资源较多的情况。既然测试程序已经可以read数据&#xff0c;那么&#xff0c;多个键值的问题其实也就引刃而解了。

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

static const char* dev_name &#61; "third_eint";

static volatile unsigned int major &#61; 0;

static struct class* third_class;

static struct class_device* third_class_device;

//定义一个结构体&#xff0c;用来描述引脚的状态&#xff0c;方便讲引脚的值管理起来

struct pin_desc

{

unsigned int pin; //引脚名称

unsigned int value; //键值

};

static struct pin_desc btn_pins[4] &#61;

{

{S3C2410_GPF0, 0x1},

//引脚名和预设的键值&#xff0c;未被按下为1&#xff0c;按下位0x81

//引脚名定义在&#xff1a;\include\asm\arch-s3c2410\regs-gpio.h

{S3C2410_GPF2, 0x2},

{S3C2410_GPG3, 0x3},

{S3C2410_GPG11, 0x4},

};

static int eints[4] &#61; {IRQ_EINT0, IRQ_EINT3, IRQ_EINT9, IRQ_EINT11,};

unsigned int status &#61; 0;//用于存放引脚值信息

unsigned int condition &#61; 0; //用于描述是否需要睡眠&#xff0c;如果非零表示可以睡眠了&#xff0c;0表示不需要睡眠

unsigned char value &#61; 0; //记录键值的内容&#xff0c;以便判断是否被按下

static DECLARE_WAIT_QUEUE_HEAD(wait_queue);

&#xff0f;*

这是一个宏&#xff0c;用来生成存放睡眠信息的结构体。定义在&#xff1a;\include\linux\wait.h头文件中。传入的参数就是结构体的名称&#xff0c;可以直接使用传入的信息来获取结构体。

睡眠的原理&#xff1a;在内核中维护一个睡眠的数组&#xff0c;需要睡眠的程序就挂在这个数组中&#xff1b;如果需要唤醒&#xff0c;内核会从这个数组里面将对应的结构体取出来&#xff0c;并唤醒

*&#xff0f;

/*

在中断函数的框架中&#xff0c;有一个非常重要的参数&#xff0c;就是void* dev_id

这个实际上是由request_irq时传入的变量&#xff0c;最终传递给注册的中断处理函数

方便传递任何类型的信息。

在这里我们传递的是关于引脚描述的信息

*/

static irqreturn_t irq_handler(int irq, void *dev_id)

{

struct pin_desc* desc &#61; (struct pin_desc*) dev_id; //强制类型转换

status &#61; s3c2410_gpio_getpin(desc->pin); //根据我们发送的引脚名称&#xff0c;从中获取到引脚值&#xff0c;和我们之前按位获取没有什么区别&#xff0c;只是一个系统封装好的函数

if(status) //如果被按下了&#xff0c;那么将value变量改为0x8x&#xff0c;比如第一个按键未被按下就是0x81&#xff0c;被按下则是0x01。以此来判断到底是哪个按键被按下了。

value &#61; desc->value | 0x80;

else

value &#61; desc->value;

wake_up_interruptible(&wait_queue); //从睡眠的队列中唤醒

condition &#61; 1; //中断处理完毕&#xff0c;表示可以进入睡眠状态&#xff0c;再次&#xff0c;read的时候&#xff0c;会将此变量传递给系统

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);//进入睡眠状态&#xff0c;阻塞于此

condition &#61; 0;//此时值为0&#xff0c;表示不需要再进入睡眠了

printk("read.........\n");

copy_to_user(buff, &value, 1);//将按键数据发送给用户空间

return 0;

}

static int third_open (struct inode *inode, struct file *file)

{

int i &#61; 0;//循环将所有的按键引脚都请求设置为中断

for(; 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 &#61; 0;

for(;i

free_irq(eints[i], &btn_pins[i]);

}

printk("button released\n");

return 0;

}

struct file_operations third_fops &#61;

{

.owner &#61; THIS_MODULE,

.open &#61; third_open,

.read &#61; third_read,

.poll &#61; third_poll,

.release &#61; third_release,

};

static int __init third_init(void)

{

major &#61; register_chrdev(major, dev_name, &third_fops);

third_class &#61; class_create(THIS_MODULE, dev_name);

third_class_device &#61; 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&#64;qq.com>");

MODULE_LICENSE("GPL");

测试代码&#xff1a;

#include

#include

#include

#include

int main()

{

int fd &#61; open("/dev/third_eint", O_RDWR);

if(fd <0)

{

printf("open error\n");

return -1;

}

while(1){

unsigned char value &#61; 0;

read(fd, &value, 1);

printf("0x%02x\n", value);

}

return 0;

}

测试结果&#xff1a;

69d24da53a54

测试结果

69d24da53a54

后台运行&#xff0c;资源占用情况

根据以上的测试结果可以看出&#xff0c;可以通过中断来正确的识别不同按键的键值。同时genuine打印信息可以看出wait_event_interruptible(wait_queue, condition);实际的阻塞点位于此处。

目前&#xff0c;测试程序为死循环read&#xff0c;但实际资源占用率并不高&#xff0c;解决了我们之前所提出的问题。

但是是不是就完美解决了呢&#xff1f;实际并没有&#xff0c;比如&#xff0c;测试程序不希望被阻塞在这里&#xff0c;而有其他的逻辑需要执行&#xff0c;那么&#xff0c;这里通过阻塞的方式来获取按键值&#xff0c;显然不是我们希望所看到的。想要知道如何解决&#xff0c;那么且听下回分解。



推荐阅读
  • 1.      准备工作: 程序:MinGW-3.1.0-1.exe     windows下的gcc,编译c语言的工具下载地址: http:umn.dl.sourceforge. ... [详细]
  • Linux 程序设计学习笔记----动手编写makefile文件
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 操作系统RetHat9.0,存储设备华为3comEX1000在linux上建立能够识别盘阵的方法有三种1、HBA卡;2、TOE卡;3、is ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • ShiftLeft:将静态防护与运行时防护结合的持续性安全防护解决方案
    ShiftLeft公司是一家致力于将应用的静态防护和运行时防护与应用开发自动化工作流相结合以提升软件开发生命周期中的安全性的公司。传统的安全防护方式存在误报率高、人工成本高、耗时长等问题,而ShiftLeft提供的持续性安全防护解决方案能够解决这些问题。通过将下一代静态代码分析与应用开发自动化工作流中涉及的安全工具相结合,ShiftLeft帮助企业实现DevSecOps的安全部分,提供高效、准确的安全能力。 ... [详细]
  • Word2vec,Fasttext,Glove,Elmo,Bert,Flairpre-trainWordEmbedding源码数据Github网址:词向量预训练实现Githubf ... [详细]
  • 第2讲 Android Camera Native Framework 初识cameraserver进程
    本讲是AndroidCameraNativeFramework专题的第2讲,我们初识CameraServer,包括如下内容:Camera ... [详细]
  • 编写一个简单的内核驱动模块时报错 “/lib/modules/3.13.032generic/bulid: 没有那个文件或目录。 停止。”...
    编写一个简单的内核驱动模块1staticinthello_init()2{3printk(“hello,Iaminkernelnow\n”);4return0;5}6voidadd ... [详细]
  • 201720181 20155315 《信息安全系统设计基础》实验四 外设驱动程序设计
    实验内容学习资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.pdf”中的第十一章在Ubuntu完成资源中全课中的“hqyj.嵌入式Linux应用程序开发标准教程.p ... [详细]
  • nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 直接从网上下载redis当然你也可以直接从别的地方拿过来直接放在redis中[root@iZ2zedckzf8nczp6xshv4mZ]#wgethttp:download ... [详细]
  • Makefile基本用法
    来源https:www.gnu.orgsoftwaremakemanualmake.pdf简单的例子其中的cc通过链接,间接指向usrbingcc。Makefile文件中列出了依赖 ... [详细]
author-avatar
穆羽默然
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有