**** 注:源码来自2.6.33内核 纯粹为个人笔记使用****
调用链: kthread_create()--->kthreadd()--->create_kthread()---->kernel_thread_helper()---->kthread()--->预定义的函数.
一 创建内核线程的内核线程 kthreadd.
在内核初始化时,为了后期的内核线程的创建, 内核特地创建了一个新的特殊的内核线程来为后期的内核线程创建服务。
init/main.c rest_init(){
kthreadd_task = find_task_by_pid_ns ( pid , & init_pid_ns );
....
}
int (*thread_fn)(void *data);
void *data;
struct task_struct *result;
struct completion done;
struct list_head list;
}
init/main.c rest_init(){
kthreadd_task = find_task_by_pid_ns ( pid , & init_pid_ns );
....
}
这个内核线程将会执行kthreadd()函数, 而全局描述符kthreadd_task将用于记录这个内核线程的任务描述符.
二 kthread_create_info -- 创建内核线程信息描述符
顾名思义,这个描述符便是用于描述将要执行的内核进程的信息.
struct kthread_create_info
{struct kthread_create_info
int (*thread_fn)(void *data);
void *data;
struct task_struct *result;
struct completion done;
struct list_head list;
}
1)threadfn 创建的内核线程将要执行的函数
2)data -- 内核线程执行thread_fn时将要执行的函数
3) thread -- 指向将要创建的内核线程的任务描述符
4) done -- 完成变量. 用于保证调用进程创建内核线程时的同步。
5) list -- 所有的kthread_create_info描述符都会连接到同一个list中
.
.
kernel/kthread.c 定义了一个通用的双向链表.
static LIST_HEAD(kthread_create_list); 所有的kthreat_create_info将会连接到这个list中。
三, 创建新内核线程的接口 --- struct task_struct *kthread_create(iint (*threadnf)(void (*data)),
void *data,
const char namefmt[]
....)
这个接口主要做几件事:
1)根据
新内核线程的需求来初始化一个kthread_create_info描述符
struct kthread_create_info create;
create.threadfn = threadfn;
create_data = data;
init_completion(&create.done);
2) 将这个新的kthread_create_info描述符 链接到上面提到的kthread_create_list双向链表中
3) 唤醒kthreadd_task指向的进程。 一开始就提到这个描述符指定kthreadd内核线程的。而该内核线程创建后便一直 处于睡眠状态。
Kthreadd内核线程一旦唤醒, 获得cpu的执行权后便会执行kthreadd()函数
4) 调用进程等待完成变量create.done而进入睡眠状态。//这个睡眠状态知道后续调用的kthread()函数才得以解除。
214 int kthreadd(void *unused)
215 {
216 struct task_struct *tsk = current;
217
218 /* Setup a clean context for our children to inherit. */
219 set_task_comm(tsk, "kthreadd");
220 ignore_signals(tsk);
221 set_cpus_allowed_ptr(tsk, cpu_all_mask);
222 set_mems_allowed(node_states[N_HIGH_MEMORY]);
223
224 current->flags |= PF_NOFREEZE | PF_FREEZER_NOSIG;
225
226 for (;;) {
227 set_current_state(TASK_INTERRUPTIBLE);
228 if (list_empty(&kthread_create_list))
229 schedule();
230 __set_current_state(TASK_RUNNING);
231
232 spin_lock(&kthread_create_lock);
233
while (!list_empty(&kthread_create_list)) {
234 struct kthread_create_info *create;
235
236 create = list_entry(kthread_create_list.next,
237 struct kthread_create_info, list);
238 list_del_init(&create->list);
239 spin_unlock(&kthread_create_lock);
240
241
create_kthread(create);
242
243 spin_lock(&kthread_create_lock);
244 }
245 spin_unlock(&kthread_create_lock);
246 }
247
248 return 0;
249 }
250
kthreadd内核线程一开始先检测kthread_create_list有没有需要内核线程需要创建.
如果没有要创建的内核线程, 那么kthreadd内核线程就
直接进入睡眠状态。等待有下一个执行kthread_create()的进程把它唤醒。
如果有要创建的内核线程, 这kthreadd遍历所有的kthread_create_list中的kthread_create_info信息, 逐一移除kthread_create_info描述符,并传递给create_kthread(state)函数作为参数。
83
84 static void create_kthread(struct kthread_create_info *create)
85 {
86 int pid;
87
88 /* We want our own signal handler (we take no signals by default). */
89 pid =
kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
90 if (pid < 0) {
91 create->result = ERR_PTR(pid);
92 complete(&create->done);
93 }
94 }
95
265 int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
266 {
267 struct pt_regs regs;
268
269 memset(®s, 0, sizeof(regs));
270
271
regs.si = (unsigned long) fn;
272
regs.di = (unsigned long) arg;
273
274 #ifdef CONFIG_X86_32
275 regs.ds = __USER_DS;
276 regs.es = __USER_DS;
277 regs.fs = __KERNEL_PERCPU;
278 regs.gs = __KERNEL_STACK_CANARY;
279 #else
280 regs.ss = __KERNEL_DS;
281 #endif
282
283 regs.orig_ax = -1;
284
regs.ip = (unsigned long) kernel_thread_helper;
285 regs.cs = __KERNEL_CS | get_kernel_rpl();
286 regs.flags = X86_EFLAGS_IF | 0x2;
287
288 /* Ok, create the new process.. */
289 return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL);
290 }
x86中, kernel_thread的主要作用就是创建内核线程的进程描述符和执行环境(execute eviroment, execute context.执行上下文.)简单点来说就是设置cpu的寄存器信息。
特别关键的一点是 regs.si = (unsigned long) fn; //注意在不同的内核版本中,这两个参数可能存放于不同的寄存器
regs.di = (unsigned long) arg; //但是ip一般都是指向
regs.ip = (unsigned long) kernel_thread_helper;
在x86架构中, cpu将会读取并执行ip(instruction pointer)指向的指令. 因此当这个内核线程开始执行时,它实际执行的函数是kernel_thread_helper(). kernel_thread_helper()很重要的一点就是它会调用传递的fn函数,而arg作为fn函数的参数。通过create_kthread()可知, 传递的fn参数政治kthread,而arg参数就是kthread_create_info描述符。
因此新的内核内核线程在开始执行时它会调用kthread()函数.
57 static int kthread(void *_create)
58 {
59 /* Copy data: it's on kthread's stack */
60 struct kthread_create_info *create = _create;
61 int (*threadfn)(void *data) = create->threadfn;
62 void *data = create->data;
63 struct kthread self;
64 int ret;
65
66 self.should_stop = 0;
67 init_completion(&self.exited);
68 current->vfork_done = &self.exited; //这里也是一个完成变量 在内核线程退出时会用到.
69
70 /* OK, tell user we're spawned, wait for stop or wakeup */
71 __set_current_state(TASK_UNINTERRUPTIBLE);
72 create->result = current;
73
complete(&create->done); //唤醒原先一直睡眠的调用进程
74
schedule(); //这里说明了 新建的内核线程唤醒了调用进程便进入了睡眠状态。
75 //因此调用进程还要手动唤醒内核线程.
76 ret = -EINTR;
77 if (!self.should_stop)
78
ret = threadfn(data); //内核线程开始真正执行预定义的函数.
79
80 /* we can't just return, we must preserve "self" on stack */
81
do_exit(ret); //内核线程完成使命.一定要返回到这里退出。
82 }
这里的重点是1)
新建的内核线程创建后必将进入不可中断的睡眠。因而创建者需要自己唤醒内核线程.
2)前面在kthread_create()提到过, 调用进程将会执行wait_for_completion(create->done).如果熟悉complete原理的话,你就知道调用进程执行这个语句后陷入了无尽的睡眠。
唯一可以唤醒这个进程的方法,就是完成相关的kthread_create_info相关的完成变量->done.也就是执行complete(create->done). 因此执行完wait_for_completion(), 原先一直睡眠的调用进程得以恢复执行。对于该调用进程而言,看起来就好像还是执行的操作是无缝的,一旦执行kthread_create()就可以获得所需要的内核线程。
3)当内核线程退出时,必须回到kthread()执行退出清理工作。
3)当内核线程退出时,必须回到kthread()执行退出清理工作。