转自:http://www.2ndmoon.net/weblog/?p=603
一、linux 进程/线程基础
进程是系统中程序执行和资源分配的最小单位。每个进程都拥有自己的数据段,代码段和堆栈段。这就造成了进程在进行切换等操作时需要有比较负责的上下文切换等动作。为了进一步减少处理机的空转时间,支持多处理器,和减少上下文开销,由此有线程的提出。
线程是进程内的一种基本调度单位,也可以称作轻量级进程。线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述符,信号处理。从而大大减少了上下文切换的花销。同进程一样,线程也将相关的变量值放在线程控制表内,一个进程有多个线程,但却共享一个用户地址空间,这样同步的问题显得非常重要。
线程按照调度者可以分为用户级线程和核心级(内核级)线程。
用户级线程: 调度算法和调度过程有用户自行决定,缺点在于,如果一个进程中的某一个线程调用一个阻塞的系统调用,则该进程中的其他线程也会被阻塞。这样就在一个进程中的多个线程的调度无法发挥出多处理器的优势。
核心级(内核级)线程: 这个线程允许不同进程中的线程按照同一个相对优先调度方法进行调度,这样就能发挥出多处理器的并发优势。
二、linux 用户线程和内核线程
1.linux 用户线程
2.linux 内核线程
Linux内核在完成初始之后,会把控制权交给应用程序。只有当硬件中断、软中断、异常等发生时,CPU才会从用户空间切换到内核空间来执行相应的处理,完成后又回来用户空间。
如果内核需要周期性地做一些事情(比如页面的换入换出,磁盘高速缓存的刷新等),又该怎么办呢?内核线程(内核进程)可以解决这个问题。
内核线程(kernel thread)是由内核自己创建的线程,也叫做守护线程(deamon)。在终端上用命令”ps -Al”列出的所有进程中,名字以k开关以d结尾的往往都是内核线程,比如kthreadd、kswapd。
(2)内核线程的特点
内核线程与用户线程的相同点是:
- 都由do_fork()创建,每个线程都有独立的task_struct 和内核栈;
- 都参与调度,内核线程也有优先级,会被调度器平等地换入换出。
不同之处在于:
- 内核线程只工作在内核态中;而用户线程则既可以运行在内核态,也可以运行在用户态;
- 内核线程没有用户空间,所以对于一个内核线程来说,它的0~3G的内存空间是空白的,它的current->mm是空的,与内核使用同一张页表;而用户线程则可以看到完整的0~4G内存空间。
(3)内核线程的创建
在Linux内核启动的最后阶段,系统(“启动”内核进程:PID为0的进程)会创建两个内核线程,一个是init,一个是kthreadd。
具体的讲,内核初始化工作的最后一部分是在函数rest_init()中完成的。在这个函数中,主要做了4件事情,分别是:创建init线程,创建kthreadd线程,执行schedule()开始调度,执行cpu_idle()让CPU进入idle状态。经过简化的代码如下:
<span style="font-size:18px;">static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
schedule();
cpu_idle();
}</span>
在内核线程创建过程中还有两个有趣的细节值得说一下:
- 虽然init线程是在kthreadd之前创建的,pid也比较小,但是在schedule()的时候,最先被选中先运行的是kthreadd。这不会有任何影响,因为kthreadd总会让出CPU,init 线程一定能启动。
- 进程号PID的分配是从0开始的,但是在”ps”命令中看不到0号进程。这是因为0号pid被分给了“启动”内核进程,就是完成了系统引导工作的那个进程。在函数rest_init()中,0号进程在创建完成了init和kthreadd两个内核线程之后,调用schedule()使得pid=1和2的两个线程得以启动,但是pid=0的线程并不参与调度,所以这个进程就再也得不到运行了。如下所示,在我们前面已经看到过的这段代码中,schedule()不会返回,最后一行的cpu_idle()其实是不会被运行到的
- 在init线程中,将运行完”/sbin/init”、”/etc/init”和”/bin/init”三个脚本,并启动shell。run_init_process(“/bin/sh”)并不会返回,init 线程就停在这里,以后所有的应用程序进程都将从/bin/sh克隆,而sh来自init内核线程,所以init线程最终成为所有用户进程的祖先。
通过命令行查看可以看出进程的ID:
tonylau@tonylau-OptiPlex-780:~$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:12 ? 00:00:01 /sbin/init 第1个内核线程(父进程ID:0-“启动”内核进程)
root 2 0 0 08:12 ? 00:00:00 [kthreadd] 第2个内核线程(父进程ID:0-“启动”内核进程)
其中init线程的作用是运行文件系统上的一系列”init”脚本,并启动shell进程,所以init线程称得上是系统中所有用户进程的祖先,它的pid是1。kthreadd 线程是内核的守护线程,在内核正常工作时,它永远不退出,是一个死循环,它的pid是2。