[toc]
kernel/softirq.c 内核中断下半部(Interrupt Bottom-Half) 核心实现 历史与背景 这项技术是为了解决什么特定问题而诞生的? kernel/softirq.c
实现的软中断(softirq)机制是为了解决一个操作系统内核设计中的核心矛盾:中断处理程序的执行时间必须极短,但中断所触发的工作任务可能很耗时 。
中断处理的紧迫性 :硬件中断发生时,CPU会立即暂停当前工作,跳转到中断处理程序(Interrupt Service Routine, ISR,也称“上半部”/“Top Half”)。在执行ISR期间,通常会屏蔽掉当前CPU上的同级甚至所有中断,以保证处理的原子性和快速性。如果ISR执行时间过长,会导致系统无法响应其他新的硬件中断,增加系统延迟(Latency),甚至丢失中断事件。
任务的复杂性 :然而,中断事件所触发的后续处理可能很复杂。例如,网卡收到一个数据包的中断,其后续处理包括解析协议栈、将数据包递交给上层应用等,这些都是耗时操作。
软中断机制就是为了将中断处理分割为两个部分而设计的:
上半部(Top Half / Hard IRQ) :在关中断的ISR中执行,只做最紧急的工作,如响应硬件、读取状态、将数据从硬件FIFO拷贝到内存,然后标记一个“软中断”请求。这个过程必须极快。
下半部(Bottom Half / Softirq) :在稍后的、更宽松的环境中(中断是打开的)执行那些耗时的任务。softirq
就是最高性能的一种下半部实现。
它的发展经历了哪些重要的里程碑或版本迭代? Linux下半部机制经历了显著的演进:
早期的BH(Bottom Halves) :Linux早期内核有一种BH机制。它很简单,但存在一个致命缺陷:在多处理器(SMP)系统上,同一个BH不能同时在多个CPU上运行,存在全局锁,扩展性极差。
Softirq和Tasklet的引入 :为了解决SMP扩展性问题,内核引入了softirq
。softirq
的一个关键设计是,同一种类型的软中断(如网络接收)可以同时在多个CPU上并发执行 ,极大地提升了多核系统的性能。与此同时,为了方便普通驱动开发者,内核在softirq
之上构建了更简单的tasklet
机制。
ksoftirqd
线程的出现 :在高负载情况下(如网络流量风暴),软中断可能被频繁地触发,导致CPU一直在处理软中断而无法执行用户进程,造成用户进程“饥饿”。为了解决这个问题,内核为每个CPU都创建了一个名为ksoftirqd/X
的内核线程。当软中断负载过高时,未处理完的工作会被这个线程接管,由于线程是受调度器管理的,可以保证用户进程也有机会运行。
目前该技术的社区活跃度和主流应用情况如何? softirq
是Linux内核中断处理和调度的基石,其代码非常成熟、稳定。它不是一个经常变动的功能,而是其他高性能子系统(如网络、定时器)赖以构建的基础。 它的应用场景高度集中在对性能和低延迟要求极高的核心子系统中:
网络栈 :几乎所有的网络数据包收发处理(NET_TX_SOFTIRQ
, NET_RX_SOFTIRQ
)都是通过软中断完成的。
定时器子系统 :定时器到期后的回调函数执行是通过TIMER_SOFTIRQ
触发的。
块设备 :I/O操作完成后的处理会通过BLOCK_SOFTIRQ
进行。
核心原理与设计 它的核心工作原理是什么? softirq
的核心是一个基于位掩码的、静态定义的、可并发的延迟任务执行框架 。
静态定义 :内核预定义了少数几种软中断类型(在enum softirq_action
中),如HI_SOFTIRQ
, TIMER_SOFTIRQ
, NET_RX_SOFTIRQ
等。它们在编译时就已确定,不能在运行时动态添加。
触发(Raising) :上半部(硬中断处理程序)在完成其紧急工作后,会调用raise_softirq(softirq_type)
。这个函数非常轻量,它只是在当前CPU的一个私有变量(一个位掩码)中设置与softirq_type
对应的位。
执行(Execution) :内核会在一些特定的、安全的时间点检查这个位掩码,如果发现有挂起的软中断,就会调用do_softirq()
来执行它们。这些时间点包括:
从硬中断处理程序返回时 。
从系统调用返回时 。
在ksoftirqd
内核线程中 。
do_softirq()
的逻辑 :该函数会检查当前CPU的软中断挂起位掩码,然后按从高到低的优先级,依次调用预先注册好的处理函数(softirq_action
数组)。
ksoftirqd
的角色 :如果在上述检查点(如硬中断返回时)处理软中断时,发现新的软中断不断被触发(高负载),处理循环会有一个次数限制,不会无休止地进行下去。未处理完的软中断位掩码仍然是置位的。此时,do_softirq
会唤醒当前CPU的ksoftirqd
线程。ksoftirqd
作为一个普通的内核线程,会被调度器调度运行,并在其执行上下文中调用do_softirq()
来清理积压的软中断任务。
它的主要优势体现在哪些方面?
高性能和低延迟 :它是所有下半部机制中性能最高的,因为它没有额外的锁开销,并且可以并发执行。
SMP扩展性好 :同一种软中断可以在多个CPU上同时运行,这对于多核网络服务器等场景至关重要。
上下文确定 :软中断在中断上下文中运行,虽然此时硬件中断是打开的,但它仍然是一个非抢占的、不能睡眠的执行环境,这使得其行为是高度可预测的。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
不能睡眠 :这是它最主要的限制。在软中断处理函数中,绝对不能调用任何可能导致睡眠的函数(如申请GFP_KERNEL
内存、获取信号量等),否则会使系统崩溃。
静态定义 :软中断的类型是编译时固定的,这使得它不适合作为一种通用的延迟任务机制给设备驱动程序使用。
编程复杂且危险 :开发者必须非常小心地处理并发问题,因为同一个软中断处理函数可能正在其他CPU上运行。此外,必须时刻警惕不能睡眠的限制。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? softirq
是为内核最核心、对性能最敏感 的子系统量身定做的,它不是给普通设备驱动开发者使用的通用工具。
网络数据包处理 :当网卡收到大量数据包时,硬中断将它们DMA到内存后,会立即触发NET_RX_SOFTIRQ
。多个CPU可以并发地处理各自接收队列中的数据包,执行IP层、TCP/UDP层的逻辑。这是softirq
最典型的应用场景。
内核定时器 :硬件定时器中断触发后,其硬中断处理程序会快速扫描到期的定时器,然后触发TIMER_SOFTIRQ
。在TIMER_SOFTIRQ
的上下文中,再安全地调用用户注册的成百上千个定时器回调函数。
是否有不推荐使用该技术的场景?为什么?
绝大多数设备驱动 :普通的设备驱动绝对不应该 直接注册和使用softirq
。它太复杂且容易出错。驱动应该使用更上层的、更简单的tasklet
(如果任务不需睡眠)或workqueue
(如果任务需要睡眠)。
任何需要睡眠的任务 :如果延迟处理的任务需要分配大量内存、等待I/O、获取锁等,必须使用workqueue
,因为它在进程上下文中运行,可以安全地睡眠。
对比分析 请将其 与 其他相似技术 进行详细对比。 Linux内核中延迟执行任务的机制(统称下半部)主要有softirq
, tasklet
, workqueue
。
特性
硬中断处理程序 (Top Half)
Softirq
Tasklet
Workqueue
执行上下文
硬中断上下文
软中断上下文
软中断上下文
进程上下文
能否睡眠
绝对不能
绝对不能
绝对不能
可以
并发性
不可重入,执行时屏蔽同级中断。
可并发 :同一种softirq可在多个CPU上同时运行。
不可并发 :同一种tasklet在任意时刻只能在一个CPU上运行。
可并发,由工作线程数量决定。
分配方式
静态(通过request_irq
)
静态 (编译时确定类型)
动态 (可随时初始化一个tasklet)
动态 (可随时初始化一个work_struct)
使用场景
对硬件的紧急、快速响应。
内核核心、高性能、高频率的任务(网络、定时器)。
普通设备驱动的延迟任务(不需睡眠)。
需要睡眠或耗时很长的延迟任务(如涉及文件I/O)。
总结关系 :tasklet
实际上是构建在softirq
之上的一个简化封装。它使用HI_SOFTIRQ
和TASKLET_SOFTIRQ
这两个软中断向量,并增加了一个锁来保证同类tasklet的串行执行,从而为驱动开发者提供了更简单的并发模型。
kernel/softirq.c invoke_softirq 触发软中断的执行 1 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 static inline void invoke_softirq (void ) { if (!force_irqthreads() || !__this_cpu_read(ksoftirqd)) { #ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK __do_softirq(); #else do_softirq_own_stack(); #endif } else { wakeup_softirqd(); } }
irq_enter 进入中断上下文 irq_exit 退出中断上下文 1 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 void irq_enter_rcu (void ) { __irq_enter_raw(); if (tick_nohz_full_cpu(smp_processor_id()) || (is_idle_task(current) && (irq_count() == HARDIRQ_OFFSET))) tick_irq_enter(); account_hardirq_enter(current); } void irq_enter (void ) { ct_irq_enter(); irq_enter_rcu(); } static inline void __irq_exit_rcu(void ){ #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED local_irq_disable(); #else lockdep_assert_irqs_disabled(); #endif account_hardirq_exit(current); preempt_count_sub(HARDIRQ_OFFSET); if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); if (IS_ENABLED(CONFIG_IRQ_FORCED_THREADING) && force_irqthreads() && local_timers_pending_force_th() && !(in_nmi() | in_hardirq())) wake_timersd(); tick_irq_exit(); } void irq_exit_rcu (void ) { __irq_exit_rcu(); lockdep_hardirq_exit(); } void irq_exit (void ) { __irq_exit_rcu(); ct_irq_exit(); lockdep_hardirq_exit(); }
open_softirq 软中断类型 1 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 enum { HI_SOFTIRQ=0 , TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, IRQ_POLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, NR_SOFTIRQS }; void open_softirq (int nr, void (*action)(void )) { softirq_vec[nr].action = action; }
softirq_init 软中断初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void __init softirq_init (void ) { int cpu; for_each_possible_cpu(cpu) { per_cpu(tasklet_vec, cpu).tail = &per_cpu(tasklet_vec, cpu).head; per_cpu(tasklet_hi_vec, cpu).tail = &per_cpu(tasklet_hi_vec, cpu).head; } open_softirq(TASKLET_SOFTIRQ, tasklet_action); open_softirq(HI_SOFTIRQ, tasklet_hi_action); }
run_ksoftirqd ksoftirqd 内核线程 1 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 58 59 static int ksoftirqd_should_run (unsigned int cpu) { return local_softirq_pending(); } static inline void ksoftirqd_run_begin (void ) { local_irq_disable(); } static inline void ksoftirqd_run_end (void ) { local_irq_enable(); } static void run_ksoftirqd (unsigned int cpu) { ksoftirqd_run_begin(); if (local_softirq_pending()) { handle_softirqs(true ); ksoftirqd_run_end(); cond_resched(); return ; } ksoftirqd_run_end(); }
softirq_handle_begin 和 softirq_handle_end
1 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 static __always_inline void __local_bh_disable_ip(unsigned long ip, unsigned int cnt){ preempt_count_add(cnt); barrier(); } static void __local_bh_enable(unsigned int cnt){ lockdep_assert_irqs_disabled(); if (preempt_count() == cnt) trace_preempt_on(CALLER_ADDR0, get_lock_parent_ip()); if (softirq_count() == (cnt & SOFTIRQ_MASK)) lockdep_softirqs_on(_RET_IP_); __preempt_count_sub(cnt); } static inline void softirq_handle_begin (void ) { __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); } static inline void softirq_handle_end (void ) { __local_bh_enable(SOFTIRQ_OFFSET); WARN_ON_ONCE(in_interrupt()); }
handle_softirqs 处理软中断
handle_softirqs(在一些内核版本中可能名为__do_softirq)是Linux中断处理下半部(bottom-half)机制的核心执行引擎。当中断上下文(或ksoftirqd线程)确定有待处理的软中断时,就会调用此函数。 它的核心作用是:在一个受控的循环中,遍历所有挂起的软中断类型,并依次调用它们注册好的处理函数(handler),直到所有挂起的软中断都被处理完毕。
1 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 #define MAX_SOFTIRQ_TIME msecs_to_jiffies(2) #define MAX_SOFTIRQ_RESTART 10 static inline bool lockdep_softirq_start (void ) { return false ; }static inline void lockdep_softirq_end (bool in_hardirq) { }static void handle_softirqs (bool ksirqd) { unsigned long end = jiffies + MAX_SOFTIRQ_TIME; unsigned long old_flags = current->flags; int max_restart = MAX_SOFTIRQ_RESTART; struct softirq_action *h ; bool in_hardirq; __u32 pending; int softirq_bit; current->flags &= ~PF_MEMALLOC; pending = local_softirq_pending(); softirq_handle_begin(); in_hardirq = lockdep_softirq_start(); restart: set_softirq_pending(0 ); local_irq_enable(); h = softirq_vec; while ((softirq_bit = ffs(pending))) { unsigned int vec_nr; int prev_count; h += softirq_bit - 1 ; vec_nr = h - softirq_vec; prev_count = preempt_count(); kstat_incr_softirqs_this_cpu(vec_nr); trace_softirq_entry(vec_nr); h->action(); trace_softirq_exit(vec_nr); if (unlikely(prev_count != preempt_count())) { pr_err("huh, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n" , vec_nr, softirq_to_name[vec_nr], h->action, prev_count, preempt_count()); preempt_count_set(prev_count); } h++; pending >>= softirq_bit; } if (!IS_ENABLED(CONFIG_PREEMPT_RT) && ksirqd) rcu_softirq_qs(); local_irq_disable(); pending = local_softirq_pending(); if (pending) { if (time_before(jiffies, end) && !need_resched() && --max_restart) goto restart; wakeup_softirqd(); } account_softirq_exit(current); lockdep_softirq_end(in_hardirq); softirq_handle_end(); current_restore_flags(old_flags, PF_MEMALLOC); }
__do_softirq 不在内核线程中处理软中断 1 2 3 4 asmlinkage __visible void __softirq_entry __do_softirq(void ) { handle_softirqs(false ); }
__raise_softirq_irqoff 设置指定类型的软中断(SoftIRQ)为待处理状态 1 2 3 4 5 6 7 8 void __raise_softirq_irqoff(unsigned int nr){ lockdep_assert_irqs_disabled(); trace_softirq_raise(nr); or_softirq_pending(1UL << nr); }
wakeup_softirqd 唤醒 ksoftirqd 内核线程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static void wakeup_softirqd (void ) { struct task_struct *tsk = __this_cpu_read(ksoftirqd); if (tsk) wake_up_process(tsk); }
raise_softirq_irqoff 用于触发指定类型的软中断(SoftIRQ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 inline void raise_softirq_irqoff (unsigned int nr) { __raise_softirq_irqoff(nr); if (!in_interrupt() && should_wake_ksoftirqd()) wakeup_softirqd(); }
timer_thread 用于处理定时器中断的内核线程 1 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 static struct smp_hotplug_thread timer_thread = { .store = &ktimerd, .setup = ktimerd_setup, .thread_should_run = ktimerd_should_run, .thread_fn = run_ktimerd, .thread_comm = "ktimers/%u" , }; static void ktimerd_setup (unsigned int cpu) { sched_set_fifo_low(current); } static int ktimerd_should_run (unsigned int cpu) { return local_timers_pending_force_th(); } static void run_ktimerd (unsigned int cpu) { unsigned int timer_si; ksoftirqd_run_begin(); timer_si = local_timers_pending_force_th(); __this_cpu_write(pending_timer_softirq, 0 ); or_softirq_pending(timer_si); __do_softirq(); ksoftirqd_run_end(); }
spawn_ksoftirqd 用于创建和初始化 ksoftirqd 内核线程 1 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 static struct smp_hotplug_thread softirq_threads = { .store = &ksoftirqd, .thread_should_run = ksoftirqd_should_run, .thread_fn = run_ksoftirqd, .thread_comm = "ksoftirqd/%u" , }; static int __init spawn_ksoftirqd (void ) { cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead" , NULL , takeover_tasklets); BUG_ON(smpboot_register_percpu_thread(&softirq_threads)); #ifdef CONFIG_IRQ_FORCED_THREADING if (force_irqthreads()) BUG_ON(smpboot_register_percpu_thread(&timer_thread)); #endif return 0 ; } early_initcall(spawn_ksoftirqd);
stm32_gpiolib_register_bank: 注册一个GPIO端口到内核 此函数是STM32驱动中将一个物理GPIO端口(例如GPIOA, GPIOB等)注册为可供Linux内核其他部分使用的gpio_chip
的核心。它的原理是收集并打包特定GPIO端口的所有硬件信息(寄存器地址、中断能力、引脚名称等), 然后调用通用的gpiochip_add_data
函数, 将这个打包好的实体正式提交给内核的gpiolib框架 。完成此操作后, 其他驱动程序就能通过标准的gpio_request()
等API来使用这个端口的引脚了。
具体执行步骤如下:
硬件准备 : 它首先将GPIO端口的硬件脱离复位状态(reset_control_deassert
), 然后解析设备树以获取该端口寄存器的物理地址, 并通过devm_ioremap_resource
将其映射到内核的虚拟地址空间, 使得CPU可以访问。
gpio_chip
结构体填充 : 这是核心步骤, gpio_chip
是gpiolib框架理解一个GPIO控制器的标准”名片”。
它从一个通用模板开始, 然后用设备树中定义的具体信息(如端口名称st,bank-name
)来填充。
它解析gpio-ranges
属性来确定该端口的引脚在Linux全局GPIO编号空间中的起始编号。如果该属性不存在, 它会采用一种旧的、基于端口顺序的编号方案, 并手动调用pinctrl_add_gpio_range
来建立pinctrl和gpiolib之间的映射关系。
它设置该gpio_chip
包含的引脚数量(ngpio
, 通常是16)、父设备等信息。
中断体系建立 : 如果pinctrl支持中断, 它会为当前这个GPIO端口创建一个层级式中断域 (irq_domain_create_hierarchy
)。这会将该端口的中断处理能力链接到上一层的EXTI(外部中断)控制器中断域。当中断发生时, 中断信号会沿着这个层级正确地上传和处理。
引脚命名 : 它会遍历该端口的所有引脚(0-15), 从pinctrl驱动的引脚数据库中查出每个引脚的名称(如 “PA0”, “PA1”), 并将这个名称列表关联到gpio_chip
。这对于调试和用户空间通过sysfs
查看信息非常有用。
最终注册 : 万事俱备后, 它调用gpiochip_add_data
, 将完全配置好的gpio_chip
注册到内核。bank
指针作为私有数据被一同传入, 这样当gpiolib调用此芯片的操作函数(如.set
, .get
)时, 这些函数可以方便地访问到该端口的寄存器基地址和锁等私有信息。
1 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 static int stm32_gpiolib_register_bank (struct stm32_pinctrl *pctl, struct fwnode_handle *fwnode) { struct stm32_gpio_bank *bank = &pctl->banks[pctl->nbanks]; int bank_ioport_nr; struct pinctrl_gpio_range *range = &bank->range; struct fwnode_reference_args args ; struct device *dev = pctl->dev; struct resource res ; int npins = STM32_GPIO_PINS_PER_BANK; int bank_nr, err, i = 0 ; struct stm32_desc_pin *stm32_pin ; char **names; if (!IS_ERR(bank->rstc)) reset_control_deassert(bank->rstc); if (of_address_to_resource(to_of_node(fwnode), 0 , &res)) return -ENODEV; bank->base = devm_ioremap_resource(dev, &res); if (IS_ERR(bank->base)) return PTR_ERR(bank->base); bank->gpio_chip = stm32_gpio_template; fwnode_property_read_string(fwnode, "st,bank-name" , &bank->gpio_chip.label); if (!fwnode_property_get_reference_args(fwnode, "gpio-ranges" , NULL , 3 , i, &args)) { bank_nr = args.args[1 ] / STM32_GPIO_PINS_PER_BANK; bank->gpio_chip.base = args.args[1 ]; npins = args.args[0 ] + args.args[2 ]; while (!fwnode_property_get_reference_args(fwnode, "gpio-ranges" , NULL , 3 , ++i, &args)) npins = max(npins, (int )(args.args[0 ] + args.args[2 ])); } else { bank_nr = pctl->nbanks; bank->gpio_chip.base = bank_nr * STM32_GPIO_PINS_PER_BANK; range->name = bank->gpio_chip.label; range->id = bank_nr; range->pin_base = range->id * STM32_GPIO_PINS_PER_BANK; range->base = range->id * STM32_GPIO_PINS_PER_BANK; range->npins = npins; range->gc = &bank->gpio_chip; pinctrl_add_gpio_range(pctl->pctl_dev, &pctl->banks[bank_nr].range); } if (fwnode_property_read_u32(fwnode, "st,bank-ioport" , &bank_ioport_nr)) bank_ioport_nr = bank_nr; bank->gpio_chip.base = -1 ; bank->gpio_chip.ngpio = npins; bank->gpio_chip.fwnode = fwnode; bank->gpio_chip.parent = dev; bank->bank_nr = bank_nr; bank->bank_ioport_nr = bank_ioport_nr; bank->secure_control = pctl->match_data->secure_control; bank->rif_control = pctl->match_data->rif_control; spin_lock_init(&bank->lock); if (pctl->domain) { bank->fwnode = fwnode; bank->domain = irq_domain_create_hierarchy(pctl->domain, 0 , STM32_GPIO_IRQ_LINE, bank->fwnode, &stm32_gpio_domain_ops, bank); if (!bank->domain) return -ENODEV; } names = devm_kcalloc(dev, npins, sizeof (char *), GFP_KERNEL); if (!names) return -ENOMEM; for (i = 0 ; i < npins; i++) { stm32_pin = stm32_pctrl_get_desc_pin_from_gpio(pctl, bank, i); if (stm32_pin && stm32_pin->pin.name) { names[i] = devm_kasprintf(dev, GFP_KERNEL, "%s" , stm32_pin->pin.name); if (!names[i]) return -ENOMEM; } else { names[i] = NULL ; } } bank->gpio_chip.names = (const char * const *)names; err = gpiochip_add_data(&bank->gpio_chip, bank); if (err) { dev_err(dev, "Failed to add gpiochip(%d)!\n" , bank_nr); return err; } dev_info(dev, "%s bank added\n" , bank->gpio_chip.label); return 0 ; }
tasklet_setup 和 tasklet_kill: Tasklet的初始化与销毁 Tasklet
是Linux内核中一种用于”下半部”(bottom half)处理的机制。它的原理是提供一个轻量级的、可被调度的函数, 用于执行那些不适合在硬件中断处理程序中完成的、耗时稍长的工作。这两个函数分别是它的”构造函数”和”析构函数”。
tasklet_setup
: 初始化一个Tasklet此函数用于在使用tasklet
之前, 对其数据结构tasklet_struct
进行正确的初始化。它是一个准备步骤, 将结构体设置为一个已知的、干净的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void tasklet_setup (struct tasklet_struct *t, void (*callback)(struct tasklet_struct *)) { t->next = NULL ; t->state = 0 ; atomic_set (&t->count, 0 ); t->callback = callback; t->use_callback = true ; t->data = 0 ; } EXPORT_SYMBOL(tasklet_setup);
tasklet_kill
: 销毁一个Tasklet此函数用于安全地移除一个tasklet。它的核心是确保tasklet不再被调度, 并等待其执行完毕(如果它恰好正在运行) 。这在驱动卸载时至关重要, 可以防止在驱动资源被释放后, 仍然有一个tasklet在尝试访问这些无效的资源, 从而导致系统崩溃。
1 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 void tasklet_kill (struct tasklet_struct *t) { if (in_interrupt()) pr_notice("Attempt to kill tasklet from interrupt\n" ); wait_on_bit_lock(&t->state, TASKLET_STATE_SCHED, TASK_UNINTERRUPTIBLE); tasklet_unlock_wait(t); tasklet_clear_sched(t); } EXPORT_SYMBOL(tasklet_kill);
Tasklet调度核心机制 此代码片段展示了Linux内核中Tasklet调度机制的核心底层实现 。Tasklet是一种轻量级的、高性能的延迟工作(Deferred Work)机制, 其目的是将那些不适合在硬件中断处理程序(硬中断上下文)中完成的、耗时稍长的工作, “推迟”到更宽松的软中断(softirq)上下文中执行。
这组函数的核心原理是通过一个原子操作的”门禁”和一个CPU本地的链表队列, 高效地将一个待执行的任务(tasklet)排入队列, 并触发一个软中断来最终处理这个队列 。
tasklet_schedule
: 调度一个Tasklet (公共API)这是驱动程序开发者最常使用的、用于调度一个Tasklet的入口API 。它的设计核心是幂等性 和效率 。
原理 : 它通过一个原子的test_and_set_bit
操作来确保, 即使一个tasklet被频繁地、连续地调度, 它也只会被添加到执行队列中一次 。这可以防止队列被同一个待办任务淹没, 是一种至关重要的优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static inline void tasklet_schedule (struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) __tasklet_schedule(t); }
__tasklet_schedule
: 调度一个普通优先级的Tasklet这是一个内部函数, 是tasklet_schedule
成功通过”门禁”后调用的下一步。它的作用是将任务分发到处理普通优先级tasklet的通用路径上 。
原理 : 它作为一个”分发器”, 调用更底层的__tasklet_schedule_common
函数, 并明确指定使用普通优先级的队列(tasklet_vec
)和普通优先级的软中断号(TASKLET_SOFTIRQ
)。
1 2 3 4 5 6 7 8 9 10 11 12 void __tasklet_schedule(struct tasklet_struct *t){ __tasklet_schedule_common(t, &tasklet_vec, TASKLET_SOFTIRQ); } EXPORT_SYMBOL(__tasklet_schedule);
__tasklet_schedule_common
: Tasklet调度的核心实现这是实际执行排队和触发动作的核心函数。
原理 : 它通过禁用本地中断 来保证对当前CPU私有队列 的原子访问 , 将新的tasklet以O(1)的效率添加到队列末尾, 然后升起一个软中断 来通知内核”有活要干了”。
对于用户指定的STM32H750(单核)架构 :
DEFINE_PER_CPU
宏只会为tasklet_vec
分配一个实例。
this_cpu_ptr
总是返回指向这个唯一实例的指针。
local_irq_save
在这种情况下依然至关重要 。它防止的不是来自其他CPU的并发访问, 而是防止当前任务在修改队列时, 被一个硬件中断处理程序抢占 , 而这个中断处理程序可能也会尝试调度一个tasklet, 从而导致对队列的竞争和破坏。
1 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 static void __tasklet_schedule_common(struct tasklet_struct *t, struct tasklet_head __percpu *headp, unsigned int softirq_nr) { struct tasklet_head *head ; unsigned long flags; local_irq_save(flags); head = this_cpu_ptr(headp); t->next = NULL ; *head->tail = t; head->tail = &(t->next); raise_softirq_irqoff(softirq_nr); local_irq_restore(flags); }