---- 在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 khelperps命令列出了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的内核态空间则属于全体线程共享;