kthread_run是一个宏,用来创建一个进程,并且将其唤醒,其定义在头文件include/linux/kthread.h中.
#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; \
})
先来看看这个宏的参数,threadfn是该线程的执行函数,data是执行函数的传入参数,namefmt是该线程的printf风格的线程名,最后的...类似printf的可变参数列表.从这个宏的实现可以看出kthread_run是通过kthread_create来创建一个进程,并返回一个task_struct,然后使用wake_up_process函数将新创建的进程唤醒.下面需要关注一下kthread_create的实现,其实它也是一个宏,同样也定义在头文件include/linux/kthread.h中.
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
该宏的参数同kthread_run,不解释了,其实现就是调用了函数kthread_create_on_node,下面来看该函数的实现.
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
...)
{
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);
if (!create)
return ERR_PTR(-ENOMEM);
create->threadfn = threadfn;//线程执行函数
create->data = data;//线程执行函数的参数
create->node = node;//NUMA系统上会用到,这里不介绍.
create->done = &done;//completion结构,一会儿会通过它判断该线程的创建是否完成.
spin_lock(&kthread_create_lock);
list_add_tail(&create->list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);//唤醒kthreadd_task内核线程.
if (unlikely(wait_for_completion_killable(&done))) {//进入睡眠,等待线程创建的完成.
if (xchg(&create->done, NULL))
return ERR_PTR(-EINTR);
wait_for_completion(&done);
}
task = create->result;
if (!IS_ERR(task)) {
static const struct sched_param param = { .sched_priority = 0 };
va_list args;
va_start(args, namefmt);
vsnprintf(task->comm, sizeof(task->comm), namefmt, args);//设置线程名字
va_end(args);
sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);
set_cpus_allowed_ptr(task, cpu_all_mask);
}
kfree(create);
return task;
}
该函数首先创建一个kthread_create_info结构,该结构封装了要创建的内核进程所需要的信息,然后将该结构添加到链表kthread_create_list的末尾,接着该函数调用wake_up_process函数唤醒kthreadd_task所表示的内核线程,然后调用wait_for_completion系列的函数使当前进程进入睡眠状态,在这里我们可以猜想kthreadd_task表示的内核线程应该会从链表kthread_create_list中依次的取下每个节点根据对应的kthread_create_info结构中的线程信息来创建内核线程,然后通过completion函数唤醒当前线程,这时kthread_create_info中的result应该已经被赋值为刚创建的内核线程的task_struct结构了,kthread_create_on_node函数会检查该task_struct结构,判断内核线程创建是否成功,如果成功,会进一步设置线程task的名称,以及调度器,最后返回新创建的task_struct结构体.
刚才猜想了kthreadd_task表示的内核线程的作用是从链表kthread_create_list上依次的取下kthread_create_info结构,然后创建相应的内核线程.现在来验证这个猜想.在内核源码中搜索kthreadd_task,会在init/main.c文件中找到为kthreadd_task赋值的地方.在函数rest_init中.
static noinline void __init_refok rest_init(void)
{
int pid;
rcu_scheduler_starting();
kernel_thread(kernel_init, NULL, CLONE_FS);
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
complete(&kthreadd_done);
......
}
rest_init算是kernel再启动函数start_kernel中执行的最后一个函数,kthreadd_task表示的内核线程就是在这里创建的,这行代码kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns)是通过pid来查找其对应的task_struct的结构体,再看pid是上面的这行代码产生的pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),kernel_thread函数是通过do_fork来创建线程的,然后返回该线程的pid,从这里可以看出kthreadd_task表示的内核线程的执行函数是kthreadd,下面来看这个函数的实现.
int kthreadd(void *unused)
{
struct task_struct *tsk = current;
......
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
__set_current_state(TASK_RUNNING);
spin_lock(&kthread_create_lock);
while (!list_empty(&kthread_create_list)) {
struct kthread_create_info *create;
create = list_entry(kthread_create_list.next, struct kthread_create_info, list);
list_del_init(&create->list);
spin_unlock(&kthread_create_lock);
create_kthread(create);
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
可以看出这个函数的主体是一个for循环,重点是其中的那个while循环,很明显是在遍历链表kthread_create_list,获取将其上面的每个节点所表示的kthread_create_info结构,然后将该结构传递给函数create_kthread,顾名思义,该函数就是来创建线程的,下面看看create_kthread的代码.
static void create_kthread(struct kthread_create_info *create)
{
int pid;
......
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
if (pid < 0) {//线程如果创建失败,下面的代码是错误处理,不是重点
/* If user was SIGKILLed, I release the structure. */
struct completion *done = xchg(&create->done, NULL);
if (!done) {
kfree(create);
return;
}
create->result = ERR_PTR(pid);
complete(done);
}
}
该函数调用了kernel_thread函数创建了一个以kthread函数为线程执行函数,以kthread_create_info结构为执行函数参数的内核线程,其实这个新创建的线程就是最终需要创建的那个内核线程,那为什么执行函数是kthread,而不是kthread_create_info结构中指定的线程执行函数.看完kthread函数的实现就知道了.
static int kthread(void *_create)
{
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn;//获取线程执行函数赋值给函数指针threadfn
void *data = create->data;//线程执行函数的参数
struct completion *done;
struct kthread self;
int ret;
......
done = xchg(&create->done, NULL);可以理解为将create->done赋值给done
if (!done) {
kfree(create);
do_exit(-EINTR);
}
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;//将该线程的task_struct结构赋值给create->result
complete(done);//调用complete函数唤醒调用kthread_create_on_node的那个线程,通知它创建的线程已经创建完毕.
schedule();//调用schedule交出处理器,给刚唤醒的调用kthread_create_on_node的线程以运行的机会,使其继续执行kthread_create_on_node函数剩下的代码.
//再次调度到该线程,从这里开始执行.
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {//判断如果该线程没有被停止掉.
......
ret = threadfn(data);//我们通过kthread_create_on_node函数传递进来的线程执行函数最终是在这个位置被执行的.
}
do_exit(ret);
}
可以看出,如果在kthread函数调用schedule交出处理器和再次调度到该线程之间的这段时间里,在别的地方调用了kthread_stop函数停止了该函数的话,那么线程执行函数是得不到执行的,该线程就直接退出了.
以上就是通过kthread_run和kthread_create创建线程的整个过程.可以看出,它们并不直接创建线程,而是将要创建的线程的相关信息打包到kthread_create_info结构中,然后委托给内核线程kthreadd来做的,从函数kthreadd也可以看出,该线程的主要工作就是遍历链表kthread_create_list创建线程.