内核线程和用户线程

来源:互联网 发布:知党章党规,系列讲话 编辑:程序博客网 时间:2024/05/17 07:18

---- 在Ubuntu系统下,使用 ps -axjf 命令可以查看详细的内核线程和用户线程状态。

 # ps USER     PID   PPID  VSIZE  RSS     WCHAN    PC         NAME root      1     0     396    256   c45ebcd0 0000875c S /init root      2     0     0      0     c4570cec 00000000 S kthreadd root      3     2     0      0     c45618e8 00000000 S ksoftirqd/0 root      4     2     0      0     c4599578 00000000 S watchdog/0 root      5     2     0      0     c456d8bc 00000000 S events/0 root      6     2     0      0     c456d8bc 00000000 S cpuset root      7     2     0      0     c456d8bc 00000000 S khelper
ps命令列出了1号线程 /init,2号线程 kthreadd,其他的线程,差不多都是1号和2号线程的子线程。

对于1号和2号线程来说,它们的父线程就是0号线程,也就是Kernel在start_kernel中创建的第一个线程,称为Idle线程。

当没有其它线程在运行时,schedule将调度它进入Idle省电状态;由于0号线程是一个特殊的线程,所以ps命令并没有把它列出来。

---- 线程与进程

在学习Linux的知识时,书上经常会有线程和进程的提法,在Linux内核中,其实是不区分进程还是线程的。

Linux kernel会使用一个统一的task_struct (进程描述符)来描述任务相关的所有信息。然而我们该怎么样来理解程序,进程,线程的区别呢?

程序:程序是静态的说法,它是保存在某种介质上的可执行文件,是code与data的集合。

进程:处于执行状态的程序以及它所包含的资源的总称,进程是一个程序的动态的执行的实体。

线程:线程是进程中的一个动态对象,是程序执行的最小单元,一个进程中可能包含有多个线程,这些线程会共享同一个进程中的资源。

            例如地址空间,例如所打开的文件等;

从实际的例子来看,假如我们生成了一个可执行的程序,它保存在文件中时,就是程序;一旦系统开始调度这个程序执行,那么它可以称为一个进程。

在这个进程中可能会有不同的线程,例如很可能存在tcp thread, ip thread,以及其它为了完成某一功能而创建的thread。

由于我们在嵌入式Linux Kernel实际工作中使用的还是线程的概念,因此之后的讨论一概以线程来称呼。

线程描述符  task_struct  (可以参考kernel/include/linux/sched.h)中包含了一个线程相关的所有信息,里面主要描述了线程的状态,虚拟内存空间信息,寄存器和堆栈上下文等。

 

---- 线程间的关系

 

在Linux中,所有的线程可以分为3种,Idle线程,内核线程,用户线程。

其中,Idle线程是特殊的0号线程,它在Linux系统一开始创建的时候就存在,并且0号线程是其他所有线程的父线程。

内核线程是由kernel_thread或kthread_create等特殊的内核线程函数所创建的,在Linux kernel 2.6以后,基本上内核线程都从2号线程do_fork()而来;

用户线程是在用户态下通过C库函数fork()所创建,并且大部分都是从1号线程继承而来;

 

0号线程: 它就是Idle线程,在kernel初始化时(start_kernel函数中)创建,并且0号线程的描述符,并不是通过fork()过程创建起来的,而是通过

INIT_TASK宏(/kernel/arch/arm/init_task.c)静态配置的。在SMP系统中,每一个CPU都对应一个0号线程,当CPU空闲时,Linux Kernel将调度Idle线程执行,

此时Idle线程进入cpu_idle函数,在该函数内部实现sleep睡眠设计,达到省电的功能;

 

1号线程:在start_kernel->rest_init函数中,系统通过调用 kernel_thread(kernel_init, ...)而创建了1号线程,当1号线程完成初始化任务后,

会通过run_init_process("/sbin/init", ... ) 函数去执行系统目录下的init程序,此时1号线程就用内核线程转为了用户线程;init程序会继续完成用户态下的

各种应用初始化过程;由于1号线程是在rest_init函数中通过kernel_thread 函数(实际上最后执行了do_fork() 函数)所创建的。

因此1号线程的父线程是0号线程;1号线程最后的显示为"init"。

 

2号线程:在start_kernel->rest_init函数中,系统通过调用kernel_thread(kthreadd, ...)创建了2号线程,2号线程主要承担创建其他内核线程的任务,

在后续kernel的执行中,如果调用 kthread_create()函数来创建内核线程,则其父线程均设置为2号线程;如果调用kernel_thread()函数来创建内核线程,

通过一些附加的操作,也可以将父线程修改为2号线程。因此,在一个规范的Linux系统中,我们可以看到几乎所有的内核线程,其父线程均为2号线程,

它也是0号线程的子线程;2号线程最后显示为"kthreadd"。

 

内核线程: 在内核态下,通过kernel_thread()或者kthread_create等API创建的线程,属于内核线程。由于kthread_create的实现方式,通过该函数创建

的内核线程,其父线程均为2号kthreadd线程;

 

用户线程: 在用户态下,通过pthread_create或者fork等C库函数调用所创建的线程。由于1号线程是第一个用户线程,同时也是启动其他线程的入口,

因此所有的用户线程都可以从父子关系上查看到,它们属于1号线程的子孙线程。

 

内核线程与用户线程的差别,主要在于:

1.  创建方式:内核线程必须在内核态下,通过kernel_thread或者kthread_create等内核函数API所创建;用户线程必须在用户态下,通过fork()或

pthread_create等C library函数所创建;

2. 运行方式:内核线程只能运行在内核态,它只能调用内核函数,不能调用用户态函数,因此无法使用C library函数;用户线程可以运行在用户态,

也可以通过系统调用,"陷入"内核态中运行,只有在内核态中才能调用内核函数;

3. 运行空间:内核线程只能访问到内核态的大于PAGE_OFFSET(3GB)的地址空间;用户态可以使用整个4GB地址空间,此时前3GB的地址空间属于

本进程独享,而大于PAGE_OFFSET的内核态空间则属于全体线程共享;




0 0