这篇文章是研习supervisor源代码的副产品,主要理解supervisor的驱动原理和操作系统对进程管理的一些基本知识。

理解什么是进程

百度百科定义如下:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

进程是操作系统对资源的一个抽象。
操作系统是计算机的管家,掌控着计算机内部的一切资源的分配、调度、回收。
而进程可以理解为计算机资源调度的一个基本单元。用播放电影来举个形象的例子,进程就是电影一针一针的画面,而程序就是播放这部电影的一个统称,电影没开始播放前,得准备放映机(cpu),电影胶片(内存)。放音机齿轮转动就好比cpu的时钟脉冲,每秒扫过N个胶片(进程被调度),图像被光打到幕布上(计算机的输出设备)。

换一个角度看操作系统的进程,linux系统开机启动到系统启动完毕,从进程的角度看就是一生二、二生三、三生万物的感觉。我们面对的就是进程的一课大树,它的源头就是进程号为1(名叫systemd)的进程,systemd是Linux下的一种负责init的进程,那字母d就是daemon的标记,又比如apache的http服务,它的进程也有个字母d,httpd。可以运行pstree命令来体验一下这种子子孙孙无穷匮也。ps和systemctl命令也有类似效果。

1
2
3
pstree
ps axjf
systemctl status

进程运行环境

在Linux中,用户程序装入系统形成一个进程的实质是操作系统为用户程序提供一个完整的运行环境。进程的运行环境是由它的程序代码和程序运行所需要的数据结构以及硬件环境组成的。进程的运行环境主要包括:

  • 1.进程空间(内存)中的代码和数据、各种数据结构、进程堆栈和共享内存区等。

  • 2.环境变量:提供进程运行所需的环境信息。

  • 3.系统数据:进程空间中的对进程进行管理和控制所需的信息,包括进程任务结构体以及内核堆栈等。

  • 4.进程访问设备或者文件时的权限。

  • 5.各种硬件寄存器。

  • 6.地址转换信息。

Linux下的进程模型

从PCB中了解进程模型

一个进程在操作系统中执行,有两种状态:内核态和用户态。如果当前运行的是用户程序(用户代码),那么对应进程就处于用户),如果出现系统调用或者发生中断,那么对应进程就处于内核模式(核心态)。

为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块 PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个 PCB,在创建进程时,建立 PCB,伴随进程运行的全过程,直到进程撤消而撤消。PCB 记录了操作系统所需的,用于描述进程的当前情况以及控制进程运行的全部信息(如打开的文件、挂起的信号、进程状态、地址空间等等)。在linux中这个PCB是一个叫做task_struct的结构体。

task_struct 在linux中,每一个进程都有一个进程描述符,这个”进程描述符”是一个结构体名字叫做task_struct,在task_struct里面保存了许多关于进程控制的信息。

task_struct是Linux内核的一种数据结构,它会被装载到RAM里并包含进程的信息。

每个进程都把它的信息放在task_struct这个数据结构里面,而task_struct包含以下内容:

进程状态(State)

表示进程的状态, 在进程执行的时候,它会有一个状态,这个状态对于进程来说是很重要的一个属性。包括:可运行、可中断的等待状态、不可中断的等待状态、将死、暂停、换入/换出。

进程标识符

每个进程都有进程标识符、用户标识符、组标识符,进程标识符对于每一个进程来说都是唯一的。内核通过进程标识符来对不同的进程进行识别,一般来说,行创建的进程都是在前一个进程的基础上PID加上1作为本进程的PID。为了linux平台兼容性,PID一般最大为32767。

进程内核栈。

stack用来维护分配给进程的内核栈,内核栈的意义在于,进程task_struct所占的内存是由内核动态分配的,确切的说就是内核根本不给task_struct分配内存,只给内核栈分配8KB内存,并且一部分会提供给task_struct使用。

标记。

flag,用来反映一个进程的状态信息,但不是运行状态,用于内核识别进程当前的状态,flags的取值可以为

可使用的标记 功能
PF_FORKNOEXEC 进程刚创建,但还没执行。
PF_SUPERPRIV 超级用户特权。
PF_DUMPCORE 关于核心。
PF_SIGNALED 进程被信号(signal)杀出。
PF_EXITING 进程开始关闭。

表示进程亲属关系的成员

考虑到进程的派生,所以进程之间会存在父进程和子进程这样的关系,当然,对于同一个父进程派生出来的进程,他们的关系当然是兄弟进程了。

成员 功能
real_parent 指向父进程的指针,如果父进程不存在了,则指向PID为1的进程
parent 指向父进程的,值与real——parent相同,需要向它的父进程发送信号
children 表示链表的头部,链表中的所有元素都是它的子进程
sibling 用于当前进程插入兄弟链表当中
group_leader 指向进程组的领头进程

ptrace系统调用

ptrace是一种提供父进程控制子进程运行,并且可以检查和改变它的核心image。

进程调度。

调度进程是一个复杂过程,所以它有比较复杂的结构。

  • 调度优先级结构
    利用这部分信息决定系统当中的那个进程最应该运行,并且结合进程的状态信息保证系统运作高效。
成员 功能
static_prio 保存静态优先级,可以通过nice系统进行修改
rt_priority 保存实时优先级
normal_prio 保存静态优先级和调度策略
prio 保存动态优先级
  • 调度控制结构
成员 功能
policy 调度策略
sched_class 调度类
se 普通进程的一个调用的实体,每一个进程都有其中之一的实体
rt 实时进程的调用实体,每个进程都有其中之一的实体
cpus_allowed 用于控制进程可以在处理器的哪里运行
  • 具体的调度策略结构

    种类 功能
    SCHED_NORMAL 用于普通进程
    SCHED_BATCH 普通进程策略的分化版本,采用分时策略
    SCHED_IDLE 优先级最低,系统空闲时才跑这类进程
    SCHED_FIFO 先入先出的调度算法
    SCHED_RR 实时调度算法,采用时间片,相同优先级的任务当用完时间片就会放到队列的尾部,保证公平性,同时,高优先级的任务抢占低优先级的任务。
    SCHED_DEADLINE 新支持的实时调度策略,正对突发性计算
  • 调度类结构

    调度类 功能
    idle_sched_class 每一个cpu的第一个pid=0的线程,是一个静态的线程
    stop_sched_class 优先级最高的线程,会中断所有其他的线程,而且不会被其他任务打断
    rt_sched_slass 作用在实时线程
    fair_sched_class 作用的一般线程

它们的优先级顺序为Stop>rt>fair>idle

进程的地址空间

包括进程所拥有的用户空间的内存描述符,执行运行使用的内存描述符。

成员 功能
mm 进程所拥有的用户空间的内存描述符。内核线程切记是没有地址空间的。
active_mm 指向进程运行时使用的内存描述符,对于普通的进程来说,mm和active_mm是一样的,但是内核线程是没有进程地址空间的,所以内核线程的mm是空的,所以需要初始化内核线程的active_mm

判定标志

用于判断进程的标志信息。

成员 功能
exit_state 进程终止的状态
exit_code 设置进程的终止代号
exit_signal 设置为-1的时候表示是某个线程组当中的一员,只有当线程组的最后一个成员终止时,才会产生型号给父进程
pdeath_signal 用来判断父进程终止时的信号

时间和定时器信息(Times and Timers)

关于时间,一个进程从创建到终止叫做该进程的生存期,进程在其生存期内使用CPU时间,内核都需要进行记录,进程耗费的时间分为两部分,一部分是用户模式下耗费的时间,一部分是在系统模式下耗费的时间。

成员 属性
utime/stime 用于记录进程在用户状态/内核态下所经过的定时器
prev_utime/prev_stime 记录当前的运行时间
utimescaled/stimescaled 分别记录进程在用户态和内核态的运行的时间
gtime 记录虚拟机的运行时间
nvcsw/nicsw 是自愿/非自愿上下文切换计数
start_time/real_start_time 进程创建时间,real还包括了进程睡眠时间

进程的定时器,一共是三种定时器。

定时器类型 解释 更新时刻
ITIMER_REAL 实时定时器 实时更新,不在乎进程是否运行
ITIMER_VIRTUAL 虚拟定时器 只在进程运行用户态时更新
ITIMER_PROF 概况定时器 进程运行于用户态和系统态进行更新
cputime_expires 用来统计进程或进程组被跟踪的处理器时间 三个成员对应的是下面的cpu_times[3]的三个链表

信号处理。

成员 功能
signal 指向进程信号描述符
sighand 指向进程信号处理程序描述符
blocked 表示被阻塞信号的掩码
pending 存放私有挂起信号的数据结构
sas_ss_sp 信号处理程序备用堆栈的地址

文件系统信息

进程可以用来打开和关闭文件,文件属于系统资源,task_struct有两个来描述文件资源,他们会描述两个VFS索引节点,两个节点分别是root和pwd,分别指向根目录和当前的工作目录。

成员 功能
struct fs_struct *fs 进程可执行镜像所在的文件系统
struct files_struct *files 进程当前打开的文件

进程状态模型

这个网上说的很多,
直接盗图了,基于task_struct
iamge
基于开发者视角的盗图
iamge

进程的内存模型

image
进程地址空间分为内核空间和用户空间  

因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

  • A.正文段。这是由cpu执行的机器指令部分。通常,正文段是可共享的,所以即使是经常执行的程序(如文本编辑程序、C编译程序、shell等)在存储器中也只需要有一个副本,另外,正文段常常是只读的,以防止程序由于意外事故而修改器自身的指令。

  • B.初始化数据段。通常将此段称为数据段,它包含了程序中需赋初值的变量。例如,C程序中任何函数之外的说明:

  • C.非初始化数据段。通常将此段称为bss段,这一名称来源于早期汇编程序的一个操作,意思是”block started by symbol”,在程序开始执行之前,内核将此段初始化为0。

  • D.栈。自动变量以及每次函数调用时所需保存的信息都存放在此段中。每次函数调用时,其返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。然后,新被调用的函数在栈上为其自动和临时变量分配存储空间。通过以这种方式使用栈,C函数可以递归调用。

  • E.堆。通常在堆中进行动态存储分配。由于历史上形成的惯例,堆位于非初始化数据段顶和栈底之间。

进程之间的关系模型

session(也称为会话)

为什么会有会话这个概念?答案是显而易见的,因为人机需要交互,而交互的识别方式就是用session。
一个或多个进程组可以构成一个会话 (session)。

一个会话中有一个领导进程(session leader)。会话领导进程的PID是会话的SID(session ID)。会话中的每个进程组称为一个工作(job)。会话可以有一个进程组成为会话的前台工作(foreground),而其他的进程组是后台工作(background)。每个会话可以连接一个控制终端(也可以不连接)。

会话的意义在于将多个工作囊括在一个终端,并取其中的一个工作作为前台,来直接接收该终端的输入输出以及终端信号。 其他工作在后台运行。当我们打开多个终端窗口时,实际上就创建了多个终端会话。每个会话都会有自己的前台工作和后台工作。工作组和会话机制在Linux的许多地方应用。
举例:

当用xshell连接到主机时,即创建了一个session。shell即是session的leader进程,随后shell里面运行的进程都将属于这个session,当shell退出后,该会话中的进程将退出。

shell里面启动一个进程后,一般都会将该进程放到一个单独的进程组,然后该进程fork的所有进程都会属于该进程组,比如多进程的程序,它的所有进程都会属于同一个进程组,当在shell里面按下CTRL+C时,该程序的所有进程都会收到SIGINT而退出。

父进程、子进程

每个进程都有一个创建自己的进程,每个进程的父进程号属性反应了系统上所有进程间的树状关系。每个进程的父进程又有自己的父进程,以此类推,回溯到1号进程–init进程,即所有进程的始祖。

如果子进程的父进程终止,则子进程也会被操作系统回收。但是被结束的父进程不是会话组的组长,则子进程就会变成“孤儿”,init进程随即将收养该进程。

进程生进程有fork操作,子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式),如果父子进程一直对这个页面是同一个页面,知道其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用,即进程的写时复制的机制。所以,粗略的可以认为子进程继承了父进程的栈空间、页表。

进程组

所有进程都是属于一个进程组的,而进程组又属于一个会话。一个会话中有一个领导进程(session leader)。会话领导进程的PID是会话的SID(session ID)。

进程组是一组进程的集合,每个进程都属于一个进程组,每个进程组有一个进程组leader进程,进程组的ID(PGID)等于leader进程的ID。对大部分进程来说,它自己就是进程组的leader,并且进程组里面就只有它自己一个进程。
一个或多个进程可以构成一个进程集合,叫做进程组。通常,它们与同一个作业相关联,可以接受来自同一终端的各种信号。每个进程除了有一个进程ID之外,还属于一个进程组。每个进程组都有一个唯一的进程组ID。且每个进程组都有一个组长进程,组长进程的ID就是进程组的ID。通常,一个进程组的组长进程就是创建的第一个进程。或者可以这么说,创建一个进程,就创建了一个进程组,只是现在这一个进程组里边现在只有一个进程(组长进程)。组长进程还可以创建改组中的其他进程。
一个进程组,只要有任意一个进程存在,该组就是存在的,与组长是否存在无关。

进程组的组长是不可以创建会话的,否者返回一个错误;为了保证该规则,通常是通过进程组中的一个进程fork()一个子进程,exit()父进程,此时子进程继承了父进程所在组的pgid,确保了不是进程组长。

可以将一个进程从一个进程组中分离出来,创建一个会话;自然而然就是会话的首领,该进程是会话的唯一一个进程,同时也是进程组组长,身兼多职;

可以通过将信号发送给一个进程组,使进程组中的所有进程都收到该信号。

进程间的通信

信号,操作系统与进程通信的一种机制。
管道,也是一种常见的通信机制。

进程与文件的关系

一句话,读写执行。

但是,要知道的不止这些。 Linux一切皆文件的思想,所有不同种类的类型都被抽象成文件,如:普通文件、目录、字符设备、块设备、套接字等。这就决定了进程绝对跟文件的关系不同一般。

当一个文件被进程打开,就会创建一个文件描述符。这时候,文件的路径就成为了寻址系统,文件描述符成为了字节流的接口。

相对于普通文件这类真实存在于文件系统中的文件,tcp socket、unix domain socket等这些存在于内存中的特殊文件在被进程打开的时候,也会创建文件描述符。所以”一切皆文件”更准确的描述应该是”一切皆文件描述符”。文件描述符(FD:file descriptors)也可以说是文件句柄,当某个进程打开文件时,内核返回相应的文件描述符,程序为了处理该文件必须引用此描述符。文件描述符是一个正整数,用以标明每一个被进程所打开的文件和socket。最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应,后面打开的文件依此类推对应3、4…… 。

linux系统对每个用户、进程、或整个系统的可打开文件描述符数量都有一个限制,一般默认为1024。当我们在系统或应用的日志中碰到“too many open files”错误记录时,这个其实不是说打开的文件过多,而是打开的文件描述符数量已达到了限制,这时就需要增加文件描述符的数量限制了。

内核中task_struct的代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
struct task_struct 
{
/*
1. state: 进程执行时,它会根据具体情况改变状态。进程状态是进程调度和对换的依据。Linux中的进程主要有如下状态:
1) TASK_RUNNING: 可运行
处于这种状态的进程,只有两种状态:
1.1) 正在运行
正在运行的进程就是当前进程(由current所指向的进程)
1.2) 正准备运行
准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源,系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行

2) TASK_INTERRUPTIBLE: 可中断的等待状态,是针对等待某事件或其他资源的睡眠进程设置的,在内核发送信号给该进程表明事件已经发生时,进程状态变为TASK_RUNNING,它只要调度器选中该进程即可恢复执行

3) TASK_UNINTERRUPTIBLE: 不可中断的等待状态
处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中,处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等
     它们不能由外部信号唤醒,只能由内核亲自唤醒

4) TASK_ZOMBIE: 僵死
进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。

5) TASK_STOPPED: 暂停
此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态
    
     6) TASK_TRACED
     从本质上来说,这属于TASK_STOPPED状态,用于从停止的进程中,将当前被调试的进程与常规的进程区分开来
      
     7) TASK_DEAD
     父进程wait系统调用发出后,当子进程退出时,父进程负责回收子进程的全部资源,子进程进入TASK_DEAD状态

8) TASK_SWAPPING: 换入/换出
*/
volatile long state;

/*
2. stack
进程内核栈,进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈
*/
void *stack;

/*
3. usage
进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态
*/
atomic_t usage;

/*
4. flags
flags是进程当前的状态标志(注意和运行状态区分)
1) #define PF_ALIGNWARN 0x00000001: 显示内存地址未对齐警告
2) #define PF_PTRACED 0x00000010: 标识是否是否调用了ptrace
3) #define PF_TRACESYS 0x00000020: 跟踪系统调用
4) #define PF_FORKNOEXEC 0x00000040: 已经完成fork,但还没有调用exec
5) #define PF_SUPERPRIV 0x00000100: 使用超级用户(root)权限
6) #define PF_DUMPCORE 0x00000200: dumped core
7) #define PF_SIGNALED 0x00000400: 此进程由于其他进程发送相关信号而被杀死
8) #define PF_STARTING 0x00000002: 当前进程正在被创建
9) #define PF_EXITING 0x00000004: 当前进程正在关闭
10) #define PF_USEDFPU 0x00100000: Process used the FPU this quantum(SMP only)
#define PF_DTRACE 0x00200000: delayed trace (used on m68k)
*/
unsigned int flags;

/*
5. ptrace
ptrace系统调用,成员ptrace被设置为0时表示不需要被跟踪,它的可能取值如下:
linux-2.6.38.8/include/linux/ptrace.h
1) #define PT_PTRACED 0x00000001
2) #define PT_DTRACE 0x00000002: delayed trace (used on m68k, i386)
3) #define PT_TRACESYSGOOD 0x00000004
4) #define PT_PTRACE_CAP 0x00000008: ptracer can follow suid-exec
5) #define PT_TRACE_FORK 0x00000010
6) #define PT_TRACE_VFORK 0x00000020
7) #define PT_TRACE_CLONE 0x00000040
8) #define PT_TRACE_EXEC 0x00000080
9) #define PT_TRACE_VFORK_DONE 0x00000100
10) #define PT_TRACE_EXIT 0x00000200
*/
unsigned int ptrace;
unsigned long ptrace_message;
siginfo_t *last_siginfo;

/*
6. lock_depth
用于表示获取大内核锁的次数,如果进程未获得过锁,则置为-1
*/
int lock_depth;

/*
7. oncpu
在SMP上帮助实现无加锁的进程切换(unlocked context switches)
*/
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif

/*
8. 进程调度
1) prio: 调度器考虑的优先级保存在prio,由于在某些情况下内核需要暂时提高进程的优先级,因此需要第三个成员来表示(除了static_prio、normal_prio之外),由于这些改变不是持久的,因此静态(static_prio)和普通(normal_prio)优先级不受影响
2) static_prio: 用于保存进程的"静态优先级",静态优先级是进程"启动"时分配的优先级,它可以用nice、sched_setscheduler系统调用修改,否则在进程运行期间会一直保持恒定
3) normal_prio: 表示基于进程的"静态优先级"和"调度策略"计算出的优先级,因此,即使普通进程和实时进程具有相同的静态优先级(static_prio),其普通优先级(normal_prio)也是不同的。进程分支时(fork),新创建的子进程会集成普通优先级
*/
int prio, static_prio, normal_prio;
/*
4) rt_priority: 表示实时进程的优先级,需要明白的是,"实时进程优先级"和"普通进程优先级"有两个独立的范畴,实时进程即使是最低优先级也高于普通进程,最低的实时优先级为0,最高的优先级为99,值越大,表明优先级越高
*/
unsigned int rt_priority;
/*
5) sched_class: 该进程所属的调度类,目前内核中有实现以下四种:
5.1) static const struct sched_class fair_sched_class;
5.2) static const struct sched_class rt_sched_class;
5.3) static const struct sched_class idle_sched_class;
5.4) static const struct sched_class stop_sched_class;
*/
const struct sched_class *sched_class;
/*
6) se: 用于普通进程的调用实体
  调度器不限于调度进程,还可以处理更大的实体,这可以实现"组调度",可用的CPU时间可以首先在一般的进程组(例如所有进程可以按所有者分组)之间分配,接下来分配的时间在组内再次分配
  这种一般性要求调度器不直接操作进程,而是处理"可调度实体",一个实体有sched_entity的一个实例标识
  在最简单的情况下,调度在各个进程上执行,由于调度器设计为处理可调度的实体,在调度器看来各个进程也必须也像这样的实体,因此se在task_struct中内嵌了一个sched_entity实例,调度器可据此操作各个task_struct
*/
struct sched_entity se;
/*
7) rt: 用于实时进程的调用实体
*/
struct sched_rt_entity rt;

#ifdef CONFIG_PREEMPT_NOTIFIERS
/*
9. preempt_notifier
preempt_notifiers结构体链表
*/
struct hlist_head preempt_notifiers;
#endif

/*
10. fpu_counter
FPU使用计数
*/
unsigned char fpu_counter;

#ifdef CONFIG_BLK_DEV_IO_TRACE
/*
11. btrace_seq
blktrace是一个针对Linux内核中块设备I/O层的跟踪工具
*/
unsigned int btrace_seq;
#endif

/*
12. policy
policy表示进程的调度策略,目前主要有以下五种:
1) #define SCHED_NORMAL 0: 用于普通进程,它们通过完全公平调度器来处理
2) #define SCHED_FIFO 1: 先来先服务调度,由实时调度类处理
3) #define SCHED_RR 2: 时间片轮转调度,由实时调度类处理
4) #define SCHED_BATCH 3: 用于非交互、CPU使用密集的批处理进程,通过完全公平调度器来处理,调度决策对此类进程给与"冷处理",它们绝不会抢占CFS调度器处理的另一个进程,因此不会干扰交互式进程,如果不打算用nice降低进程的静态优先级,同时又不希望该进程影响系统的交互性,最适合用该调度策略
5) #define SCHED_IDLE 5: 可用于次要的进程,其相对权重总是最小的,也通过完全公平调度器来处理。要注意的是,SCHED_IDLE不负责调度空闲进程,空闲进程由内核提供单独的机制来处理
只有root用户能通过sched_setscheduler()系统调用来改变调度策略
*/
unsigned int policy;

/*
13. cpus_allowed
cpus_allowed是一个位域,在多处理器系统上使用,用于控制进程可以在哪里处理器上运行
*/
cpumask_t cpus_allowed;

/*
14. RCU同步原语
*/
#ifdef CONFIG_TREE_PREEMPT_RCU
int rcu_read_lock_nesting;
char rcu_read_unlock_special;
struct rcu_node *rcu_blocked_node;
struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
/*
15. sched_info
用于调度器统计进程的运行信息
*/
struct sched_info sched_info;
#endif

/*
16. tasks
通过list_head将当前进程的task_struct串联进内核的进程列表中,构建;linux进程链表
*/
struct list_head tasks;

/*
17. pushable_tasks
limit pushing to one attempt
*/
struct plist_node pushable_tasks;

/*
18. 进程地址空间
1) mm: 指向进程所拥有的内存描述符
2) active_mm: active_mm指向进程运行时所使用的内存描述符
对于普通进程而言,这两个指针变量的值相同。但是,内核线程不拥有任何内存描述符,所以它们的mm成员总是为NULL。当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的active_mm值
*/
struct mm_struct *mm, *active_mm;

/*
19. exit_state
进程退出状态码
*/
int exit_state;

/*
20. 判断标志
1) exit_code
exit_code用于设置进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)
2) exit_signal
exit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程
*/
int exit_code, exit_signal;
/*
3) pdeath_signal
pdeath_signal用于判断父进程终止时发送信号
*/
int pdeath_signal;
/*
4) personality用于处理不同的ABI,它的可能取值如下:
enum
{
PER_LINUX = 0x0000,
PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT,
PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS,
PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE,
PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS |
WHOLE_SECONDS | SHORT_INODE,
PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS,
PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE,
PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS,
PER_BSD = 0x0006,
PER_SUNOS = 0x0006 | STICKY_TIMEOUTS,
PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE,
PER_LINUX32 = 0x0008,
PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB,
PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,
PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,
PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,
PER_RISCOS = 0x000c,
PER_SOLARIS = 0x000d | STICKY_TIMEOUTS,
PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_OSF4 = 0x000f,
PER_HPUX = 0x0010,
PER_MASK = 0x00ff,
};
*/
unsigned int personality;
/*
5) did_exec
did_exec用于记录进程代码是否被execve()函数所执行
*/
unsigned did_exec:1;
/*
6) in_execve
in_execve用于通知LSM是否被do_execve()函数所调用
*/
unsigned in_execve:1;
/*
7) in_iowait
in_iowait用于判断是否进行iowait计数
*/
unsigned in_iowait:1;

/*
8) sched_reset_on_fork
sched_reset_on_fork用于判断是否恢复默认的优先级或调度策略
*/
unsigned sched_reset_on_fork:1;

/*
21. 进程标识符(PID)
在CONFIG_BASE_SMALL配置为0的情况下,PID的取值范围是0到32767,即系统中的进程数最大为32768个
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用
返回的是当前进程的tgid值而不是pid值。
*/
pid_t pid;
pid_t tgid;

#ifdef CONFIG_CC_STACKPROTECTOR
/*
22. stack_canary
防止内核堆栈溢出,在GCC编译内核时,需要加上-fstack-protector选项
*/
unsigned long stack_canary;
#endif

/*
23. 表示进程亲属关系的成员
1) real_parent: 指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程
2) parent: 指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同
*/
struct task_struct *real_parent;
struct task_struct *parent;
/*
3) children: 表示链表的头部,链表中的所有元素都是它的子进程(子进程链表)
4) sibling: 用于把当前进程插入到兄弟链表中(连接到父进程的子进程链表(兄弟链表))
5) group_leader: 指向其所在进程组的领头进程
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;

struct list_head ptraced;
struct list_head ptrace_entry;
struct bts_context *bts;

/*
24. pids
PID散列表和链表
*/
struct pid_link pids[PIDTYPE_MAX];
/*
25. thread_group
线程组中所有进程的链表
*/
struct list_head thread_group;

/*
26. do_fork函数
1) vfork_done
在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址
2) set_child_tid、clear_child_tid
如果copy_process函数的clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID,则会把child_tidptr参数的值分别复制到set_child_tid和clear_child_tid成员。这些标志说明必须改变子
进程用户态地址空间的child_tidptr所指向的变量的值。
*/
struct completion *vfork_done;
int __user *set_child_tid;
int __user *clear_child_tid;

/*
27. 记录进程的I/O计数(时间)
1) utime
用于记录进程在"用户态"下所经过的节拍数(定时器)
2) stime
用于记录进程在"内核态"下所经过的节拍数(定时器)
3) utimescaled
用于记录进程在"用户态"的运行时间,但它们以处理器的频率为刻度
4) stimescaled
用于记录进程在"内核态"的运行时间,但它们以处理器的频率为刻度
*/
cputime_t utime, stime, utimescaled, stimescaled;
/*
5) gtime
以节拍计数的虚拟机运行时间(guest time)
*/
cputime_t gtime;
/*
6) prev_utime、prev_stime是先前的运行时间
*/
cputime_t prev_utime, prev_stime;
/*
7) nvcsw
自愿(voluntary)上下文切换计数
8) nivcsw
非自愿(involuntary)上下文切换计数
*/
unsigned long nvcsw, nivcsw;
/*
9) start_time
进程创建时间
10) real_start_time
进程睡眠时间,还包含了进程睡眠时间,常用于/proc/pid/stat,
*/
struct timespec start_time;
struct timespec real_start_time;
/*
11) cputime_expires
用来统计进程或进程组被跟踪的处理器时间,其中的三个成员对应着cpu_timers[3]的三个链表
*/
struct task_cputime cputime_expires;
struct list_head cpu_timers[3];
#ifdef CONFIG_DETECT_HUNG_TASK
/*
12) last_switch_count
nvcsw和nivcsw的总和
*/
unsigned long last_switch_count;
#endif
struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
u64 acct_rss_mem1;
u64 acct_vm_mem1;
cputime_t acct_timexpd;
#endif

/*
28. 缺页统计
*/
unsigned long min_flt, maj_flt;

/*
29. 进程权能
*/
const struct cred *real_cred;
const struct cred *cred;
struct mutex cred_guard_mutex;
struct cred *replacement_session_keyring;

/*
30. comm[TASK_COMM_LEN]
相应的程序名
*/
char comm[TASK_COMM_LEN];

/*
31. 文件
1) fs
用来表示进程与文件系统的联系,包括当前目录和根目录
2) files
表示进程当前打开的文件
*/
int link_count, total_link_count;
struct fs_struct *fs;
struct files_struct *files;

#ifdef CONFIG_SYSVIPC
/*
32. sysvsem
进程通信(SYSVIPC)
*/
struct sysv_sem sysvsem;
#endif

/*
33. 处理器特有数据
*/
struct thread_struct thread;

/*
34. nsproxy
命名空间
*/
struct nsproxy *nsproxy;

/*
35. 信号处理
1) signal: 指向进程的信号描述符
2) sighand: 指向进程的信号处理程序描述符
*/
struct signal_struct *signal;
struct sighand_struct *sighand;
/*
3) blocked: 表示被阻塞信号的掩码
4) real_blocked: 表示临时掩码
*/
sigset_t blocked, real_blocked;
sigset_t saved_sigmask;
/*
5) pending: 存放私有挂起信号的数据结构
*/
struct sigpending pending;
/*
6) sas_ss_sp: 信号处理程序备用堆栈的地址
7) sas_ss_size: 表示堆栈的大小
*/
unsigned long sas_ss_sp;
size_t sas_ss_size;
/*
8) notifier
设备驱动程序常用notifier指向的函数来阻塞进程的某些信号
9) otifier_data
指的是notifier所指向的函数可能使用的数据。
10) otifier_mask
标识这些信号的位掩码
*/
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;

/*
36. 进程审计
*/
struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
uid_t loginuid;
unsigned int sessionid;
#endif

/*
37. secure computing
*/
seccomp_t seccomp;

/*
38. 用于copy_process函数使用CLONE_PARENT标记时
*/
u32 parent_exec_id;
u32 self_exec_id;

/*
39. alloc_lock
用于保护资源分配或释放的自旋锁
*/
spinlock_t alloc_lock;

/*
40. 中断
*/
#ifdef CONFIG_GENERIC_HARDIRQS
struct irqaction *irqaction;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif

/*
41. pi_lock
task_rq_lock函数所使用的锁
*/
spinlock_t pi_lock;

#ifdef CONFIG_RT_MUTEXES
/*
42. 基于PI协议的等待互斥锁,其中PI指的是priority inheritance/9优先级继承)
*/
struct plist_head pi_waiters;
struct rt_mutex_waiter *pi_blocked_on;
#endif

#ifdef CONFIG_DEBUG_MUTEXES
/*
43. blocked_on
死锁检测
*/
struct mutex_waiter *blocked_on;
#endif

/*
44. lockdep,
*/
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
u64 curr_chain_key;
int lockdep_depth;
unsigned int lockdep_recursion;
struct held_lock held_locks[MAX_LOCK_DEPTH];
gfp_t lockdep_reclaim_gfp;
#endif

/*
45. journal_info
JFS文件系统
*/
void *journal_info;

/*
46. 块设备链表
*/
struct bio *bio_list, **bio_tail;

/*
47. reclaim_state
内存回收
*/
struct reclaim_state *reclaim_state;

/*
48. backing_dev_info
存放块设备I/O数据流量信息
*/
struct backing_dev_info *backing_dev_info;

/*
49. io_context
I/O调度器所使用的信息
*/
struct io_context *io_context;

/*
50. CPUSET功能
*/
#ifdef CONFIG_CPUSETS
nodemask_t mems_allowed;
int cpuset_mem_spread_rotor;
#endif

/*
51. Control Groups
*/
#ifdef CONFIG_CGROUPS
struct css_set *cgroups;
struct list_head cg_list;
#endif

/*
52. robust_list
futex同步机制
*/
#ifdef CONFIG_FUTEX
struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
struct compat_robust_list_head __user *compat_robust_list;
#endif
struct list_head pi_state_list;
struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
struct perf_event_context *perf_event_ctxp;
struct mutex perf_event_mutex;
struct list_head perf_event_list;
#endif

/*
53. 非一致内存访问(NUMA Non-Uniform Memory Access)
*/
#ifdef CONFIG_NUMA
struct mempolicy *mempolicy; /* Protected by alloc_lock */
short il_next;
#endif

/*
54. fs_excl
文件系统互斥资源
*/
atomic_t fs_excl;

/*
55. rcu
RCU链表
*/
struct rcu_head rcu;

/*
56. splice_pipe
管道
*/
struct pipe_inode_info *splice_pipe;

/*
57. delays
延迟计数
*/
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif

/*
58. make_it_fail
fault injection
*/
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif

/*
59. dirties
FLoating proportions
*/
struct prop_local_single dirties;

/*
60. Infrastructure for displayinglatency
*/
#ifdef CONFIG_LATENCYTOP
int latency_record_count;
struct latency_record latency_record[LT_SAVECOUNT];
#endif

/*
61. time slack values,常用于poll和select函数
*/
unsigned long timer_slack_ns;
unsigned long default_timer_slack_ns;

/*
62. scm_work_list
socket控制消息(control message)
*/
struct list_head *scm_work_list;

/*
63. ftrace跟踪器
*/
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
int curr_ret_stack;
struct ftrace_ret_stack *ret_stack;
unsigned long long ftrace_timestamp;
atomic_t trace_overrun;
atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
unsigned long trace;
unsigned long trace_recursion;
#endif
};