当前位置: 首页 > article >正文

Linux 内核学习(3) --- 内核中断机制

目录

      • 中断来源
      • 中断处理程序
        • Linux 中断处理程序架构
      • 获取中断信息
      • ARM GIC
      • 申请和释放中断
      • DTS中的配置

中断来源

根据中断的来源,中断可以分为外部中断内部中断,
内部中断的来源是 CPU 内部(软件中断指令,溢出,除法错误等), 例如操作系统从用户态切换到内核态, 需要借助于CPU的软件中断,
外部中断的中断源来自于外设,由外设提出请求。

根据中断是否可以被屏蔽分为可屏蔽中断不可屏蔽中断(NMI),可屏蔽中断可以通过屏蔽字进行屏蔽,屏蔽后该中断不再得到响应,不可屏蔽中断不能被屏蔽

根据中断入口的跳转方法不同,中断可以分为 向量中断非向量中断 ,采用向量中断的 CPU 通常为不同的中断
分配不同的中断号,当检测到某个中断号到来之后,自动跳转到该中断对应的地址去执行,不同的中断有不同的入口地址

非向量中断多个中断共享一个入口地址,进入该地址后,通过软件判断中断标志来识别具体是哪个中断
(类型于微机原理中提到的中断拓展的做法)

中断处理程序

当响应一个特定的中断的时候,内核会执行一个函数,该函数称为中断处理程序(interrupt handler)或者中断服务例程(interrupt service routine ISR)

linux 内核中,中断处理函数也是普通的C函数, 只不过执行函数要按照特性的类型声明,以便于内核能够按照标准方式传递处理程序的信息,
区别在于:中断服务程序是被内核调用来相应中断的,它运行在中断上下文中,中断上下文也称为原子上下文,不可以被阻塞

中断随时可能发生,那么中断服务程序就有可能随时执行,所以必须保证中断服务程序的快速执行,
这样才能尽可能的恢复中断代码的执行,但是,中断处理程序往往还需要完成其他大量的工作,
比如网络设备的驱动程序:除了对硬件进行应答,还需要把来自硬件的网络数据包拷贝到内存等,因此需要在中断处理时间和 中断完成工作量之间有一个平衡

Linux 中断处理程序架构

下图描述了 Linux 的中断处理机制,为了在中断执行时间尽量短和中断处理需要完成的工作尽量大之间找到一个平衡点,
interrupt
顶半部(top half) 用于完成比较小的紧急功能,往往只是简单读取寄存器状态,并清除中断标志后进行 登记中断 的工作,
登记中断意味着将底半部处理程序挂到底半部的执行队列中去,这样,顶半部的执行速度回很快,可以服务
更多的中断请求

底半部(bottom half) 几乎做了中断处理程序所有的事情,而且可以被新的中断打断,这也是顶半部(top half)和底半部(bottom half)的最大不同,
因为顶半部往往设计成不可中断,底半部相对来说并不是非常紧急的,而且相对比较耗时,不在硬件中断服务
程序中执行

获取中断信息

Linux 中可以通过 cat /proc/interrupts 文件获取系统的中断统计信息,并统计处每个中断号上中断
在每个CPU上发生的次数,具体如下所示:
中断说明
读取到的内容从左到右,分别为:1、逻辑中断号,2、中断在各CPU发生的次数,3、中断所属设备类名称,4、硬件中断号,5、中断处理函数

ARM GIC

在 ARM 多核系统中最常用的中断控制器是 GIC(Generic Interrupt Controller),它支持三种类型的中断
ARM_GIC

SGI(software Generated Interrupt):
软件产生的中断,可以用于多核之间的通信,一个CPU 可以通过
GIC 的寄存器给另一个 CPU 产生中断,多核通信的 IPI_WAKEUP, IPI_TIMER … 都是由 SGI 产生

PPI(Priavte Peripheral Interrupt):
某个 CPU 私有外设的中断,这类中断只能交给绑定的CPU

SPI(Shared Peripheral Interrupt):
共享外设中断,这类外设的中断可以路由到任何一个CPU

(设置在哪个CPU上触发中断)
对于 SPI 类型的中断,内核可以通过下面的 API 设定触发中断的 CPU 核心:

extern int irq_set_affinity(unsigned int irq, const struct cpumask *m)

在 ARM Linux 默认情况下,中断都是在 CPU0上产生的,比如可以通过下面的代码将中断设置到 CPU i上去

irq_set_affinity(irq, cpumaks_of(i));

申请和释放中断

使用内核提供的 request_irq()free_irq() 函数注册中断处理函数和释放中断:
request_irq 函数注册一个中断处理程序,并且激活相应的中断线,以处理中断:

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

irq:申请的硬件中断号
handler:向系统登记的中断处理函数(顶半部),是一个回调函数,中断发生时,系统调用到这个函数,dev 参数将传递给它
中断处理程序的原型:

typedef irqreturn_t (*irq_handler_t)(int, void*);

irqflags:中断的处理属性,可以指定中断的触发方式和处理方式,触发方式可以选择为:

  • IRQF_TRIGGER_RISING(上升沿)
  • IRQF_TRFGGER_FALLING(下降沿)
  • IRQF_TRIGGER_HIGH(高电平)
  • IRQF_TRIGGER_LOW(低电平)
    在处理方式方面, 如果设置了 IRQF_SHARED,则表示多个设备共享中断, dev 是传递给中断服务程序的私有数据,
    一般设置为这个设备的设备结构体或者为 NULL

也可以是下面的标志:

  • IRQF_DISABLED:意味着内核在处理中断程序本身期间,要禁止所有其他的中断
  • IRQ_SAMPLE_RANDOM
  • IRQF_TIMER: 为系统定时器中断准备
  • IRQF_SHARED:可以在多个中断处理之间共享中断线,同一个给定线上注册的每个处理函数必须指定这个标志,需要内部判断中断的类型

request_irq 返回 0 表示成功,用 -EINVAL 表示中断号无效或者函数处理指针为 NULL,
返回 -EBUSY 表示中断号已经被占用,而且不能共享

也可以使用 devm 接口

int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *devid);

typedef irqreturn_t (*irq_handler_t)(int, void*);
typedef int irqreturn_t;

devm 开头的 API 申请的是 Managed 资源,一般不需要在出错处理或者 remove 接口里再显式的释放, 有点类似于
JAVA 的垃圾回收机制

  • return 0: 执行成功
  • 非 0 值:表示有错误发生,在这种情况下,指定的中断处理程序不会被注册,最常见的错误是 -EBUSY,
    它表示在给定的中断线已经在使用 (或者当前的用户没有指定 IRQ_SHARED标志)
void free_irq(unsigned int irq, void *dev);

如果指定的中断线不是共享的,那么 函数删除程序的同时将要禁用这条中断线
如果中断线是共享的,那么仅删除 dev 代表的处理程序,这条中断线本身只有在删除了最后一个处理程序时,才会被禁用

在设备驱动中可以简单的通过下面的接口获取 dts 中配置的中断号

struct platform_device *pdev = to_platform_device(dev);
mdev->irq  = platform_get_irq(pdev, 0);

DTS中的配置

interrupt 属性的一般配置格式如下:

interrupt-parent = <&中断控制器节点>;
interrupts = <中断类型 中断号 中断触发方式>;

//比如在 GPU 中断的配置:
gpu:gpu@30400000 {
	compatible = "arm,mali-g52","arm,mali-bifrost";
	status = "okay";
	reg = <0x30400000 0x10000>;
	interrupts = <GIC_SPI INT_GPU_JOB IRQ_TYPE_LEVEL_HIGH>,
					<GIC_SPI INT_GPU_MMU IRQ_TYPE_LEVEL_HIGH>,
					<GIC_SPI INT_GPU_GPU IRQ_TYPE_LEVEL_HIGH>,
					<GIC_SPI INT_GPU_IRQEVENT IRQ_TYPE_LEVEL_HIGH>;

	interrupts-names = "JOB","MMU","GPU","EVENT"

  1. interrupt-parent: 指定中断控制器节点的 phandle(一种标识节点的唯一标识符),这个节点通常是中断控制器(如GICPIC等)的设备树节点.

  2. interrupts: 描述中断的详细信息,通常是一个或多个三元组的列表,每个三元组的含义如下:

  • 中断类型: 指示中断的类型
  • 中断号: 指示该中断线上的具体中断号。
  • 中断触发方式: 指示中断的触发方式,例如电平触发、边沿触发等

关于中断类型,在ARM架构中,尤其是在使用Generic Interrupt Controller (GIC)的情况下:
0 表示SPI (Shared Peripheral Interrupts)
1 表示PPI (Private Peripheral Interrupts)

SPI是共享的外部中断,可以连接到多个外设,而PPI是私有的,通常与特定的CPU核心相关联。

如果有四个参数,表示有一个可选的类型中断 flag
这是一个可选的字段,用于提供额外的中断控制信息。这些flags可以包括中断的优先级、中断的安全属性(在安全和非安全环境中)、电源管理相关信息等。


http://www.kler.cn/a/462977.html

相关文章:

  • 【Pytorch报错】AttributeError: cannot assign module before Module.__init__() call
  • 给vscode的新项目选择虚拟环境
  • Rust 泛型、特征与生命周期详解
  • websocket-sharp:.NET平台上的WebSocket客户端与服务器开源库
  • C++ —— 模板类扩展
  • 虚拟机中的时统卡功能和性能调优
  • 单片机-- 51-keil使用查看空间占用
  • C++ 设计模式:状态模式(State Pattern)
  • FristiLeaks_1.3靶场渗透
  • [羊城杯 2024]1z_misc
  • [创业之路-230]:《华为闭环战略管理》-5-华为的组织架构与业务架构是不同的,组织架构是为业务架构服务
  • Docker网络与数据卷持久化
  • 三、AI知识(自然语言处理)
  • 记录uniapp组件swiper自适应高度
  • 期权懂|个股期权的流动性如何?
  • 生成埃里克卡特曼人工智能语音听起来像他或配音视频
  • PyTorch transpose、permute、view和einops.rearrange
  • LeetCode 热题 100_二叉树的直径(40_543_简单_C++)(二叉树;递归)
  • pip安装paddle失败
  • 【AIGC篇】“智” 造元宇宙新境:AIGC 于虚拟现实的奇幻征途
  • 亚马逊国际站商品爬虫:Python实战指南
  • 【操作系统进程与线程管理:从PCB到多线程并发编程】
  • 基本语法与数据结构:全面掌握 Java 的基础
  • STM32使用UART发送字符串与printf输出重定向
  • 自动驾驶---Tesla FSD Version 13
  • Java排序算法全解析