Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?
<p><img title="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" alt="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/d8a04a44a5d539855ff04094601ab338.jpg"></p>
<p>
别人的经验,我们的阶梯!
</p>
<p>
大家好,我是道哥,今天我为大伙儿解说的技术知识点是:【中断处理中的下半部分机制-工作队列】。
</p>
<p>
在刚开始介绍中断处理的时候,曾经贴出下面这张图:
</p>
<p>
<img title="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" alt="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/96c9410bc140ae7df243f5aa2461a1e5.jpg"></p>
<p>
图中描述了中断处理中的下半部分都有哪些机制,以及如何根据实际的业务场景、限制条件来进行选择。
</p>
<p>
可以看出:这些不同的实现之间,有些是重复的,或者是相互取代的关系。
</p>
<p>
也正因为此,它们之间的使用方式几乎是大同小异,至少是在API接口函数的使用方式上,从使用这的角度来看,都是非常类似的。
</p>
<p>
这篇文章,我们就通过实际的代码操作,来演示一下工作队列(workqueue)的使用方式。
</p>
<h3>
工作队列是什么
</h3>
<p>
工作队列是Linux操作系统中,进行中断下半部分处理的重要方式!
</p>
<p>
从名称上可以猜到:一个工作队列就好像业务层常用的消息队列一样,里面存放着很多的工作项等待着被处理。
</p>
<p>
<img title="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" alt="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/9b3c6d96d1ca87abb1030baf17143714.jpg"></p>
<p>
工作队列中有两个重要的结构体:工作队列(workqueue_struct) 和 工作项(work_struct):
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>struct workqueue_struct { </span></span>
</li>
<li>
<span>struct list_head pwqs; /* WR: <span class="op">all</span><span> pwqs </span><span class="keyword">of</span><span> this wq */ </span></span>
</li>
<li class="alt">
<span>struct list_head list; /* PR: list <span class="keyword">of</span><span> </span><span class="op">all</span><span> workqueues */ </span></span>
</li>
<li>
<span>... </span>
</li>
<li class="alt">
<span><span class="keyword">char</span><span> </span><span class="keyword">name</span><span>; /* I: workqueue </span><span class="keyword">name</span><span> */ </span></span>
</li>
<li>
<span>... </span>
</li>
<li class="alt">
<span>/* hot fields used during command issue, aligned <span class="keyword">to</span><span> cacheline */ </span></span>
</li>
<li>
<span>unsigned <span class="keyword">int</span><span> flags ____cacheline_aligned; /* WQ: WQ_* flags */ </span></span>
</li>
<li class="alt">
<span>struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */ </span>
</li>
<li>
<span>struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed <span class="keyword">by</span><span> node */ </span></span>
</li>
<li class="alt">
<span>}; </span>
</li>
</ol>
<ol class="dp-sql">
<li class="alt">
<span><span>struct work_struct { </span></span>
</li>
<li>
<span>atomic_long_t data; </span>
</li>
<li class="alt">
<span>struct list_head entry; </span>
</li>
<li>
<span>work_func_t func; // 指向处理函数 </span>
</li>
<li class="alt">
<span>#ifdef CONFIG_LOCKDEP </span>
</li>
<li>
<span>struct lockdep_map lockdep_map; </span>
</li>
<li class="alt">
<span>#endif </span>
</li>
<li>
<span>}; </span>
</li>
</ol>
<p>
在内核中,工作队列中的所有工作项,是通过链表串在一起的,并且等待着操作系统中的某个线程挨个取出来处理。
</p>
<p>
这些线程,可以是由驱动程序通过 kthread_create 创建的线程,也可以是由操作系统预先就创建好的线程。
</p>
<p>
这里就涉及到一个取舍的问题了。
</p>
<p>
如果我们的处理函数很简单,那么就没有必要创建一个单独的线程来处理了。
</p>
<p>
原因有二:
</p>
<ol>
<li>
创建一个内核线程是很耗费资源的,如果函数很简单,很快执行结束之后再关闭线程,太划不来了,得不偿失;
</li>
<li>
如果每一个驱动程序编写者都毫无节制地创建内核线程,那么内核中将会存在大量不必要的线程,当然了本质上还是系统资源消耗和执行效率的问题;
</li>
</ol>
<p>
为了避免这种情况,于是操作系统就为我们预先创建好一些工作队列和内核线程。
</p>
<p>
我们只需要把需要处理的工作项,直接添加到这些预先创建好的工作队列中就可以了,它们就会被相应的内核线程取出来处理。
</p>
<p>
例如下面这些工作队列,就是内核默认创建的(include/linux/workqueue.h):
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>/* </span></span>
</li>
<li>
<span>* System-wide workqueues which are always present. </span>
</li>
<li class="alt">
<span>* </span>
</li>
<li>
<span>* system_wq <span class="keyword">is</span><span> the one used </span><span class="keyword">by</span><span> schedule_work(). </span></span>
</li>
<li class="alt">
<span>* Multi-CPU multi-threaded. There are users which expect relatively </span>
</li>
<li>
<span>* short queue flush <span class="keyword">time</span><span>. Don't queue works which can run </span><span class="keyword">for</span><span> too </span></span>
</li>
<li class="alt">
<span>* long. </span>
</li>
<li>
<span>* </span>
</li>
<li class="alt">
<span>* system_highpri_wq <span class="keyword">is</span><span> similar </span><span class="keyword">to</span><span> system_wq but </span><span class="keyword">for</span><span> </span><span class="keyword">work</span><span> items which </span></span>
</li>
<li>
<span>* require WQ_HIGHPRI. </span>
</li>
<li class="alt">
<span>* </span>
</li>
<li>
<span>* system_long_wq <span class="keyword">is</span><span> similar </span><span class="keyword">to</span><span> system_wq but may host long running </span></span>
</li>
<li class="alt">
<span>* works. Queue flushing might take relatively long. </span>
</li>
<li>
<span>* </span>
</li>
<li class="alt">
<span>* system_unbound_wq <span class="keyword">is</span><span> unbound workqueue. Workers are </span><span class="op">not</span><span> bound </span><span class="keyword">to</span><span> </span></span>
</li>
<li>
<span>* <span class="op">any</span><span> specific CPU, </span><span class="op">not</span><span> concurrency managed, </span><span class="op">and</span><span> </span><span class="op">all</span><span> queued works are </span></span>
</li>
<li class="alt">
<span>* executed immediately <span class="keyword">as</span><span> long </span><span class="keyword">as</span><span> max_active limit </span><span class="keyword">is</span><span> </span><span class="op">not</span><span> reached </span><span class="op">and</span><span> </span></span>
</li>
<li>
<span>* resources are available. </span>
</li>
<li class="alt">
<span>* </span>
</li>
<li>
<span>* system_freezable_wq <span class="keyword">is</span><span> equivalent </span><span class="keyword">to</span><span> system_wq </span><span class="keyword">except</span><span> that it's </span></span>
</li>
<li class="alt">
<span>* freezable. </span>
</li>
<li>
<span>* </span>
</li>
<li class="alt">
<span>* *_power_efficient_wq are inclined towards saving power <span class="op">and</span><span> converted </span></span>
</li>
<li>
<span>* <span class="keyword">into</span><span> WQ_UNBOUND variants if </span><span class="string">'wq_power_efficient'</span><span> </span><span class="keyword">is</span><span> enabled; otherwise, </span></span>
</li>
<li class="alt">
<span>* they are same <span class="keyword">as</span><span> their non-power-efficient counterparts - e.g. </span></span>
</li>
<li>
<span>* system_power_efficient_wq <span class="keyword">is</span><span> identical </span><span class="keyword">to</span><span> system_wq if </span></span>
</li>
<li class="alt">
<span>* <span class="string">'wq_power_efficient'</span><span> </span><span class="keyword">is</span><span> disabled. See WQ_POWER_EFFICIENT </span><span class="keyword">for</span><span> more info. </span></span>
</li>
<li>
<span>*/ </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>extern struct workqueue_struct *system_wq; </span>
</li>
<li class="alt">
<span>extern struct workqueue_struct *system_highpri_wq; </span>
</li>
<li>
<span>extern struct workqueue_struct *system_long_wq; </span>
</li>
<li class="alt">
<span>extern struct workqueue_struct *system_unbound_wq; </span>
</li>
<li>
<span>extern struct workqueue_struct *system_freezable_wq; </span>
</li>
<li class="alt">
<span>extern struct workqueue_struct *system_power_efficient_wq; </span>
</li>
<li>
<span>extern struct workqueue_struct *system_freezable_power_efficient_wq; </span>
</li>
</ol>
<p>
以上这些默认工作队列的创建代码是(kernel/workqueue.c):
</p>
<ol class="dp-sql">
<li class="alt">
<span><span class="keyword">int</span><span> __init workqueue_init_early(void) </span></span>
</li>
<li>
<span>{ </span>
</li>
<li class="alt">
<span>... </span>
</li>
<li>
<span>system_wq = alloc_workqueue(<span class="string">"events"</span><span>, 0, 0); </span></span>
</li>
<li class="alt">
<span>system_highpri_wq = alloc_workqueue(<span class="string">"events_highpri"</span><span>, WQ_HIGHPRI, 0); </span></span>
</li>
<li>
<span>system_long_wq = alloc_workqueue(<span class="string">"events_long"</span><span>, 0, 0); </span></span>
</li>
<li class="alt">
<span>system_unbound_wq = alloc_workqueue(<span class="string">"events_unbound"</span><span>, WQ_UNBOUND, </span></span>
</li>
<li>
<span>WQ_UNBOUND_MAX_ACTIVE); </span>
</li>
<li class="alt">
<span>system_freezable_wq = alloc_workqueue(<span class="string">"events_freezable"</span><span>, </span></span>
</li>
<li>
<span>WQ_FREEZABLE, 0); </span>
</li>
<li class="alt">
<span>system_power_efficient_wq = alloc_workqueue(<span class="string">"events_power_efficient"</span><span>, </span></span>
</li>
<li>
<span>WQ_POWER_EFFICIENT, 0); </span>
</li>
<li class="alt">
<span>system_freezable_power_efficient_wq = alloc_workqueue(<span class="string">"events_freezable_power_efficient"</span><span>, </span></span>
</li>
<li>
<span>WQ_FREEZABLE | WQ_POWER_EFFICIENT, </span>
</li>
<li class="alt">
<span>0); </span>
</li>
<li>
<span>... </span>
</li>
<li class="alt">
<span>} </span>
</li>
</ol>
<p>
此外,由于工作队列 system_wq 被使用的频率很高,于是内核就封装了一个简单的函数(schedule_work)给我们使用:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>/** </span></span>
</li>
<li>
<span>* schedule_work - put <span class="keyword">work</span><span> task </span><span class="op">in</span><span> </span><span class="keyword">global</span><span> workqueue </span></span>
</li>
<li class="alt">
<span>* @<span class="keyword">work</span><span>: job </span><span class="keyword">to</span><span> be done </span></span>
</li>
<li>
<span>* </span>
</li>
<li class="alt">
<span>* <span class="keyword">Returns</span><span> %</span><span class="keyword">false</span><span> if @</span><span class="keyword">work</span><span> was already </span><span class="keyword">on</span><span> the kernel-</span><span class="keyword">global</span><span> workqueue </span><span class="op">and</span><span> </span></span>
</li>
<li>
<span>* %<span class="keyword">true</span><span> otherwise. </span></span>
</li>
<li class="alt">
<span>* </span>
</li>
<li>
<span>* This puts a job <span class="op">in</span><span> the kernel-</span><span class="keyword">global</span><span> workqueue if it was </span><span class="op">not</span><span> already </span></span>
</li>
<li class="alt">
<span>* queued <span class="op">and</span><span> leaves it </span><span class="op">in</span><span> the same position </span><span class="keyword">on</span><span> the kernel-</span><span class="keyword">global</span><span> </span></span>
</li>
<li>
<span>* workqueue otherwise. </span>
</li>
<li class="alt">
<span>*/ </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span><span class="keyword">static</span><span> inline bool schedule_work(struct work_struct *</span><span class="keyword">work</span><span>){ </span></span>
</li>
<li>
<span><span class="keyword">return</span><span> queue_work(system_wq, </span><span class="keyword">work</span><span>); </span></span>
</li>
<li class="alt">
<span>} </span>
</li>
</ol>
<p>
当然了,任何事情有利就有弊!
</p>
<p>
由于内核默认创建的工作队列,是被所有的驱动程序共享的。
</p>
<p>
如果所有的驱动程序都把等待处理的工作项委托给它们来处理,那么就会导致某个工作队列中过于拥挤。
</p>
<p>
根据先来后到的原则,工作队列中后加入的工作项,就可能因为前面工作项的处理函数执行的时间太长,从而导致时效性无法保证。
</p>
<p>
因此,这里存在一个系统平衡的问题。
</p>
<p>
关于工作队列的基本知识点就介绍到这里,下面来实际操作验证一下。
</p>
<h3>
驱动程序
</h3>
<p>
之前的几篇文章,在驱动程序中测试中断处理的操作流程都是一样的,因此这里就不在操作流程上进行赘述了。
</p>
<p>
这里直接给出驱动程序的全貌代码,然后查看 dmesg 的输出信息。
</p>
<p>
创建驱动程序源文件和 Makefile:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>$ cd tmp/linux-4.15/drivers </span></span>
</li>
<li>
<span>$ mkdir my_driver_interrupt_wq </span>
</li>
<li class="alt">
<span>$ touch my_driver_interrupt_wq.c </span>
</li>
<li>
<span>$ touch Makefile </span>
</li>
</ol>
<h3>
示例代码全貌
</h3>
<p>
测试场景是:加载驱动模块之后,如果监测到键盘上的ESC键被按下,那么就往内核默认的工作队列system_wq中增加一个工作项,然后观察该工作项对应的处理函数是否被调用。
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>#include <linux kernel.h></linux></span></span>
</li>
<li>
<span>#include <linux module.h></linux></span>
</li>
<li class="alt">
<span>#include <linux interrupt.h></linux></span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> irq; </span></span>
</li>
<li>
<span><span class="keyword">static</span><span> </span><span class="keyword">char</span><span> * devname; </span></span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span><span class="keyword">static</span><span> struct work_struct mywork; </span></span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 接收驱动模块加载时传入的参数 </span>
</li>
<li class="alt">
<span>module_param(irq, <span class="keyword">int</span><span>, 0644); </span></span>
</li>
<li>
<span>module_param(devname, charp, 0644); </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 定义驱动程序的 ID,在中断处理函数中用来判断是否需要处理 </span>
</li>
<li class="alt">
<span>#define MY_DEV_ID 1226 </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>// 驱动程序数据结构 </span>
</li>
<li>
<span>struct myirq </span>
</li>
<li class="alt">
<span>{ </span>
</li>
<li>
<span><span class="keyword">int</span><span> devid; </span></span>
</li>
<li class="alt">
<span>}; </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>struct myirq mydev ={ MY_DEV_ID }; </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>#define KBD_DATA_REG 0x60 </span>
</li>
<li>
<span>#define KBD_STATUS_REG 0x64 </span>
</li>
<li class="alt">
<span>#define KBD_SCANCODE_MASK 0x7f </span>
</li>
<li>
<span>#define KBD_STATUS_MASK 0x80 </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 工作项绑定的处理函数 </span>
</li>
<li class="alt">
<span><span class="keyword">static</span><span> void mywork_handler(struct work_struct *</span><span class="keyword">work</span><span>) </span></span>
</li>
<li>
<span>{ </span>
</li>
<li class="alt">
<span>printk(<span class="string">"mywork_handler is called. \n"</span><span>); </span></span>
</li>
<li>
<span>// do <span class="op">some</span><span> other things </span></span>
</li>
<li class="alt">
<span>} </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>//中断处理函数 </span>
</li>
<li>
<span><span class="keyword">static</span><span> irqreturn_t myirq_handler(</span><span class="keyword">int</span><span> irq, void * dev) </span></span>
</li>
<li class="alt">
<span>{ </span>
</li>
<li>
<span>struct myirq mydev; </span>
</li>
<li class="alt">
<span>unsigned <span class="keyword">char</span><span> key_code; </span></span>
</li>
<li>
<span>mydev = *(struct myirq*)dev; </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 检查设备 id,只有当相等的时候才需要处理 </span>
</li>
<li class="alt">
<span>if (MY_DEV_ID == mydev.devid) </span>
</li>
<li>
<span>{ </span>
</li>
<li class="alt">
<span>// 读取键盘扫描码 </span>
</li>
<li>
<span>key_code = inb(KBD_DATA_REG); </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>if (key_code == 0x01) </span>
</li>
<li class="alt">
<span>{ </span>
</li>
<li>
<span>printk(<span class="string">"ESC key is pressed! \n"</span><span>); </span></span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 初始化工作项 </span>
</li>
<li class="alt">
<span>INIT_WORK(&mywork, mywork_handler); </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>// 加入到工作队列 system_wq </span>
</li>
<li>
<span>schedule_work(&mywork); </span>
</li>
<li class="alt">
<span>} </span>
</li>
<li>
<span>} </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span><span class="keyword">return</span><span> IRQ_HANDLED; </span></span>
</li>
<li class="alt">
<span>} </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>// 驱动模块初始化函数 </span>
</li>
<li>
<span><span class="keyword">static</span><span> </span><span class="keyword">int</span><span> __init myirq_init(void) </span></span>
</li>
<li class="alt">
<span>{ </span>
</li>
<li>
<span>printk(<span class="string">"myirq_init is called. \n"</span><span>); </span></span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 注册中断处理函数 </span>
</li>
<li class="alt">
<span>if(request_irq(irq, myirq_handler, IRQF_SHARED, devname, &mydev)!=0) </span>
</li>
<li>
<span>{ </span>
</li>
<li class="alt">
<span>printk(<span class="string">"register irq[%d] handler failed. \n"</span><span>, irq); </span></span>
</li>
<li>
<span><span class="keyword">return</span><span> -1; </span></span>
</li>
<li class="alt">
<span>} </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>printk(<span class="string">"register irq[%d] handler success. \n"</span><span>, irq); </span></span>
</li>
<li>
<span><span class="keyword">return</span><span> 0; </span></span>
</li>
<li class="alt">
<span>} </span>
</li>
<li>
<span></span>
</li>
<li class="alt">
<span>// 驱动模块退出函数 </span>
</li>
<li>
<span><span class="keyword">static</span><span> void __exit myirq_exit(void) </span></span>
</li>
<li class="alt">
<span>{ </span>
</li>
<li>
<span>printk(<span class="string">"myirq_exit is called. \n"</span><span>); </span></span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>// 释放中断处理函数 </span>
</li>
<li class="alt">
<span>free_irq(irq, &mydev); </span>
</li>
<li>
<span>} </span>
</li>
<li class="alt">
<span></span>
</li>
<li>
<span>MODULE_LICENSE(<span class="string">"GPL"</span><span>); </span></span>
</li>
<li class="alt">
<span>module_init(myirq_init); </span>
</li>
<li>
<span>module_exit(myirq_exit); </span>
</li>
</ol>
<p>
<linux kernel.h=""><linux module.h=""><linux interrupt.h="">ntk("myirq_exit is called. \n"); // 释放中断处理函数 free_irq(irq, &mydev);} MODULE_LICENSE("GPL");module_init(myirq_init);module_exit(myirq_exit);</linux></linux></linux></p>
<h3>
Makefile 文件
</h3>
<ol class="dp-sql">
<li class="alt">
<span><span>ifneq ($(KERNELRELEASE),) </span></span>
</li>
<li>
<span>obj-m := my_driver_interrupt_wq.o </span>
</li>
<li class="alt">
<span><span class="keyword">else</span><span> </span></span>
</li>
<li>
<span>KERNELDIR ?= /lib/modules/$(shell uname -r)/build </span>
</li>
<li class="alt">
<span>PWD := $(shell pwd) </span>
</li>
<li>
<span><span class="keyword">default</span><span>: </span></span>
</li>
<li class="alt">
<span>$(MAKE) -C $(KERNELDIR) M=$(PWD) modules </span>
</li>
<li>
<span>clean: </span>
</li>
<li class="alt">
<span>$(MAKE) -C $(KERNEL_PATH) M=$(PWD) clean </span>
</li>
<li>
<span>endif </span>
</li>
</ol>
<h3>
编译、测试
</h3>
<ol class="dp-sql">
<li class="alt">
<span><span>$ make </span></span>
</li>
<li>
<span>$ sudo insmod my_driver_interrupt_wq.ko irq=1 devname=mydev </span>
</li>
</ol>
<p>
检查驱动模块是否加载成功:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>$ lsmod | grep my_driver_interrupt_wq </span></span>
</li>
<li>
<span>my_driver_interrupt_wq 16384 0 </span>
</li>
</ol>
<p>
再看一下 dmesg 的输出信息:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>$ dmesg </span></span>
</li>
<li>
<span>... </span>
</li>
<li class="alt">
<span>[ 188.247636] myirq_init <span class="keyword">is</span><span> called. </span></span>
</li>
<li>
<span>[ 188.247642] register irq handler success. </span>
</li>
</ol>
<p>
说明:驱动程序的初始化函数 myirq_init 被调用了,并且成功注册了 1 号中断的处理程序。
</p>
<p>
此时,按一下键盘上的 ESC 键。
</p>
<p>
操作系统在捕获到键盘中断之后,会依次调用此中断的所有中断处理程序,其中就包括我们注册的 myirq_handler 函数。
</p>
<p>
在这个函数中,当判断出是ESC按键时,就初始化一个工作项(把结构体 work_struct 类型的变量与一个处理函数绑定起来),然后丢给操作系统预先创建好的工作队列(system_wq)去处理,如下所示:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>if (key_code == 0x01) </span></span>
</li>
<li>
<span>{ </span>
</li>
<li class="alt">
<span>printk(<span class="string">"ESC key is pressed! \n"</span><span>); </span></span>
</li>
<li>
<span>INIT_WORK(&mywork, mywork_handler); </span>
</li>
<li class="alt">
<span>schedule_work(&mywork); </span>
</li>
<li>
<span>} </span>
</li>
</ol>
<p>
因此,当相应的内核线程从这个工作队列(system_wq)中取出工作项(mywork)来处理的时候,函数 mywork_handler 就会被调用。
</p>
<p>
现在来看一下 dmesg 的输出信息:
</p>
<ol class="dp-sql">
<li class="alt">
<span><span>[ 305.053155] ESC </span><span class="keyword">key</span><span> </span><span class="keyword">is</span><span> pressed! </span></span>
</li>
<li>
<span>[ 305.053177] mywork_handler <span class="keyword">is</span><span> called. </span></span>
</li>
</ol>
<p>
可以看到:mywork_handler函数被正确调用了。
</p>
<p>
完美!
</p>
<p>
本文转载自微信公众号「IOT物联网小镇」
</p>
<p>
<img title="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" alt="Linux驱动实践:中断处理中的【工作队列】 Workqueue 是什么鬼?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/67434cdb9ca3fe3cb2ab83e76ad478dd.jpg"></p>
<p>原文链接:https://mp.weixin.qq.com/s/Kvsz-rsPSTdcvY9PWPz9Sg
</p>
頁:
[1]