文章目录
1.中断的基本概念
在 Linux 系统中,总中断(interrupt)是一个重要的概念,用于处理异步事件,如硬件设备请求或定时器事件。以下是总中断的基本概念:
中断的定义
中断是一种信号,它会打断当前正在执行的程序流,转而执行中断处理程序(Interrupt Service Routine, ISR)或中断处理器。
中断的分类
中断通常分为以下几类:
- 硬件中断:由硬件设备触发的中断,如键盘输入、网络数据包到达等。
- 软件中断:由软件触发的中断,如系统调用、异常等。
中断向量表
中断向量表是一个数组,其中每个元素都是一个指向特定中断处理程序的指针。当中断发生时,CPU 根据中断号在中断向量表中查找相应的处理程序。
中断处理过程
中断处理的基本步骤如下:
- 保存上下文:保存当前执行的程序的状态,以便中断处理完成后能够恢复。
- 查找中断处理程序:通过中断向量表找到对应的中断处理程序。
- 执行中断处理程序:执行中断处理程序,处理中断请求。
- 恢复上下文:恢复之前保存的程序状态,继续执行被中断的程序。
中断优先级
在多中断源系统中,中断有优先级的概念。高优先级中断可以打断低优先级中断的处理。这有助于确保关键事件得到及时处理。
中断屏蔽
为了防止某些关键代码段被中断,可以临时屏蔽中断。屏蔽中断可以防止中断处理程序在特定代码段执行期间被调用,从而保证代码段的原子性。
中断嵌套
中断嵌套是指在处理一个中断时,又发生了另一个中断。一般情况下,只有优先级更高的中断可以嵌套发生。Linux 内核通过中断控制器来管理中断嵌套。
中断控制器
中断控制器是管理中断的硬件组件,它负责接收中断信号并向 CPU 发出中断请求。常见的中断控制器有 PIC(可编程中断控制器)、APIC(高级可编程中断控制器)等。
理解和管理中断对操作系统的实时性和响应速度至关重要。Linux 内核提供了丰富的接口和机制来处理中断,使得系统能够高效地处理各种异步事件。
2.中断上下文
在 Linux 操作系统中,中断上下文(Interrupt Context)指的是当处理硬件中断时,系统所处的特定状态和环境。中断上下文与进程上下文不同,它不是在用户进程的上下文中执行的,而是在内核空间执行。中断上文:完成尽可能少且比较急的任务,特点就是相当快。 中断下文:处理比较耗时的任务。
中断上下文的主要特点
- 无进程关联
- 中断处理程序在内核空间运行,不与任何用户进程直接关联。
- 不可阻塞
- 中断处理程序不能执行会导致睡眠或阻塞的操作,因为中断处理必须尽快完成,以便处理下一个中断。
- 有限的工作量
- 中断处理程序应该执行尽量少的工作,通常只处理紧急任务,然后将复杂或耗时的任务推迟到稍后执行(如通过软中断或工作队列)。
中断上下文的限制
- 不能睡眠
- 中断处理程序不能调用可能导致进程睡眠的函数,如阻塞式的内存分配函数。
- 有限的内核 API
- 许多内核 API 在中断上下文中不可用,因为它们可能会导致睡眠或长时间阻塞。
- 优先级和嵌套
- 中断处理程序可能会被更高优先级的中断打断,因此需要考虑中断优先级和嵌套问题。
顶半部和底半部
为了提高中断处理的效率,Linux 内核将中断处理分为两部分:
- 顶半部(Top Half):直接响应中断,执行最紧急的任务。
- 底半部(Bottom Half):推迟执行非紧急任务,可以通过软中断(Softirq)、任务队列(Tasklet)或工作队列(Workqueue)来实现。
3.中断子系统架构
中断控制器GIC
通用中断控制器(Generic Interrupt Controller,GIC)是一个在许多ARM架构处理器中使用的硬件组件,用于管理和处理中断。GIC的主要目的是帮助CPU高效地处理中断请求,支持复杂的中断优先级和分配策略。以下是GIC的主要特点和功能:
GIC可以管理来自多个中断源的中断请求,包括外部设备、定时器等。
GIC负责将中断分配给适当的CPU核进行处理。在多核系统中,这意味着它可以平衡中断负载,提高系统性能。
GIC允许设置中断的优先级,以便更高优先级的中断可以打断低优先级的中断处理。
GIC可以屏蔽某些中断,使它们暂时不被处理,直到屏蔽被解除。
GIC 的层级结构
1. Distributor(分配器)
- 管理所有中断源,确定哪个中断应该被处理,以及哪个 CPU 应该处理这个中断。
- 分配器处理全局的中断配置和分配任务,将中断请求分配给适当的 CPU 核。
2. Redistributor(重分配器)
- 这是 GICv3 和更高版本中引入的组件。
- Redistributor 进一步优化中断分配,尤其是在 NUMA(非统一内存访问)系统中,它可以将中断请求分配给更合适的 CPU 核,减少中断处理的延迟。
3. CPU Interface(CPU 接口)
- 每个 CPU 核都有一个 CPU 接口,用于接收来自 Distributor 或 Redistributor 的中断请求。
- CPU 接口负责将中断信号传递给 CPU,并处理中断的优先级和屏蔽。
中断控制器级联
在计算机系统中,级联中断控制器是一种设计,用于扩展中断源的数量,超出单个中断控制器的处理能力。这种设计通过连接多个中断控制器,使得一个中断控制器可以管理另一个中断控制器,从而实现更多的中断输入。以下是有关级联中断控制器的详细介绍:
基本概念
- 级联中断控制器
- 多个中断控制器通过级联连接,形成一个层次结构。上级(主)中断控制器可以管理下级(从)中断控制器。
- 主/从控制器
- 主中断控制器(Master PIC)直接连接到CPU,并处理来自从中断控制器(Slave PIC)的中断请求。
- 从中断控制器连接到主中断控制器,处理来自多个设备的中断请求。
级联中断控制器的工作原理
- 中断请求传递
- 当从中断控制器接收到一个设备的中断请求时,它会将该请求传递给主中断控制器。
- 主中断控制器接收到从中断控制器的请求后,将该请求传递给CPU。
- 中断处理流程
- CPU接收到中断请求后,查询主中断控制器以确定中断源。
- 如果中断源来自从中断控制器,CPU会进一步查询从中断控制器以确定具体的中断源设备。
流程如图
中断号概念
中断号(Interrupt Number)是一个整数,用于唯一标识计算机系统中的特定中断请求(IRQ)。中断号帮助操作系统识别和处理来自不同硬件设备或软件的中断请求。以下是中断号的基本概念和相关内容:
基本概念
-
中断请求(IRQ)
- 中断请求是硬件设备或软件向 CPU 发出的信号,表示需要 CPU 的注意或服务。
- 每个中断请求都有一个唯一的中断号。
-
中断向量
- 中断向量是与中断号相关联的一个指针,指向中断处理程序(Interrupt Service Routine, ISR)。
- 中断向量表(Interrupt Vector Table, IVT)是一个数据结构,其中存储了所有中断号及其对应的中断处理程序地址。
中断号的作用
-
唯一标识
- 中断号唯一标识系统中的每一个中断源,确保 CPU 能正确区分和处理不同的中断请求。
-
查找中断处理程序
- 当中断发生时,CPU 使用中断号在中断向量表中查找对应的中断处理程序,然后跳转到该程序执行中断处理。
中断号的分配
-
硬件中断
- 硬件设备(如键盘、鼠标、网络接口等)通过特定的中断号向 CPU 发送中断请求。
- 不同硬件设备通常有固定的中断号,这些中断号在系统启动时由 BIOS 或固件初始化。
-
软件中断
- 软件中断由操作系统或应用程序触发,用于执行特定的系统服务或功能。
- 软件中断通常也有特定的中断号,由操作系统定义和管理。
ARM 架构和 GIC
在 ARM 架构中,使用 GIC(Generic Interrupt Controller)管理中断。中断号在 GIC 中被称为中断 ID。GIC 支持大量的中断源,可以支持数百甚至上千个中断号:
- SPI(Shared Peripheral Interrupts):共享外围设备中断,通常分配给外部设备。32-1020之间
- PPI(Private Peripheral Interrupts):私有外围设备中断,通常分配给每个 CPU 的私有设备。中断号在16-31号之间
- SGI(Software Generated Interrupts):软件生成中断,通常用于 CPU 间通信。中断号在0-15之间
总体架构图
对驱动工程师来讲,只需关注内核驱动驱动层相关即可,具体CPU中断处理相关,一般由Soc厂商进行指定
4.申请一个GPIO中断
注:需要触发相关硬件,因为设备受限,没有能够触发的GPIO引脚。无法做具体的实验。
触发GPIO中断的通用流程
找到一个可以被中断的 GPIO
- 通常,外设(如触摸屏、键盘、鼠标等)会连接到 GPIO 引脚。这些引脚可以配置为中断源,用于检测事件(如按键按下、触摸屏触摸等)。
配置 GPIO 引脚
- 将 GPIO 引脚配置为输入模式,并设置中断触发条件(如上升沿触发)。这可以通过设备树或内核代码进行配置。
注册中断处理程序
- 在内核中注册一个中断处理程序(ISR),使得在中断发生时能够执行相应的处理逻辑。使用
request_irq()
函数来注册中断处理程序。
触发中断
- 当外部事件发生时(如按键按下),GPIO 引脚的电平状态发生变化(如上升沿触发)。硬件会触发中断请求(IRQ)。
中断控制器(GIC)
- 中断请求发送给中断控制器(如 ARM 的通用中断控制器 GIC)。GIC 负责管理和分配中断请求,将中断请求发送给 CPU。
CPU 处理中断
- CPU 收到中断请求后,会暂停当前执行的任务,保存当前的执行上下文,然后转移到中断处理程序。
- CPU 执行中断处理程序(ISR),处理具体的中断事件。
恢复正常执行
- 中断处理程序执行完毕后,CPU 恢复之前的执行上下文,继续执行被中断的任务。
通用示例代码
设备树节点配置
假设一个设备树节点定义了一个按钮,连接到 GPIO 控制器的第 16 个引脚,并且设置为下降沿触发中断。
dts
/ {
gpio_keys {
compatible = "gpio-keys";
my_button {
label = "My Button";
gpios = <&gpio1 16 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
};
};
};
必要的头文件
c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
中断处理程序
c
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
printk(KERN_INFO "GPIO interrupt occurred\n");
// 处理中断的具体逻辑
return IRQ_HANDLED;
}
初始化函数
c
static int __init gpio_irq_init(void) {
int irq_number;
int gpio_number;
int result;
struct device_node *node;
// 获取设备树节点
node = of_find_node_by_name(NULL, "my_button");
if (!node) {
printk(KERN_ERR "Failed to find device tree node\n");
return -ENODEV;
}
// 获取 GPIO 编号
gpio_number = of_get_named_gpio(node, "gpios", 0);
if (!gpio_is_valid(gpio_number)) {
printk(KERN_ERR "Invalid GPIO number\n");
return -EINVAL;
}
// 请求 GPIO 引脚
result = devm_gpio_request_one(NULL, gpio_number, GPIOF_IN, "my_button");
if (result) {
printk(KERN_ERR "Failed to request GPIO\n");
return result;
}
// 获取中断号
irq_number = gpio_to_irq(gpio_number);
if (irq_number < 0) {
printk(KERN_ERR "Failed to get IRQ number for GPIO\n");
return irq_number;
}
// 申请中断
result = request_irq(irq_number, gpio_irq_handler, IRQF_TRIGGER_FALLING, "my_button", NULL);
if (result) {
printk(KERN_ERR "Failed to request IRQ\n");
return result;
}
printk(KERN_INFO "GPIO interrupt initialized successfully\n");
return 0;
}
清理函数
c
static void __exit gpio_irq_exit(void) {
int irq_number;
int gpio_number;
struct device_node *node;
// 获取设备树节点
node = of_find_node_by_name(NULL, "my_button");
if (node) {
// 获取 GPIO 编号
gpio_number = of_get_named_gpio(node, "gpios", 0);
if (gpio_is_valid(gpio_number)) {
// 获取中断号
irq_number = gpio_to_irq(gpio_number);
if (irq_number >= 0) {
// 释放中断
free_irq(irq_number, NULL);
}
}
}
printk(KERN_INFO "GPIO interrupt uninitialized\n");
}
模块入口和出口
c
module_init(gpio_irq_init);
module_exit(gpio_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple GPIO IRQ example");
request_irq函数介绍
request_irq
函数在 Linux 内核中用于注册中断处理程序。它是设备驱动程序中常用的函数,用于为特定中断向量分配中断处理程序。
request_irq
函数在 Linux 内核中用于注册中断处理程序。它是设备驱动程序中常用的函数,用于为特定中断向量分配中断处理程序。以下是 request_irq
函数的详细介绍和一个与相机设备相关的示例。
函数原型
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
参数说明
- irq: 中断号,表示你要注册的中断向量。
- handler: 中断处理程序,当指定的中断发生时,系统将调用这个处理程序。
- flags : 标志,用于指定中断类型和处理选项。常见的标志包括
IRQF_TRIGGER_RISING
(上升沿触发),IRQF_TRIGGER_FALLING
(下降沿触发),IRQF_SHARED
(共享中断)等。 - name: 注册中断处理程序的名称,通常是设备的名称,用于调试和标识。
- dev: 设备的私有数据指针,通常指向设备结构体。
返回值
- 成功时返回 0。
- 失败时返回负的错误代码。
request_irq向内核申请中断步骤
-
查找中断描述符:
-
内核中每个中断源都有一个对应的
irq_desc
结构体,包含了中断的各种信息。request_irq
函数首先查找并获取对应的irq_desc
结构体。struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
-
-
检查并设置中断标志:
- 根据传入的标志
flags
,检查中断触发类型(上升沿、下降沿等)并设置相关属性。
- 根据传入的标志
-
分配和初始化中断处理程序:
-
创建并初始化
irqaction
结构体,这个结构体包含中断处理程序和其他相关信息。struct irqaction *action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;action->handler = handler;
action->flags = flags;
action->name = name;
action->dev_id = dev;
-
-
注册中断处理程序:
-
将初始化的
irqaction
结构体添加到irq_desc
的action
链表中。这一步将中断号与处理程序关联起来。ret = setup_irq(irq, action);
if (ret) {
kfree(action);
return ret;
}
-
-
使能中断:
-
根据具体的硬件平台和中断控制器(如 GIC),使能中断。这通常通过调用中断控制器相关的函数来完成。
irq_enable(irq);
-
irq_desc
:描述中断源的结构体,包含中断号、状态、处理程序链表等信息。irqaction
:描述中断处理程序的结构体,包含处理程序指针、标志、设备信息等。