Linux驱动学习笔记:SPI子系统中的内核线程初始化

进入(KWorker_Thread)
  • 来源 :SPI 子系统的标准机制。在 __spi_sync -> __spi_queued_transfer 内部,代码会将消息挂入队列,并无条件地唤醒后台工作线程 (kthread_queue_work)。
  • 时机:虽然用户线程已经开始干活了,但后台线程(kworker)被唤醒需要调度时间(几十微秒到几毫秒)。当它终于醒来时,就发生了第二次进入。
  • 结果 :它进入函数后,发现 master->cur_msg 不为空(因为用户线程正在处理),或者 master->busy 为真。于是它什么也没做,直接退出

kthread_queue_work 明明只是把一个"任务"扔进队列,为什么它最终执行的代码 偏偏会是 __spi_pump_messages 这个函数?它是怎么绑定上去的?

答案是:在 SPI 控制器初始化(注册)阶段,这个"任务"就已经和这个"函数"通过函数指针绑定在一起了。

我们需要把目光从 __spi_sync(运行时)移回到 spi_register_controller(初始化时)。

1. 核心机制:kthread_init_work

在 Linux 内核源码 drivers/spi/spi.c 中,当一个 SPI 控制器(Master/Controller)被注册时,会执行初始化队列的逻辑。

请看源码中的这一关键段落(通常在 spi_controller_initialize_queue 函数中):

c 复制代码
/* drivers/spi/spi.c */

static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{
    /* ... */

    /* 1. 初始化内核工作线程 (Worker) */
    kthread_init_worker(&ctlr->kworker);

    /* 2. 创建并启动线程 */
    ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,
                     "%s", dev_name(&ctlr->dev));
    
    /* * 3. 【真相在这里】绑定任务与函数!
     * 这行代码的意思是:
     * 初始化 ctlr->pump_messages 这个任务结构体,
     * 并指定:将来谁运行这个任务,谁就去执行 __spi_pump_messages 函数。
     */
    kthread_init_work(&ctlr->pump_messages, __spi_pump_messages);

    /* ... */
}

2. 运行时流程还原

明白了初始化绑定后,我们再回看运行时的流程,一切就豁然开朗了:

第一步:触发 (In __spi_queued_transfer)

当你调用 __spi_sync 时,它内部调用了:

c 复制代码
/* 把 master->pump_messages 这个"任务单"扔给 kworker */
kthread_queue_work(&master->kworker, &master->pump_messages);

这就好比你在餐厅的订单栏上挂了一张具体的**"炒菜单"**(pump_messages),而不是一张白纸。这张单子上写着一行小字:"执行动作:__spi_pump_messages"

第二步:调度 (In Kernel Scheduler)

后台的 kworker 线程(它平时在死循环里睡觉)被唤醒。它醒来后检查自己的任务列表,发现了这张"炒菜单"。

第三步:执行 (In kthread_worker_fn)

内核线程的通用执行逻辑大概长这样(简化版):

c 复制代码
/* kernel/kthread.c - kthread_worker_fn */
int kthread_worker_fn(void *worker_ptr)
{
    while (1) {
        /* ... 睡眠等待 ... */
        
        /* 醒来,取出任务 */
        work = list_first_entry(...);
        
        /* ... */
        
        /* 4. 【执行回调】 
         * work->func 就是初始化时绑定的 __spi_pump_messages 
         */
        work->func(work); 
    }
}

3. 为什么会出现两次进入__spi_pump_messages

  1. **Caller 线程:
    • ioctl 里,你把消息放入队列。
    • 你调用 kthread_queue_work,把"炒菜单"挂到了 KWorker 的待办栏上。(此时 KWorker 还在睡觉,或者刚听到闹钟还没起床)。
    • Caller 优化 :你自己这会儿也没事干,你想"干脆我自己先炒了吧"。于是你直接调用函数 __spi_pump_messages(false)
    • 你成功了,硬件归你了,你开始炒菜(传输数据)。
  2. KWorker 线程
    • 过了几十微秒,它终于穿好衣服起床了(被调度到了 CPU 上)。
    • 它走到待办栏,拿下了那张"炒菜单"(master->pump_messages)。
    • 它根据单子上的指令,执行 work->func(),也就是第二次调用 __spi_pump_messages(true)
    • 撞车 :它一进函数,看到 master->cur_msg 不为空(你正在炒菜),或者 master->busy 是真的。
    • 结果:它耸耸肩,把单子扔了(return),回去继续睡觉。

总结

kthread_queue_work 之所以会进入 __spi_pump_messages,是因为在驱动初始化阶段 ,使用 kthread_init_work 将两者死死地绑定在一起了。

相关推荐
xuxg200511 小时前
4G 模组 AT 命令解析框架课程正式发布
stm32·嵌入式·at命令解析框架
CODECOLLECT12 小时前
京元 I62D Windows PDA 技术拆解:Windows 10 IoT 兼容 + 硬解码模块,如何降低工业软件迁移成本?
stm32·单片机·嵌入式硬件
BackCatK Chen13 小时前
STM32+FreeRTOS:嵌入式开发的黄金搭档,未来十年就靠它了!
stm32·单片机·嵌入式硬件·freertos·低功耗·rtdbs·工业控制
全栈游侠15 小时前
STM32F103XX 02-电源与备份寄存器
stm32·单片机·嵌入式硬件
辰哥单片机设计18 小时前
STM32项目分享:车辆防盗报警系统
stm32·单片机·嵌入式硬件
風清掦19 小时前
【江科大STM32学习笔记-05】EXTI外部中断11
笔记·stm32·学习
小龙报19 小时前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
czwxkn20 小时前
4STM32(stdl)TIM定时器
stm32·单片机·嵌入式硬件
Love Song残响20 小时前
NVIDIA显卡终极优化指南
stm32·单片机·嵌入式硬件
BackCatK Chen1 天前
第 8 篇:TMC2240 电机正反转实现|DIR 引脚控制 + 代码优化(稳定不抖动)
stm32·单片机·嵌入式硬件·保姆级教程·电机正反转·tmc2240·dir引脚控制