第三十六章 中断
36.1 什么是中断?
36.1.1 中断的概念
当CPU执行程序时,外部或内部事件可触发中断,使CPU暂停当前任务,转而处理紧急事件。处理完毕后,CPU自动返回原任务继续执行。
36.1.2 中断的上下半部
中断处理在 Linux系统中需要迅速响应且应避免过长延时,以维护系统的实时性和稳定性。
Linux中断不支持嵌套,意味着在处理一个中断时,其他中断会被暂时屏蔽,直到当前中断处理完成。长时间的中断处理可能导致系统响应变慢或错过关键事件。
为优化中断处理,中断服务程序(ISR)被分为两部分:
中断上文(Top Half)、中断下文(Bottom Half)。
中断上文:
快速执行,主要任务是保存必要的寄存器状态,更新系统状态(如中断计数器),并决定是否需要进一步处理。做一些中断下文需要的准备工作。
中断下文:
处理耗时的任务,如复杂的计算、IO操作等。
36.2 中断子系统框架
一个中断子系统框架的精简层次结构可以概括如下:
用户层:
主要是设备驱动,它们使用中断服务来与硬件交互。
通过通用层提供的接口申请和注册中断处理函数。
当硬件事件触发中断时,相应的驱动函数被调用以执行必要操作。
通用层:
硬件无关,提供跨平台的统一中断管理和处理接口。
允许设备驱动在不关心具体硬件细节的情况下使用中断。
示例接口可能包括request_irq(), free_irq(), 等。
硬件相关层:
分两部分:
处理器架构相关:处理特定CPU架构的中断机制,如中断向量表设置。
中断控制器驱动:与具体中断控制器通信,配置中断源和优先级。
这部分代码高度依赖于硬件,每个平台和控制器都可能有独特的实现。
硬件层:
包括外设与SoC的物理连接和信号路径。
负责中断信号从外设到中断控制器的物理传递。
设计决定了中断的响应速度和可靠性。
36.2.1 中断控制器 GIC
GIC(Generic Interrupt Controller)是中断子系统中的一个核心组件,由ARM公司设计,用于高效管理和分发来自多个中断源的中断请求到处理器核心。
GIC目前主要有四个版本(V1至V4),其中V2.0、V3.1和V4.1是较为常用的版本。每个版本在功能和支持的处理器架构上有所增强。
GIC版本特性概览:
GICv1:
支持最多8个处理器核心和1020个中断ID,适用于早期ARM处理器如Cortex-A5和A9。
GICv2:
在GICv1基础上增加了虚拟化支持,适用于如Cortex-A7和A15等处理器。
GICv3:
支持超过8个处理器核心、基于消息的中断、超过1020个中断ID、系统寄存器访问等,
主要运行在Armv8-A和Armv9-A架构上。
GICv4:
在GICv3基础上增加了虚拟中断的直接注入等特性。
GIC中断控制器由中断仲裁器、重新分配器、CPU接口组成,
用以高效地管理和分发中断到处理器核心。
Distributor(中断仲裁器):
负责全局中断管理,影响所有处理器核心。它提供了以下主要功能:
启用/禁用共享外设中断(SPI)。
设置SPI的优先级和路由规则。
配置SPI的触发方式(电平或边沿)。
生成基于消息的中断。
控制SPI的活动和挂起状态。
根据安全级别选择中断处理模式。
Redistributor(重新分配器):
为每个处理器核心提供专门的接口,用于管理其专有的中断。主要功能包括:
启用/禁用软件中断(SGI)和处理器专用中断(PPI)。
设置 SGI和 PPI的优先级和触发方式。
将 SGI和 PPI分配给中断组。
控制 SGI和 PPI的状态。
支持低功耗中断和电源管理相关功能。
CPU接口:
直接连接到每个处理器的接口,负责中断的最终处理和确认。主要功能有:
启用中断处理功能并进行通用配置。
确认接收到的中断,以开始中断服务例程。
管理中断优先级,包括降低和停用。
设置中断优先级屏蔽,
定义中断抢占策略,确保高优先级中断能及时得到处理。
确定并报告处理器当前最高优先级的挂起中断。
36.2.2 中断类型
GIC-V3 支持四种中断类型:
SPI(共享外设中断):
全局外设中断,可路由到指定处理器核心(PE)或一组 PE。
中断号 ID32-ID1019,允许多个 PE 接收并处理同一中断。
SGI(软件生成中断):
通过软件写入 GIC 寄存器生成,用于处理器间通信。
中断号 ID0-ID15,允许一个 PE向一个或多个指定 PE 发送中断。
PPI(私有外设中断):
针对特定 PE 的外设中断,不与其他 PE 共享。
中断号 ID16-ID31,专用于连接至特定 PE 的外设。
LPI(特定局部外设中断):
GICv3 新增类型,基于消息的中断。
其配置存储在内存表中而非寄存器中,提供了更大的灵活性和扩展性。
LPI 允许更细粒度的中断管理和路由控制。
Inactive(非活动状态):中断源当前未被触发。
Pending(等待状态): 中断源已被触发,但尚未被处理器核心确认。
Active(活动状态): 中断源已被触发,并且已被处理器核心确认。
Active and Pending(活动且等待状态):中断源已触发且被处理器核心确认,但另一个中断正在处理。
每个外设中断可以是以下两种类型之一:
边沿触发:
检测到中断信号上升沿时触发中断,然后无论信号状态如何,都保持触发状态,直到满足本规范定义的条件来清除中断。
电平触发:
在中断信号电平处于活动状态时触发中断,并且在电平不处于活动状态时取消触发。
36.2.3 中断号
在Linux内核中,中断的标识涉及到两个关键ID:
IRQ number(中断号)和 HW interrupt ID(硬件中断ID)。
中断号:
是CPU用来唯一标识每个外设中断的虚拟编号,与具体硬件无关。
它是内核和驱动层面使用的中断标识。
硬件中断ID:
是 GIC中断控制器用来区分不同外设中断的编号。
在单个GIC中断控制器的情况下,IRQ number和 HW interrupt ID可以一一对应。
但在GIC中断控制器级联的复杂系统中,HW interrupt ID需要结合其所属的GIC中断控制器才能唯一标识一个中断,因为不同 GIC中断控制器上的HW interrupt ID可能会重复。
为了统一驱动视角和CPU视角,Linux内核中断子系统引入了irq domain机制。
irq domain机制负责将 HW interrupt ID映射到 IRQ number上,这样驱动软件就可以只关心IRQ number,而无需关心具体的硬件中断控制器和HW interrupt ID。这样做的好处是,当中断相关的硬件配置发生变化时(如中断控制器的增减或中断号的重新分配),驱动软件无需修改,提高了系统的灵活性和可维护性。
36.2.4 中断申请函数
request_irq() 用于在 Linux 内核中注册中断处理程序。它关联一个中断号(IRQ)与特定的中断处理函数。
/*中断申请函数.成功返回0,失败返回负数*/
int request_irq(unsigned int irq, //中断号
irq_handler_t handler,//中断处理函数
unsigned long flags, //触发方式
const char *name, //中断处理函数的名称
void *dev); //设备标识
free_irq()用于释放之前注册的中断处理程序。
/*释放申请的中断号*/
void free_irq(unsigned int irq, //中断号
void *dev_id); //在request_irq使用的设备标识
gpio_to_irq() 将 GPIO 引脚号转换为对应的中断号。
/*GPIO引脚号转换为对应的中断号.成功返回中断号,失败返回负数*/
unsigned int gpio_to_irq(unsigned int gpio);
36.2.5 中断服务函数
中断处理程序 handler()是响应硬件中断的自动调用函数,用于处理特定中断事件。
/*中断处理函数*/
irqreturn_t handler(int irq, //中断号
void *dev_id); //设备标识
/*
返回值 irqreturn_t
IRQ_NONE:未处理该中断。
IRQ_HANDLED:已处理该中断。
IRQ_WAKE_THREAD:已处理并请求唤醒内核线程继续处理。
*/
中断处理程序需快速执行,处理完中断后立即返回,避免长时间占用CPU。处理共享中断时需识别具体设备。可能需与其他代码同步或通信。
/*中断处理函数示例*/
#include <linux/interrupt.h>
#include <linux/irq.h>
irqreturn_t my_irq_handler(int irq, void *dev_id)
{
// 假设 dev_id 指向设备结构体,用于识别设备
struct my_device *dev = dev_id;
// 检查设备是否是我们关心的设备
if (dev->id != MY_DEVICE_ID)
return IRQ_NONE; // 不是我们关心的设备,不处理
// 处理中断,例如读取数据
// dev->read_data();
// 清除中断标志,这取决于硬件
// dev->clear_interrupt();
// 返回处理状态
return IRQ_HANDLED; // 表示已处理中断
}
// 假设在某个地方注册了中断处理程序
// request_irq(MY_IRQ_NUMBER, my_irq_handler, IRQF_SHARED, "my_device", dev);
36.3 实验程序编写
本实验旨在实现一个Linux内核驱动,用于处理iTOP-RK3568开发板上LCD触摸屏的中断。
当触摸屏被触摸时,会触发一个中断,并在中断服务例程中打印出相应的信息。
iTOP-RK3568 有 5 组 GPIO bank:
GPIO0~GPIO4,每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分。
常用以下公式计算中断引脚:
GPIO pin 脚计算公式: pin = bank * 32 + number //bank 为组号,number 为小组编号
GPIO 小组编号计算公式: number= group * 8 + X
LCD 触摸屏对应的中断引脚标号为 TP_INT_L_GPIO3_A5,可得编号为 3*32+5=101。
得知触摸屏的中断引脚后,就可以编写对应的驱动程序。
/*触摸屏驱动程序*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_PIN 101 // 通过计算得出的GPIO编号
// 中断处理函数
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);
if (irq_num < 0) {
printk(KERN_ERR "Failed to map GPIO %d to IRQ\n", GPIO_PIN);
return -EINVAL;
}
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);
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("Your Name");
第三十七章 中断申请流程
37.1 request_irq 函数
request_irq()用于请求中断并关联中断处理函数,
底层通过调用 request_threaded_irq() 来实现。
/*申请中断,将中断号与中断服务函数关联起来*/
request_irq(unsigned int irq, //中断号
irq_handler_t handler,//中断服务函数
unsigned long flags, //触发方式标志位
const char *name, //中断处理函数的名称,用于调试
void *dev) //设备标识id
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
37.2 request_threaded_irq 函数
request_threaded_irq() 是 Linux 内核中用于请求并注册中断处理函数的关键函数。允许你注册一个主中断处理函数和一个线程化的中断处理函数。
中断请求:向内核请求分配一个中断号,并准备相关资源。
处理函数关联:将主中断处理函数(handler)
和可选的线程化中断处理函数(thread_fn)与中断号关联。
中断属性设置:通过 irqflags 设置中断的各种属性,如触发方式、中断类型等。
设备标识关联:将中断处理与设备标识id(dev_id)关联。
资源分配与设置:为中断处理分配资源,将中断处理函数与中断描述符(irq_desc)关联。
错误处理:如果请求失败,返回错误码;成功则返回 0。
/*申请中断,将中断号与中断服务函数关联起来,支持主中断处理函数、线程化中断处理函数*/
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); //设备标识id
如果线程化的中断处理函数(thread_fn)不为NULL,
则当主中断处理函数返回 IRQ_WAKE_THREAD时,线程化的中断处理函数将在另一个线程中异步执行。用于避免在中断上下文中执行耗时的操作。
request_threaded_irq()源码逻辑如下:
1. 声明变量和初始化
struct irqaction *action; // 中断动作结构体指针
struct irq_desc *desc; // 中断描述符指针
int retval; // 返回值
2. 获取中断描述符
中断描述符irq_desc包含了中断的详细信息,比如中断号、中断处理函数、中断标志等。
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
3. 处理中断处理函数
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
4. 分配并初始化中断动作数据结构
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler; //主中断设置
action->thread_fn = thread_fn; //线程中断设置
action->flags = irqflags; //中断触发方式标志位
action->name = devname; //中断名称
action->dev_id = dev_id; //设备标识符id
5. 获取中断的电源管理引用计数
/*接收一个指向 irq_data 结构,这个结构通常包含了中断请求(IRQ)的详细信息,
比如中断号、处理函数等。*/
if (irq_chip_pm_get(&desc->irq_data) < 0) { //如果中断电源管理irq_pm获取irq_data失败
kfree(action); //释放之前开辟的内存
return -EBUSY;
}
6. 设置中断并关联中断动作
retval = __setup_irq(irq, desc, action); //设置中断并关联动作
if (retval) { //如果成功
//释放之前通过 irq_chip_pm_get 获取的中断控制器资源的控制权。
irq_chip_pm_put(&desc->irq_data);
kfree(action);
return retval;
}
在Linux内核中,中断控制器GIC负责管理硬件中断的接收和分发。
当系统进入低功耗状态(如休眠或挂起)时,中断控制器可能需要被适当地配置或禁用,以确保系统能够安全地进入和退出这些状态。
irq_chip_pm_get 和 irq_chip_pm_put 函数对就是通过中断描述符irq_desc的中断数据成员irq_data用来在电源管理期间管理GIC的控制权的。
7. 可选的共享中断处理(调试目的)
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
//之前某个操作成功执行,且中断触发方式标志中设置了 IRQF_SHARED 标志位。
if (!retval && (irqflags & IRQF_SHARED)) {
// 禁用中断、调用处理函数、重新使能中断(用于调试)
}
#endif
CONFIG_DEBUG_SHIRQ_FIXME 是一个用于调试共享中断(Shared IRQ)问题的配置选项。在Linux中,多个设备可以共享同一个中断号,通过不同的中断处理函数来区分和处理。然而,这种机制可能会引入复杂性和潜在的问题,特别是在调试和诊断中断行为时。
37.3 irq_desc 结构体
irq_desc 结构体是 Linux 内核中用于描述中断的数据结构之一。
每个硬件中断都有一个对应的 irq_desc 实例,它用于记录与该中断相关的各种信息和状态。该结构体的主要功能是管理中断处理函数、中断行为以及与中断处理相关的其他数据。
handle_irq 字段,指向具体的中断处理函数。
action 结构体管理中断行为列表,包括注册、注销和处理中断相关事件的回调函数。
kstat_irqs 提供了中断统计信息,如发生次数和处理情况,用于性能分析和调试。
irq_data 存储了中断的特定信息,如中断号和类型,帮助识别和区分不同的中断。
irq_common_data 包含处理中断所需的通用信息,如中断控制器状态和屏蔽设置。
其他字段管理中断的当前状态,如嵌套中断的禁用状态和唤醒使能计数,确保中断处理的正确性和效率。在 irq_desc 结构体中最重要的就是 action 字段。
struct irq_desc {
struct irq_common_data irq_common_data; /* 通用中断数据 */
struct irq_data irq_data; /* 中断数据 */
unsigned int __percpu *kstat_irqs; /* 中断统计信息 */
irq_flow_handler_t handle_irq; /* 中断处理函数 */
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler; /* 预处理中断处理函数 */
#endif
struct irqaction*action; * IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it; /* 内核内部状态标志位,请勿修改 */
unsigned int depth; /* 嵌套中断禁用计数 */
unsigned int wake_depth; /* 嵌套唤醒使能计数 */
unsigned int tot_count;
unsigned int irq_count; /* 用于检测损坏的 IRQ 计数 */
unsigned long last_unhandled; /* 未处理计数的老化计时器 */
unsigned int irqs_unhandled; /* 未处理的中断计数 */
atomic_t threads_handled; /* 处理中断的线程计数 */
int threads_handled_last;
raw_spinlock_t lock; /* 自旋锁 */
struct cpumask *percpu_enabled; /* 指向每个 CPU 的使能掩码 */
const struct cpumask *percpu_affinity; /* 指向每个 CPU 亲和性掩码 */
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint; /* CPU 亲和性提示 */
struct irq_affinity_notify *affinity_notify; /* CPU 亲和性变化通知 */
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask; /* 等待处理的中断掩码 */
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active; /* 活动中的线程计数 */
wait_queue_head_t wait_for_threads; /* 等待线程的等待队列头 */
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir; /* proc 文件系统目录项 */
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file; /* 调试文件系统文件 */
const char *dev_name; /* 设备名称 */
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj; /* 内核对象 */
#endif
struct mutex request_mutex; /* 请求互斥锁 */
int parent_irq; /* 父中断号 */
struct module *owner; /* 模块拥有者 */
const char *name; /* 中断名称 */
} ____cacheline_internodealigned_in_smp;
37.4 irqaction 结构体
作为irq_desc结构体的成员,irqaction 结构体用于管理和描述中断行为。
handler 字段:指向中断处理函数的指针,当中断发生时调用。
flags 字段:控制中断处理行为的各种标志,如触发方式、中断类型等。
dev_id 字段:设备标识符,通常是设备结构体。
next 字段:指向下一个 irqaction 结构体的指针,用于构建中断处理函数的链表。
dir 字段:与 /proc 文件系统相关的目录项指针,用于导出中断信息到用户空间。
/*irqaction结构体*/
struct irqaction {
irq_handler_t handler; // 中断处理函数
void *dev_id; // 设备 ID
struct irqaction *next; // 链表指针
unsigned int flags; // 中断处理标志
unsigned int irq; // 中断号
const char *name; // 设备名称
};
第三十八章 中断下文 tasklet 实验
申请 GPIO 中断,使用 request_irq,但是 request_irq 绑定的中断服务程序指的是中断上文。
38.1 什么是 tasklet
内核中,tasklet是一种基于软中断的轻量级任务调度机制,设计用于处理那些需要延迟执行但又不能引起休眠的任务。
Tasklet结构体tasklet_struct定义在include/linux/interrupt.h中,它包含了几个关键成员:
state:表示tasklet的当前状态,tasklet是否正在执行或等待执行。
count:原子变量,用作引用计数。
func:函数指针,指向 tasklet将要执行的函数。
data:传递给 func函数的参数。
next:指向下一个tasklet结构体的指针,内核遍历tasklet链表来管理tasklet。
// tasklet结构体的精简定义
struct tasklet_struct {
struct tasklet_struct *next; // 指向下一个tasklet的指针
unsigned long state; // tasklet状态
atomic_t count; // 引用计数
void (*func)(unsigned long); // tasklet绑定的函数指针
unsigned long data; // 传递给函数的参数
};
// tasklet_t作为tasklet_struct的别名
typedef struct tasklet_struct tasklet_t;
// 使用tasklet_t声明tasklet变量
tasklet_t my_tasklet;
在Linux内核编程中,使用tasklet通常涉及以下几个步骤:
初始化tasklet,设置其函数和参数。
调度tasklet执行。
在tasklet函数中实现具体的任务逻辑。
当不再需要tasklet时,可以选择取消其调度。
38.2 tasklet 相关接口函数
38.2.1 静态初始化宏
DECLARE_TASKLET 和 DECLARE_TASKLET_DISABLED宏提供了一种静态初始化tasklet的便捷方式。这些宏允许开发者在编译时指定tasklet的名称、处理函数以及传递给该函数的参数,并将tasklet的初始状态设置为使能或非使能。
静态初始化Tasklet
DECLARE_TASKLET():用于静态初始化一个处于使能状态的tasklet。
DECLARE_TASKLET_DISABLED():用于静态初始化一个处于非使能状态的tasklet。
#include <linux/interrupt.h>
// 定义 tasklet 的处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ...
}
// 静态初始化 tasklet,初始状态为使能
DECLARE_TASKLET(my_tasklet, my_tasklet_handler, 0);
// 驱动程序的其他代码
// ...
// 在需要时调度 tasklet
tasklet_schedule(&my_tasklet);
38.2.2 动态初始化函数
如果需要在运行时动态地初始化和销毁tasklet,应该使用 tasklet_init()函数进行初始化,并使用 tasklet_kill()函数进行销毁。
动态初始化Tasklet
tasklet_init()用于动态初始化tasklet结构体。
/*动态初始化tasklet*/
void tasklet_init(struct tasklet_struct *t, //tasklet结构体
void (*func)(unsigned long), //tasklet绑定的函数
unsigned long data); //函数的参数
38.2.3 销毁函数
tasklet_kill()用于动态销毁tasklet结构体。
/*动态销毁tasklet结构体*/
void tasklet_kill(struct tasklet_struct *t);
taklet_kill()销毁 tasklet结构体之前,需要确保 tasklet已经被tasklet_disable()失能。
38.2.4 使能/失能函数
tasklet_enable()用于使能一个已经初始化的tasklet,允许其被调度执行。
tasklet_disable()用于禁用一个已经初始化的tasklet,防止其被调度执行。
这是通过设置一个标志来实现的,失能是阻止tasklet_schedule()或tasklet_hi_schedule()函数将 tasklet添加到调度队列中,使能则是允许。
/*失能tasklet,不允许添加到调度队列*/
void tasklet_disable(struct tasklet_struct *t);
/*使能tasklet,允许添加到调度队列*/
void tasklet_enable(struct tasklet_struct *t);
38.2.5 调度函数
tasklet_schedule()用于调度一个已经初始化的tasklet执行。
一旦tasklet被调度,它将等待一个合适的时间点(通常是在中断返回或软中断/tasklet的下一个调度时机)来执行其处理函数。
定义一个tasklet_handler()处理函数,初始化一个tasklet结构体,通过tasklet_schedule()函数调度该 tasklet执行。不需要时,使用tasklet_kill()销毁它。
#include <linux/interrupt.h>
// 定义 tasklet 处理函数
void my_tasklet_handler(unsigned long data)
{
// Tasklet 处理逻辑
// ...
}
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet 并调度它执行
void init_and_schedule_tasklet(void)
{
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
tasklet_schedule(&my_tasklet);
// 在此处,my_tasklet 已经被调度,并将在未来某个时间点执行
}
// 销毁 tasklet
void destroy_tasklet(void)
{
// 通常不需要显式禁用tasklet,因为tasklet_kill会处理
// 但如果tasklet可能在执行中,建议禁用它以避免潜在的竞争条件
// tasklet_disable(&my_tasklet);
tasklet_kill(&my_tasklet);
// 现在,my_tasklet 被销毁,不能再次使用
}
// 驱动程序的其他代码...
// 可以调用 init_and_schedule_tasklet() 来初始化和调度 tasklet
// 并在适当的时候调用 destroy_tasklet() 来销毁它
第三十九章 软中断实验
tasklet_shcedule()底层是软中断,可实现中断下文,直接通过软中断也可以实现中断下文。
39.1 什么是软中断
软中断枚举类型定义
Linux内核通过枚举类型定义了多种软中断,每种软中断用于处理不同类型的任务。
软中断的优先级通过它们在枚举中的顺序隐式确定,编号越小优先级越高。
/*软中断枚举类型定义*/
enum {
HI_SOFTIRQ, // 高优先级软中断
TIMER_SOFTIRQ, // 定时器软中断
NET_TX_SOFTIRQ, // 网络传输发送软中断
NET_RX_SOFTIRQ, // 网络传输接收软中断
BLOCK_SOFTIRQ, // 块设备软中断
IRQ_POLL_SOFTIRQ, // 中断轮询软中断
TASKLET_SOFTIRQ, // 任务软中断(实际上,tasklet是通过这个软中断类型来处理的)
SCHED_SOFTIRQ, // 调度软中断
HRTIMER_SOFTIRQ, // 未使用,但保留编号
RCU_SOFTIRQ, // RCU软中断,建议总是最后一个
NR_SOFTIRQS // 软中断总数
};
/*
与RCU(Read-Copy Update,读-拷贝更新)锁相关的同步任务,
允许读者(即访问共享数据的进程或线程)与写者(即修改共享数据的进程或线程)之间的并发访问,
*/
添加自定义软中断
尽管不推荐,但可以通过在枚举中添加新项来定义自定义软中断。
enum {
// ... 原有的软中断类型 ...
RCU_SOFTIRQ,
TEST_SOFTIRQ, // 自定义软中断
NR_SOFTIRQS
};
39.2 软中断接口函数
软中断是Linux内核中用于延迟执行某些任务的机制,它允许在不影响当前处理流程的情况下,将任务排入队列以便稍后处理。
注册软中断
使用open_softirq()来注册一个软中断。
使用raise_softirq()来触发已经注册的软中断。
使用raise_softirq_irqoff()可以在禁用硬件中断的情况下触发软中断。
/*注册一个软中断*/
void open_softirq(int nr, //中断号
void (*action)(struct softirq_action *)); //处理函数指针
/*触发软中断*/
void raise_softirq(unsigned int nr);//中断号
/*在禁用硬件中断的情况下触发软中断*/
void raise_softirq_irqoff(unsigned int nr);//中断号
// 假设的软中断处理函数
void my_softirq_handler(void *arg) {
// 处理软中断逻辑
printk(KERN_INFO "My softirq handler is running\n");
}
int nr = /* 假设的软中断编号 */;
/*注册软中断,注意这里用了函数指针的强制转换*/
open_softirq(nr, (void (*)(struct softirq_action *))my_softirq_handler);
// 触发软中断
raise_softirq(nr);
// 在禁用硬件中断的情况下触发软中断
raise_softirq_irqoff(nr);
用触摸屏中断举例,在驱动入口函数中,用gpio_to_irq()将外部引脚转成中断号,用request_irq()请求中断,并绑定上半部处理程序,用open_softirq()注册软中断,并绑定下半部处理程序。在上半部处理程序中,使用 raise_softirq()触发软中断。
这样,点击触摸屏,上半部中断根据中断号被触发,上半部处理程序触发软中断,而软中断触发函数绑定了软中断宏和软中断处理函数,这样就会触发下半部的处理函数。
// 软中断处理程序
void testsoft_func(struct softirq_action *softirq_action)
{
printk("This is testsoft_func\n");
}
/*中断上半部处理程序*/
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
raise_softirq(TEST_SOFTIRQ); // 触发软中断
return IRQ_RETVAL(IRQ_HANDLED);
}
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将 GPIO 映射为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 注册软中断函数
open_softirq(TEST_SOFTIRQ, testsoft_func);
return 0;
}
//驱动出口函数
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");
第四十章 特殊软中断 tasklet 分析
tasklet 是 Linux 内核中一种特殊的软中断机制,用于处理需要延迟执行的轻量级任务。它简化了内核编程模型,并提供了低延迟和自适应调度的优点。
40.1 Tasklet 的初始化与注册
在内核初始化过程中,softirq_init() 函数会注册两种类型的软中断:
TASKLET_SOFTIRQ 和 HI_SOFTIRQ,分别对应普通和高优先级的 tasklet。这些软中断的处理函数分别是 tasklet_action() 和 tasklet_hi_action()。
/*open_softirq注册普通优先级软中断*/
open_softirq(TASKLET_SOFTIRQ, //中断号
tasklet_action); //中断处理函数
/*open_softirq注册高优先级软中断*/
open_softirq(HI_SOFTIRQ, //中断号
tasklet_hi_action);//高优先级中断处理函数
40.2 Tasklet 的处理流程
当 tasklet 被调度时,它会被添加到 CPU 特定的链表(tasklet_vec或 tasklet_hi_vec)中。
随后,当对应的软中断被触发时,tasklet_action() 或 tasklet_hi_action() 函数会被调用,进而调用 tasklet_action_common() 来处理链表中的所有 tasklet。
static void tasklet_action_common(...) {
// 禁用中断,处理链表中的 tasklet,然后重新启用中断
// ... 遍历链表,执行每个 tasklet 的处理函数 ...
}
40.3 Tasklet 的调度
tasklet 通过 __tasklet_schedule_common() 被调度到 CPU 特定的链表中。
这个函数将 tasklet 添加到链表的末尾,并触发相应的软中断。
/*tasklet_schedule底层调用的是tasklet_schedule_common()*/
static void __tasklet_schedule_common(...) {
// ... 将 tasklet 添加到链表末尾,并触发软中断 ...
}
第四十一章 共享工作队列
共享工作队列也可以实现中断下文。
具体做法和tarlet差不多,
对工作项work_struct使用INIT_WORK()宏初始化,或者使用DECLARE_WORK()宏一次性完成声明、定义和初始化,然后在中断处理函数(中断上文)中,使用shcedule_work()将工作项提交到工作队列并开始调度。最后在出口函数使用work_cancel_sync()取消调度。
41.1 什么是工作队列
工作队列是Linux内核中用于管理和分配任务给工作线程或进程的机制,特别适用于需要延迟执行的任务。
工作队列与tasklet机制有所不同,主要区别在于工作队列可以休眠,而tasklet不能。这使得工作队列能够处理更耗时的任务。
工作队列通过 work_struct结构体表示工作项,这些工作项被组织在workqueue_struct结构体表示的工作队列中。
当有新的任务到达时,它会被添加到队列的末尾,并由内核线程(工作线程)从队列的头部取出并执行。
/*工作项结构体*/
struct work_struct {
atomic_long_t data; // 内部使用,用于状态管理
struct list_head entry; // 链表节点,用于将工作项链接到工作队列
work_func_t func; // 指向处理该工作项的函数
};
typedef void (*work_func_t)(struct work_struct *work); // 工作函数类型
//工作队列结构体原型
struct workqueue_struct {
spinlock_t lock; // 自旋锁,用于保护队列的并发访问
struct list_head worklist; // 链表头,指向挂起的工作项队列
struct list_head delayed_works;// 链表头,指向延迟执行的工作项列表。
struct timer_list dwork_timer; // 定时器,用于管理延迟工作项的触发时间。
struct worker_pool *pools; // 指向工作线程池的指针数组,用于并行处理
...
//省略线程池配置、队列标志位、工作队列名称等
};
41.2 工作队列相关接口函数
44.2.1 初始化工作项
在Linux内核驱动开发中,我们通常只需要关注工作项(work_struct)的定义、初始化和提交,而不需要直接管理底层的工作队列(workqueue_struct)和工作者线程(由内核的worker_pool管理)。这些底层的结构和线程池由内核的工作队列子系统自动管理。
1. 定义工作项
工作项是通过work_struct结构体定义的。这个结构体是内核中定义好的,用于描述一个待执行的工作。
#include <linux/workqueue.h>
// 假设的工作处理函数
void my_work_function(struct work_struct *work) {
// 这里是工作处理的具体实现
printk(KERN_INFO "Executing my work function\n");
}
// 定义工作项
static struct work_struct my_work;
2. 初始化工作项
使用INIT_WORK宏来初始化工作项。这个宏设置工作项的处理函数。
/*初始化工作项,设置工作项的工作处理函数*/
INIT_WORK(&my_work, my_work_function);
#define INIT_WORK(_work, _func) \
do { \
(_work)->func = (_func); \
INIT_LIST_HEAD(&(_work)->entry); \
// 可能还有其他初始化代码
} while (0)
3. 一次性创建和初始化工作项
使用DECLARE_WORK宏可以一次性完成工作项的声明、定义和初始化。
// DECLARE_WORK 宏(实际内核中可能不存在)
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
//使用 DECLARE_WORK宏
DECLARE_WORK(my_static_work, //工作项
my_work_function);//工作项处理函数
41.2.2 调度/取消调度工作队列函数
使用工作队列(workqueues)时,工作项(work_struct)需要通过调度函数来提交到工作队列中,以便在内核的某个合适时机执行。
调度工作项
schedule_work()用于将工作项提交到工作队列中,并请求内核的调度器在适当的时机执行该工作项。
/*提交工作项到工作队列,请求调度*/
static inline bool schedule_work(struct work_struct *work);
取消工作项
cancel_work_sync()用于取消已经调度但尚未执行的工作项。如果工作项正在执行,则该函数会等待其执行完成。它返回一个布尔值,指示工作项是否被成功取消。
/*取消工作项的调度*/
bool cancel_work_sync(struct work_struct *work);
struct work_struct test_workqueue;
// 工作项处理函数
void test_work(struct work_struct *work)
{
msleep(1000);
printk("This is test_work\n");
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
// 提交工作项到工作队列
schedule_work(&test_workqueue);
return IRQ_RETVAL(IRQ_HANDLED);
}
/*驱动入口函数*/
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将 GPIO 映射为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 初始化工作项
INIT_WORK(&test_workqueue, test_work);
return 0;
}
/*驱动出口函数,释放中断*/
...
第四十二章 自定义工作队列
共享队列是由内核管理的全局工作队列,自定义工作队列是由内核或驱动程序创建的特定工作队列,用于处理特定的任务。
41.1 工作队列相关结构体
在Linux内核中,工作项 work_struct和工作队列 workqueue_struct是工作队列机制的核心结构体,用于组织和调度延迟执行的任务。
/*工作项结构体*/
struct work_struct {
atomic_long_t data; // 内部使用,用于状态管理
struct list_head entry; // 链表节点,用于将工作项链接到工作队列
work_func_t func; // 指向处理该工作项的函数
// 省略了lockdep_map,因为它依赖于配置选项
};
typedef void (*work_func_t)(struct work_struct *work); // 工作函数类型
//工作队列结构体原型
struct workqueue_struct {
spinlock_t lock; // 自旋锁,用于保护队列的并发访问
struct list_head worklist; // 链表头,指向挂起的工作项队列
struct list_head delayed_works;// 链表头,指向延迟执行的工作项列表。
struct timer_list dwork_timer; // 定时器,用于管理延迟工作项的触发时间。
struct worker_pool *pools; // 指向工作线程池的指针数组,用于并行处理
...
//省略线程池配置、队列标志位、工作队列名称等
};
41.2 工作队列相关接口函数
在Linux内核中,工作队列提供了一种机制来延迟或异步执行任务。
41.2.1 创建工作队列
create_workqueue():为每个CPU创建一个工作队列,工作队列的名称由name参数指定。
create_singlethread_workqueue():宏定义,创建一个仅在一个CPU上运行的工作队列。
/*为每个CPU创建一个工作队列*/
struct workqueue_struct *create_workqueue(const char *name);//工作队列名称
/*创建单个CPU上运行的工作队列*/
#define create_singlethread_workqueue(name) \
alloc_workqueue(name, WQ_UNBOUND | WQ_MEM_RECLAIM, 1)
41.2.2 调度工作项
queue_work_on():将工作项work调度到指定CPU的指定工作队列上执行。
调度到指定CPU上的指定工作队列,可以交给自定义的工作队列。
/*调度工作项到指定的CPU上的工作队列执行*/
bool queue_work_on(int cpu, //指定CPU
struct workqueue_struct *wq, //指定工作队列
struct work_struct *work); //工作项
schedule_work()用于将工作项提交到默认工作队列中(CPU的),并请求内核的调度器在适当的时机执行该工作项。
/*提交工作项到默认工作队列,请求调度*/
static inline bool schedule_work(struct work_struct *work);
41.2.3 取消工作项
cancel_work_sync():取消一个已经调度但尚未执行的工作项,如果工作项正在执行,则等待其完成。
/*取消一个尚未调度的工作项*/
bool cancel_work_sync(struct work_struct *work);
41.2.4 刷新工作队列
flush_workqueue():刷新工作队列,确保队列中的所有工作项都被执行完毕。
/*刷新工作对列,确保每个工作项执行完毕*/
void flush_workqueue(struct workqueue_struct *wq);
41.2.5 销毁工作队列
destroy_workqueue():删除工作队列。删除前确保队列中的工作项都已完成或被取消。
/*销毁指定的工作队列*/
void destroy_workqueue(struct workqueue_struct *wq);
第四十三章 延迟工作
43.1 什么是延迟工作
延迟工作是一种在Linux内核中常用的技术,用于将任务的执行延迟到未来的某个时间点,以避免阻塞主线程或实现定时任务。
在Linux内核中,延迟工作通过struct delayed_work结构体实现。
/*延时工作结构体*/
struct delayed_work{
struct work_struct work;// 工作项结构体
struct timer_list timer;// 定时器,用于延迟执行工作
};
43.2 延迟工作相关接口函数
43.2.1 初始化延迟工作函数
静态初始化
DECLARE_DELAYED_WORK宏用于声明并初始化struct delayed_work变量。
/*静态初始化 延迟工作结构体delayed_work变量*/
#define DECLARE_DELAYED_WORK(n, f) \ //delayed_work工作项变量, 工作函数
struct delayed_work n = { .work = __WORK_INITIALIZER(n.work, f) }
动态初始化
INIT_DELAYED_WORK宏用于已声明的struct delayed_work变量,初始化它或重新初始化。
#define INIT_DELAYED_WORK(_work, _func) \ //delayed_work工作项,工作函数
do { \
INIT_WORK(&(_work)->work, _func); \
// 注意:通常不需要手动设置_work->timer,因为内核的workqueue机制会处理
} while (0)
43.2.2 调度/取消调度延迟工作函数
调度延迟工作
schedule_delayed_work()在共享工作队列上调度延迟工作。
queue_delayed_work()在自定义工作队列上调度延迟工作
/*在共享工作队列上调度延迟工作*/
static inline bool schedule_delayed_work(struct delayed_work *dwork, //延迟工作结构体
unsigned long delay); //延时时间,jiffies单位
/*在自定义工作队列上调度延迟工作*/
static inline bool queue_delayed_work(struct workqueue_struct *wq, //指定工作队列
struct delayed_work *dwork, //延时工作项结构体
unsigned long delay); //延时时间,jiffies单位
取消延迟工作
使用cancel_delayed_work_sync()尝试取消延迟工作,或等待它完成再取消。
/*取消延迟工作的调度*/
extern bool cancel_delayed_work_sync(struct delayed_work *dwork);//指定的延迟工作结构体
// 假设你已经有了一个名为my_work的延迟工作和一个工作队列wq(对于自定义工作队列)
// 在共享工作队列上调度延迟工作
schedule_delayed_work(&my_work, 100 * HZ); // 延迟100秒
// 在自定义工作队列上调度延迟工作
queue_delayed_work(wq, &my_work, 50 * HZ); // 在wq上延迟50秒
// 取消延迟工作并等待其完成
if (cancel_delayed_work_sync(&my_work)) {
// 延迟工作已被取消并等待完成
} else {
// 延迟工作可能已经开始执行或无法被取消
}
第四十四章 工作队列传参
1. 定义工作项结构:将work_struct嵌入到自定义结构体中,以便传递额外参数。
/*自定义工作项结构,将工作项结构体struct_work添加到自定义工作项结构体中*/
struct work_data {
struct work_struct test_work;
int a;
int b;
};
struct work_data test_workqueue_work;
2. 初始化工作队列和工作项:在模块初始化函数中创建工作队列并初始化工作项。
/*创建工作队列*/
struct workqueue_struct *test_workqueue = create_workqueue("test_workqueue");
/*初始化工作项结构体 */
INIT_WORK(&test_workqueue_work.test_work, test_work);
3. 设置工作项参数:为自定义的工作项结构体设置必要的参数。
test_workqueue_work.a = 1;
test_workqueue_work.b = 2;
4. 提交工作项到工作队列:在中断处理函数或其他上下文中,将工作项提交到工作队列。
/*将自定义工作项结构体,添加到工作队列*/
queue_work(test_workqueue, &test_workqueue_work.test_work);
5. 工作项处理函数:在工作项处理函数中,使用container_of宏从work_struct指针获取自定义结构体的指针,并访问其成员。
/*工作项处理函数*/
void test_work(struct work_struct *work)
{
/*使用 container_of从自定义工作项结构体获取工作项参数*/
struct work_data *pdata = container_of(work, struct work_data, test_work);
printk("a is %d\n", pdata->a);
printk("b is %d\n", pdata->b);
}
container_of宏是Linux内核中用于通过嵌入在另一个结构中的成员地址反向获取整个结构地址的宏。
第四十五章 并发管理工作队列
在现代的软件开发中,我们常常面临着需要同时处理多个任务的挑战。这些任务可能是并行的、独立的,或者需要以某种顺序进行处理。
45.1 工作队列介绍
在单核线程的系统中,通常会为每个 CPU(核心)初始化一个工作线程并关联一个工作队列。这种默认设置确保每个 CPU 都有一个专门的线程来处理与其绑定的工作队列上的工作项。
当有新的工作任务(work)产生时,系统通过负载均衡策略将这些任务分配到各个工作队列中。每个工作线程从自己的队列中取出任务并执行。
45.2 workqueue 队列弊端
假如有三个 work 放到同一个工作队列上,接下来 CPU 会启动工作线程去执行这三个work。
传统工作队列弊端精简表述
任务调度延迟:工作项执行和睡眠期间的等待导致队列积压,影响系统响应。
资源利用不均衡:长时间任务阻塞短时间任务,导致处理器空闲。
同步开销:多线程访问工作队列需同步机制,引入额外开销。
优先级控制缺失:FIFO方式无法支持优先级调度。
上下文切换开销:频繁切换线程上下文影响性能。
45.3 什么是并发管理工作队列
CMWQ(Concurrency Managed Workqueue,并发管理工作队列)是一种并发编程模式,旨在通过有效管理和调度工作项来充分利用多核处理器的计算能力,并实现对不同优先级工作项的公平调度。它允许在多线程或多进程环境中并发执行任务,提高系统性能和响应速度。
工作被放入工作队列中,多个工作线程可以独立地从队列中取出请求并处理,无需等待其他线程完成。
45.4 并发管理工作队列接口函数
alloc_workqueue() 允许内核开发者根据需要创建自定义的工作队列,用于并发处理和异步任务执行。
/*创建任务队列*/
struct workqueue_struct *
alloc_workqueue(const char *name, //工作队列名称
unsigned int flags,//行为和属性标志位
int max_active, //活跃(正执行或等待执行的)最大工作项数量
const struct alloc_wq_attrs *attrs);//工作队列的额外属性,如CPU亲和性。
/*
并发任务队列的行为和属性标志位:
WQ_UNBOUND 工作队列中的工作项可以在任何CPU上执行。
WQ_HIGHPRI 表示这是一个高优先级的工作队列。
*/
int irq;
struct workqueue_struct *test_workqueue; //并发工作队列
struct work_struct test_workqueue_work; //工作项
// 工作项处理函数
void test_work(struct work_struct *work)
{
msleep(1000);
printk("This is test_work\n");
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
queue_work(test_workqueue, &test_workqueue_work); // 提交工作项到工作队列
return IRQ_RETVAL(IRQ_HANDLED);
}
//驱动入口函数
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将 GPIO 映射为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 用于创建和分配一个工作队列
test_workqueue = alloc_workqueue("test_workqueue", WQ_UNBOUND, 0);
INIT_WORK(&test_workqueue_work, test_work); // 初始化工作项
return 0;
}
/*中断出口函数*/
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
cancel_work_sync(&test_workqueue_work); // 取消工作项
flush_workqueue(test_workqueue); // 刷新工作队列
destroy_workqueue(test_workqueue); // 销毁工作队列
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);
第四十六章 中断线程化
46.1 什么是中断线程化
中断线程化是一种优化策略,用于提升多线程程序的性能。
它将中断处理与主线程执行分离,通过专门的线程来处理中断事件,减少主线程的频繁中断,从而专注于核心任务。
这种分离使得中断处理与主线程工作并行进行,降低了上下文切换的开销,提高了程序的响应速度和执行效率。同时,需确保中断线程与主线程间数据的同步和共享安全,以保证数据一致性和正确性。
46.2 中断线程化接口函数
request_threaded_irq() 函数用于请求并注册一个线程化的中断处理机制。
此机制将中断处理分为两部分:
第一部分是快速响应的上半部(由handler参数指定,设为NULL以使用系统默认处理),
第二部分是更耗时的下半部,在独立的内核线程中执行(由thread_fn参数指定)。
/*请求一个线程化中断*/
int request_threaded_irq(unsigned int irq, //中断号
irq_handler_t handler, //上半部处理函数
irq_handler_t thread_fn, //下半部处理函数
unsigned long flags, //中断属性标志位,中断触发方式、中断类型
const char *name, //中断名称
void *dev); //设备标识符
/*中断属性标志
IRQF_SHARED表示中断可以被多个设备共享,
IRQF_TRIGGER_*指定触发类型(如边沿触发或电平触发)。
*/
int irq;
// 中断处理函数的底半部(线程化中断处理函数)
irqreturn_t test_work(int irq, void *args)
{
// 执行底半部的中断处理任务
msleep(1000);
printk("This is test_work\n");
return IRQ_RETVAL(IRQ_HANDLED);
//在Linux内核的中断处理函数中,IRQ_HANDLED 是一个宏,用于指示中断已经被成功处理。
}
// 中断处理函数的顶半部
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This is test_interrupt\n");
// 将中断处理工作推迟到底半部
return IRQ_WAKE_THREAD;
//指示需要唤醒“底半部”线程)。
}
/*中断入口函数*/
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将 GPIO 映射为中断号
printk("irq is %d\n", irq);
/* 请求并注册一个线程化的中断处理函数 */
ret = request_threaded_irq(irq, //中断号
test_interrupt, //上半部处理函数
test_work, //下半部处理函数
IRQF_TRIGGER_RISING,//中断属性标志
"test", //中断名称
NULL); //设备标识
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
return 0;
}
/*中断出口函数*/
static void interrupt_irq_exit(void)
{
free_irq(irq, NULL); // 释放中断
printk("bye bye\n");
}
module_init(interrupt_irq_init);
module_exit(interrupt_irq_exit);