---- 在Ubuntu系统下,使用 ps -axjf(ps -eLf) 命令可以查看详细的内核线程和用户线程状态。
ps:report a snapshot of the current processes, displays information about a selection of the active processes.
top: if you want a repetitive update of the selection and the displayed information.
# 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号进程的子进程(子线程)。
PID指示该进程的进程号,PPID指示该进程的父进程号。
可以看出,1号和2号进程的父进程就是0号线程,PPID=0,是Kernel在start_kernel中创建的第一个线程,称为Idle线程。
当没有其他线程在运行时,schedule将调度它进入Idle省电状态;
由于0号进程是一个特殊的进程,所以ps命令并没有把它列出来。
ps命令参数解析:
-A:Select all processes. Identical to -e
-a:Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal.
-u:代表user,在需要查看特定用户进程的情况下使用,如ps -u dong
-f :do full-format listing(全格式列出)
-L:NLMP(number of threads),LMP(thread id)columns will be added
如果想根据CPU或内存用量来筛选,用-aux参数
---- 线程与进程
在学习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的内核态空间则属于全体线程共享;
特权指令:一类只能在核心态下运行而不能在用户态下运行的特殊指令。
不同的操作系统特权指令会有所差异,但是一般来说主要是和硬件相关的一些指令。