一、概述
工作队列提供了一种通过线程同步或者异步运行内核函数的通用机制。通过completion和等待队列实现了同步运行功能(调用者等待被调用函数执行完毕),不使用completion则可实现异步运行的功能。相关的定义和代码主要在以下文件中:
linux\kernel\Workqueue.c
linux\include\linux\Workqueue.h
二、数据结构
插入工作队列的任务结构:pending表明是否在工作队列中(待执行),entry是链接字段,func是执行函数,data指向参数,wq_data指向所在工作队列
-------------linux\include\linux\Workqueue.h
struct work_struct {
unsigned long pending;
struct list_head entry;
void (*func)(void *);
void *data;
void *wq_data;
struct timer_list timer;
};
以下是初始化一个任务的宏
#define __WORK_INITIALIZER(n, f, d) { \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
.data = (d), \
.timer = TIMER_INITIALIZER(NULL, 0, 0), \
}
#define DECLARE_WORK(n, f, d) \
struct work_struct n = __WORK_INITIALIZER(n, f, d)
/*
* initialize a work-struct's func and data pointers:
*/
#define PREPARE_WORK(_work, _func, _data) \
do { \
(_work)->func = _func; \
(_work)->data = _data; \
} while (0)
/*
* initialize all of a work-struct:
*/
#define INIT_WORK(_work, _func, _data) \
do { \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->pending = 0; \
PREPARE_WORK((_work), (_func), (_data)); \
init_timer(&(_work)->timer); \
} while (0)
-------------linux\kernel\Workqueue.c
cpu工作队列结构
struct cpu_workqueue_struct {
spinlock_t lock;
long remove_sequence; /* Least-recently added (next to run) */
long insert_sequence; /* Next to add */
struct list_head worklist;
wait_queue_head_t more_work;
wait_queue_head_t work_done;
struct workqueue_struct *wq;
task_t *thread;
int run_depth; /* Detect run_workqueue() recursion depth */
} ____cacheline_aligned;
工作队列结构,其中包含一个cpu工作队列数组,每个cpu一个队列,任务被插入的是cpu工作队列
struct workqueue_struct {
struct cpu_workqueue_struct cpu_wq[NR_CPUS];
const char *name;
struct list_head list; /* Empty if single thread */
};
三、工作队列的创建
1、两个宏
创建单线程工作队列(单个队列)
#define create_singlethread_workqueue(name) __create_workqueue((name), 1)
创建多线程工作队列(每个cpu一个队列)
#define create_workqueue(name) __create_workqueue((name), 0)
2、创建工作队列的主函数是__create_workqueue()。
参数说明:name——工作队列的名称字符串指针;singlethread——单线程标志,等于1则创建单线程队列,为0则创建多线程队列
流程如下:
(1)为工作队列结构分配内存,内容清0,地址存于wq。
(2)若singlethread不为0(创建单线程),初始化wq->list,调用create_workqueue_thread在0号cpu上创建一个线程,
由wq的0号cpu工作队列的thread字段wq->cpu_wq[0].thread指向,并唤醒该线程。
(3)若singlethread为0(创建多线程),wq->list加入workqueues链表,然后每个cpu上创建一个线程,与该cpu绑定,
由wq->cpu_wq[cpu].thread指向,并唤醒线程。
(4)若以上创建线程失败,destroy=1,则调用destroy_workqueue()销毁工作队列。
struct workqueue_struct *__create_workqueue(const char *name,
int singlethread)
{
int cpu, destroy = 0;
struct workqueue_struct *wq;
struct task_struct *p;
BUG_ON(strlen(name) > 10);
wq = kmalloc(sizeof(*wq), GFP_KERNEL);
if (!wq)
return NULL;
memset(wq, 0, sizeof(*wq));
wq->name = name;
/* We don't need the distraction of CPUs appearing and vanishing. */
lock_cpu_hotplug();
if (singlethread) {
INIT_LIST_HEAD(&wq->list);
p = create_workqueue_thread(wq, 0);
if (!p)
destroy = 1;
else
wake_up_process(p);
} else {
spin_lock(&workqueue_lock);
list_add(&wq->list, &workqueues);
spin_unlock(&workqueue_lock);
for_each_online_cpu(cpu) {
p = create_workqueue_thread(wq, cpu);
if (p) {
kthread_bind(p, cpu);
wake_up_process(p);
} else
destroy = 1;
}
}
unlock_cpu_hotplug();
/*
* Was there any error during startup? If yes then clean up:
*/
if (destroy) {
destroy_workqueue(wq);
wq = NULL;
}
return wq;
}
3、创建执行工作队列任务的线程的函数create_workqueue_thread()。
参数说明:wq指向工作队列,cpu指向要创建线程所对应的cpu号。通过cwq = wq->cpu_wq + cpu,cwp即对应的cpu工作队列指针
流程:
(1)初始化cpu工作队列结构cwp。
(2)调用kthread_create()创建cwp上的执行线程。
(3)cwq->thread 指向 p。
(4)返回线程指针p。
从代码可以看出,核心功能是通过调用kthread_create()实现的。
static struct task_struct *create_workqueue_thread(struct workqueue_struct *wq,
int cpu)
{
struct cpu_workqueue_struct *cwq = wq->cpu_wq + cpu;
struct task_struct *p;
spin_lock_init(&cwq->lock);
cwq->wq = wq;
cwq->thread = NULL;
cwq->insert_sequence = 0;
cwq->remove_sequence = 0;
INIT_LIST_HEAD(&cwq->worklist);
init_waitqueue_head(&cwq->more_work);
init_waitqueue_head(&cwq->work_done);
if (is_single_threaded(wq))
p = kthread_create(worker_thread, cwq, "%s", wq->name);
else
p = kthread_create(worker_thread, cwq, "%s/%d", wq->name, cpu);
if (IS_ERR(p))
return NULL;
cwq->thread = p;
return p;
}
创建内核线程的函数kthread_create()
参数说明:threaadfn是被创建线程要执行的内核函数;data指向其参数;namefmt[]是创建结果包含的字符串。
功能:创建内核线程,其执行的内核函数由threadfn指定,内核函数的参数由data指向。
流程:
(1)create定义为内核线程创建中使用的数据结构kthread_create_info
(2)定义一个任务work,要执行的函数是keventd_create_kthread(),参数是create的指针。
(3)如果工作队列helper_wq未创建,则直接执行work.func(work.data),即keventd_create_kthread(&create)。
(4)如果工作队列helper_wq已创建,将任务work插入helper_wq队列,通过工作队列执行work.func(work.data),即keventd_create_kthread(&create)。
(5)执行完成后,初始化create.result,返回。
可见,内核线程的创建最终是通过keventd_create_kthread()实现的。
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
DECLARE_WORK(work, keventd_create_kthread, &create);
create.threadfn = threadfn;
create.data = data;
init_completion(&create.started);
init_completion(&create.done);
/*
* The workqueue needs to start up first:
*/
if (!helper_wq)
work.func(work.data);
else {
queue_work(helper_wq, &work);
wait_for_completion(&create.done);
}
if (!IS_ERR(create.result)) {
va_list args;
va_start(args, namefmt);
vsnprintf(create.result->comm, sizeof(create.result->comm),
namefmt, args);
va_end(args);
}
return create.result;
}
以上是壳,最终内核线程是通过keventd_create_kthread()函数创建的,而keventd_create_kthread()调用了kernel_thread()创建线程,线程调用kthread(create),最后运行结构参数create中的函数。
函数keventd_create_kthread()说明
功能:创建内核线程,参数_create是kthread_create_info结构的指针,结构中包含线程要执行的函数(create->threadfn)。
该函数通过kernel_thread()创建了一个内核线程,该线程执行函数kthread(create),而kthread()将执行create中指定的函数。
static void keventd_create_kthread(void *_create)
{
struct kthread_create_info *create = _create;
int pid;
/* We want our own signal handler (we take no signals by default). */
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {
create->result = ERR_PTR(pid);
} else {
wait_for_completion(&create->started);
create->result = find_task_by_pid(pid);
}
complete(&create->done);
}
四、工作的插入和删除
1、插入一个任务
(1)函数queue_work()
参数说明:wq是工作队列指针,work是队列元素结构指针,指向要插入的任务。
流程说明:首先判断work->pending的0位值并设为1,如果第0位原来不为0的话,说明已经在某个工作队列里了,便不再插入;否则执行插入操作。wq->list是否在workqueues链表中可以判断工作是否多线程(在链表中的话是多线程,否则是单线程,见队列的创建),对于单线程的情况,work将被插入0号cpu对应的队列中,否则将插入当前cpu的队列。实际的插入操作通过调用__queue_work()函数进行。
int fastcall queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
int ret = 0, cpu = get_cpu();
if (!test_and_set_bit(0, &work->pending)) {
if (unlikely(is_single_threaded(wq)))
cpu = 0;
BUG_ON(!list_empty(&work->entry));
__queue_work(wq->cpu_wq + cpu, work);
ret = 1;
}
put_cpu();
return ret;
}
(2)函数__queue_work()
参数说明:cwq是工作队列中对应到某个cpu的队列结构cpu_workqueue_struct的指针;work是待插入的任务结构指针。
流程说明:work->wq_data指向所在的cpu工作队列;work通过entry字段链入cwq->worklist指向的队列;插入计数cwq->insert_sequence加1;唤醒cwq->more_work上睡眠的进程(即运行工作队列的线程worker_thread【见队列的创建】,每个cpu队列一个)。
static void __queue_work(struct cpu_workqueue_struct *cwq,
struct work_struct *work)
{
unsigned long flags;
spin_lock_irqsave(&cwq->lock, flags);
work->wq_data = cwq;
list_add_tail(&work->entry, &cwq->worklist);
cwq->insert_sequence++;
wake_up(&cwq->more_work);
spin_unlock_irqrestore(&cwq->lock, flags);
}
2、删除任务
工作队列运行完一个任务后,会将任务从队列中移除,详见run_workqueue()。
五、工作队列的运行
概述:工作队列的运行线程的函数是worker_thread(),当工作队列中有任务插入,队列上的运行线程就被唤醒,worker_thread()便开始执行。
参数:__cwq,指向cpu工作队列结构cpu_workqueue_struct的指针。
流程:
1、首次运行时定义等待队列元素wait;进程标志设置PF_NOFREEZE;阻塞信号集设为全1(除了关键的kill等信号外全部阻塞);SIGCHLD信号的处理句柄 sa_handler 设为 SIG_IGN;进程状态初始设置为TASK_INTERRUPTIBLE。
2、经过以上初始化后,执行流进入一个无限循环。
(1)首先,当前进程插入cwq->more_work指向的等待队列;
(2)如果cwq->worklist工作队列空的话,调用进程调度函数schedule()进入睡眠(之前进程状态已改为了TASK_INTERRUPTIBLE);
(3)否则进程状态设为TASK_RUNNING;
(4)以下是在唤醒的状态下运行,先将当前进程从cwq->more_work等待队列中移去;然后判断工作队列cwq->worklist是否为空,不空的情况下调用run_workqueue(cwq)执行队列中的任务。
(5)任务执行完,当前进程状态设置为TASK_INTERRUPTIBLE,转向步骤(1),进行下一轮循环。
static int worker_thread(void *__cwq)
{
struct cpu_workqueue_struct *cwq = __cwq;
DECLARE_WAITQUEUE(wait, current);
struct k_sigaction sa;
sigset_t blocked;
current->flags |= PF_NOFREEZE;
set_user_nice(current, -5);
/* Block and flush all signals */
sigfillset(&blocked);
sigprocmask(SIG_BLOCK, &blocked, NULL);
flush_signals(current);
/* SIG_IGN makes children autoreap: see do_notify_parent(). */
sa.sa.sa_handler = SIG_IGN;
sa.sa.sa_flags = 0;
siginitset(&sa.sa.sa_mask, sigmask(SIGCHLD));
do_sigaction(SIGCHLD, &sa, (struct k_sigaction *)0);
set_current_state(TASK_INTERRUPTIBLE);
while (!kthread_should_stop()) {
add_wait_queue(&cwq->more_work, &wait);
if (list_empty(&cwq->worklist))
schedule();
else
__set_current_state(TASK_RUNNING);
remove_wait_queue(&cwq->more_work, &wait);
if (!list_empty(&cwq->worklist))
run_workqueue(cwq);
set_current_state(TASK_INTERRUPTIBLE);
}
__set_current_state(TASK_RUNNING);
return 0;
}
六、工作队列的销毁
参数:工作队列指针wq
流程:
(1)调用flush_workqueue(wq)等待工作队列中的所有任务执行完
(2)对于单线程队列,调用cleanup_workqueue_thread()停止wq->cpu_wq[0]上的执行线程
(3)对于多线程队列,执行一个循环,每次停止wq->cpu_wq[cpu]上的执行线程,直至停掉所有cpu工作队列上的线程为止,然后把wq->list从workqueues链表中移除。
void destroy_workqueue(struct workqueue_struct *wq)
{
int cpu;
flush_workqueue(wq);
/* We don't need the distraction of CPUs appearing and vanishing. */
lock_cpu_hotplug();
if (is_single_threaded(wq))
cleanup_workqueue_thread(wq, 0);
else {
for_each_online_cpu(cpu)
cleanup_workqueue_thread(wq, cpu);
spin_lock(&workqueue_lock);
list_del(&wq->list);
spin_unlock(&workqueue_lock);
}
unlock_cpu_hotplug();
kfree(wq);
}
七、三个重要的内核工作队列的创建
1、keventd_wq
函数调用流程: start_kernel()->init()->do_basic_setup()->init_workqueues(),创建工作队列keventd_wq。
keventd_wq = create_workqueue("events");
2、khelper_wq
函数调用流程:start_kernel()->init()->do_basic_setup()->usermodehelper_init(),创建工作队列khelper_wq。
khelper_wq = create_singlethread_workqueue("khelper");
3、helper_wq
helper_wq通过helper_init()函数建立,而helper_init()被定义在.initcall段中,这里边的函数在系统初始化过程中会自动被执行。
helper_wq = create_singlethread_workqueue("kthread");
--------linux\kernel\Kthread.c
core_initcall(helper_init);
--------linux\include\linux\Init.h
#define core_initcall(fn) __define_initcall("1",fn)
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
八、其它函数分析
1、flush_workqueue()
功能:等待cpu工作队列中的任务完成,是通过调用flush_cpu_workqueue()实现的,可用于同步。
void fastcall flush_workqueue(struct workqueue_struct *wq)
{
might_sleep();
if (is_single_threaded(wq)) {
/* Always use cpu 0's area. */
flush_cpu_workqueue(wq->cpu_wq + 0);
} else {
int cpu;
lock_cpu_hotplug();
for_each_online_cpu(cpu)
flush_cpu_workqueue(wq->cpu_wq + cpu);
unlock_cpu_hotplug();
}
}
2、flush_cpu_workqueue()
功能:等待当前cpu工作队列中的任务完成。通过循环比较cwq->insert_sequence和cwq->remove_sequence,当两者相等时(即进入队列的任务数=离开队列的任务数)表示任务全部已执行完毕,循环终止并返回。等待执行的过程中,线程的wait被链入了cwq->work_done等待队列,每当队列中的一个任务执行完后,会唤醒cwq->work_done上的线程,详见run_workqueue()的流程。
static void flush_cpu_workqueue(struct cpu_workqueue_struct *cwq)
{
if (cwq->thread == current) {
/*
* Probably keventd trying to flush its own queue. So simply run
* it by hand rather than deadlocking.
*/
run_workqueue(cwq);
} else {
DEFINE_WAIT(wait);
long sequence_needed;
spin_lock_irq(&cwq->lock);
sequence_needed = cwq->insert_sequence;
while (sequence_needed - cwq->remove_sequence > 0) {
prepare_to_wait(&cwq->work_done, &wait,
TASK_UNINTERRUPTIBLE);
spin_unlock_irq(&cwq->lock);
schedule();
spin_lock_irq(&cwq->lock);
}
finish_wait(&cwq->work_done, &wait);
spin_unlock_irq(&cwq->lock);
}
}
3、is_single_threaded()
功能:判断工作队列wq是单线程队列还是多线程队列(即一个cpu工作队列还是多个cpu工作队列)。由工作队列的创建过程可以知道,创建单线程执行工作队列任务时,wq->list是置为空链表的;否则wq->list会加入workqueues链表中,所以只要判断list_empty(&wq->list)。
static inline int is_single_threaded(struct workqueue_struct *wq)
{
return list_empty(&wq->list);
}
4、init_workqueues()
功能:工作队列初始化,创建工作队列keventd_wq。
void init_workqueues(void)
{
hotcpu_notifier(workqueue_cpu_callback, 0);
keventd_wq = create_workqueue("events");
BUG_ON(!keventd_wq);
}
5、schedule_work()
功能:调用queue_work()在工作队列keventd_wq中插入任务。
int fastcall schedule_work(struct work_struct *work)
{
return queue_work(keventd_wq, work);
}
6、cleanup_workqueue_thread()
功能:终止cpu工作队列wq->cpu_wq[cpu]的执行线程。主要是通过调用kthread_stop(p)实现的。
static void cleanup_workqueue_thread(struct workqueue_struct *wq, int cpu)
{
struct cpu_workqueue_struct *cwq;
unsigned long flags;
struct task_struct *p;
cwq = wq->cpu_wq + cpu;
spin_lock_irqsave(&cwq->lock, flags);
p = cwq->thread;
cwq->thread = NULL;
spin_unlock_irqrestore(&cwq->lock, flags);
if (p)
kthread_stop(p);
}
7、kthread_stop()
参数:k指向待终止的内核线程
流程:
(1)初始化completion变量kthread_stop_info.done,作为同步用。
(2)kthread_stop_info.k指向待终止的内核线程k,唤醒线程k。
(3)等待线程停止。
(4)kthread_stop_info.k重新置为空。
(5)返回错误码kthread_stop_info.err。
因为先前创建的工作队列执行线程worker_thread在每次被唤醒时都要调用kthread_should_stop()检测kthread_stop_info.k是否指向自己,是的话就退出循环从而终止线程(详见worker_thread代码),所以上面步骤2能够实现终止线程k的功能。
int kthread_stop(struct task_struct *k)
{
int ret;
down(&kthread_stop_lock);
/* It could exit after stop_info.k set, but before wake_up_process. */
get_task_struct(k);
/* Must init completion *before* thread sees kthread_stop_info.k */
init_completion(&kthread_stop_info.done);
wmb();
/* Now set kthread_should_stop() to true, and wake it up. */
kthread_stop_info.k = k;
wake_up_process(k);
put_task_struct(k);
/* Once it dies, reset stop ptr, gather result and we're done. */
wait_for_completion(&kthread_stop_info.done);
kthread_stop_info.k = NULL;
ret = kthread_stop_info.err;
up(&kthread_stop_lock);
return ret;
}
8、keventd_up()
功能:判断keventd_wq工作队列是否已创建
int keventd_up(void)
{
return keventd_wq != NULL;
}
九、典型工作队列helper_wq分析
(1)队列的创建
static __init int helper_init(void)
{
helper_wq = create_singlethread_workqueue("kthread");
BUG_ON(!helper_wq);
return 0;
}
(2)应用实例:
装载内核模块的函数request_module(),通过调用call_usermodehelper(modprobe_path, argv, envp, 1)载入路径modprobe_path指向的模块文件,argv、envp等分别指向参数列表和环境变量列表。函数首先定义了一个任务work,其执行的函数是__call_usermodehelper(),参数是sub_info结构指针,其中包含上面的参数。该任务通过queue_work(khelper_wq, &work)插入工作队列khelper_wq中执行,通过completion变量done来同步(等待任务执行完),并通过sub_info.retval返回执行结果。因为这些值都在sub_info中,作为任务的参数传递给任务,任务可以对其进行修改,以记录任务执行情况和结果。
int call_usermodehelper(char *path, char **argv, char **envp, int wait)
{
DECLARE_COMPLETION(done);
struct subprocess_info sub_info = {
.complete = &done,
.path = path,
.argv = argv,
.envp = envp,
.wait = wait,
.retval = 0,
};
DECLARE_WORK(work, __call_usermodehelper, &sub_info);
if (!khelper_wq)
return -EBUSY;
if (path[0] == '\0')
return 0;
queue_work(khelper_wq, &work);
wait_for_completion(&done);
return sub_info.retval;
}