第24 节解释了在处理中断时,Linux 内核将完整的中断处理分为“上半部分”和“下半部分”,以解决“想要更快地处理它们,但又想要多做。\’\’ ”。所有耗时但实时性要求不高的处理部分都放在下半部分。
Linux 内核2.6.26 版本具有三个“子部分”,分别为软中断、微线程和工作队列。前面两节主要讲了软中断和tasklet。由于软中断位于中断上下文中,因此软中断处理程序不能阻塞或休眠,我们已经知道这会限制软中断处理程序的性能。这同样适用于微线程,因为它们是基于Softirq 机制实现的。
Linux 内核中的工作队列如果您想将工作的后半部分推迟到进程上下文,或者需要休眠推迟的任务,则应该使用工作队列机制。 Linux 内核将工作队列中的工作传递给内核线程执行。内核线程在进程上下文中运行,允许工作队列重新调度和休眠。
工作队列子系统实际上是一组接口,通过这些接口创建进程(所谓的工作线程)来执行Linux内核或其他模块放入工作队列中的任务。 Linux内核提供了一个默认的工作线程(event/n,其中n是处理器编号)来处理队列中的任务。这样,一个工作队列就变成了一组移交给特定任务的延迟任务(排队任务)。用于处理的线程(默认工作线程)。 Linux内核工作队列中使用的数据结构是workqueue_struct结构。其C语言代码为:
struct workqueue_struct { struct cpu_workqueue_struct *cpu_wq; int singlethread; /* 挂起时冻结线程*/#ifdef struct lockdep_map lockdep_map;#endif};该struct是一个链表中的节点,成员cpu_wq是一个方面。由于每个处理器对应一个工作线程,因此结构体cpu_workqueue_struct的C语言代码为:请参阅。
struct cpu_workqueue_struct {spinlock_t lock; struct list_head_worklist; /* run_workqueue() 检测递归深度*/} 连接到____cacheline_struct 链表;每个工作线程都关联自己的工作队列wq,线程成员负责关联线程。
处理工作队列中的任务。结构体cpu_workqueue_struct的成员current_work表示工作线程正在处理的工作。它的数据结构由work_struct结构体定义。请参考以下内容。
struct work_struct {atomic_long_t data; struct list_headentry; work_func_t func;};成员func指向处理函数,data是其参数。这些结构体连接成链表,每个处理器上的每种类型的队列都对应这样一个链表。比如上面提到的“默认工作线程”就有这样一个链表。
当一个工作线程启动时,它会执行链表中的所有工作,并在完成后从链表中删除相应的work_struct。当链表为空时,工作线程再次进入休眠状态。这个过程是由worker_thread()函数完成的。其C语言代码为:请参阅。
第299章300章!kthread_Should_stop() || 315 list_empty(cwq-more_work, wait);|| 320 中断;|| cwq);|| 324 | 325 return 326 } for( ; ) 在无限循环中,work_thread() 函数将自身设置为TASK_INTERRUPTIBLE 状态,并将进程添加到等待队列中,否则等待结束。并调用run_workqueue()函数来执行链表中的工作。请参考以下内容。
run_workqueue()函数的C语言代码虽然较长,但逻辑并不复杂。只需将链表中的节点对象一一删除即可运行。我这里就不详细说了。现在我们了解了Linux内核是如何设计和实现工作队列的,接下来我们来看看如何使用它们。
使用工作队列Linux 内核定义了DECLARE_WORK 宏来静态创建一个work_struct 结构。其C语言代码为:
#define DECLARE_WORK(n, f) \\ struct work_struct n=__WORK_INITIALIZER(n, f) #define __WORK_INITIALIZER(n, f) { \\ .data=WORK_DATA_INIT(), \\ .entry={ (n).entry, (n) .entry }, \\ .func=(f), \\ __WORK_INIT_LOCKDEP_MAP(#n, (n)) \\}实际上,您将创建一个work_struct 结构并为其赋值。当然,你也可以在C语言程序运行时使用INIT_WORK宏通过指针来创建作业。 INIT_WORK宏的C语言代码为:
#define INIT_WORK(_work, _func) \\ do { \\ (_work)-data=(atomic_long_t) WORK_DATA_INIT() \\ INIT_LIST_HEAD((_work)-entry); (0) 我们看一下工作队列处理函数的原型。
void work_handler(void *data); 因为这个函数是由工作线程执行的,所以它运行在进程上下文中,可以响应中断,并且可以休眠和调度。创建作业后,可以通过schedule_work()函数将其传递给默认工作线程进行处理。其C语言代码为:
intSchedule_work(struct work_struct *work) { return queue_work(keventd_wq, work);} 这个动作很容易理解,因为schedule_work()函数实际上是添加到全局工作队列keventd_wq中。其C语言代码为:
int queue_work(struct workqueue_struct *wq, struct work_struct *work){ int ret=0; if (!test_and_set_bit(WORK_STRUCT_PENDING, work_data_bits(work))) { BUG_ON(!list_empty(work-entry)), get_cpu(); ); put_cpu(); } return ret;} 容易看出queue_work()函数的核心功能是由__queue_work()函数完成的。它们如下:
此时,schedule_work()函数的关键其实就是将工作添加到工作队列中并等待执行。
当然,Linux内核允许你创建新的工作线程,但是不推荐这样做,所以我们把相关的分析留给你。至此,本节中我们已经完成了对Linux 内核“下半部分”中三种类型中断的高级分析。 “软中断”适用于频繁执行、实时性要求较高的处理任务,但必须保证共享数据的同步安全。 Tasklet可以防止同一类型的工作同时在多个处理器上执行,但它们是基于“软中断”实现的,因此它们处于像“软中断”这样的中断上下文中,不能休眠或阻塞。如果需要在流程上下文中执行延迟工作,则应选择工作队列。
欢迎大家在评论区讨论、提问。所有文章均为手写且原创(本文部分内容讨论Linux内核的原理和设计)。如果您喜欢我的文章,我会提供有关C、Linux 和其他嵌入式开发的每日报道。关注我们以查看最新更新和过去的文章。
本文和图片来自网络,不代表火豚游戏立场,如若侵权请联系我们删除:https://www.huotun.com/game/623134.html