作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++、嵌入式、Linux。

关注下方公众号,回复【书籍】,获取 Linux、嵌入式领域经典书籍;回复【PDF】,获取所有原创文章( PDF 格式)。

目录

别人的经验,我们的阶梯!

大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【Linux 中断的注册和处理】。

在前两篇文章中,描述的是在应用层如何调用驱动函数来控制GPIO,以及在驱动中如何发送发送信号给应用层。

假如存在这样一个需求:应用程序需要监控某个硬件GPIO口的电平状态,当发生变化时,应用程序就做出相应的动作。

利用之前已经介绍的知识,是可以完成这个需求的。

比如:在驱动程序中不停的读取GPIO口的状态,一旦发生变化,就把新的电平状态通过信号发送到应用层。

这样的方式称作:轮询。

轮询方式的缺点显而易见:轮询的时间间隔应该是多少毫秒(or 微秒),才比较合适呢?

轮询太慢:可能会丢失信号;轮询太快:消耗 CPU 资源!

因此,在实际的产品中,用中断触发的方式才是更切合实际的选择!

本文所有的描述和测试,都是在 x86 平台上完成的;

Linux 中断的知识点梳理

中断的分类

Linux 的版本在持续更新,对中断的处理方式也在不停的发生变化。

下面几张图,是以前在学习时画的思维导图。

这几张图比较清晰地描述了在Linux操作系统中,关于中断的一些基本概念。

这张图的结构还是比较清晰的,基本上概括了Linux系统中的中断分类。

另外,在很多关于中断的书籍中,大部分都是从基础的 PIC(可编程中断控制器)开始讲解的。

如果您想非常具体、专业、深入的了解关于中断的相关内容,有一篇文章《Interrupt in Linux.pdf》讲得非常好(文章的后面部分我也没有看懂)。

在文末有下载链接,感兴趣的小伙伴可以学习一下。

中断号和中断向量

这张图只要记住中断号与中断向量的关系就可以了:

  1. 中断号与中断控制器(PIC/APIC)相关;

  2. 中断向量与 CPU 相关,用来查找中断处理函数的入口地址;

中断服务例程 ISR

中断服务程序,就是针对每一个中断如何进行处理。

如果您了解Linux中断的相关内容,一定会看到这样的描述:中断处理分为上半部分和下半部分。

上半部分不能消耗太多的时间,主要处理与硬件相关的重要工作;其他不重要的工作,都放在下半部分去做。

从上面这张图中可以看出,用来完成下半部分工作有好几种机制可以选择,每一种方式都是针对不同的需求场景。

在每一种下半部分机制中,Linux都设计了非常方便的接口函数。

作为开发者的我们来说,使用这些下半部分的机制很简单,只需要几个函数调用即可。

例如:如果使用工作队列来实现下半部分的工作,只需要2步动作:

1. 定义处理函数

static struct work_struct mywork;

static void mywork_handler(struct work_struct *work)
{
printk("This is myword_handler...\n");
}

2. 在中断处理函数中,注册注册函数

INIT_WORK(&mywork, mywork_handler);
schedule_work(&mywork);

下面几张图,是针对每一种“下半部分”处理机制的一些特点,注意:有些机制在新版本中已经废弃不用了,了解即可。

中断处理的注册和注销 API

所谓的中断注册,就是告诉操作系统:我对哪个中断感兴趣。

当这些中断发生的时候,请通知我。通知的方式就是:调用一个预先注册好的回调函数。

驱动程序可以通过函数 request_irq(),向操作系统注册,并且激活指定的中断线:

int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *devname,
void *dev_id);

参数说明:

irq: 申请的硬件中断号;

handler: 中断处理函数。一旦中断发生,这个函数就被调用;

flags: 中断的属性,例如:IRQF_DISABLED,IRQF_TIMER,IRQF_SHARED;

devname: 中断驱动程序的名称,在 /proc/interrupts 文件中看到对应的内容;

dev_id: 中断程序的唯一标识,比如:在共享中断中,可以用来区分不同的中断处理程序;

驱动程序通过函数 free_irq(),向操作系统注销一个中断处理函数:

void free_irq(unsigned int irq, void *dev_id);

参数说明:

irq: 硬件中断号;

dev_id: 中断程序的唯一标识;

实操:捕获键盘中断

示例代码

有了上面的知识铺垫,下面就来实操一下,实现的功能是:

捕获键盘的中断,在中断处理函数中,打印出按键的扫描码,如果是 ESC 键被按下,就打印出指定的信息。

与往常一样,操作的目录位于: tmp/linux-4.15/drivers 目录下。

$ mkdir my_driver_interrupt
$ touch driver_interrupt.c

文件内容:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h> // 中断号
static int irq; // 驱动程序名称
static char * devname; // 用来接收加载驱动模块时传入的参数
module_param(irq, int, 0644);
module_param(devname, charp, 0644); // 定义驱动程序的 ID,在中断处理函数中用来判断是否需要处理
#define MY_DEV_ID 1211 // 驱动程序数据结构
struct myirq
{
int devid;
}; // 保存驱动程序的所有信息
struct myirq mydev ={ MY_DEV_ID }; // 键盘相关的 IO 端口
#define KBD_DATA_REG 0x60
#define KBD_STATUS_REG 0x64
#define KBD_SCANCODE_MASK 0x7f
#define KBD_STATUS_MASK 0x80 // 中断处理函数
static irqreturn_t myirq_handler(int irq, void * dev)
{
struct myirq mydev;
unsigned char key_code;
mydev = *(struct myirq*)dev; // 检查设备 id,只有当相等的时候才需要处理
if (MY_DEV_ID == mydev.devid)
{
// 读取键盘扫描码
key_code = inb(KBD_DATA_REG); /* 这里如果放开,每次按键都会打印出很多信息
printk("key_code: %x %s\n",
key_code & KBD_SCANCODE_MASK,
key_code & KBD_STATUS_MASK ? "released" : "pressed");
*/ // 判断:是否为 ESC 键
if (key_code == 0x01)
{
printk("EXC key is pressed! \n");
}
} return IRQ_HANDLED;
} // 驱动模块初始化函数
static int __init myirq_init(void)
{
printk("myirq_init is called. \n"); // 注册中断处理函数
if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0)
{
printk("register irq[%d] handler failed. \n", irq);
return -1;
} printk("register irq[%d] handler success. \n", irq);
return 0;
} // 驱动模块退出函数
static void __exit myirq_exit(void)
{
printk("myirq_exit is called. \n"); // 注销中断处理函数
free_irq(irq, &mydev);
} MODULE_LICENSE("GPL");
module_init(myirq_init);
module_exit(myirq_exit);

上面的代码,有两个小的知识点。

向驱动程序传参

示例代码中,在调用 request_irq 时,需要指定中断号和驱动程序的名称。

这两个参数是在加载驱动模块的时候,从命令行传入的。

在驱动程序中,通过下面两行代码即可实现参数的接收:

module_param(irq, int, 0644);
module_param(devname, charp, 0644);

module_param 是一个宏定义,定义在 include/linux/moduleparam.h 文件中,具体定义如下:

#define module_param(name, type, perm)
module_param_named(name, name, type, perm);

name: 存储参数的变量名;

type: 变量的类型;

perm: 访问参数的权限,表示此参数在sysfs文件系统中所对应的文件节点的属性;

IO地址:IO端口和IO内存

这是读取 IO 外设的两种不同方式。

IO 端口有两种编址方式:统一编址和独立编址。

统一编制

把主存单元所在的地址空间,划出一部分出来,专门用来把IO外设寄存器的地址映射到这部分划出来的地址空间中。

统一编址的好处是:读取IO外设的时候,就好像读取普通的内存地址空间中的数据一样。

独立编址

IO 外设的地址空间,与主存单元的地址空间是两个独立的地址空间,此时,IO地址一般称作: IO端口。

我们在读写IO外设的时候,从这些 “IO端口” 中读写就可以了。不同的外设,被分配了不同的 IO 端口号。

CPU 提供了一些列函数来读写 IO 端口,例如:

// 读写一个字节
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port); // 读写一个字
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);

编译、验证

编译驱动模块:

$ make
输出文件:driver_interrupt.ko

因为我们捕获的是键盘中断(中断号:1),先看一下在加载驱动模块之前的中断驱动程序 head /proc/interrupts

可以把 demsg 的输出也清理一下:dmesg -c

执行下面指令来加载驱动模块(传递2个参数):

insmod driver_interrupt.ko irq=1 devname=myirq

再次执行一下指令 head /proc/interrupts 查看驱动程序:

在中断号 1 的右侧,是不是看到了我们的驱动程序:my_irq?

再来看一下 dmesg 的输出信息:

成功注册了中断号1的处理函数!

此时,按几次键盘左上角的 ESC 键,然后再查看 dmesg 的输出信息:

以上,就是最简单的中断注册和相应的中断处理函数!

在实际的项目中,如果要把中断信息通知到应用层,可以通过上一篇文章介绍的发送信号来实现,或者通过其他的回调机制也可以。

下一篇文章,我们在这个示例代码上进行扩展,看一下:中断处理中每一个“下半部分”机制应该如何编程。

------ End ------

文中的测试代码和相关文档,已经放在网盘了。

在公众号【IOT物联网小镇】后台回复关键字:1212,即可获取下载地址。

强烈建议您看一下网盘里的这篇文档:《Interrupt in Linux.pdf》,一定有很大收获!

谢谢!

推荐阅读

【1】《Linux 从头学》系列文章

【2】C语言指针-从底层原理到花式技巧,用图文和代码帮你讲解透彻

【3】原来gdb的底层调试原理这么简单

【4】内联汇编很可怕吗?看完这篇文章,终结它!

其他系列专辑:精选文章应用程序设计物联网C语言

星标公众号,第一时间看文章!

Linux驱动实践:一起来梳理中断的前世今生(附代码)的更多相关文章

  1. Linux驱动实践:中断处理函数如何【发送信号】给应用层?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  2. Linux驱动技术(六) _内核中断

    在硬件上,中断源可以通过中断控制器向CPU提交中断,进而引发中断处理程序的执行,不过这种硬件中断体系每一种CPU都不一样,而Linux作为操作系统,需要同时支持这些中断体系,如此一来,Linux中就提 ...

  3. Linux驱动实践:你知道【字符设备驱动程序】的两种写法吗?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  4. Linux驱动实践:带你一步一步编译内核驱动程序

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  5. Linux驱动实践:如何编写【 GPIO 】设备的驱动程序?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  6. Linux驱动设计—— 中断与时钟

    中断和时钟技术可以提升驱动程序的效率 中断 中断在Linux中的实现 通常情况下,一个驱动程序只需要申请中断,并添加中断处理函数就可以了,中断的到达和中断函数的调用都是内核实现框架完成的.所以程序员只 ...

  7. Linux开源模块迁移概述暨交叉编译跨平台移植总结--从《嵌入式Linux驱动模板简洁和工程实践》

    本文摘录<嵌入式Linux驱动模板简洁和工程实践>一本书"开发和调试技术". Linux强大的是,有那么多的开源项目可以使用.通常非常需要可以通过寻找相关的源模块被定义 ...

  8. zynq linux驱动之PL-PS中断【转】

    转自:https://blog.csdn.net/h244259402/article/details/83993524 PC:Windows 10 虚拟机:ubuntu 16.04 vivado:2 ...

  9. Linux驱动之按键驱动编写(中断方式)

    在Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以 ...

随机推荐

  1. [luogu6466]分散层叠算法

    做法1 对于每一个询问,直接暴力在每一个序列中二分查询 时间复杂度为$o(nk)-o(k\log n)$ 做法2 将所有序列合并后排序,并对每一个元素预处理出每个序列中第一个大于等于其的元素(位置), ...

  2. [hdu7099]Just Another Data Structure Problem

    不难发现,问题即求满足以下条件的$(i,j)$对数: 1.$1\le i<j\le n$且$a_{i}=a_{j}$ 2.$\min_{i\le k\le j}y_{k}\ge l$且$\max ...

  3. [cf1495E]Qingshan and Daniel

    选择其中卡片总数较少的一类,当相同时选择$t_{1}$所对应的一类(以下记作$A$类) 如果$t_{1}$不是$A$类,就先对$t_{1}$操作一次(即令$a_{1}$减少1) 下面,问题即不断删去$ ...

  4. [cf674E]Bear and Destroying Subtrees

    令$f_{i,j}$表示以$i$为根的子树中,深度小于等于$j$的概率,那么$ans_{i}=\sum_{j=1}^{dep}(f_{i,j}-f_{i,j-1})j$ 大约来估计一下$f_{i,j} ...

  5. 【Tool】IntelliJ IDEA Ultimate2019.1 中文版 安装

    IntelliJ IDEA Ultimate2019.1 2019-07-26  09:26:24  by冲冲 1. 下载 https://mp.weixin.qq.com/s/SdFQqGzMy-g ...

  6. Anaconda建立新的环境,出现CondaHTTPError: HTTP 000 CONNECTION FAILED for url ...... 解决过程

    2020.3.7准备scrapy,使用anaconda创建一个新的环境,执行"conda create -n scrapyEnv python=3.6",结果出现了"Co ...

  7. 【机器学习与R语言】12- 如何评估模型的性能?

    目录 1.评估分类方法的性能 1.1 混淆矩阵 1.2 其他评价指标 1)Kappa统计量 2)灵敏度与特异性 3)精确度与回溯精确度 4)F度量 1.3 性能权衡可视化(ROC曲线) 2.评估未来的 ...

  8. windows下typora图片自动上传到图床(附带腾讯云教程)

    目录 1. 背景 2. 在Typora中使用 3. 安装 3.1 安装教程环境说明 3.2 安装步骤 3.3 配置图床 3.3.1 插件配置 3.3.2 腾讯云参数获取 3.3.2.1 新建子用户,获 ...

  9. kubernetes部署haproxy、keepalived为kube-apiserver做集群

    也可以用nginx.keepalived做负载均衡,看大家的需求. # yum -y install haproxy keepalived haproxy的配置文件(三台一样): cat > / ...

  10. 13 — springboot集成mybatis-plus — 更新完毕

    1.mybatis-plus需要掌握的知识 1).mybatis-plus是什么? 不写了,老衲一般都是直接进官网 mybatis-plus官网地址:https://baomidou.com/guid ...