前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux驱动实践:一起来梳理【中断】的前世今生(附代码)

Linux驱动实践:一起来梳理【中断】的前世今生(附代码)

作者头像
IOT物联网小镇
发布于 2021-12-13 06:01:32
发布于 2021-12-13 06:01:32
1.3K00
代码可运行
举报
文章被收录于专栏:IOT物联网小镇IOT物联网小镇
运行总次数:0
代码可运行

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

目录

  • Linux 中断的知识点梳理
    • 中断的分类
    • 中断号和中断向量
    • 中断服务程序ISR
    • 上半部分和下半部分
  • 中断处理的注册和注销 API
  • 实操:捕获键盘中断
    • 示例代码
    • 驱动程序传参
    • IO编址:IO端口和IO内存
    • 编译、测试、验证

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

大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【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. 定义处理函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static struct work_struct mywork;

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
INIT_WORK(&mywork, mywork_handler);                                                         
schedule_work(&mywork);

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

中断处理的注册和注销 API

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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(),向操作系统注销一个中断处理函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void free_irq(unsigned int irq, void *dev_id);

参数说明:

irq: 硬件中断号; dev_id: 中断程序的唯一标识;

实操:捕获键盘中断

示例代码

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ mkdir my_driver_interrupt
$ touch driver_interrupt.c

文件内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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 时,需要指定中断号和驱动程序的名称。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
module_param(irq, int, 0644);
module_param(devname, charp, 0644);

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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 端口,例如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 读写一个字节
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);

// 读写一个字
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
编译、验证

编译驱动模块:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make
输出文件:driver_interrupt.ko

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insmod driver_interrupt.ko irq=1 devname=myirq

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

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

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

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

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

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

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

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

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

谢谢!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IOT物联网小镇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux驱动实践:中断处理中的【工作队列】 workqueue 是什么鬼?
大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【中断处理中的下半部分机制-工作队列】。
IOT物联网小镇
2021/12/28
2.1K0
Linux驱动实践:中断处理中的【工作队列】 workqueue 是什么鬼?
Linux驱动实践:中断处理函数如何【发送信号】给应用层?
大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【中断程序如何发送信号给应用层】。
IOT物联网小镇
2021/12/20
3.6K0
Linux驱动实践:中断处理函数如何【发送信号】给应用层?
深入浅出:Linux设备驱动之中断与定时器
“我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我 就望一望天上那 闪烁的繁星 有我寻觅你的 目光” 谢谢你,曾经来过~ 中断与定时器是我们再熟悉不过的问题了,我们在进行裸机开发学习的 时候,这几乎就是重难点,也是每个程序必要的模块信息,那么在Linux中,我们又怎么实现延时、计数,和中断呢? 一、中断 1.概述 所谓中断是指cpu在执行程序的过程中,出现了某些
小小科
2018/05/03
3.2K0
深入浅出:Linux设备驱动之中断与定时器
6.分析request_irq和free_irq函数如何注册注销中断(详解)
上一节讲了如何实现运行中断,这些都是系统给做好的,当我们想自己写个中断处理程序,去执行自己的代码,就需要写irq_desc->action->handler,然后通过request_irq()来向内核
诺谦
2018/01/03
3.3K0
i.MX283开发板按键驱动和GPIO中断
由于手头上的i.MX283开发板没有独立按键,所以只能用一个IO口手动拉高拉低来模拟按键,但是这样会造成一个小问题,这个后面会提到。按键驱动与LED驱动最大的区别就是前者是GPIO输入,后者是GPIO输出,我们只需要读取IO口电平即可,同样的这也是一个字符设备,按照字符设备驱动框架编写驱动即可。
知否知否应是绿肥红瘦
2025/02/19
990
i.MX283开发板按键驱动和GPIO中断
Linux驱动开发-外部中断的注册使用(按键为例)
前面有篇文章使用杂项设备完成了按键驱动的编写,实现了按键轮询检测,通过read函数向应用层传递按键值,这篇文章使用按键为例,介绍Linux内核里中断的注册方法,使用中断的方式检测按键是否按下,中断在单片机、设备驱动开发里使用的都非常多,可以更加实时的检测到按键触发的情况。
DS小龙哥
2022/04/08
5.1K1
Linux驱动开发-外部中断的注册使用(按键为例)
中断与异常简介与分析
在高执行级别下,代码可以执行特权指令,访问任意的物理地址,这种CPU执行级别就对应着内核态。
杨源鑫
2019/07/04
1.2K0
中断与异常简介与分析
linux 中断机制《Rice linux 学习笔记》
在我们的开发中,检测按键是否触发,无非就两种方法—轮询和中断。作者认为两种方法最大的区别就是CPU的利用率。
Rice加饭
2022/05/09
5K0
linux 中断机制《Rice linux 学习笔记》
《Linux Device Drivers》第十章 中断处理——note
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/115579.html原文链接:https://javaforall.cn
全栈程序员站长
2022/07/10
6500
硬中断和软中断_软中断和硬中断的优先级
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。
全栈程序员站长
2022/11/03
2.8K0
中断处理机制解析
其中,irq 是一个整数,是中断信号。dev_id 是一个 void * 的通用指针,主要用于区分同一个中断处理函数对于不同设备的处理。
穿过生命散发芬芳
2025/01/26
1070
Linux内核硬中断 / 软中断的原理和实现
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。
秃头哥编程
2019/10/09
23K1
Linux内核硬中断 / 软中断的原理和实现
Linux内核中断顶半部和底半部的理解
  设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。   下图描述了Linux内核的中断处理机制。为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部和底半部。
嵌入式与Linux那些事
2021/05/20
1.9K0
Linux内核中断顶半部和底半部的理解
Linux设备驱动workqueue(工作队列)案例实现
工作队列(work queue)是另外一种将工作推后执行的形式,tasklet(小任务机制)有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
杨源鑫
2019/07/04
5.5K0
韦东山:剥丝抽茧分析linux中断系统的重要数据结构
最核心的结构体是irq_desc,之前为了易于理解,我们说在Linux内核中有一个中断数组,对于每一个硬件中断,都有一个数组项,这个数组就是irq_desc数组。
韦东山
2020/09/30
1.2K0
韦东山:剥丝抽茧分析linux中断系统的重要数据结构
PCI设备驱动程序「建议收藏」
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huangweiqing80/article/details/83347495
全栈程序员站长
2022/11/18
2.5K0
PCI设备驱动程序「建议收藏」
Linux的中断下半部机制的对比
 中断服务程序一般都是在中断请求关闭的条件下执行的,以避免嵌套而使中断控制复杂化。但是,中断是一个随机事件,它随时会到来,如果关中断的时间太长,CPU就不能及时响应其他的中断请求,从而造成中断的丢失。因此,Linux内核的目标就是尽可能快的处理完中断请求,尽其所能把更多的处理向后推迟。例如,假设一个数据块已经达到了网线,当中断控制器接受到这个中断请求信号时,Linux内核只是简单地标志数据到来了,然后让处理器恢复到它以前运行的状态,其余的处理稍后再进行(如把数据移入一个缓冲区,接受数据的进程就可以在缓冲区找到数据)。因此,内核把中断处理分为两部分:上半部(tophalf)和下半部(bottomhalf),上半部(就是中断服务程序)内核立即执行,而下半部(就是一些内核函数)留着稍后处理。
灯珑LoGin
2024/02/06
5070
Linux的中断下半部机制的对比
吐血整理 | 肝翻 Linux 中断所有知识点
GIC,Generic Interrupt Controller。是ARM公司提供的一个通用的中断控制器。主要作用为:接受硬件中断信号,并经过一定处理后,分发给对应的CPU进行处理。
刘盼
2021/08/25
4.1K1
吐血整理 | 肝翻 Linux 中断所有知识点
ZYNQ XC7Z020的PL PS中断驱动程序编写测试(linux4.14版本下)
ARM和FPGA的交互是这个芯片最重要的部分,PL和PS的交互使用中断是较为快捷的方法,本文使用bram存储数据并通过外部pl端发出中断通知ps端读写数据。程序思路是按键产生中断,按键是直接连到pl端的,驱动产生异步通知,应用开始往BRAM写数据,然后再读取数据(阻塞读取),均打印出来比较
用户9736681
2022/12/05
1.6K0
ZYNQ XC7Z020的PL PS中断驱动程序编写测试(linux4.14版本下)
Linux驱动开发-内核共享工作队列
工作队列常见的使用形式是配合中断使用,在中断的服务函数里无法调用会导致休眠的相关函数代码,有了工作队列机制以后,可以将需要执行的逻辑代码放在工作队列里执行,只需要在中断服务函数里触发即可,工作队列是允许被重新调度、睡眠。
DS小龙哥
2022/04/08
2.1K0
推荐阅读
相关推荐
Linux驱动实践:中断处理中的【工作队列】 workqueue 是什么鬼?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验