linux将建立内核线程的工做交给了一个专门的内核线程kthreadd来完成,该线程会检查全局链表kthread_create_list,若是为NULL,就会调schedule()放弃cpu进入睡眠状态,不然就取下该链表中的一项建立对应的线程。本文就从khtreadd内核线程的建立开始来展现一下内核线程的建立过程。
1 kthreadd
linux2.6.30,建立内核线程是经过kethradd内核守护线程来完成的,虽然机制上有所变化,可是最终仍是调用do_fork来完成线程的建立。Kthreadd守护线程是在linux内核启动时就已经建立的内核线程。在start_kernel调用的结束位置,会调用rest_init接口,而kthreadd就是在这个接口中建立的,代码以下所示:
asmlinkage void __init start_kernel(void)
{
。。。。。。。。。。。。。。。。。。。。。。
rest_init();
}
static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;
/*在启动时建立内核线程,该线程主要用来建立内核线程*/
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
}
Kthreadd守护线程自己也是一个内核线程,这个内核线程本质上与其余内核线程都同样,只不过是这个内核线程所作的工做是用来建立内核线程的。函数的调用关系以下所示:
Start_kernel=》rest_init=》kernel_thread=》do_fork
没有内核线程建立请求时,Kthreadd守护线程就处于睡眠状态,一旦有内核线程建立请求,则唤醒kthreadd守护线程,完成内核线程的建立工做。
kthreadd守护线程主要经过传入内核线程建立参数来建立特定的内核线程,所以,在讲述内核线程的建立以前,有必要了解一下内核线程的建立参数。内核线程建立相关的数据结构主要是struct kthread_create_info,这个数据结构表示建立一个内核线程所须要的信息。结构体定义以下所示:
structkthread_create_info
{
/* Information passed to kthread() fromkthreadd. */
int (*threadfn)(void *data);/*内核线程的回调函数*/
void *data;/*回调函数的参数*/
struct completion started;/*kthreadd等待新建立的线程启动*/
/* Result passed back to kthread_create()from kthreadd. */
struct task_struct *result;/*建立成功以后返回的task_struct*/
struct completion done;/*用户等待线程建立完成*/
struct list_head list;/*主要用于将建立参数结构放在一个全局链表中*/
};
用户须要建立一个内核线程时,会填充该数据结构,并将该结构体挂在全局的kthread_create_list链表中,而后唤醒kthreadd守护线程来建立内核线程,而用户线程则阻塞等待内核线程建立完成。详细的用户接口下文会介绍,这里就不在赘述。
根据前面对内核线程建立结构体的描述,kthreadd守护线程须要完成的工做就比较简单,主要就是遍历kthread_create_list,若是链表中有须要建立的内核线程,则调用create_kthread完成内核线程的建立工做。反之,没有内核线程须要建立,那么kthreadd守护线程将睡眠,等待下次内核线程建立请求的唤醒。
static voidcreate_kthread(struct kthread_create_info *create)
{
int pid;
/* We want our own signal handler (wetake 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);/*等待新建立的线程启动*/
complete(&create->done);/*通知用户请求建立的线程已经完成*/
}
create_kthread主要开始调用kernel_thread来建立内核线程,到这里,内核线程的建立工做跟kthreadd内核守护线程建立过程就一致了,可谓是异曲同工。若是Kthreadd建立线程失败,则直接通知用户请求建立动做已经完成,至因而不是建立成功,须要用户本身判断。反之,内核线程建立成功,kthreadd守护线程会等待新建立的线程启动,而后才通知用户请求建立内核线程的动做已经完成。完成了一个内核线程的建立以后,从kthread_create_list链表中摘下下一个须要建立的内核线程,指定全部请求的内核线程建立成功,ktheadd线程睡眠。
2 kthread_create and kthread_run
既然linux2.6.30采用了kthreadd守护线程来建立内核线程,那么在内核编程的时候,用户就不该该直接调用kernel_thread来建立内核线程。(这里只是猜想,具体细节有待研究)linux内核提供给用户用来建立内核线程的接口有两个,一个是kthread_create,另外一个是kthread_run。kthread_run接口其实也就是kthread_create的封装。所不一样的是,kthread_run在内核线程建立成功以后,就直接调用了wake_up_process,来唤醒该内核线程,使其可以马上获得调度,而经过kthread_create建立的内核线程默认是睡眠的,并不会获得调度,而是须要显示的唤醒该内核线程才能获得调度。代码以下所示:
structtask_struct * kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
/*填充create结构体*/
create.threadfn = threadfn;
create.data = data;
init_completion(&create.started);
init_completion(&create.done);
/*将create结构体挂到全局的kthread_create_list链表中*/
spin_lock(&kthread_create_lock);
list_add_tail(&create.list,&kthread_create_list);
spin_unlock(&kthread_create_lock);
/*唤醒kthradd守护线程来建立内核线程*/
wake_up_process(kthreadd_task);
/*等待线程的建立结束*/
wait_for_completion(&create.done);
/*kthreadd线程并不能保证100%建立成功,这里须要在作验证*/
if (!IS_ERR(create.result)) {
struct sched_param param = {.sched_priority = 0 };
va_list args;
* root may have changed our (kthreadd's)priority or CPU mask.
* The kernel thread should not inherit theseproperties.
*/
/*设置线程的优先级和cpu亲和性*/
sched_setscheduler_nocheck(create.result,SCHED_NORMAL, ¶m);
set_user_nice(create.result,KTHREAD_NICE_LEVEL);
set_cpus_allowed_ptr(create.result,cpu_all_mask);
}
return create.result;
}
由前文可知,建立一个内核线程,首先要作的就是填充kthread_create_info结构体,将线程的处理函数以及相关参数传递给kthreadd守护线程,这样才能完成用户须要新建立线程所完成的工做。填充完结构体以后,还须要将结构体挂到全局的kthread_create_list链表中,而后唤醒kthreadd线程开始建立内核线程,用户线程睡眠等待新线程建立动做的完成,新线程完成以后,设置线程的相关属性,此时,线程就能够完成相关的工做了。
3 内核线程处理函数
内核线程的线程处理函数并非用户自定义的接口,而是内核实现的一个统一的接口。这个统一的接口就是kthread,而用户自定义的线程处理函数是经过该统一接口进行回调的。Kthread接口完成了不少方面的工做,首先,就是咱们刚刚说的回调用户自定义的接口,以便于内核线程可以完成用户想让该线程完成的工做。其次,就是通知kthreadd守护线程,新建立的线程已经开始启动,khtreadd线程能够建立下一个内核线程,第三,就是实现新建立的线程在没有显式调用唤醒以前,该线程是睡眠的;第四,就是能够很方便的将新建的内核线程终止,释放相关的资源。代码以下所示:
static int kthread(void *_create)
{
struct kthread_create_info *create =_create;
int (*threadfn)(void *data);
void *data;
int ret = -EINTR;
/* Copy data: it's on kthread's stack*/
threadfn = create->threadfn;
data = create->data;
/* OK, tell user we're spawned, waitfor stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
complete(&create->started);
schedule();
/*线程没有被终止,则调用用户的回调接口完成相关的工做*/
if (!kthread_should_stop())
ret = threadfn(data);
/* It might have exited on its own, w/okthread_stop. Check. */
/*若是用户但愿终止该线程,则通知用户已经完成终止的准备动做*/
if(kthread_should_stop()) {
kthread_stop_info.err = ret;
complete(&kthread_stop_info.done);
}
return 0;
}
4 终止内核线程
内核线程的终止是经过kthread_stop接口完成的。用户一旦调用了kthread_stop接口,首先会唤醒该内核线程,并设置须要终止的线程,而后睡眠,等待线程处理函数的唤醒。由前面的内核线程的处理函数能够看到,就不会在执行用户自定义的回调接口,同时会唤醒用户线程,完成终止内核线程的工做。Kthread_stop用来trace_point来完成内核线程的清理工做,trace_point的工做原理还不清楚,姑且就认为其能够完成咱们须要的清理工做吧。
5 绑定内核线程到指定的cpu
对应SMP架构的CPU,能够经过kthread_bind接口将建立的内核线程绑定到某个指定的CPU上执行。经过绑定,能够减小内核线程在CPU之间的迁移,提升内核线程的工做效率。绑定CPU,主要原理是经过CPU的亲和性来实现的。这也是LINUX调度器天生就支持的,这里只不过是利用了调度器的亲和性功能实现了内核线程的绑定功能。
6总结
内核线程在linux内核中常常被使用,了解linux内核线程的建立以及销毁过程,能够帮助咱们理解内核中利用内核线程完成的某些功能的工做原理。例如,work_queue,work_queue就是经过建立内核线程来完成相关的功能,若是咱们知道内核线程的工做原理,在利用work_queue的时候,就能作到心中有底,能够知道何时work_queue在工做,何时在休眠等等。总之,从点滴开始学习linxu内核,能够帮助咱们恰好的理解内核的一些功能的机制和原理。
linux