02、Linux 中断 API 函数汇总
一、中断号
中断号是计算机用来区分不同硬件信号的唯一编号。当键盘输入、定时器到期或硬盘完成任务等事件发生时,硬件会通过中断号通知CPU,让CPU暂停当前工作,转去处理这些紧急事件。
在Linux系统中,中断号通常用整数(int类型)表示。这个整数能覆盖从简单设备到复杂系统的中断需求。比如早期计算机最多支持15个中断,而现代系统可支持256个甚至更多,用整数存储完全足够。
当硬件触发中断时,Linux内核会根据中断号找到对应的处理程序。系统预先将每个中断号与处理代码绑定,形成类似"电话簿"的中断表(IDT)。这样CPU就能快速定位到正确的处理流程,确保不同设备的中断不会混淆。
除了基础功能,中断号还包含额外信息。例如在多核电脑里,某些中断会被分配到特定CPU核心处理;虚拟机环境下,中断号需要转换才能让虚拟设备正常工作。这些设计让系统既能高效运行,又能适应复杂场景。
总之,中断号就像硬件事件的"身份编号",是连接硬件信号与软件处理的关键纽带。Linux用整数类型管理中断号,既简单直接又能满足各种需求,保证系统稳定运行。
二、request_irq 函数
在 Linux 内核中要想使用某个中断是需要申请的, request_irq 函数用于申请中断, request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。 request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断, request_irq 函数原型如下:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
2
3
4
5
1.2.1. 参数解析
irq(中断号)
irq
是用于标识特定中断的编号,它对应于硬件中断控制器中的某一条中断线。每个中断号都唯一地映射到某个硬件设备或事件上。通过指定 irq
,系统能够准确定位并响应特定的硬件中断请求。
handler(中断处理函数)
当中断发生时,内核会调用由 handler
指定的中断处理函数。这个函数是驱动程序的核心逻辑所在,负责对中断事件进行快速响应和处理。设计良好的中断处理函数应尽可能简洁高效,以减少对系统性能的影响。
flags(中断标志)
flags
是一组控制中断行为的标志位,定义了中断的触发条件、共享模式以及其他特性。这些标志可以在内核头文件 include/linux/interrupt.h
中找到完整的定义。通过选择合适的标志,开发者可以精确控制中断的行为,从而满足不同场景的需求。
name(中断名称)
name
是为中断赋予的一个可读性较高的字符串标识符。设置后,该名称会出现在 /proc/interrupts
文件中,便于调试和监控。一个精心命名的中断描述不仅提升了代码的可维护性,还能帮助开发者快速定位问题。
dev(设备标识)
当 flags
设置为 IRQF_SHARED
时,多个设备可以共享同一条中断线。此时,dev
参数成为区分这些设备的关键标识。通常,dev
被设置为指向设备结构体的指针,并在中断处理函数中作为第二个参数传递,以便识别具体的设备来源。
1.2.2. 返回值
request_irq
函数的返回值用于指示中断申请的结果:
- 0 表示中断申请成功;
- 负值 表示失败,其中
-EBUSY
特别指出目标中断已被其他设备占用。
1.2.3. 常用中断标志详解
IRQF_SHARED:共享中断的优雅协作
当多个设备需要共享同一中断线时,必须显式指定此标志。共享中断的设计体现了资源复用的理念,但同时也要求开发者确保每个设备都能通过 dev
参数被正确区分。这种机制在资源受限的嵌入式系统中尤为常见。
IRQF_ONESHOT:单次触发的专注守护
此标志适用于仅需执行一次的中断场景。一旦中断处理完成,系统将自动禁用该中断,避免重复触发。这种模式适合那些仅需一次性响应的任务,如初始化操作或状态检查。
IRQF_TRIGGER_NONE:无触发的静默等待
该标志表示不指定任何触发条件,通常用于特殊场景下的自定义中断配置。虽然不常见,但它为高级开发者提供了更大的灵活性。
IRQF_TRIGGER_RISING:上升沿触发的敏锐捕捉
上升沿触发意味着中断会在信号从低电平跳变到高电平时被激活。这种触发方式常用于检测瞬间事件,例如按键按下或传感器启动。
IRQF_TRIGGER_FALLING:下降沿触发的精准捕获
与上升沿相反,下降沿触发会在信号从高电平跳变到低电平时触发中断。这种方式同样适用于瞬态事件检测,如按键释放或设备关闭。
IRQF_TRIGGER_HIGH:高电平触发的持续响应
高电平触发意味着只要信号保持在高电平状态,中断就会被持续激活。这种模式适合需要长时间监测的状态变化,例如某些传感器的持续输出。
IRQF_TRIGGER_LOW:低电平触发的沉稳应对
与高电平触发相对,低电平触发会在信号保持低电平期间持续激活中断。它为低功耗设备或特殊信号处理提供了另一种选择。
三、free_irq 函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:
void free_irq(unsigned int irq, void *dev_id)
函数参数与返回值的含义阐释如下:
irq: 此参数代表需要释放的中断资源。在计算机系统中,中断是硬件与操作系统之间沟通的重要桥梁,而“释放中断”则意味着将该中断资源归还给系统,以便其能够被重新分配或进入闲置状态。
dev_id: 当中断被配置为共享模式(IRQF_SHARED)时,此参数扮演了至关重要的角色。它作为一种标识符,用于区分共享同一中断线的不同设备或处理函数。这种机制类似于在一场盛大的宴会上,每位宾客都持有独特的邀请函,以确保他们的身份得以明确。值得注意的是,只有在释放最后一个与该中断相关的处理函数时,中断才会被真正禁用,从而避免对其他共享设备造成干扰。
返回值: 该函数不返回任何值(即返回值为空)。这一设计体现了函数的纯粹性与专注性——它的使命仅在于完成中断的释放操作,而不附带额外的信息或状态反馈。
通过上述描述,我们可以感受到中断管理机制背后的精密逻辑与优雅设计,它不仅保障了系统的高效运行,也展现了工程师们在资源调度与冲突规避方面的智慧结晶。
四、中断处理函数(上半部驱动)
在使用 request_irq
函数申请中断时,需要为中断处理程序指定一个回调函数。该回调函数的原型如下所示:
irqreturn_t (*irq_handler_t)(int, void *);
其中,第一个参数是触发中断的中断号,用于标识具体的硬件中断源;第二个参数是一个通用指针(void *
),通常与 request_irq
函数中的 dev_id
参数保持一致。这一设计不仅允许开发者通过 dev_id
区分共享同一中断线的不同设备,还能够灵活地将该指针指向设备的私有数据结构,从而为中断处理提供上下文支持。
中断处理函数的返回值类型为 irqreturn_t
,它是一个枚举类型,定义如下:
enum irqreturn {
IRQ_NONE = (0 << 0), // 中断未被处理
IRQ_HANDLED = (1 << 0), // 中断已成功处理
IRQ_WAKE_THREAD = (1 << 1), // 唤醒线程化中断处理
};
typedef enum irqreturn irqreturn_t;
2
3
4
5
6
7
从上述定义可以看出,irqreturn_t
提供了三种可能的返回值,每一种都蕴含着不同的语义:
**IRQ_NONE**
:表示当前中断并未被处理。这种情况通常发生在共享中断线的场景中,当某个设备的中断处理函数确认该中断并非由自身触发时,便会返回此值。**IRQ_HANDLED**
:表示中断已被成功处理。这是大多数中断处理函数的标准返回值,表明中断源已被正确识别并处理完毕。**IRQ_WAKE_THREAD**
:这是一种特殊的情况,用于指示内核唤醒线程化的中断处理程序。这种机制常用于需要复杂处理逻辑的场景,避免在硬中断上下文中执行耗时操作。
在实际编程中,中断处理函数的返回值通常采用以下形式:
return IRQ_RETVAL(IRQ_HANDLED);
这里的 IRQ_RETVAL
宏是对返回值的封装,旨在提升代码的可读性和一致性。通过这种方式,开发者可以清晰地表达中断处理的状态,同时确保代码风格符合内核开发的最佳实践。
总结而言,中断处理函数的设计体现了操作系统对硬件资源管理的精妙权衡:既要高效响应硬件事件,又要兼顾灵活性与可扩展性。而 irqreturn_t
枚举类型及其相关机制,则是这一设计理念的具体体现,宛如一位优雅的指挥家,在硬件与软件之间奏响和谐的交响乐章。
五、中断使能与禁止函数
在嵌入式系统开发中,中断管理是一项至关重要的任务。合理地使能或禁止中断,不仅能够确保系统的实时性和稳定性,还能有效避免多任务环境下的资源竞争问题。以下将详细介绍几种常用的中断控制函数及其使用场景。
首先,我们来看两个基础的中断控制函数:
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);
2
这两个函数分别用于使能和禁止指定的中断源,其中 irq
参数表示要操作的具体中断号。需要注意的是,disable_irq
函数在执行时会等待当前正在处理的中断服务程序完成后再返回。因此,调用者必须确保在调用该函数后不会产生新的中断,并且所有已经开始执行的中断处理程序均已退出,以避免潜在的死锁或逻辑错误。
然而,在某些情况下,等待当前中断处理程序完成可能并不是最佳选择。为此,提供了一个更为“急切”的替代方案:
void disable_irq_nosync(unsigned int irq);
与 disable_irq
不同,disable_irq_nosync
在调用后会立即返回,而无需等待当前中断处理程序的执行完成。这种设计为开发者提供了更大的灵活性,但也要求调用者自行评估风险,确保不会因中断状态的不一致而导致系统异常。
上述函数的操作对象是特定的中断源。但在某些场景下,我们需要对整个处理器的中断系统进行全局控制。这时,可以使用以下两个函数:
local_irq_enable();
local_irq_disable();
2
local_irq_enable
用于重新开启当前处理器的全局中断,而 local_irq_disable
则用于关闭全局中断。然而,这种简单的开关操作可能会引发潜在的问题。例如,假设任务 A 调用了 local_irq_disable
来关闭全局中断并计划持续 10 秒。然而,2 秒后,任务 B 开始运行并同样调用了 local_irq_disable
,随后又在 3 秒后通过 local_irq_enable
将全局中断重新打开。此时,全局中断实际上只被关闭了 5 秒,远低于任务 A 的预期。这种行为可能导致任务 A 的逻辑崩溃,甚至引发整个系统的不稳定。
为了避免这种问题,任务 B 在操作全局中断时需要更加“体贴”,不能简单粗暴地直接打开中断,而是应该恢复到之前的中断状态。为此,系统提供了以下两个更为优雅的函数:
local_irq_save(flags);
local_irq_restore(flags);
2
这对函数的设计堪称精妙。local_irq_save
不仅会关闭全局中断,还会将当前的中断状态保存到 flags
中,以便后续恢复。而 local_irq_restore
则负责根据 flags
的值将中断状态还原到调用 local_irq_save
时的状态。这种机制确保了每个任务在操作全局中断时都能够“和平共处”,既不影响其他任务的正常运行,也维护了系统的整体稳定性。
总结而言,中断管理是一门艺术,既需要对底层硬件有深刻的理解,也需要在多任务环境下展现出高度的责任感和协作精神。通过合理使用上述函数,开发者可以在保证系统性能的同时,最大限度地减少潜在的风险和冲突。正如一位优秀的指挥家,能够在复杂的乐章中协调每一个音符,让整个交响乐团和谐共鸣。