当前位置: 首页 > news >正文

[Linux]学习笔记系列 -- [kernel][irq]softirq


title: softirq
categories:

  • linux
  • kernel
  • irq
    tags:
  • linux
  • kernel
  • irq
    abbrlink: 366cb710
    date: 2025-10-03 09:01:49

文章目录

  • kernel/softirq.c 内核中断下半部(Interrupt Bottom-Half) 核心实现
      • 历史与背景
        • 这项技术是为了解决什么特定问题而诞生的?
        • 它的发展经历了哪些重要的里程碑或版本迭代?
        • 目前该技术的社区活跃度和主流应用情况如何?
      • 核心原理与设计
        • 它的核心工作原理是什么?
        • 它的主要优势体现在哪些方面?
        • 它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
      • 使用场景
        • 在哪些具体的业务或技术场景下,它是首选解决方案?
        • 是否有不推荐使用该技术的场景?为什么?
      • 对比分析
        • 请将其 与 其他相似技术 进行详细对比。
  • kernel/softirq.c
    • invoke_softirq 触发软中断的执行
    • irq_enter 进入中断上下文 irq_exit 退出中断上下文
    • open_softirq 软中断类型
    • softirq_init 软中断初始化
    • run_ksoftirqd ksoftirqd 内核线程
    • softirq_handle_begin 和 softirq_handle_end
    • handle_softirqs 处理软中断
    • __do_softirq 不在内核线程中处理软中断
    • __raise_softirq_irqoff 设置指定类型的软中断(SoftIRQ)为待处理状态
    • wakeup_softirqd 唤醒 ksoftirqd 内核线程
    • raise_softirq_irqoff 用于触发指定类型的软中断(SoftIRQ)
    • timer_thread 用于处理定时器中断的内核线程
    • spawn_ksoftirqd 用于创建和初始化 ksoftirqd 内核线程
      • stm32_gpiolib_register_bank: 注册一个GPIO端口到内核
    • tasklet_setup 和 tasklet_kill: Tasklet的初始化与销毁
      • `tasklet_setup`: 初始化一个Tasklet
      • `tasklet_kill`: 销毁一个Tasklet
    • Tasklet调度核心机制
      • `tasklet_schedule`: 调度一个Tasklet (公共API)
      • `__tasklet_schedule`: 调度一个普通优先级的Tasklet
      • `__tasklet_schedule_common`: Tasklet调度的核心实现

在这里插入图片描述

https://github.com/wdfk-prog/linux-study

kernel/softirq.c 内核中断下半部(Interrupt Bottom-Half) 核心实现

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/softirq.c 实现的软中断(softirq)机制是为了解决一个操作系统内核设计中的核心矛盾:中断处理程序的执行时间必须极短,但中断所触发的工作任务可能很耗时

  • 中断处理的紧迫性:硬件中断发生时,CPU会立即暂停当前工作,跳转到中断处理程序(Interrupt Service Routine, ISR,也称“上半部”/“Top Half”)。在执行ISR期间,通常会屏蔽掉当前CPU上的同级甚至所有中断,以保证处理的原子性和快速性。如果ISR执行时间过长,会导致系统无法响应其他新的硬件中断,增加系统延迟(Latency),甚至丢失中断事件。
  • 任务的复杂性:然而,中断事件所触发的后续处理可能很复杂。例如,网卡收到一个数据包的中断,其后续处理包括解析协议栈、将数据包递交给上层应用等,这些都是耗时操作。

软中断机制就是为了将中断处理分割为两个部分而设计的:

  1. 上半部(Top Half / Hard IRQ):在关中断的ISR中执行,只做最紧急的工作,如响应硬件、读取状态、将数据从硬件FIFO拷贝到内存,然后标记一个“软中断”请求。这个过程必须极快。
  2. 下半部(Bottom Half / Softirq):在稍后的、更宽松的环境中(中断是打开的)执行那些耗时的任务。softirq就是最高性能的一种下半部实现。
它的发展经历了哪些重要的里程碑或版本迭代?

Linux下半部机制经历了显著的演进:

  • 早期的BH(Bottom Halves):Linux早期内核有一种BH机制。它很简单,但存在一个致命缺陷:在多处理器(SMP)系统上,同一个BH不能同时在多个CPU上运行,存在全局锁,扩展性极差。
  • Softirq和Tasklet的引入:为了解决SMP扩展性问题,内核引入了softirqsoftirq的一个关键设计是,同一种类型的软中断(如网络接收)可以同时在多个CPU上并发执行,极大地提升了多核系统的性能。与此同时,为了方便普通驱动开发者,内核在softirq之上构建了更简单的tasklet机制。
  • ksoftirqd线程的出现:在高负载情况下(如网络流量风暴),软中断可能被频繁地触发,导致CPU一直在处理软中断而无法执行用户进程,造成用户进程“饥饿”。为了解决这个问题,内核为每个CPU都创建了一个名为ksoftirqd/X的内核线程。当软中断负载过高时,未处理完的工作会被这个线程接管,由于线程是受调度器管理的,可以保证用户进程也有机会运行。
目前该技术的社区活跃度和主流应用情况如何?

softirq是Linux内核中断处理和调度的基石,其代码非常成熟、稳定。它不是一个经常变动的功能,而是其他高性能子系统(如网络、定时器)赖以构建的基础。
它的应用场景高度集中在对性能和低延迟要求极高的核心子系统中:

  • 网络栈:几乎所有的网络数据包收发处理(NET_TX_SOFTIRQ, NET_RX_SOFTIRQ)都是通过软中断完成的。
  • 定时器子系统:定时器到期后的回调函数执行是通过TIMER_SOFTIRQ触发的。
  • 块设备:I/O操作完成后的处理会通过BLOCK_SOFTIRQ进行。

核心原理与设计

它的核心工作原理是什么?

softirq的核心是一个基于位掩码的、静态定义的、可并发的延迟任务执行框架

  1. 静态定义:内核预定义了少数几种软中断类型(在enum softirq_action中),如HI_SOFTIRQ, TIMER_SOFTIRQ, NET_RX_SOFTIRQ等。它们在编译时就已确定,不能在运行时动态添加。
  2. 触发(Raising):上半部(硬中断处理程序)在完成其紧急工作后,会调用raise_softirq(softirq_type)。这个函数非常轻量,它只是在当前CPU的一个私有变量(一个位掩码)中设置与softirq_type对应的位。
  3. 执行(Execution):内核会在一些特定的、安全的时间点检查这个位掩码,如果发现有挂起的软中断,就会调用do_softirq()来执行它们。这些时间点包括:
    • 从硬中断处理程序返回时
    • 从系统调用返回时
    • ksoftirqd内核线程中
  4. do_softirq()的逻辑:该函数会检查当前CPU的软中断挂起位掩码,然后按从高到低的优先级,依次调用预先注册好的处理函数(softirq_action数组)。
  5. 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)SoftirqTaskletWorkqueue
执行上下文硬中断上下文软中断上下文软中断上下文进程上下文
能否睡眠绝对不能绝对不能绝对不能可以
并发性不可重入,执行时屏蔽同级中断。可并发:同一种softirq可在多个CPU上同时运行。不可并发:同一种tasklet在任意时刻只能在一个CPU上运行。可并发,由工作线程数量决定。
分配方式静态(通过request_irq静态(编译时确定类型)动态(可随时初始化一个tasklet)动态(可随时初始化一个work_struct)
使用场景对硬件的紧急、快速响应。内核核心、高性能、高频率的任务(网络、定时器)。普通设备驱动的延迟任务(不需睡眠)。需要睡眠或耗时很长的延迟任务(如涉及文件I/O)。

总结关系tasklet实际上是构建在softirq之上的一个简化封装。它使用HI_SOFTIRQTASKLET_SOFTIRQ这两个软中断向量,并增加了一个锁来保证同类tasklet的串行执行,从而为驱动开发者提供了更简单的并发模型。

kernel/softirq.c

invoke_softirq 触发软中断的执行

/** 这是一个静态内联函数,用于触发软中断的执行。* 它在硬中断退出路径上被调用。*/
static inline void invoke_softirq(void)
{/** 首先,判断是否应该将软中断处理委托给ksoftirqd内核线程。* 条件是:系统没有强制要求中断线程化,或者当前CPU的ksoftirqd线程还没有被创建。* 如果这个条件满足,我们就尝试立即在当前上下文中执行软中断。*/if (!force_irqthreads() || !__this_cpu_read(ksoftirqd)) {
#ifdef CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK/** 如果内核配置为在专用的IRQ栈上退出中断(这是为了健壮性),* 那么我们就可以安全地在当前栈上执行软中断。* 因为在中断退出的这个阶段,IRQ栈应该是几乎空的。*//** __do_softirq() 是真正执行软中断处理循环的核心函数。*/__do_softirq();
#else/** 否则,irq_exit()是在任务自己的内核栈上被调用的,* 而这个任务栈可能已经很深了(例如,经过了多层系统调用)。* 在这种情况下,为了防止任何可能的栈溢出,我们需要切换到一个* 专用的软中断栈来执行软中断处理。*/do_softirq_own_stack();
#endif} else {/** 如果执行到这里,说明系统被配置为强制中断线程化,* 或者ksoftirqd线程已经存在。* 这种情况下,我们不直接执行软中断,而是唤醒它。*/wakeup_softirqd();}
}

irq_enter 进入中断上下文 irq_exit 退出中断上下文

/*** irq_enter_rcu - 进入一个受RCU监控的中断上下文。*/
void irq_enter_rcu(void)
{/** 调用底层的__irq_enter_raw(),它会原子性地增加抢占计数器* 中的硬中断计数值(preempt_count_add(HARDIRQ_OFFSET)),* 并做一些RCU相关的状态更新。这是“记账”的核心。*/__irq_enter_raw();/** 如果当前CPU是一个NOHZ_FULL CPU(完全无动态时钟,用于高性能计算),* 或者当前CPU正处于idle任务中且这是唤醒它的第一个硬中断,* 那么就需要调用tick_irq_enter()来重新激活该CPU的时钟节拍。*/if (tick_nohz_full_cpu(smp_processor_id()) ||(is_idle_task(current) && (irq_count() == HARDIRQ_OFFSET)))tick_irq_enter();/** 更新CPU时间统计,将CPU从之前的状态(如idle或user)切换到* 硬中断(HARDIRQ)状态,以便进行精确的时间消耗统计。*/account_hardirq_enter(current);
}/*** irq_enter - 进入一个中断上下文,包括RCU更新。* 这是通用的中断入口函数。*/
void irq_enter(void)
{/** 通知上下文追踪(Context Tracking)子系统,我们即将进入IRQ状态。*/ct_irq_enter();/** 调用核心的irq_enter_rcu()函数,完成抢占计数、RCU和时间统计等工作。*/irq_enter_rcu();
}/** 这是一个静态内联函数,是irq_exit的核心实现,不直接导出。*/
static inline void __irq_exit_rcu(void)
{
#ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED/* 如果当前架构没有保证在退出时中断是关闭的,这里手动关闭一下。*/local_irq_disable();
#else/* 否则,断言中断确实是关闭的,用于调试。*/lockdep_assert_irqs_disabled();
#endif/* 更新CPU时间统计,标记硬中断状态结束。*/account_hardirq_exit(current);/* 原子性地减少抢占计数器中的硬中断计数值。*/preempt_count_sub(HARDIRQ_OFFSET);/** 如果!in_interrupt()为真(表示这是最外层中断的退出),* 并且有待处理的软中断,则调用invoke_softirq()来执行它们。* 这是Linux下半部机制的核心触发点。*/if (!in_interrupt() && local_softirq_pending())/* 软中断处理(Softirq Handling): 在退出最外层中断时,irq_exit负责检查并触发所有待处理的软中断。这是Linux将中断处理分为“上半部(紧急)”和“下半部(可延迟)”两部分的关键机制 */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();
}/*** irq_exit_rcu() - 退出一个中断上下文,不更新RCU。** 也会在需要和可能的情况下处理软中断。*/
void irq_exit_rcu(void)
{/* 调用核心的退出逻辑。*/__irq_exit_rcu();/* 必须是最后一步!通知锁依赖检查器,硬中断上下文已结束。*/lockdep_hardirq_exit();
}/*** irq_exit - 退出一个中断上下文,更新RCU和锁依赖检查器。* 这是通用的中断退出函数。** 也会在需要和可能的情况下处理软中断。*/
void irq_exit(void)
{/* 调用核心的退出逻辑。*/__irq_exit_rcu();/* 通知上下文追踪子系统,IRQ状态已结束。*/ct_irq_exit();/* 必须是最后一步!通知锁依赖检查器,硬中断上下文已结束。*/lockdep_hardirq_exit();
}

open_softirq 软中断类型

enum
{/* 高优先级软中断,通常用于处理紧急任务。
这些任务需要尽快完成,但不需要阻塞其他任务。 */HI_SOFTIRQ=0,/* 定时器软中断,用于处理定时器事件。
例如,周期性任务或超时事件的处理。 */TIMER_SOFTIRQ,/* 网络发送(NET_TX_SOFTIRQ)和接收(NET_RX_SOFTIRQ)软中断。
用于处理网络数据包的发送和接收,确保网络通信的高效性。 */NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,/* 块设备软中断,用于处理块设备(如硬盘)的 I/O 操作。
例如,完成磁盘读写任务 */BLOCK_SOFTIRQ,/* 断轮询软中断,用于处理中断轮询相关的任务。
通常用于优化设备的中断处理 */IRQ_POLL_SOFTIRQ,/* 任务软中断,用于处理任务队列中的任务。
任务队列是一种轻量级的延迟任务机制。 */TASKLET_SOFTIRQ,/* 调度软中断,用于处理任务调度相关的操作。
例如,重新分配 CPU 资源。 */SCHED_SOFTIRQ,/* 高精度定时器软中断,用于处理高精度定时器事件。
适用于需要精确时间控制的任务。 */HRTIMER_SOFTIRQ,/* RCU(Read-Copy-Update)软中断,用于处理 RCU 机制相关的任务。
RCU 是一种高效的读写同步机制,通常用于内核数据结构的更新。 */RCU_SOFTIRQ,    /* 首选RCU应该始终是最后一个软中断 */NR_SOFTIRQS
};void open_softirq(int nr, void (*action)(void))
{softirq_vec[nr].action = action;
}

softirq_init 软中断初始化

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 内核线程

/** 这是一个静态函数,作为smpboot框架的回调,用于判断ksoftirqd线程是否应该运行。* @cpu: 当前CPU的编号(尽管在此函数中未使用,但为保持回调接口一致性而存在)。** 返回值: 如果有待处理的软中断,则返回非零值(true);否则返回0(false)。*/
static int ksoftirqd_should_run(unsigned int cpu)
{/** 直接调用local_softirq_pending()宏,检查当前CPU是否有挂起的软中断。* 这个宏会读取一个per-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();
}/** 这是一个静态函数,作为smpboot框架的核心工作回调,由ksoftirqd线程执行。* @cpu: 当前CPU的编号(在此函数中未使用,但为保持回调接口一致性而存在)。*/
static void run_ksoftirqd(unsigned int cpu)
{/* 调用此函数,将当前ksoftirqd线程状态置为TASK_RUNNING,并做一些准备。*/ksoftirqd_run_begin();/* 再次检查是否有待处理的软中断,防止在唤醒到执行的间隙中已被处理。*/if (local_softirq_pending()) {/** 我们可以安全地在内联栈上运行softirq,因为我们在这里* 并不处于任务栈的深处。*//** 调用handle_softirqs(),这是处理所有类型软中断的核心函数。* 它会循环处理所有挂起的软中断,直到处理完毕或达到循环上限。* 传入true参数可能表示这是一个从ksoftirqd上下文的调用。*/handle_softirqs(true);/* 调用此函数,将ksoftirqd线程状态恢复为TASK_INTERRUPTIBLE。*/ksoftirqd_run_end();/** 调用条件重新调度函数。这会检查是否有更高优先级的任务在等待,* 如果有,就主动让出CPU。这是保证系统响应性的关键。*/cond_resched();/* 处理完毕,返回。smpboot_thread_fn会继续下一次主循环。*/return;}/* 如果没有待处理的软中断,直接调用结束函数并返回。*/ksoftirqd_run_end();
}

softirq_handle_begin 和 softirq_handle_end

  • 这两个函数用于在处理软中断时禁用和启用本地
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),直到所有挂起的软中断都被处理完毕。
/** 我们最多重新启动软中断处理 MAX_SOFTIRQ_RESTART 次,* 但如果设置了 need_resched() 或超过 2 毫秒,则中断循环。* MAX_SOFTIRQ_TIME 在大多数情况下提供了一个不错的上限,但在某些情况下,* 例如 stop_machine(),jiffies 可能会停止递增,因此我们还需要* MAX_SOFTIRQ_RESTART 限制以确保最终从此方法返回。** 这些限制是通过实验确定的。* 需要平衡的两件事是延迟与公平性——* 我们希望尽快处理软中断,但它们不应该能够锁住系统。*/#define MAX_SOFTIRQ_TIME  msecs_to_jiffies(2)
#define MAX_SOFTIRQ_RESTART 10static inline bool lockdep_softirq_start(void) { return false; }
static inline void lockdep_softirq_end(bool in_hardirq) { }/** 这是一个静态函数,负责处理所有挂起的软中断。* @ksirqd: 一个布尔值,如果为true,表示本次调用来自于ksoftirqd内核线程上下文。*/
static void handle_softirqs(bool ksirqd)
{/* end: 定义一个超时时间戳,防止软中断处理占用CPU过久。*/unsigned long end = jiffies + MAX_SOFTIRQ_TIME;/* old_flags: 保存当前任务原始的flags,以便处理后恢复。*/unsigned long old_flags = current->flags;/* max_restart: 最大的重入处理次数,防止活锁。*/int max_restart = MAX_SOFTIRQ_RESTART;/* h: 指向软中断向量表的指针。*/struct softirq_action *h;/* in_hardirq: 用于锁依赖检测器,记录进入时是否在硬中断上下文中。*/bool in_hardirq;/* pending: 32位掩码,用于存储当前CPU所有待处理的软中断。*/__u32 pending;/* softirq_bit: 在循环中,存储当前正在处理的软中断的位号。*/int softirq_bit;/** 清除PF_MEMALLOC标志,因为当前任务上下文被软中断借用了。* 一个被处理的软中断,例如网络RX,如果socket与交换(swapping)相关,* 可能会再次设置PF_MEMALLOC。*/current->flags &= ~PF_MEMALLOC;/* 调用local_softirq_pending()获取当前所有挂起的软中断位掩码。*/pending = local_softirq_pending();/* 通知相关子系统(如lockdep),软中断处理即将开始。*/softirq_handle_begin();in_hardirq = lockdep_softirq_start();/* 为当前任务记账进入软中断所消耗的时间。*/// account_softirq_enter(current);restart: /* 这是处理循环的入口点。*//* 在开启中断之前,重置待处理位掩码。*/set_softirq_pending(0);/* 开启本地硬件中断。软中断总是在中断开启的状态下执行。*/local_irq_enable();/* h指向软中断向量表的起始处。*/h = softirq_vec;/** 只要pending掩码中还有被置位的位,就一直循环。* ffs(pending) (find first set) 返回第一个被置位的位的编号(从1开始)。*/while ((softirq_bit = ffs(pending))) {unsigned int vec_nr;int prev_count;/* 将h指针移动到对应的软中断处理函数结构体。*/h += softirq_bit - 1;/* 计算出向量号(数组索引)。*/vec_nr = h - softirq_vec;/* 保存执行前的抢占计数值。*/prev_count = preempt_count();/* 增加该CPU上此类型软中断的统计计数。*/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指针移动到下一个向量。*/h++;/* 将pending掩码右移,清除已经处理过的位,准备下一次ffs。*/pending >>= softirq_bit;}/* 如果不是在RT内核中,并且是从ksirqd上下文调用的,则通知RCU这是一个静止状态点。*/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)/* ...则跳转到restart,开始新一轮处理。*/goto restart;/* 否则,唤醒ksoftirqd内核线程,让它来处理剩余的工作。*/wakeup_softirqd();}/* 为当前任务记账退出软中断。*/account_softirq_exit(current);/* 通知锁依赖检测器,软中断处理结束。*/lockdep_softirq_end(in_hardirq);/* 其他清理工作。*/softirq_handle_end();/* 恢复当前任务原始的flags(主要是恢复可能被修改的PF_MEMALLOC)。*/current_restore_flags(old_flags, PF_MEMALLOC);
}

__do_softirq 不在内核线程中处理软中断

asmlinkage __visible void __softirq_entry __do_softirq(void)
{handle_softirqs(false);
}

__raise_softirq_irqoff 设置指定类型的软中断(SoftIRQ)为待处理状态

void __raise_softirq_irqoff(unsigned int nr)
{/* 确保当前运行环境中断已被禁用 */lockdep_assert_irqs_disabled();trace_softirq_raise(nr);/* 将指定类型的软中断标记为待处理状态。 */or_softirq_pending(1UL << nr);
}

wakeup_softirqd 唤醒 ksoftirqd 内核线程

/** 我们不能在这里无限循环以避免用户空间匮乏,* 但我们也不想给待处理事件引入最坏情况的 1/HZ 延迟,* 所以让调度器为我们平衡软中断负载。*/
static void wakeup_softirqd(void)
{/* 中断被禁用:无需停止抢占*//* ksoftirqd 是一个专门用于处理软中断的内核线程,每个 CPU 都有一个独立的实例 */struct task_struct *tsk = __this_cpu_read(ksoftirqd);/* 如果 tsk 非空,表示当前 CPU 上存在 ksoftirqd 内核线程。调用 wake_up_process 唤醒该线程,使其能够处理挂起的软中断任务 */if (tsk)wake_up_process(tsk);
}

raise_softirq_irqoff 用于触发指定类型的软中断(SoftIRQ)

/** 这个函数必须在禁用 irqs 的情况下运行!*/
inline void raise_softirq_irqoff(unsigned int nr)
{/* 设置软中断的状态 将指定的软中断类型(nr)标记为待处理状态*/__raise_softirq_irqoff(nr);/* in_interrupt():检查当前是否处于中断或软中断上下文。如果是,则无需进一步操作,因为软中断会在中断或软中断处理完成后自动执行 *//* should_wake_ksoftirqd():检查是否需要唤醒 ksoftirqd 内核线程。ksoftirqd 是一个专门用于处理软中断的内核线程。 static inline bool should_wake_ksoftirqd(void){return true;}*/if (!in_interrupt() && should_wake_ksoftirqd())/* 唤醒内核线程,以确保软中断能够尽快被调度和执行 */wakeup_softirqd();
}

timer_thread 用于处理定时器中断的内核线程

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",
};/** ktimerd_setup - ktimerd线程的初始化设置函数* @cpu: 运行此线程的CPU号*/
static void ktimerd_setup(unsigned int cpu)
{/** 将当前进程(即ktimerd线程)的调度策略设置为先进先出(FIFO)的低优先级。* SCHED_FIFO是一种实时调度策略,但设置为低优先级(fifo_low)意味着它* 仍然会比普通的SCHED_NORMAL任务优先执行,但不会抢占更高优先级的实时任务。* 这样做的目的是为了确保定时器相关的下半部任务,能够比常规的用户态* 或内核态任务更早地得到处理,保证定时器的精度和系统的响应性。*/sched_set_fifo_low(current);
}/** ktimerd_should_run - 判断ktimerd线程是否需要运行的条件函数* @cpu: 运行此线程的CPU号*/
static int ktimerd_should_run(unsigned int cpu)
{/** 调用local_timers_pending_force_th(),这个函数会检查当前CPU上* 是否有挂起的、并且被标记为“强制在线程中处理”的定时器软中断。* * "force_th" (force thread) 这个标记非常关键。有些定时器任务* (特别是高精度定时器hrtimer)如果在硬中断返回路径上处理,* 可能会因为执行时间过长而增加中断延迟。设置了这个标记的定时器* 软中断,就会被特意留给ktimerd来处理。* * 返回真,则ksoftirqd框架会唤醒并运行ktimerd。*//* static inline unsigned int local_timers_pending_force_th(void){return __this_cpu_read(pending_timer_softirq);} */return local_timers_pending_force_th();
}/** run_ktimerd - ktimerd线程的主体执行函数* @cpu: 运行此线程的CPU号*/
static void run_ktimerd(unsigned int cpu)
{unsigned int timer_si; // 用于临时存储待处理的定时器软中断掩码。/* 通知ksoftirqd框架,我们即将开始执行软中断,用于统计和调试。*/ksoftirqd_run_begin();/** **第一步:获取并清空任务列表*** 再次调用local_timers_pending_force_th(),获取当前所有待处理的、* 强制线程化的定时器软中断掩码,并将其存入timer_si。* * 紧接着,原子地将per-cpu的pending_timer_softirq清零。* 这个“获取并清零”的操作确保了我们不会丢失在处理期间新到达的任务。* 新任务会被raise_ktimers_thread设置到这个刚刚被清零的变量上,* 等待下一次ktimerd被唤醒时处理。*/timer_si = local_timers_pending_force_th();__this_cpu_write(pending_timer_softirq, 0);/** **第二步:将任务转移到通用软中断队列*** or_softirq_pending(timer_si) 会将我们刚刚从线程专用列表* (pending_timer_softirq)中取出的任务,合并到内核通用的* “待处理软中断”掩码中。*//* #define or_softirq_pending(x)	(__this_cpu_or(local_softirq_pending_ref, (x))) */or_softirq_pending(timer_si);/** **第三步:执行所有挂起的软中断*** __do_softirq() 是内核中实际执行软中断的核心函数。它会检查* 通用的“待处理软中断”掩码,并调用所有被标记的软中断对应的处理函数* (handler),当然也包括我们刚刚放进去的定时器任务。* 这是一个循环,会尽可能多地处理软中断,直到处理完毕或达到一个时间限制。*/__do_softirq();/* 通知ksoftirqd框架,本轮处理结束。*/ksoftirqd_run_end();
}

spawn_ksoftirqd 用于创建和初始化 ksoftirqd 内核线程

static struct smp_hotplug_thread softirq_threads = {.store			= &ksoftirqd,.thread_should_run	= ksoftirqd_should_run,.thread_fn		= run_ksoftirqd,.thread_comm		= "ksoftirqd/%u",
};/** 这是一个静态的、仅在初始化阶段调用的函数,负责衍生ksoftirqd等线程。*/
static int __init spawn_ksoftirqd(void)
{/** 向CPU热插拔(cpuhp)框架注册一个状态回调。* 当一个CPU进入CPUHP_SOFTIRQ_DEAD状态(即即将死亡)时,* takeover_tasklets函数将被调用,以处理该CPU上残留的tasklet。*/cpuhp_setup_state_nocalls(CPUHP_SOFTIRQ_DEAD, "softirq:dead", NULL,takeover_tasklets);/** 调用smpboot_register_percpu_thread,向smpboot框架注册一个* per-cpu线程模板softirq_threads。smpboot框架将在后续每个CPU* 上线时,根据这个模板自动创建一个ksoftirqd线程。* BUG_ON()确保如果注册失败(极不正常),系统会立即停机。*/BUG_ON(smpboot_register_percpu_thread(&softirq_threads));/* 如果内核配置了“强制中断线程化”支持。*/
#ifdef CONFIG_IRQ_FORCED_THREADING/* force_irqthreads()检查内核启动参数是否要求开启此功能。*/if (force_irqthreads())/* 如果开启,则同样地注册用于处理定时器中断的timer_thread模板。*/BUG_ON(smpboot_register_percpu_thread(&timer_thread));
#endif/* initcall函数要求返回一个int值,0表示成功。*/return 0;
}
/** 通过early_initcall()宏,将spawn_ksoftirqd函数注册到内核的早期* 初始化调用列表中。这确保了它在SMP被激活之前执行,能够为所有* 即将上线的CPU做好准备。*/
early_initcall(spawn_ksoftirqd);

stm32_gpiolib_register_bank: 注册一个GPIO端口到内核

此函数是STM32驱动中将一个物理GPIO端口(例如GPIOA, GPIOB等)注册为可供Linux内核其他部分使用的gpio_chip的核心。它的原理是收集并打包特定GPIO端口的所有硬件信息(寄存器地址、中断能力、引脚名称等), 然后调用通用的gpiochip_add_data函数, 将这个打包好的实体正式提交给内核的gpiolib框架。完成此操作后, 其他驱动程序就能通过标准的gpio_request()等API来使用这个端口的引脚了。

具体执行步骤如下:

  1. 硬件准备: 它首先将GPIO端口的硬件脱离复位状态(reset_control_deassert), 然后解析设备树以获取该端口寄存器的物理地址, 并通过devm_ioremap_resource将其映射到内核的虚拟地址空间, 使得CPU可以访问。
  2. gpio_chip结构体填充: 这是核心步骤, gpio_chip是gpiolib框架理解一个GPIO控制器的标准"名片"。
    • 它从一个通用模板开始, 然后用设备树中定义的具体信息(如端口名称st,bank-name)来填充。
    • 它解析gpio-ranges属性来确定该端口的引脚在Linux全局GPIO编号空间中的起始编号。如果该属性不存在, 它会采用一种旧的、基于端口顺序的编号方案, 并手动调用pinctrl_add_gpio_range来建立pinctrl和gpiolib之间的映射关系。
    • 它设置该gpio_chip包含的引脚数量(ngpio, 通常是16)、父设备等信息。
  3. 中断体系建立: 如果pinctrl支持中断, 它会为当前这个GPIO端口创建一个层级式中断域(irq_domain_create_hierarchy)。这会将该端口的中断处理能力链接到上一层的EXTI(外部中断)控制器中断域。当中断发生时, 中断信号会沿着这个层级正确地上传和处理。
  4. 引脚命名: 它会遍历该端口的所有引脚(0-15), 从pinctrl驱动的引脚数据库中查出每个引脚的名称(如 “PA0”, “PA1”), 并将这个名称列表关联到gpio_chip。这对于调试和用户空间通过sysfs查看信息非常有用。
  5. 最终注册: 万事俱备后, 它调用gpiochip_add_data, 将完全配置好的gpio_chip注册到内核。bank指针作为私有数据被一同传入, 这样当gpiolib调用此芯片的操作函数(如.set, .get)时, 这些函数可以方便地访问到该端口的寄存器基地址和锁等私有信息。
/** 静态函数声明: stm32_gpiolib_register_bank* @pctl: 指向驱动核心数据结构的指针.* @fwnode: 指向当前要注册的GPIO bank的设备树节点的句柄.* @return: 成功时返回0, 失败时返回负错误码.*/
static int stm32_gpiolib_register_bank(struct stm32_pinctrl *pctl, struct fwnode_handle *fwnode)
{/* 获取一个指向当前要填充的 bank 结构体的指针. pctl->nbanks 记录了已成功注册的数量. */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; // 默认每个bank有16个引脚int bank_nr, err, i = 0;struct stm32_desc_pin *stm32_pin;char **names;/* 如果复位控制器存在, 则将其解除复位状态, 使硬件开始工作. */if (!IS_ERR(bank->rstc))reset_control_deassert(bank->rstc);/* 从设备树节点的 'reg' 属性中解析出该bank的寄存器物理地址范围. */if (of_address_to_resource(to_of_node(fwnode), 0, &res))return -ENODEV;/* 将物理地址映射到内核虚拟地址空间, 以便访问寄存器. devm_* 版本会自动管理释放. */bank->base = devm_ioremap_resource(dev, &res);if (IS_ERR(bank->base))return PTR_ERR(bank->base);/* gpiolib的核心结构是 gpio_chip. 这里从一个预设的模板开始填充. */bank->gpio_chip = stm32_gpio_template;/* 从设备树读取 "st,bank-name" 属性 (例如 "GPIOA"), 作为 gpio_chip 的标签. */fwnode_property_read_string(fwnode, "st,bank-name", &bank->gpio_chip.label);/* 尝试解析 "gpio-ranges" 属性, 这是将pinctrl和gpiolib关联起来的现代标准方法. */if (!fwnode_property_get_reference_args(fwnode, "gpio-ranges", NULL, 3, i, &args)) {/* 如果成功, 根据解析出的信息计算bank的编号和Linux全局GPIO基准号. */bank_nr = args.args[1] / STM32_GPIO_PINS_PER_BANK;bank->gpio_chip.base = args.args[1];/* 计算该bank实际管理的引脚数量, 以支持某些bank可能不是完整的16个引脚的情况. */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 {/* 如果没有 "gpio-ranges" 属性 (兼容旧的设备树), 则采用一种简单的线性编号方案. */bank_nr = pctl->nbanks;bank->gpio_chip.base = bank_nr * STM32_GPIO_PINS_PER_BANK;/* 并且需要手动填充一个range结构, 并调用 pinctrl_add_gpio_range 告知pinctrl子系统. */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);}/* 读取可选的 "st,bank-ioport" 属性. */if (fwnode_property_read_u32(fwnode, "st,bank-ioport", &bank_ioport_nr))bank_ioport_nr = bank_nr;/* base设为-1表示希望gpiolib动态分配一个未使用的基准号, 这是推荐的做法. */bank->gpio_chip.base = -1;/* 填充gpio_chip的其他字段. */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;/* 初始化用于保护该bank寄存器访问的自旋锁. */spin_lock_init(&bank->lock);/* 如果pinctrl支持中断. */if (pctl->domain) {/* 为这个bank创建一个层级式中断域, 链接到pinctrl的主中断域(EXTI). */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;/* 遍历该bank的所有引脚, 从pinctrl的数据库中查找并复制其名称. */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;}}/* 将名称数组关联到gpio_chip. */bank->gpio_chip.names = (const char * const *)names;/* 最关键的一步: 将填充好的gpio_chip注册到gpiolib核心. */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进行正确的初始化。它是一个准备步骤, 将结构体设置为一个已知的、干净的状态。

/** tasklet_setup: 设置一个tasklet结构体.* @t: 指向要被初始化的 tasklet_struct 的指针.* @callback: 一个函数指针, 指向当tasklet被调度执行时要运行的函数.*/
void tasklet_setup(struct tasklet_struct *t,void (*callback)(struct tasklet_struct *))
{t->next = NULL; // 确保其不链接在任何链表中t->state = 0; // 状态清零, 清楚"已调度"和"正在运行"的标志atomic_set(&t->count, 0); // 将使能/禁用计数器清零 (0表示使能)t->callback = callback; // 存储核心的回调函数t->use_callback = true; // 标记为使用标准回调t->data = 0; // 传递给回调函数的参数, 初始化为0
}
EXPORT_SYMBOL(tasklet_setup);

tasklet_kill: 销毁一个Tasklet

此函数用于安全地移除一个tasklet。它的核心是确保tasklet不再被调度, 并等待其执行完毕(如果它恰好正在运行)。这在驱动卸载时至关重要, 可以防止在驱动资源被释放后, 仍然有一个tasklet在尝试访问这些无效的资源, 从而导致系统崩溃。

/** tasklet_kill: 杀死一个tasklet, 等待其完成.* @t: 指向要被杀死的 tasklet_struct 的指针.*/
void tasklet_kill(struct tasklet_struct *t)
{/* 检查是否在硬中断上下文中调用, 这是不推荐的, 因为kill可能睡眠. */if (in_interrupt())pr_notice("Attempt to kill tasklet from interrupt\n");/* * 第一步等待: 等待 TASKLET_STATE_SCHED 位被清除. * 这确保了tasklet不再位于调度队列中. _lock版本还会原子地设置该位,* 防止在我们等待期间, 它被重新调度.*/wait_on_bit_lock(&t->state, TASKLET_STATE_SCHED, TASK_UNINTERRUPTIBLE);/** 第二步等待: 调用 tasklet_unlock_wait, 它会等待 TASKLET_STATE_RUN 位被清除.* 这确保了如果tasklet在我们调用kill时正在运行, 我们会一直等到它执行完毕.* 在单核STM32上, 这意味着等待 softirq 上下文完成对该tasklet的执行.*/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被频繁地、连续地调度, 它也只会被添加到执行队列中一次。这可以防止队列被同一个待办任务淹没, 是一种至关重要的优化。

/** static inline: 定义一个静态内联函数, 通常在头文件中实现, 以提高性能.*/
static inline void tasklet_schedule(struct tasklet_struct *t)
{/** test_and_set_bit 是一个原子操作. 它会:* 1. 测试 t->state 中的 TASKLET_STATE_SCHED 位 (检查它原来是0还是1).* 2. 无论原来是什么, 都将该位设置为 1.* 3. 返回该位 *之前* 的值.** if (!test_and_set_bit(...)) 的条件只有在该位*原来是0*时才为真.* 这意味着: 如果 tasklet 尚未被调度 (SCHED位为0), 那么就设置该位并继续执行 __tasklet_schedule.* 如果 tasklet 已经被调度 (SCHED位为1), 那么这个函数什么也不做, 直接返回.* 这就保证了一个tasklet在被执行前, 只会被排队一次.*/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)。

void __tasklet_schedule(struct tasklet_struct *t)
{/** 调用通用的调度函数 __tasklet_schedule_common, 并传入:* - t: 要被调度的tasklet.* - &tasklet_vec: 指向 per-CPU 的、用于普通优先级tasklet的队列头.* - TASKLET_SOFTIRQ: 用于处理普通优先级tasklet的软中断号.*/__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, 从而导致对队列的竞争和破坏。
/** __tasklet_schedule_common: tasklet调度的通用核心实现* @t: 要被调度的tasklet* @headp: 指向 per-CPU 队列头数组的指针 (例如 &tasklet_vec)* @softirq_nr: 要触发的软中断号*/
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;/** 禁用当前CPU的本地中断, 并保存之前的中断状态到 'flags'.* 这是保护 per-CPU 队列的关键, 防止中断处理程序并发地修改它.*/local_irq_save(flags);/** 获取指向 *当前CPU* 的 tasklet_head 实例的指针.* 这是tasklet高性能和无锁设计的核心: 每个CPU只操作自己的队列.*/head = this_cpu_ptr(headp);/** 将新的tasklet 't' 添加到队列的尾部.* 这是一个 O(1) (常数时间) 的高效链表追加操作, 因为 'head->tail'* 直接指向了链表最后一个节点的 'next' 字段的地址.*/t->next = NULL;*head->tail = t;head->tail = &(t->next);/** 升起(Raise)指定的软中断.* 这个动作会设置一个标志位, 内核在退出中断或调度器运行时会检查这个标志位,* 如果被设置, 就会去执行对应的软中断处理函数(例如, tasklet_action).* 这个处理函数会遍历队列并执行所有排队的tasklet.* _irqoff 后缀表示这个函数可以在中断被禁用的状态下安全调用.*/raise_softirq_irqoff(softirq_nr);/** 恢复之前保存的中断状态.*/local_irq_restore(flags);
}
http://www.dtcms.com/a/494471.html

相关文章:

  • 家庭相册私有化:Immich+cpolar构建你的数字记忆堡垒
  • 存储同步管理器SyncManager 归纳
  • 做游戏网站多少钱建设电子商务网站要多少钱
  • iBizModel 实体通知(PSDENOTIFY)模型详解
  • mysql函数大全及举例
  • 人工智能综合项目开发3-----农业病虫害识别dataclean.py
  • R语言手搓一个计算生存分析C指数(C-index)的函数算法
  • 使用leaflet库加载服务器离线地图瓦片(这边以本地nginx服务器为例)
  • 无状态协议HTTP/HTTPS (笔记)
  • 模式识别与机器学习课程笔记(8):特征提取与选择
  • python+uniapp基于微信美食点餐系统小程序
  • 【邀请函】锐成信息 × Sectigo | CLM - SSL 证书自动化运维解决方案发布会
  • 基于MATLAB实现基于距离的离群点检测算法
  • 冠县网站建设电话wordpress插件 电商
  • 【Android】RecyclerView LayoutManager 重写方法详解
  • 数据流通合规新基建 隐私计算平台的三重安全防线
  • MySQL-2--数据库的查询
  • 微信公众号商城网站开发wordpress 留言板制作
  • 虚幻基础:角色旋转控制角色视角控制
  • 【轨物方案】智慧供暖全景运营物联网解决方案
  • 超越“接收端”:解析视频推拉流EasyDSS在RTMP推流生态中的核心价值与中流砥柱作用
  • 自助建站上建的网站免费吗信息技术网站建设专业
  • 融合SpringBoot3和Vue3工程
  • 怎么学做网站制作商水县住房城乡建设网站
  • 16-机器学习与大模型开发数学教程-第1章 1-8 泰勒展开与高阶近似
  • 【学习系列】SAP RAP 6:行为定义-Concurrency Control
  • docker 运行容器限制 CPU
  • Python自动化从入门到实战(24)如何高效的备份mysql数据库,数据备份datadir目录直接复制可行吗?一篇给小白的完全指南
  • 个人可以备案网站的内容国外直播平台tiktok
  • C语言也能干大事网站开发pdf企业网站管理系统多站多语言版