一、request_irq(...)
request_irq
函数主要用于硬中断相关操作,它的核心作用是把一个中断处理函数和特定的中断号进行绑定。当硬件设备触发该中断号对应的中断时,内核就会调用绑定的中断处理函数,像 irqhandler_func
这类。
此函数在多种硬件设备驱动中都会被使用,不只是系统时钟(GP Timer)驱动。在驱动开发里,只要涉及到硬件中断处理,一般都会借助 request_irq
来注册中断处理函数。
其主要功能包含以下方面:
- 注册中断处理函数:把指定的中断处理函数与特定中断号关联起来,让内核在对应中断发生时能调用该处理函数。
- 中断资源管理:对中断资源进行管理,像检查中断号是否可用、将新申请的中断信息添加到内核的中断管理数据结构(例如 IRQ 链表)中。
- 中断标志处理:依据传入的中断标志参数(如
IRQF_DISABLED
、IRQF_SHARED
等)对中断的行为进行配置。
如果 request_irq
函数申请中断成功,会返回 0;若申请失败,则返回一个负的错误码,以此来表明具体的错误原因。
具体内核源码设计如下:
参数irq对于中断号:
- 取值是从 0--16640,系统已经使用的是 0-31,其中 IRQ9/10/15 系统保留;
- 用户用于自定义中断:32--16640。
如request_irq(IRQ_LCDC_INT, lcdc_isr, IRQF_DISABLED, "LCDC", &lcdc);
什么时候才执行lcdc_isr, 呢?
在
request_irq(IRQ_LCDC_INT, lcdc_isr, IRQF_DISABLED, "LCDC", &lcdc);
中,当中断号为IRQ_LCDC_INT
的中断事件发生时,就会执行lcdc_isr
中断处理函数。具体来说,当与之相关的硬件设备(这里是 LCDC 设备 )产生中断信号,且该中断信号被 CPU 检测和响应后,系统就会调用已注册的lcdc_isr
函数来处理这个中断事件。
IRQF_DISABLED
:表示中断处理程序是快速处理程序。当该标志位被设置,在调用此中断处理函数时,会屏蔽掉所有其他中断。这样做是为了确保当前中断处理能不受干扰地快速执行,适用于那些需要立即响应且处理过程不能被打断的紧急中断场景。比如一些关键硬件的状态变化中断,必须尽快处理且不能被其他中断干扰。IRQF_SAMPLE_RANDOM
:该中断可用于为系统提供随机数相关的熵源。系统在生成随机数时,会利用这类中断提供的信息,增加随机数的随机性和不确定性。IRQF_SHARED
:意味着多个设备可以共享同一个中断号。当设置此标志,多个设备可以共用一条中断线。在中断发生时,内核会循环调用该中断线上注册的所有中断处理函数,由每个函数自行判断是否是自己对应的设备产生的中断 。比如 PCI 总线上多个设备可能共享一个中断号。IRQF_PROBE_SHARED
:用于探测共享中断的情况。当调用者预期可能会出现共享中断的不匹配等情况时设置此标志,帮助系统更好地处理共享中断相关的探测和管理。IRQF_TIMER
:专用于时钟中断相关场景。时钟中断是系统中很重要的一种中断,用于维护系统时间、调度任务等,此标志明确该中断是用于时钟相关功能。IRQF_PERCPU
:表示该中断会在每个 CPU 周期执行。适用于一些需要在每个 CPU 上都单独进行处理的中断情况,比如某些与 CPU 本地状态或资源相关的中断处理。IRQF_NOBALANCING
:该标志表示此中断不参与中断负载均衡。在多 CPU 系统中,内核通常会尝试将中断负载均衡到各个 CPU 上,但设置此标志的中断不会参与这种均衡操作。IRQF_IRQPOLL
:在共享中断的场景下,用于根据注册时间等因素来判断中断处理顺序等相关操作,辅助管理共享中断的处理逻辑。IRQF_ONESHOT
:单次触发中断,即中断处理函数执行一次后,该中断就不再触发,除非再次进行相关设置或硬件重新产生触发条件。IRQF_TRIGGER_NONE
:表示无触发条件的中断,一般较少使用,更多是作为标志位组合等用途。IRQF_TRIGGER_RISING
:指定中断触发类型为上升沿有效,即当硬件信号从低电平变为高电平时,触发中断。IRQF_TRIGGER_FALLING
:指定中断触发类型为下降沿有效,即当硬件信号从高电平变为低电平时,触发中断。IRQF_TRIGGER_HIGH
:指定中断触发类型为高电平有效,只要硬件信号处于高电平状态,就会触发中断。IRQF_TRIGGER_LOW
:指定中断触发类型为低电平有效,只要硬件信号处于低电平状态,就会触发中断。IRQF_TRIGGER_MASK
:是上升沿、下降沿、高电平、低电平触发标志位的组合掩码,用于方便地对这几种触发类型标志位进行批量操作或判断 。IRQF_TRIGGER_PROBE
:用于触发式检测中断相关操作,帮助系统探测和管理中断触发条件等情况。
【代码案例】
requestirq.c
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/slab.h>
// 定义一个结构体来包含额外的参数
struct my_irq_data {
int extra_param1;
char *extra_param2;
};
static int irq = 10; // 中断号
#define IRQF_DISABLED 0x00000020 // 中断禁止
// 定义中断处理函数
static irqreturn_t irqhandler_func(int irq, void *dev_id) {
struct my_irq_data *data = (struct my_irq_data *)dev_id;
printk(KERN_INFO "调用自定义中断函数:irqhandler_func(...)函数.\n");
printk(KERN_INFO "打印输出对应的中断号为:%d\n", irq);
printk(KERN_INFO "额外参数1的值为:%d\n", data->extra_param1);
printk(KERN_INFO "额外参数2的值为:%s\n", data->extra_param2);
printk(KERN_INFO "退出自定义中断函数:irqhandler_func(...)函数.\n");
return IRQ_NONE;
}
static struct my_irq_data *my_data;
static int __init setupirq_initfunc(void) {
int rst = 0;
printk(KERN_INFO "调用内核初始化模块函数:setupirq_initfunc(...)函数.\n");
// 分配内存给结构体
my_data = kmalloc(sizeof(struct my_irq_data), GFP_KERNEL);
if (!my_data) {
printk(KERN_ERR "内存分配失败\n");
return -ENOMEM;
}
// 初始化额外的参数
my_data->extra_param1 = 42;
my_data->extra_param2 = "Hello, World!";
// 调用 request_irq 申请中断
rst = request_irq(irq, irqhandler_func, IRQF_DISABLED, "Test_New_Device", my_data);
if (rst < 0) {
printk(KERN_ERR "申请中断失败,错误码:%d\n", rst);
kfree(my_data);
return rst;
}
printk(KERN_INFO "打印输出中断申请结果:成功\n");
printk(KERN_INFO "退出内核初始化模块函数:setupirq_initfunc(...)函数.\n");
return 0;
}
static void __exit setupirq_exitfunc(void) {
printk(KERN_INFO "正常退出内核:开始释放中断资源.\n");
// 调用 free_irq 释放中断
free_irq(irq, my_data);
// 释放分配的内存
kfree(my_data);
printk(KERN_INFO "调用 free_irq 删除申请的中断成功,内存释放完成.\n");
}
MODULE_LICENSE("GPL");
module_init(setupirq_initfunc);
module_exit(setupirq_exitfunc);
Makefile
#!/bin/bash
ccflags_y += -O2
ifneq ($(KERNELRELEASE),)
obj-m := requestirq.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.ko *.mod.c
depend .depend dep:
$(CC) -M *.c > .depend
编译插入卸载
二、request_threaded_irq(...)
该函数主要功能是根据传递的参数进行正确定检查,然后动态创建一个irqaction描述符且初始化,最后调用相关API将此描述符加入到IRQ链表中,完成中断动态申请以及注册。具体Linux内核源码设计如下:
- 参数 irq:中断号
- 参数 handler:中断处理函数
- 参数 thread_fn:中断线程处理函数
- 参数 irqflags:中断标识符
- 参数 devname:中断对应的设备名称
- 参数 dev_id:设备描述符指针
- 此函数返回值:返回类型为 irq_handler_t,返回值为 0 证明申请成功,申请失败返回值非零
request_irq
和request_threaded_irq
都是 Linux 内核中用于申请中断的函数 ,二者主要区别如下:1. 函数参数
request_irq
:原型为int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
。参数包括中断号irq
、中断处理函数handler
、中断标志flags
、中断名字name
、设备区分参数dev
。request_threaded_irq
:原型为int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev)
。相比request_irq
,多了一个thread_fn
参数,该参数是在线程中运行的函数。2. 中断处理方式
request_irq
:中断处理函数handler
在中断上下文执行,该上下文不可抢占。若中断处理函数执行时间长,会阻塞其他中断和任务,影响系统实时性。request_threaded_irq
:将中断处理分为两部分。handler
作为快速的硬件中断处理程序,在中断发生时快速响应,执行确认中断源、禁用中断源等必要硬件操作;thread_fn
作为线程中断处理程序,在独立的线程上下文运行,可被抢占,适合执行耗时操作 。比如网络数据包处理场景,可把耗时逻辑放thread_fn
中,减少中断上下文执行时间。3. 执行效率与适用场景
request_irq
:适用于中断处理简单、耗时短的场景。如果处理过程不复杂,能快速执行完,用request_irq
可简单高效处理中断。request_threaded_irq
:适用于中断处理有耗时操作的场景。在实时或嵌入式系统中,对实时性要求高,通过线程化中断处理,可提高系统实时性能和响应性 。 多 CPU 系统中,多个中断的内核线程可分配到不同 CPU 执行,提高整体处理效率。4. 本质关系
request_irq
本质上是request_threaded_irq
的封装,相当于调用request_threaded_irq
时将thread_fn
参数置为NULL
。
【代码案例】
requestthreadedirq.c
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/interrupt.h>
static int irq = 11; // 中断号
#define IRQF_DISABLED 0x00000020 /*中断禁止*/
// 自定义中断处理函数
static irqreturn_t irqhandler_func(int irq,void *dev_id){
printk("打印输出中断处理函数data所对应的中断号为:%d\n",irq);
return IRQ_WAKE_THREAD; // 触发中断线程函数执行
}
// 自定义中断线程处理函数
static irqreturn_t irqthread_func(int irq,void *dev_id){
printk("打印输出中断线程处理函数data所对应的中断号为:%d\n",irq);
return IRQ_HANDLED;
}
static int __init requestthreadedirq_initfunc(void){
int rst=0;
printk("调用内核模块函数:requestthreadedirq_initfunc(...).\n");
rst = request_threaded_irq(irq,irqhandler_func,irqthread_func,IRQF_DISABLED,"Test_New_Device",NULL);
printk("打印输出调用request_threaded_irq(...)函数返回的rst值为:%d\n",rst);
disable_irq(irq);
enable_irq(irq);
printk("退出内核模块函数:requestthreadedirq_initfunc(...).\n");
return 0;
}
static void requestthreadedirq_voidfunc(void){
free_irq(irq,NULL); // 释放申请的中断处理
printk("内核正常退出:request_threaded_irq(...)函数.\n");
}
MODULE_LICENSE("GPL");
module_init(requestthreadedirq_initfunc);
module_exit(requestthreadedirq_voidfunc);
Makefile
#!/bin/bash
ccflags_y += -O2
ifneq ($(KERNELRELEASE),)
obj-m := requestthreadedirq.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *.ko *.mod.c
depend .depend dep:
$(CC) -M *.c > .depend
编译插入卸载
https://github.com/0voice