http://tech.ddvip.com/2010-04/1271144007150731.html
本文研究多个用于在内核环境当中延迟处理的方法(特别是在 Linux 内核版本 2.6.27.14 当中)。 尽管这些方法针对 Linux 内核,但方法背后的理念, 对于系统架构研究具有更广泛的意义。例如, 可以将这些理念应用到传统的嵌入式系统当中,取代原有的调度程序来进行任务调度 。
在开始研究用于内核中的可延迟函数之前, 让我们先了解一下相关问题的背景情况。 操作系统会因为一个硬件事件而产生中断(例如出现了一个来自网卡的数据包), 对该事件的处理过程从一个中断开始。 通常,中断会导致大量任务停止。 其中一些任务是在中断上下文完成的,然后任务被传递给软件栈来继续处理(参见
图 1. Top-half 以及 bottom-half 处理过程
问题在于,有多少任务需要在中断上下文完成? 关于中断上下文的问题是,在此期间部分或者全部中断可以被禁止, 这就增加了处理其他硬件问题的时延(并导致处理习惯的改变)。 因此,有必要简化中断过程中要完成的任务, 把一些任务转移到内核上下文中去完成(在该上下文, 处理器资源更有可能被高效共享)。
正如
Linux 能够快速响应各种功能需求,延迟功能也不例外。 自 Linux2.3版本内核开始,就提供了软中断功能, 具有一组 32 个静态定义的 bottom halves。 作为静态元素,其定义在编译过程中完成(不同于新的动态机制)。 软中断用于在内核线程上下文中处理时间要求严格的处理过程(软件中断)。 可以在 ./kernel/softirq.c 中找到软中断的来源。 在 2.3 版本的 Linux 内核中还引入了微线程(参见 ./include/linux/interrupt.h)。 微线程的构建基于软中断,用于允许动态生成可延迟函数。 最终,在 2.5 版本 Linux 内核中引入了工作队列(参见 ./include/linux/workqueue.h)。 工作队列允许将任务延迟到中断上下文之外,进入内核处理上下文。
现在我们探讨一下任务延迟、微线程以及工作队列的动态机制。
软中断最初为具有 32 个软中断条目的矢量, 用来支持一系列的软件中断特性。 当前,只有 9 个矢量被用于软中断, 其中之一是TASKLET_SOFTIRQ
(参见 ./include/linux/interrupt.h)。 虽然软中断还存在于内核中,推荐采用微线程和工作队列,而不是分配新的软中断矢量。
微线程是一个延迟方法,可以实现将已登记的函数进行推后运行。 top half(中断处理程序)完成少量的任务,然后安排微线程在晚些的 bottom half 中执行。
清单 1. 声明并调度微线程
|
一个给定的微线程只运行在一个 CPU 中(就是用于调用该微线程的那个 CPU), 同一微线程永远不会同时运行在多个 CPU 中。 但是不同的微线程可以同时运行在不同的 CPU 中。
微线程可由 tasklet_struct 结构体表示(参见 atomic_t
图 2. tasklet_struct 结构体的内部情况
通过软中断机制来调度微线程,当机器处于严重软件中断负荷之下时, 可通过 ksoftirqd(一种每 CPU 内核线程)软中断来调度。 下面将探讨微线程应用编程接口(API)中支持的各类函数。
嵌入式系统继承物
微线程通过宏调用来定义 DECLARE_TASKLET
(参见 tasklet_struct
DECLARE_TASKLET_DISABLED
tasklet_enable
tasklet_enable
tasklet_disable
tasklet_init
tasklet_struct
清单 2. 微线程的创建以及 enable/disable 函数
DECLARE_TASKLET( name, func, data ); |
有两个 disable 函数,每一个都对微线程发出 disable 请求, 但是,微线程被终止后,只有 tasklet_disable
tasklet_disable_nosync
tasklet_enable
),另一个用于允许高优先级调度(tasklet_hi_enable
)。 正常优先级调度通过 TASKLET_SOFTIRQ
-level 软中断来执行, 高优先级调度则通过 HI_SOFTIRQ
-level 软中断执行。
由于存在正常优先级和高优先级的 enable 函数, 因此要有正常优先级和高优先级的调度函数(参见 tasklet_vec
tasklet_hi_vec
清单 3. 微线程调度函数
void tasklet_schedule( struct tasklet_struct * ); |
最后,微线程生成之后,就可以通过函数 tasklet_kill
tasklet_kill
tasklet_kill_immediate
清单 4. 微线程 kill 函数
void tasklet_kill( struct tasklet_struct * ); |
通过该 API,可见微线程 API 比较简单,实现也很简单。 可以通过 ./kernel/softirq.c 和 ./include/linux/interrupt.h 来了解微线程的实现机制。
我们来看一个使用微线程 API 的简单例子(参见 my_tasklet_function
my_tasklet_data
)通过相关数据生成, 然后由 DECLARE_TASKLET
tasklet_kill
清单 5. 微线程处于内核模块上下文的简单例子
#include <linux/kernel.h> |
工作队列是实现延迟的新机制,从 2.5 版本 Linux 内核开始提供该功能。 不同于微线程一步到位的延迟方法,工作队列采用通用的延迟机制, 工作队列的处理程序函数能够休眠(这在微线程模式下无法实现)。 工作队列可以有比微线程更高的时延,并为任务延迟提供功能更丰富的 API。 从前,延迟功能通过 keventd
events/X
工作队列提供一个通用的办法将任务延迟到 bottom halves。 处于核心的是工作队列(结构体 workqueue_struct
), 任务被安排到该结构体当中。 任务由结构体 work_struct
events/X
work_struct
图 3. 工作队列背后的处理过程
由于 work_struct
工作队列 API 比微线程稍复杂,主要是因为它支持很多选项。 我们首先探讨一下工作队列,然后再看一下任务和变体。
通过 create_workqueue
,返回一个 workqueue_struct
destroy_workqueue
struct workqueue_struct *create_workqueue( name ); void destroy_workqueue( struct workqueue_struct * ); |
通过工作队列与之通信的任务可以由结构体 work_struct
INIT_WORK
INIT_DELAYED_WORK
INIT_DELAYED_WORK_DEFERRABLE
。
清单 6. 任务初始化宏
INIT_WORK( work, func ); |
任务结构体的初始化完成后,接下来要将任务安排进工作队列。 可采用多种方法来完成这一操作(参见 queue_work
简单地将任务安排进工作队列(这将任务绑定到当前的 CPU)。 或者,可以通过 queue_work_on
来指定处理程序在哪个 CPU 上运行。 两个附加的函数为延迟任务提供相同的功能(其结构体装入结构体 work_struct
清单 7. 工作队列函数
int queue_work( struct workqueue_struct *wq, struct work_struct *work ); |
可以使用全局的内核全局工作队列,利用 4 个函数来为工作队列定位。 这些函数(见
清单 8. 内核全局工作队列函数
int schedule_work( struct work_struct *work ); |
还有一些帮助函数用于清理或取消工作队列中的任务。想清理特定的任务项目并阻塞任务, 直到任务完成为止, 可以调用flush_work
flush_workqueue
flush_scheduled_work
。
int flush_work( struct work_struct *work ); |
还没有在处理程序当中执行的任务可以被取消。 调用 cancel_work_sync
cancel_delayed_work_sync
。
|
最后,可以通过调用 work_pending
delayed_work_pending
work_pending( work ); |
这就是工作队列 API 的核心。在 ./kernel/workqueue.c 中能够找到工作队列 API 的实现方法, API 在 ./include/linux/workqueue.h 中定义。 下面我们看一个工作队列 API 的简单例子。
下面的例子说明了几个核心的工作队列 API 函数。 如同微线程的例子一样,为方便起见,可将这个例子部署在内核模块上下文。
首先,看一下将用于实现 bottom half 的任务结构体和处理程序函数(参见 my_wq
)以及 my_work_t
my_work_t
work_struct
work_struct
my_work_t
清单 9. 任务结构体和 bottom-half 处理程序
#include <linux/kernel.h> |
清单 10 init_module
create_workqueue
kmalloc
INIT_WORK
queue_work
清单 10. 工作队列和任务创建
|
最终的元素在
清单 11. 工作队列清理和销毁
|
从对微线程和工作队列的简短介绍中, 可以发现两个将任务从 top halves 延迟到 bottom halves 的不同方法。 微线程提供低延迟机制,该方式简单而直接, 而工作队列提供复杂的 API 来允许对多个任务项目进行排队。 每种方法都在中断上下文延迟任务,但只有微线程采用 run-to-complete 的风格自动运行, 而在此处,如果需要,工作队列允许处理程序休眠。 为有效实现任务延迟,可根据具体需求来选择相应的方法。
这里所探讨的任务延迟方法涉及了历史的和当前的应用在 Linux 内核中的延迟方法(除了计时器之外,这将在以后的文章中讨论)。 它们当然不是新的 — 事实上,它们在过去已经以其他形式存在 — 但是它们代表了一种有趣的架构模式,这在 Linux 中和其他地方都很有用。 从软中断到微线程再到任务队列再到延迟的工作队列,Linux 在提供一致的和兼容的用户空间体验的同时,保持其内核各方面的持续发展。