Kernel Thread 的创建
在 Linux 中有很多的内核线程,可以通过 ps
command 查看到,比如: kthreadd ksoftirqd watchdog 等等等 … 它们都是由内核从无到有创建的,通过它们的 pid 以及 ppid 可以得出以下几点:
- 在内核初始化
rest_init
函数中,由进程 0 (swapper 进程)创建了两个 process <ol style="list-style-type: decimal;"> - init 进程 (pid = 1, ppid = 0)
- kthreadd (pid = 2, ppid = 0)
- 所有其它的内核线程的 ppid 都是 2,也就是说它们都是由 kthreadd thread 创建的
- 所有的内核线程在大部分时间里都处于阻塞状态(TASK_INTERRUPTIBLE)只有在系统满足进程需要的某种资源的情况下才会运行
</ol>
创建一个内核 thread 的接口函数是:
kthread_create()
kthread_run()
这两个函数的区别就是 kthread_run()
函数中封装了前者,由 kthread_create()
创建的 thread 不会立即运行,而后者创建的 thread 会立刻运行,原因是在 kthread_run()
中调用了 wake_up_process()
.用什么函数看你自己的需求,如果你要让你创建的 thread 运行在指定的 cpu 上那必须用前者(因为它创建的 thread 不会运行),然后再用 kthread_bind()
完成绑定,最后 wake up.
下面就大概说一下内核 thread 创建的过程.
kthreadd
rest_init()
{
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid(pid);
}
kernel thread
kthread_create(usb_stor_control_thread, us, "usb-storage")
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
create.threadfn = threadfn;
新创建的线程运行的函数
create.data = data;
init_completion(&create.done);
初始化一个 completion 用于一个线程告诉其它线程某个工作已经完成
spin_lock(&kthread_create_lock);
list_add_tail(&create.list, &kthread_create_list);
加到待创建 thread list 中去
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);
唤醒 kthreadd 线程来创建新的内核线程
wait_for_completion(&create.done);
等待新的线程创建完毕(睡眠在等待队列中)
......
}
kthreadd 是一个死循环,大部分时间在睡眠,只有创建内核线程时才被唤醒.
int kthreadd(void *unused)
{
关注循环体
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (list_empty(&kthread_create_list))
schedule();
首先将线程状态设置为 TASK_INTERRUPTIBLE, 如果当前
没有要创建的线程则主动放弃 CPU 完成调度.此进程变为阻塞态
__set_current_state(TASK_RUNNING);
运行到此表示 kthreadd 线程被唤醒(就是我们当前)
设置进程运行状态为 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);
从链表中取得 kthread_create_info 结构的地址,在上文中已经完成插入操作(将
kthread_create_info 结构中的 list 成员加到链表中,此时根据成员 list 的偏移
获得 create)
list_del_init(&create->list);
取出后从列表删除
spin_unlock(&kthread_create_lock);
create_kthread(create);
完成真正线程的创建
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
}
create_kthread 函数完成真正线程的创建
static void create_kthread(struct kthread_create_info *create)
{
int pid;
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
其实就是调用首先构造一个假的上下文执行环境,最后调用 do_fork()
返回进程 id, 创建后的线程执行 kthread 函数
if (pid < 0) {
create->result = ERR_PTR(pid);
complete(&create->done);
}
}
此时回到 kthreadd thread,它在完成了进程的创建后继续循环,检查 kthread_create_list 链表,如果为空,则 kthreadd 内核线程昏睡过去 ……
当前有三个线程:
- kthreadd thread 已经光荣完成使命,睡眠
- 唤醒 kthreadd 的线程由于新创建的线程还没有创建完毕而继续睡眠 (在 kthread_create 函数中)
-
新创建的线程已经正在运行
kthread
,但是由于还有其它工作没有做所以还没有最终创建完成.
新创建的线程 kthread 函数
static int kthread(void *_create) { struct kthread_create_info *create = _create; // create 指向 kthread_create_info 中的 kthread_create_info int (*threadfn)(void *data) = create->threadfn; // 新的线程创建完毕后执行的函数 void *data = create->data; struct kthread self; int ret; self.should_stop = 0; // 在 kthread_should_stop() 函数中会检查这个值,表示当前线程是否 // 运行结束. kthread_should_stop() 常被用于线程函数中. init_completion(&self.exited); current->vfork_done = &self.exited; /* OK, tell user we're spawned, wait for stop or wakeup */ __set_current_state(TASK_UNINTERRUPTIBLE); // 设置运行状态为 TASK_UNINTERRUPTIBLE create->result = current; // current 表示当前新创建的 thread 的 task_struct 结构 complete(&create->done); // new thread 创建完毕 schedule(); // 执行任务切换,让出 CPU ...... }
线程创建完毕:
- 创建新 thread 的进程恢复运行
kthread_create()
并且返回新创建线程的任务描述符 - 新创建的线程由于执行了
schedule()
调度,此时并没有执行.
最后唤醒新创建的线程 :
wake_up_process(p);
当线程被唤醒后,继续 kthread()
ret = -EINTR;
if (!self.should_stop)
ret = <span style="color: #ff0000;">threadfn</span>(data);
执行相应的线程函数
do_exit(ret);
最后退出
总结 {#总结}
- 任何一个内核线程入口都是
kthread()
- 通过
kthread_create()
创建的内核线程不会立刻运行.需要手工 wake up. - 通过
kthread_create()
创建的内核线程有可能不会执行相应线程函数threadfn
而直接退出
最后写了一个创建内核线程的模块仅供参考:
#include <linux/init.h> #include <linux/time.h> #include <linux/errno.h> #include <linux/module.h> #include <linux/sched.h> #include <linux/kthread.h> #include <linux/cpumask.h> #define sleep_millisecs 1000*60 static int thread(void *arg) { long ns1, ns2, delta; unsigned int cpu; struct timespec ts1, ts2; cpu = *((unsigned int *)arg); printk(KERN_INFO "### [thread/%d] test start \n", cpu); while (!kthread_should_stop()) { /* * Do What you want */ schedule_timeout_interruptible( msecs_to_jiffies(1)); } printk(KERN_INFO "### [thread/%d] test end \n", cpu); return 0; } static int __init XXXX(void) { int cpu; unsigned int cpu_count = num_online_cpus(); unsigned int parameter[cpu_count]; struct task_struct *t_thread[cpu_count]; for_each_present_cpu(cpu){ parameter[cpu] = cpu; t_thread[cpu] = kthread_create(thread, (void *) (parameter+cpu), "thread/%d", cpu); if (IS_ERR(t_thread[cpu])) { printk(KERN_ERR "[thread/%d]: creating kthread failed\n", cpu); goto out; } kthread_bind(t_thread[cpu], cpu); wake_up_process(t_thread[cpu]); } schedule_timeout_interruptible( msecs_to_jiffies(sleep_millisecs)); for (cpu = 0; cpu < cpu_count; cpu++) { kthread_stop(t_thread[cpu]); } out: return 0; } static void __exit XXXX_exit(void) { } module_init(XXXX); module_exit(XXXX_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("bluezd"); MODULE_DESCRIPTION("Kernel study");