03、Linux 注册中断实验
一、中断的request_irq注册接口
<include/linux/interrupt.h>
static inline int
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
2
3
4
request_irq()是Linux系统中较老的中断注册函数。从Linux 2.6.30版本开始,新增了request_threaded_irq()这个通过线程处理中断的新接口。这种线程化中断处理方式是实时Linux系统开发中的新功能,主要目的是减少中断处理对系统实时性能的延迟影响。
简单来说,旧版函数把中断处理全放在内核中断上下文中执行,而新版函数允许将部分处理转移到独立线程中完成。这样可以避免长时间的中断处理占用CPU,从而提升系统对实时任务的响应能力。
二、request_threaded_irq
<include/linux/interrupt.h>
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
2
3
4
5
request_threaded_irq() 参数说明
- irq(中断号) 表示软件分配的中断编号,与硬件物理中断号不同,是系统内部用来标识中断线的编号。
- handler(主处理函数) 中断触发时最先执行的函数。若未指定主处理函数(设为NULL),但提供了线程函数(thread_fn),则系统会自动调用默认主处理函数
irq_default_primary_handler()
。 - thread_fn(线程处理函数) 用于在内核线程中执行的处理函数。若设置此参数,系统会自动创建线程。 注意:主处理函数(handler)和线程函数(thread_fn)不能同时为空。
- irqflags(中断标志) 设置中断行为的标志位(如是否允许中断嵌套等)。
- devname(设备名称) 用于标识中断来源的名称,方便调试和管理。
- dev_id(设备参数) 需要传递给中断处理函数的参数(如设备结构体指针)。
关键点总结
- 主处理函数负责快速响应中断,线程函数用于耗时操作(需切换到线程执行)。
- 若不指定主函数,必须提供线程函数,反之亦然。
- 中断号是系统分配的软件编号,而非硬件直接提供的物理编号。
2.1、IRQF_的中断标志
Irqflags 有哪些:
/*
* These correspond to the IORESOURCE_IRQ_* defines in
* linux/ioport.h to select the interrupt line behaviour. When
* requesting an interrupt without specifying a IRQF_TRIGGER, the
* setting should be assumed to be "as already configured", which
* may be as per machine or firmware initialisation.
*/
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend. Does not guarantee
* that this interrupt will wake the system from a suspended
* state. See Documentation/power/suspend-and-interrupts.txt
* IRQF_FORCE_RESUME - Force enable it on resume even if IRQF_NO_SUSPEND is set
* IRQF_NO_THREAD - Interrupt cannot be threaded
* IRQF_EARLY_RESUME - Resume IRQ early during syscore instead of at device
* resume time.
* IRQF_COND_SUSPEND - If the IRQ is shared with a NO_SUSPEND user, execute this
* interrupt handler after suspending interrupts. For system
* wakeup devices users need to implement wakeup detection in
* their interrupt handlers.
*/
#define IRQF_SHARED 0x00000080
#define IRQF_PROBE_SHARED 0x00000100
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000
#define IRQF_NO_SUSPEND 0x00004000
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
① 触发类型
这些标志设置中断触发的条件:
**IRQF_TRIGGER_NONE**
:不指定任何触发条件(默认状态)。**IRQF_TRIGGER_RISING**
:当信号线从低到高变化时触发。**IRQF_TRIGGER_FALLING**
:当信号线从高到低变化时触发。**IRQF_TRIGGER_HIGH**
:信号线保持高电平时触发。**IRQF_TRIGGER_LOW**
:信号线保持低电平时触发。**IRQF_TRIGGER_MASK**
:允许组合多种触发条件(比如同时设置上升沿+下降沿)。
② 共享与特殊功能
这些标志控制中断的高级行为:
**IRQF_SHARED**
:允许多个设备共享同一个中断线。**IRQF_PROBE_SHARED**
:提示系统可能存在共享冲突需要检测。**IRQF_PERCPU**
:让中断只分配给特定CPU核心。**IRQF_NOBALANCING**
:禁止系统自动调整中断在CPU间的分配。**IRQF_IRQPOLL**
:将中断转为轮询模式(用于某些特殊设备)。**IRQF_ONESHOT**
:处理完中断后不再自动重新启用。**IRQF_NO_SUSPEND**
:即使系统休眠,该中断仍保持启用状态。**IRQF_FORCE_RESUME**
:系统恢复时强制启用中断(即使用了NO_SUSPEND
)。**IRQF_NO_THREAD**
:禁止将中断处理转为线程模式。**IRQF_EARLY_RESUME**
:系统恢复时优先处理该中断。**IRQF_COND_SUSPEND**
:若与其他共享中断同时存在,休眠时仍会处理它。
2.2、IRQS_的中断标志
kernel/irq/internals.h
/*
* Bit masks for desc->core_internal_state__do_not_mess_with_it
*
* IRQS_AUTODETECT - autodetection in progress
* IRQS_SPURIOUS_DISABLED - was disabled due to spurious interrupt
* detection
* IRQS_POLL_INPROGRESS - polling in progress
* IRQS_ONESHOT - irq is not unmasked in primary handler
* IRQS_REPLAY - irq is replayed
* IRQS_WAITING - irq is waiting
* IRQS_PENDING - irq is pending and replayed later
* IRQS_SUSPENDED - irq is suspended
*/
enum {
IRQS_AUTODETECT = 0x00000001,
IRQS_SPURIOUS_DISABLED = 0x00000002,
IRQS_POLL_INPROGRESS = 0x00000008,
IRQS_ONESHOT = 0x00000020,
IRQS_REPLAY = 0x00000040,
IRQS_WAITING = 0x00000080,
IRQS_PENDING = 0x00000200,
IRQS_SUSPENDED = 0x00000800,
IRQS_TIMINGS = 0x00001000,
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
以下是Linux内核中中断描述符(irq_desc)的一些状态标志位说明:
常用状态标志:
IRQS_ONESHOT
这个标志表示中断会"只触发一次处理"。
它通常在注册中断时通过设置
IRQF_ONESHOT
选项开启。处理完成后需要特别注意收尾工作(参考
irq_finalize_oneshot()
函数)。IRQS_PENDING
当中断被标记为"待处理"时会启用这个标志。
通常发生在两种情况: a) 中断处理程序未配置 b) 中断当前被禁用(如设置了
IRQD_IRQ_DISABLED
标志)标记后该中断会被暂时挂起,等待后续处理。
其他状态标志:
- IRQS_AUTODETECT:系统正在自动检测中断状态
- IRQS_SPURIOUS_DISABLED:检测到无效中断并已禁用
- IRQS_POLL_INPROGRESS:正在轮询执行中断处理
- IRQS_REPLAY:需要重新触发中断
- IRQS_WAITING:中断处于等待状态
- IRQS_SUSPENDED:中断被暂停
关键点说明:
- 当处理
IRQS_ONESHOT
中断时,必须确保处理完成后正确清理状态(如调用相关函数)。 IRQS_PENDING
通常出现在中断被禁用或未配置处理逻辑时,系统会暂时挂起该中断并记录状态。
三、实验代码
说明:
::: 硬件IO表:https://lceda001.feishu.cn/wiki/UN0pw7Y1KixKirkMh0rcP3Tandc
操作第七个管脚触发中断,对应GPIO1_A4 ::: ::: 引脚编号计算:
公式1:GPIO 小组编号计算公式:number = group * 8 + X
其中:group为小组号(A为0,B为1,C为2,D为3)
公式2:GPIO pin 脚计算公式:pin = bank * 32 + number
其中:bank 为组号,number由公式1计算得来
故:GPIO1_A4
1*32+0*8+4 = 36 :::
实验代码:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_PIN 36
// 中断处理函数
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt occurred on GPIO %d\n", GPIO_PIN);
printk(KERN_INFO "This is irq_handler\n");
return IRQ_HANDLED;
}
static int __init interrupt_init(void)
{
int irq_num;
printk(KERN_INFO "Initializing GPIO Interrupt Driver\n");
// 将GPIO引脚映射到中断号
irq_num = gpio_to_irq(GPIO_PIN);
printk(KERN_INFO "GPIO %d mapped to IRQ %d\n", GPIO_PIN, irq_num);
// 请求中断
if (request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "irq_test", NULL) != 0) {
printk(KERN_ERR "Failed to request IRQ %d\n", irq_num);
// 请求中断失败,释放GPIO引脚
gpio_free(GPIO_PIN);
return -ENODEV;
}
return 0;
}
static void __exit interrupt_exit(void)
{
int irq_num = gpio_to_irq(GPIO_PIN);
// 释放中断
free_irq(irq_num, NULL);
printk(KERN_INFO "GPIO Interrupt Driver exited successfully\n");
}
module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("linux");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
四、实验截图
insmod interrupt.ko
驱动加载打印:
拉高GPIO,触发中断服务程序,打印如下: