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

介绍一个进程在内核眼里的来龙去脉

[复制链接]
gaosmile 发布时间:2020-10-5 19:52
本篇算是进程管理的的揭幕篇,简单介绍一个进程在内核眼里的来龙去脉,为接下来的进程创建,进程调度,进程管理等篇章做好学习准备。
1 ]' A; I( ^3 p
从程序到进程再到内核
啥是程序,啥是进程,一张图可以给我们解释:
  Y/ E- a; h( `) u# o% g
微信图片_20201005195032.png
% ]! U( G7 P. [: r
我们转换镜头,站在内核OS的视角看什么是程序,什么是进程。ELF可执行文件送给内核后,OS是如何看待它的呢?换句话讲,内核OS眼里只有进程:
7 s7 y3 D, `1 ~/ i9 r+ e
微信图片_20201005195036.jpg

* l. N% F5 }% H- F6 a: K
% \* H) ]8 c1 g! v0 t% R

6 P7 G, k8 l0 {: L4 P
通过 top 命令我们可以看到 linux 的各种进程(即上右图)。
# U/ p: B$ w6 [5 \3 b1 _

' f! z3 q8 t# f9 a7 ^8 d5 f5 O
内核通过 task_struct 描述进程
用命令 pstree 可以让内核以树形的结构把进程之间的关系列出来,如下图:& }8 }( W9 w0 m' F6 Y* O6 A( v

6 u* ?6 n% E, [5 l
* s% \9 f6 z/ E
微信图片_20201005195039.png
2 v) u) q7 o! j5 F. @  O4 y

$ I* P( `9 ^# I/ c1 Y
这是进程在内核中的结构形式,那么内核是如何来以树形结构管理描述这些进程的呢?用来描述进程的数据结构,可以理解为进程的属性。比如进程的状态、进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,一起来看下今天的主角—— task_struct 结构体。
  1. : @+ N3 M! ^0 |
  2. struct task_struct {
    0 L1 W/ z0 E8 _$ {% ]& C
  3. volatile long state;  //说明了该进程是否可以执行,还是可中断等信息 -1 unrunnable, 0 runnable, >0 stopped
    0 E' R0 }* m2 [3 }' j. O
  4. unsigned long flags;  //Flage 是进程号,在调用fork()时给出( g* h, a6 q4 A7 \1 R- d( H& L4 D# S
  5. int sigpending;    //进程上是否有待处理的信号2 ]% p3 F* o3 {2 N
  6. mm_segment_t addr_limit; //进程地址空间,区分内核进程与普通进程在内存存放的位置不同
    . a( d  j! C3 x' c; `; r
  7.                         //0-0xBFFFFFFF for user-thead
    ; i9 \$ A; n, T8 {, m  A  k
  8.                         //0-0xFFFFFFFF for kernel-thread
    ; \% H9 Z; h8 g6 N  L# c
  9. //调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度: b/ v8 d, w3 {" O* [
  10. volatile long need_resched;4 ]! U* Y) y; F0 q7 ]! H
  11. int lock_depth;  //锁深度7 K( a+ L* b$ x9 s
  12. long nice;       //进程的基本时间片
    1 V" ?  V/ d) q/ a$ D' P9 g- u
  13. //进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER7 }7 Q! m0 a! j+ Q, v$ U$ b+ m
  14. unsigned long policy;2 ?; v  \+ Z' q& g% ?' b! @
  15. struct mm_struct *mm; //进程内存管理信息7 g/ d. n% C2 K- n4 b, r
  16. int processor;
    # v5 \3 Q, g/ F* w+ f( j8 l9 m6 R, O/ E
  17. //若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新, p- ^  H! f8 \  \
  18. unsigned long cpus_runnable, cpus_allowed;/ x$ ^# F( r" ]
  19. struct list_head run_list; //指向运行队列的指针
    % n' @: a& |2 W5 ~2 G0 x. n
  20. unsigned long sleep_time;  //进程的睡眠时间/ F. S  J8 [3 M* c: m2 s
  21. //用于将系统中所有的进程连成一个双向循环链表, 其根是init_task3 }5 ^( x9 ^! l: }. ?. \
  22. struct task_struct *next_task, *prev_task;
    2 w5 e2 {) Y8 v) |( n$ p8 c
  23. struct mm_struct *active_mm;; N7 N4 v; B! v' [9 v
  24. struct list_head local_pages;       //指向本地页面      9 }+ L6 @% l9 M
  25. unsigned int allocation_order, nr_local_pages;
    % q! F! z& p( G; W
  26. struct linux_binfmt *binfmt;  //进程所运行的可执行文件的格式+ {8 l7 t* w7 F: C
  27. int exit_code, exit_signal;! B- t1 V5 H6 h: p4 E% X
  28. int pdeath_signal;     //父进程终止时向子进程发送的信号- B) D4 q" P7 ]$ {/ h
  29. unsigned long personality;
    ; ~7 W7 D. c, T/ F- P
  30. //Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
    + K8 o2 I1 ?$ d9 Q5 b& h! J3 x
  31. int did_exec:1;
    : R+ S7 C! Q( j% A& q7 c
  32. pid_t pid;    //进程标识符,用来代表一个进程  K( w5 \6 V" [- e4 ?
  33. pid_t pgrp;   //进程组标识,表示进程所属的进程组
    + p6 }4 j# Q# u
  34. pid_t tty_old_pgrp;  //进程控制终端所在的组标识
    6 ]% W# r6 |( _  K0 K2 F( P
  35. pid_t session;  //进程的会话标识$ S! z+ v' U5 q# c$ h0 I1 c4 |5 u
  36. pid_t tgid;
    . |' ^1 T+ |8 i  ~& L
  37. int leader;     //表示进程是否为会话主管' l  V5 Z7 k2 J3 `1 A
  38. struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
    ) g6 ]- t* A9 U# ]3 n0 S5 |' S
  39. struct list_head thread_group;   //线程链表
    5 V# v8 n4 j9 t3 ]8 m
  40. struct task_struct *pidhash_next; //用于将进程链入HASH表
    ; e/ `* h+ l, Z4 x5 M! L
  41. struct task_struct **pidhash_pprev;
    . J9 ?. P! B6 n& }% w% t
  42. wait_queue_head_t wait_chldexit;  //供wait4()使用  I3 \5 _: K6 a1 ]. A/ \$ j' ?
  43. struct completion *vfork_done;  //供vfork() 使用/ y- X; h( d! h7 D* u& m
  44. unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
    8 N2 C; V* W2 ?3 M  ~+ J) p
  45. struct timer_list real_timer;   //指向实时定时器的指针5 r- ~! N3 J/ R* \
  46. struct tms times;      //记录进程消耗的时间
    9 Y5 O9 ~, a$ M
  47. unsigned long start_time;  //进程创建的时间
    7 R  O! ~; g# ~$ @% X8 |; I( t
  48. //记录进程在每个CPU上所消耗的用户态时间和核心态时间( Y' I7 R- h6 X5 o1 n+ G
  49. long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS]; # Z0 W4 L1 w) K1 e% Y" |! _4 k0 \
  50. int swappable:1; //表示进程的虚拟地址空间是否允许换出/ m+ X# p. b9 ]- H9 g: C
  51. int ngroups; //记录进程在多少个用户组中
    % y5 U3 A/ @* N" w, m1 L) L/ x
  52. gid_t groups[NGROUPS]; //记录进程所在的组: K9 r- j1 y9 P  s9 d# h, N
  53. //进程的权能,分别是有效位集合,继承位集合,允许位集合$ b8 D* x+ u( Y# {- }- g/ R
  54. kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
    3 U# V: \2 @* e( T+ ?9 c
  55. int keep_capabilities:1;, `9 K/ K2 C, |2 g- h9 Z
  56. struct user_struct *user;* z* @5 d& ?) t( q- }. T3 g, j/ {
  57. struct rlimit rlim[RLIM_NLIMITS];  //与进程相关的资源限制信息
    0 ]; M7 H( q- G& ?
  58. unsigned short used_math;   //是否使用FPU
    ) n  a5 Q  O1 m4 `& d
  59. char comm[16];   //进程正在运行的可执行文件名! q9 G3 l5 B+ y* I& f
  60. //文件系统信息
    : K" ~8 S; ~* c% x
  61. int link_count, total_link_count;
    ( j+ [3 o0 Q  X9 ?8 L
  62. //NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空3 j3 z( X; T5 m8 F* e5 @9 ~( Q
  63. struct tty_struct *tty;
    4 J1 p  h9 ^$ W/ a$ Y$ |
  64. unsigned int locks;
    % B1 p1 a8 ]+ s6 w$ \1 D5 z
  65. //进程间通信信息
    . [% G/ }/ x( a% l% b/ g  ]
  66. struct sem_undo *semundo;  //进程在信号灯上的所有undo操作
    ! ^9 g8 \/ O9 H
  67. struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
    # T8 n0 T: X2 _# O. m- F& v
  68. //进程的CPU状态,切换时,要保存到停止进程的task_struct中
      |  r0 U$ Y0 Z/ k- ^- J
  69. struct thread_struct thread;9 P" h* U  N& y7 Q9 k$ S  \
  70.   //文件系统信息
    . F5 _8 t" C" i( {0 x% U
  71. struct fs_struct *fs;
    ' z0 ?9 l6 s" I+ b8 Q6 U* G/ F
  72.   //打开文件信息" [7 b! l9 {  A4 U" a3 g, r
  73. struct files_struct *files;
    ( n/ q8 g& }9 J
  74.   //信号处理函数" z9 N( Z, C. c6 B7 A, O
  75. spinlock_t sigmask_lock;
    , S& }. V4 ^' w
  76. struct signal_struct *sig; //信号处理函数
    7 g* R3 A) n1 w1 e8 A) Z  K
  77. sigset_t blocked;  //进程当前要阻塞的信号,每个信号对应一位
    8 W* V  g; J2 S9 G2 P; z# C4 P
  78. struct sigpending pending;  //进程上是否有待处理的信号
    8 E0 x& |, }5 H" W; a8 V
  79. ......
    2 ?. D. W5 f& `: A
  80. };
复制代码
内核就是通过list_head链表把各个进程关系以树形结构管理起来的。& P$ O, R. _! `4 |. j
task_struct 结构体内容太多,这里只列出部分成员变量,感兴趣的读者可以去源码 include/linux/sched.h头文件查看。task_struct 中的主要信息分类:
6 `2 h8 W  s6 w7 V
1. 标示符:描述本进程的唯一标识符,用来区别其他进程。
2. 状态:任务状态,退出代码,退出信号等
3. 优先级:相对于其他进程的优先级
4. 程序计数器:程序中即将被执行的下一条指令的地址
5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
6. 上下文数据:进程执行时处理器的寄存器中的数据
7. I/O状态信息:包括显示的I/O请求,分配的进程I/O设备和进程使用的文件列表
8. 记账信息:可能包括处理器时间总和,使用的时钟总和,时间限制,记帐号等
3 e5 J1 x" l. w! b$ ^7 f' a
这些信息每类都可以单独开个章节去讲解,这里先简单描述下任务状态的转换,以后篇章再深入介绍各个分类。

) j5 b% q* Q' B" K$ T
任务状态转换
上面可以看到变量定义后面的注释,它说明变量内容<0是不运行的,=0是运行状态,>0是停止状态。下面我们介绍几个常用的取值:

  y9 e0 r" a$ @$ d

6 Q6 ^  P* i* N, L$ B
微信图片_20201005195042.png

# ~, M' i9 B6 d. H4 _+ g
5 u% T0 s6 J/ a- T+ w8 E4 U6 {0 R! G
任务状态在不同情况下的状态转换如下:4 r5 [+ Z$ ?: E
% }) \& y0 {+ d7 O# @5 N

  N8 O. Y7 v" m+ V; j: I
微信图片_20201005195045.png
9 u) V0 D6 s; `. x9 _; b
4 i/ c3 Y2 ~& ]/ S
内核如何存放 task_struct
我们知道一个进程所占的栈空间有用户栈和内核栈,用户栈的分布方式见之前的文章《C语言在ARM中函数调用时,栈是如何变化的?》。那么内核栈是如何存放进程描述符的呢?6 g1 f8 ]" u" d0 i
内核栈对于应用程序是不可见的,因为它位于内核空间中。在应用程序执行过程中,如果发生异常、中断或系统调用的话,应用程序会被暂停,系统进入内核态,转去执行异常响应等代码,这个时候所使用的栈就是内核栈。
2 H% r+ t+ j- Q7 }4 y
% U8 i. u; o5 \- d5 D$ B0 x
微信图片_20201005195049.png

. ]$ D/ t  u& w8 ?
' l& X" k1 w6 ?. S
为了节省空间,linux把内核栈和紧挨着task_struct的thread_info放在一起,如上所示,thread_info中存放了进程/线程(内核不大区分进程与线程)的一些数据,其中包括指向task_struct结构的指针。数组stack即内核栈,stack占据8K/4K(依配置不同)空间。8 r9 e& W9 P; k9 j
  1. , x$ y3 `' O/ M0 |9 s' G7 S
  2. union thread_union {1 b+ C( M6 k2 g2 N7 }/ n0 s
  3. #ifndef CONFIG_THREAD_INFO_IN_TASK
    1 y7 Z' {4 |( p+ `8 ~! c: ]! d
  4.   struct thread_info thread_info;0 ?( x; S7 c7 j' `
  5. #endif
    ( v3 x  Z* D- U! J, `+ g
  6.   unsigned long stack[THREAD_SIZE/sizeof(long)];
    , V8 ~- N1 f# W* ]0 X1 ~6 K  q7 ?
  7. };
复制代码

5 y$ p% |% ^9 X1 K. o

5 H! I. J: h* {
最后
到这里应该已经了解了一个程序如何转换为进程,内核如何描述进程,又如何存储进程,当然还有很多关于进程的描述没有介绍,比如进程的调度,优先级,内存管理等等,这些会在以后的文章里单独分开详细介绍。但这些所有的信息都存储在今天的主角里——task_struct。, I/ l# s8 a) I: H* S

& W. Q% y- V% Y% f
赞 收藏 评论0 发布时间:2020-10-5 19:52

举报

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