|
其实简单来说,进程无非就是处于运行期的程序及其相关资源的总和。这里读者应该注意“相关资源”一词,Linux 在内核中是如何记录进程的资源的呢? 首先应该明白,Linux 内核大都是采用C语言编写的,因此要弄清楚内核如何记录进程资源,只需要查看相关的C语言代码就可以了。事实上,Linux 内核是使用 task_struct 结构体描述进程的资源的,它的C语言部分代码如下,请看:
鉴于 task_struct 结构体过长,这里不可能将其成员一一介绍清楚。如果读者和我一样好奇,粗略的浏览 task_struct 结构体,应该能够发现一些比较令人熟悉的成员,例如:9 r k6 F5 A ^2 m* r 0 G8 s, M! g' h7 Q F' f9 I
通过C语言注释以及成员的变量名,能够看到 task_struct 结构体包含了文件系统,线程结构体,以及进程打开的文件等信息,这就与上一节文章的内容对应上了。其他成员在我之后的文章中会涉及到,这里暂不赘述。% u) T! x9 w( s; A6 } $ K# X$ I4 Y& w7 @; m5 v+ X5 a 在创建进程时,Linux 通过 slab 分配器分配 task_struct 结构,这样可以避免动态分配和释放带来的开销,提高内存的使用效率。' Q( R0 L- d+ [! e 那么创建 task_struct 结构后,内核如何访问它呢?
Linux 通常会在内核栈底或者栈顶保留 thread_info 结构,而内核栈通常大小都是可知的,因此每个进程都能方便的从自己的栈中找到 thread_info 结构,进而找到 task_struct 结构。" g1 p k; L6 k% C' u0 H: b 3 B3 |; Q3 g0 K0 g) e5 b6 E' `& y0 F 查找当前进程的 thread_info 结构,可以调用 current_thread_info() 函数,它的C语言代码如下,请看:
- j. I! y0 _. e4 S1 S 可见,current_thread_info() 函数其实就是通过进程栈计算的,因此它的实现与平台架构有关,上述C语言代码其实只是 arm 平台的实现方法,其他平台的实现方法,读者可自行查阅。 & j+ @) {. j8 P& k 此时,要获取当前进程的资源,可以通过 current_thread_info()->task 索引。& w1 `/ G9 q- y ! a) d3 n8 b$ z5 ^+ _' }, b 进程 PID 0 x$ x; c& S2 {! `( k9 @ Linux 内核为每一个进程分配独一无二的进程标识(process identification,PID)用于区分不同的进程。PID 是一个整数,在内核的C语言源码中表示为 pid_t 类型(其实就是 int 类型)。在Linux命令行输入 ps 命令,即可查看进程的 PID,例如:) z0 l0 q t$ |& {+ b% w+ e- H0 { D
3 Y& @9 V8 h6 d! V task_struct 结构体使用成员 pid 记录进程的 PID 值,相关的C语言代码如下,请看:7 O& f( [1 K9 J) w
% Y3 P; }9 x% M- O- F 在Linux系统中,PID 的最大值是可以调整的,早期为了兼容老版本的 Unix 和 Linux,默认最大值为 32768(short int 类型能够表示的最大值),这个值可以通过 cat 命令查看:
# M, D& I* A3 X! H 进程的状态 4 j& k+ _7 E3 f9 t 现在知道了Linux内核是如何描述和记录进程资源,以及如何区分不同进程的了。那么进程有哪些状态呢?读者应该注意到 task_struct 结构体的第一个成员 state 了,它就是用于记录进程状态的。进程的状态在C语言源代码中是使用几个宏定义的:
Linux 系统中的进程必定处于这 5 种状态之一。从上到下,分别表示进程处于: ; K% b; N0 i$ y ) u' i9 Y X( A) O
现在就明白有时无法通过 kill 命令杀死 D 状态的进程了,这是因为这些进程处于不响应信号的状态,kill 命令本质上是发送 SIGKILL 信号,自然无法杀死该进程。 父进程和子进程 $ u8 w/ H, V- a5 a9 F 进程的父进程和子进程也属于进程的资源,因此也被记录在 task_struct 结构体中,请看相关C语言代码:5 \- W w$ M0 z5 \
% N" D1 |' T5 m" \) Y) a 所以要访问当前进程的父进程和子进程是方便的,例如:5 F+ x3 V6 C+ X. h" I
稍稍思考一下,应该能够发现进程结构体 task_struct 中的 parent 指针和 children 指针其实构成了一条链表,通过这样的链表,我们能够轻易的访问进程的父进程,祖父进程…, 以及子进程,孙进程… 等。不过应该明白,对于拥有大量进程的系统来说,重复遍历所有进程的开销是很大的。 小结 |
微信公众号
手机版