你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux 在内核中是如何记录进程的资源的呢?

[复制链接]
gaosmile 发布时间:2020-3-31 12:30
其实简单来说,进程无非就是处于运行期的程序及其相关资源的总和。这里读者应该注意“相关资源”一词,Linux 在内核中是如何记录进程的资源的呢?
5 q. U8 h+ _8 v+ K, w+ U/ U
/ b# F2 I. s6 I8 g. Z- L首先应该明白,Linux 内核大都是采用C语言编写的,因此要弄清楚内核如何记录进程资源,只需要查看相关的C语言代码就可以了。事实上,Linux 内核是使用 task_struct 结构体描述进程的资源的,它的C语言部分代码如下,请看:
6 X9 j5 s6 Y" [7 |/ v7 n! o  D
6 d2 p' R, D# m$ O4 b( _1 y& Y
微信图片_20200331122852.jpg
task_struct 结构体很长task_struct 结构体很长,在我手中的 Linux 内核C语言源代码中,它占用了280行。当然了,这其中包含很多条件编译部分,在 32 位机器上,task_struct 大约要占用 1.7 KB 的内存空间,不过考虑到它可以管理完整的进程,1.7kB 其实并不算大了。
5 n6 j, i  J4 q: b1 Q

* X' R& q! l" @$ X8 V. S; H& q
鉴于 task_struct 结构体过长,这里不可能将其成员一一介绍清楚。如果读者和我一样好奇,粗略的浏览 task_struct 结构体,应该能够发现一些比较令人熟悉的成员,例如:9 r  k6 F5 A  ^2 m* r
0 G8 s, M! g' h7 Q  F' f9 I
微信图片_20200331122856.jpg
task_struct 结构体令人熟悉的成员* U4 G5 L2 ~. I8 _  w2 ~

2 N! Q5 n+ x- u! N
通过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

6 ^" R6 c  A7 F/ R
那么创建 task_struct 结构后,内核如何访问它呢?

/ h: w3 F) e: `5 |
根据我手上的内核C语言源代码,Linux 中还有一个结构体 thread_info,它的其中一个成员 task 指针正好适合用于索引 task_struct 结构体,在X86_64平台上,thread_info 的相关C语言代码如下,请看:
8 d9 b) U( @6 ?

" q. ?# s% C8 U" n; E0 S
微信图片_20200331122858.jpg
task 指针
% A" o2 ^. b& a

" [+ J. _* z! G% t) b0 m
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语言代码如下,请看:

  1. ! o7 T% d" v0 U4 a' a+ b# ?
  2. static inline struct thread_info *current_thread_info(void). G# A0 u9 _, L8 ]. K+ Y
  3. {
    - P0 q/ N$ q( w) g* J
  4. register unsigned long sp asm ("sp");3 O. s* c4 m/ ]# g
  5. return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));; E+ _, r" q4 b) K8 p1 h# x
  6. }
复制代码

- V2 ?, Y& S2 i. l/ w, H- O

8 k& O! ^  \+ H, Q- J! |$ C9 Q
微信图片_20200331122902.png
current_thread_info() 函数
3 N" k, T% f, I- j. I! y0 _. e4 S1 S
可见,current_thread_info() 函数其实就是通过进程栈计算的,因此它的实现与平台架构有关,上述C语言代码其实只是 arm 平台的实现方法,其他平台的实现方法,读者可自行查阅。
! Z+ `9 l9 q# p* ?
& 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
微信图片_20200331122905.png
查看进程的 PID, H& D) Y! M9 I" ]/ P
3 Y& @9 V8 h6 d! V
task_struct 结构体使用成员 pid 记录进程的 PID 值,相关的C语言代码如下,请看:7 O& f( [1 K9 J) w

! B2 m, J: ]0 {5 i' B% x
微信图片_20200331122908.png
task_struct 结构体使用成员 pid 记录进程的 PID 值* y: i# @2 n+ q0 F, R1 z. {8 a
% Y3 P; }9 x% M- O- F
在Linux系统中,PID 的最大值是可以调整的,早期为了兼容老版本的 Unix 和 Linux,默认最大值为 32768(short int 类型能够表示的最大值),这个值可以通过 cat 命令查看:
  1. <font color="#001000"><font style="background-color:rgb(255, 255, 255)">
    * l) U; [( f, I  c# I
  2. # cat /proc/sys/kernel/pid_max32768</font></font><font color="#003000"><font face="Tahoma">
    $ y8 L/ c2 ]$ ?  }7 a% J
  3. </font></font>
复制代码
PID 的最大值对于Linux系统的运行是有影响的,因为 PID 值是独一无二的,所以它的最大值实际上就表示系统可以同时运行的最多进程数目。对于普通的个人用户来说,32768 足够多,但是对于大型服务器来说,32768 可能就远远不够了,这时可以修改 pid_max 解决这一问题。' u8 S! u8 Y0 G' D
# M, D& I* A3 X! H
进程的状态
4 j& k+ _7 E3 f9 t
现在知道了Linux内核是如何描述和记录进程资源,以及如何区分不同进程的了。那么进程有哪些状态呢?读者应该注意到 task_struct 结构体的第一个成员 state 了,它就是用于记录进程状态的。进程的状态在C语言源代码中是使用几个宏定义的:
0 }1 s0 d! S* g

; V# g$ u, F  e  U
微信图片_20200331122911.jpg
进程的状态在C语言源代码中是使用几个宏定义的5 f  N$ a. n9 o4 P2 J* P! H

4 T' j( a. W; t8 ]  I
Linux 系统中的进程必定处于这 5 种状态之一。从上到下,分别表示进程处于:
9 D9 B: f/ h/ h4 f' G% ~& n' z2 S; K% b; N0 i$ y
) u' i9 Y  X( A) O
  • 正在运行或者准备运行
  • 正在睡眠,但是可中断,接收到信号会被提前唤醒
  • 正在睡眠,并且不可中断,也即即使接收到信号也不会被唤醒
  • 被其他进程跟踪中
  • 停止运行
    4 y* Q" [5 b  A$ V4 k. m
% J, D- q# z# L9 B
现在就明白有时无法通过 kill 命令杀死 D 状态的进程了,这是因为这些进程处于不响应信号的状态,kill 命令本质上是发送 SIGKILL 信号,自然无法杀死该进程。
8 t4 H3 y4 \6 d" R) J/ @

0 b: F. Z7 y8 e5 }& B8 B5 k
父进程和子进程
$ u8 w/ H, V- a5 a9 F
进程的父进程和子进程也属于进程的资源,因此也被记录在 task_struct 结构体中,请看相关C语言代码:5 \- W  w$ M0 z5 \

3 }, j, \  f+ O5 k2 c7 A+ \
微信图片_20200331122914.jpg 访问当前进程的父进程和子进程是方便的
. U- [6 I: D7 e8 C" v& w% N" D1 |' T5 m" \) Y) a
所以要访问当前进程的父进程和子进程是方便的,例如:5 F+ x3 V6 C+ X. h" I

  1. 5 c* s4 H. v: @* C  Y0 A
  2. struct task_struct *p = current->parent;6 \" x. }1 J% g3 K* O( c9 G
  3. struct task_stuck *c = current->children;
复制代码

) r2 y- E6 E8 T# g# p  H/ W稍稍思考一下,应该能够发现进程结构体 task_struct 中的 parent 指针和 children 指针其实构成了一条链表,通过这样的链表,我们能够轻易的访问进程的父进程,祖父进程…, 以及子进程,孙进程… 等。不过应该明白,对于拥有大量进程的系统来说,重复遍历所有进程的开销是很大的。
; H0 k! G$ ~+ d) y  r

3 Q" l1 c. S7 ?7 v3 y小结

0 V8 x& T3 j* N' M6 M
本节先讨论了Linux内核如何记录和描述进程资源,可以看出,内核管理进程其实就是管理 task_struct 结构体。接着,通过C语言源代码查看了内核如何访问 task_struct 结构体,以及如何区分进程,最后我们一起还讨论了进程的状态和家族树,可见,Linux内核源代码也并不是神秘到不可理解。
! ]* a$ J% ?. y# }% G$ \$ |% H
赞 收藏 评论0 发布时间:2020-3-31 12:30

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版