内核线程和用户线程

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

# 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线程。tcp

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

---- 线程与进程学习

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

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

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

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

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

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

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

在这个进程中可能会有不一样的线程,例如极可能存在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的内核态空间则属于全体线程共享;