最近發現在內核創建線程的時候經常會用到kthread_run()這樣的一個調用。於是准備拿出來學習一下。首先看看它的定義之處才發現它是一個宏函數,而不是一個真正意義上的函數。
在include/linux/Kthread.h裡有
/**
* kthread_run - create and wake a thread.
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @namefmt: printf-style name for the thread.
*
* Description: Convenient wrapper for kthread_create() followed by
* wake_up_process(). Returns the kthread or ERR_PTR(-ENOMEM).
*/
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
這個函數的英文注釋裡很明確的說明: 創建並啟動一個內核線程。可見這裡的函數kthread_create()只是創建了內核線程,而最後啟動是怎麼啟動的呢,我們看到了後面的wake_up_process()函數,沒錯就是這個函數啟動了這個線程,讓它在一開始就一直運行下去。知道遇見kthread_should_stop函數或者kthread_stop()函數。那我們具體看看前一個函數到底做了什麼吧。
在這個宏裡面主要是調用了函數:kthread_create()
這個函數是干什麼的呢?在Kernel/Kthread.c裡面我們可以看到:
/**
* kthread_create - create a kthread.
* @threadfn: the function to run until signal_pending(current).
* @data: data ptr for @threadfn.
* @namefmt: printf-style name for the thread.
*
* Description: This helper function creates and names a kernel
* thread. The thread will be stopped: use wake_up_process() to start
* it. See also kthread_run(), kthread_create_on_cpu().
*
* When woken, the thread will run @threadfn() with @data as its
* argument. @threadfn can either call do_exit() directly if it is a
* standalone thread for which noone will call kthread_stop(), or
* return when 'kthread_should_stop()' is true (which means
* kthread_stop() has been called). The return value should be zero
* or a negative error number; it will be passed to kthread_stop().
*
* Returns a task_struct or ERR_PTR(-ENOMEM).
*/
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;
}
EXPORT_SYMBOL(kthread_create);
注意到上面的這段英文解釋:說這個函數會創建一個名為namefmt的內核線程,這個線程剛創建時不會馬上執行,要等到它將kthread_create() 返回的task_struct指針傳給wake_up_process(),然後通過此函數運行線程。我們看到creat結構體,我們將傳入的參數付給了它,而threadfn這個函數就是創建的運行函數。在使用中我們可以在此函數中調用kthread_should_stop()或者kthread_stop()函數來結束線程。這裡我們看到創建線程函數中使用工作隊列DECLARE_WORK,我們跟蹤一下發現這只是將函數#define DECLARE_WORK(n, f, d) \
struct work_struct n = __WORK_INITIALIZER(n, f, d)
然後再跟進:
#define __WORK_INITIALIZER(n, f, d) { \
.entry = { &(n).entry, &(n).entry }, \
.func = (f), \
.data = (d), \
.timer = TIMER_INITIALIZER(NULL, 0, 0), \
}
反正目的是創建一個工作組隊列,而其中keventd_create_kthread()函數主要是起到創建線程的功能
/* We are keventd: create a thread. */
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);
read_lock(&tasklist_lock);
create->result = find_task_by_pid(pid);
read_unlock(&tasklist_lock);
}
complete(&create->done);
}
再看看kernel_thread()函數最後調用到了哪裡:
/*
* Create a kernel thread.
*/
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
struct pt_regs regs;
long pid;
memset(®s, 0, sizeof(regs));
regs.ARM_r1 = (unsigned long)arg;
regs.ARM_r2 = (unsigned long)fn;
regs.ARM_r3 = (unsigned long)do_exit;
regs.ARM_pc = (unsigned long)kernel_thread_helper;
regs.ARM_cpsr = SVC_MODE;
pid = do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
MARK(kernel_thread_create, "%ld %p", pid, fn);
return pid;
}
EXPORT_SYMBOL(kernel_thread);
好,最後我們看到了線程通過申請進程的pid號來被創建,關鍵是我們要知道如何使用這個宏函數,也就是如何應用它。要注意的是它調用了創建線程函數,同時也激活了線程。所以代碼中調用了它的話就隱含著已經啟動一個線程。