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");