基于VL822的USB 3.1 Gen2四口HUB原理图设计实战
2026/11/3 8:43:18
__spi_sync->__spi_queued_transfer内部,代码会将消息挂入队列,并无条件地唤醒后台工作线程(kthread_queue_work)。master->cur_msg不为空(因为用户线程正在处理),或者master->busy为真。于是它什么也没做,直接退出。kthread_queue_work明明只是把一个“任务”扔进队列,为什么它最终执行的代码偏偏会是__spi_pump_messages这个函数?它是怎么绑定上去的?
答案是:在 SPI 控制器初始化(注册)阶段,这个“任务”就已经和这个“函数”通过函数指针绑定在一起了。
我们需要把目光从__spi_sync(运行时)移回到spi_register_controller(初始化时)。
kthread_init_work在 Linux 内核源码drivers/spi/spi.c中,当一个 SPI 控制器(Master/Controller)被注册时,会执行初始化队列的逻辑。
请看源码中的这一关键段落(通常在spi_controller_initialize_queue函数中):
/* drivers/spi/spi.c */staticintspi_controller_initialize_queue(structspi_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);/* ... */}明白了初始化绑定后,我们再回看运行时的流程,一切就豁然开朗了:
__spi_queued_transfer)当你调用__spi_sync时,它内部调用了:
/* 把 master->pump_messages 这个"任务单"扔给 kworker */kthread_queue_work(&master->kworker,&master->pump_messages);这就好比你在餐厅的订单栏上挂了一张具体的**“炒菜单”**(pump_messages),而不是一张白纸。这张单子上写着一行小字:“执行动作:__spi_pump_messages”。
后台的kworker线程(它平时在死循环里睡觉)被唤醒。它醒来后检查自己的任务列表,发现了这张“炒菜单”。
kthread_worker_fn)内核线程的通用执行逻辑大概长这样(简化版):
/* kernel/kthread.c - kthread_worker_fn */intkthread_worker_fn(void*worker_ptr){while(1){/* ... 睡眠等待 ... *//* 醒来,取出任务 */work=list_first_entry(...);/* ... *//* 4. 【执行回调】 * work->func 就是初始化时绑定的 __spi_pump_messages */work->func(work);}}ioctl里,你把消息放入队列。kthread_queue_work,把“炒菜单”挂到了 KWorker 的待办栏上。(此时 KWorker 还在睡觉,或者刚听到闹钟还没起床)。__spi_pump_messages(false)。master->pump_messages)。work->func(),也就是第二次调用__spi_pump_messages(true)。master->cur_msg不为空(你正在炒菜),或者master->busy是真的。kthread_queue_work之所以会进入__spi_pump_messages,是因为在驱动初始化阶段,使用kthread_init_work将两者死死地绑定在一起了。