linux内核线程的创建与销毁

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内核,可以帮助我们刚好的理解内核的一些功能的机制和原理。

时间: 2024-08-07 19:15:26

linux内核线程的创建与销毁的相关文章

Linux内核线程kernel thread详解--Linux进程的管理与调度(十)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-02 Linux-4.5 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的描述 内核线程 为什么需要内核线程 Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求). 内核需要多个执行流并行,为了防止可能的阻塞,支持多线程是必要的. 内核线程就是内核的分身,一个分身可以处理一件特定事情.内核线程的调度由内核负责,一个内核线程处于阻

Android线程的创建与销毁

摘要: 在Android开发中经常会使用到线程,一想到线程,很多同学就立即使用new Thread(){...}.start()这样的方式.这样如果在一个Activity中多次调用上面的代码,那么将创建多个匿名线程,程序运行的越久可能会越来越慢.因此,需要一个Handler来启动一个线程,以及删除一个线程,保证线程不会重复的创建. 正文: 1.创建Handler的一般方式 一般会使用Handler handler = new Handler(){...}创建.这样创建的handler是在主线程即

Java线程与Linux内核线程的映射关系(转)

Java线程与Linux内核线程的映射关系 Java线程与Linux内核线程的映射关系Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程. Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的.Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程有一一对应的关系.线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个

linux内核线程,进程,线程

http://blog.csdn.net/dyllove98/article/details/8917197 Linux对于内存的管理涉及到非常多的方面,这篇文章首先从对进程虚拟地址空间的管理说起.(所依据的代码是2.6.32.60) 无论是内核线程还是用户进程,对于内核来说,无非都是 task_struct这个数据结构的一个实例而已,task_struct被称为进程描述符(process descriptor),因为它记录了这个进程所有的context.其中有一个被称为'内存描述符‘(memo

Linux C线程的创建和使用 [转]

1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中 去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多 线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux. 为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题. 使用多线程的理由之一是和进程相比

Linux内核线程

<背景> 内核线程类似于用户进程,通常用于并并发处理性质的任务,并且可以抢占调度.不同于用户进程,内核线程位于内核空间,并且可以访问内核函数和内核数据. <创建内核线程> a:ret = kernel_thread(mythread,null,CLONE_FS | CLONE_FILES | CLONE_SIGHAND | SIGCHLD) 参数注释: CLONE_FILES:大开的文件共享 CLONE_SIGHAND:信号处理程序共享 注:由于内核线程通常对设备驱动程序起到辅助作

Java线程与Linux内核线程的映射关系

Linux从内核2.6开始使用NPTL (Native POSIX Thread Library)支持,但这时线程本质上还轻量级进程. Java里的线程是由JVM来管理的,它如何对应到操作系统的线程是由JVM的实现来确定的.Linux 2.6上的HotSpot使用了NPTL机制,JVM线程跟内核轻量级进程有一一对应的关系.线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个例子,在linux下,只要一个Thread.run就会调用一个fork产生一个线程.

Linux内核是如何创建一个新进程的?

进程描述 进程描述符(task_struct) 用来描述进程的数据结构,可以理解为进程的属性.比如进程的状态.进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct 进程控制块(PCB) 是操作系统核心中一种数据结构,主要表示进程状态. 进程状态 fork() fork()在父.子进程各返回一次.在父进程中返回子进程的 pid,在子进程中返回0. fork一个子进程的代码 #include <stdio.h> #include <stdli

转:Android线程的创建与销毁

http://mt.sohu.com/20140813/n403395303.shtml?recsyssohu=2 在Android开发中经常会使用到线程,一想到线程,很多同学就立即使用new Thread(){...}.start()这样的方式.这样如果在一个Activity中多次调用上面的代码,那么将创建多个匿名线程,程序运行的越久可能会越来越慢.因此,需要一个Handler来启动一个线程,以及删除一个线程,保证线程不会重复的创建. 1.创建Handler的一般方式  一般会使用Handle