一、僵尸状态 & 孤儿进程
进程退出:内核数据结构(task_struct
维护)+ 代码和数据(直接释放)
- 代码不会执行了
首先可以立即释放的就是进程对应的程序信息数据。 - 进程退出要有退出信息(进程的退出码),其保存在自己的
task_struct
的内部。
结构体 ➡️ 成员属性 ➡️ 退出信息(int code
, 其他) - 管理结构
task_struct
必须被OS维护起来,方便用户未来获取进程退出的信息。
先创建内核数据机构再加载代码和数据,即使代码和数据没加载进来,创建内核数据结构后进程即被创建,只是不会被调度。
#include <stdio.h>
#include <unistd.h>int main(){printf("父进程运行:pid = %d,ppid = %d\n", getpid(), getppid());pid_t id = fork();if(id == 0){//子进程int cnt = 10;while(cnt){printf("循环第%d次,子进程对应的 pid = %d, ppid = %d\n", 11 - cnt, getpid(), getppid());sleep(2);--cnt;}}else{//父进程while(1){printf("父进程对应的 pid = %d\n", getpid());sleep(1);}}return 0;
}
通过如下指令查看进程状态:
while :; do ps ajx | head -1; ps ajx | grep myproc; sleep 1; done
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND617150 617285 617285 617123 pts/1 617285 S+ 1000 0:00 ./myproc617285 617286 617285 617123 pts/1 617285 S+ 1000 0:00 ./myproc617270 617361 617360 617253 pts/2 617360 S+ 1000 0:00 grep --color=auto myprocPPID PID PGID SID TTY TPGID STAT UID TIME COMMAND617150 617285 617285 617123 pts/1 617285 S+ 1000 0:00 ./myproc617285 617286 617285 617123 pts/1 617285 Z+ 1000 0:00 [myproc] <defunct>617270 617366 617365 617253 pts/2 617365 S+ 1000 0:00 grep --color=auto myproc
发现子进程进入僵尸状态(Z:维护自己的task_struct
,方便未来父进程读取退出状态)。
如果没人管,就会一直维持僵尸状态(task_struct
一直存在会消耗内存,造成内存泄漏!)。
默认没人管怎么处理?一般需要父进程读取子进程信息,子进程才会自动退出。
语言层面的内存泄漏问题,如果在常驻内存的进程中出现,影响比较大。
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”,孤儿进程被1号 initd
或 systemd
进程领养、回收。
二、【总结】Linux下的进程状态及其转换过程
1. 进程状态的分类
在Linux中,进程通常会处于以下几种状态之一:
- R (Running): 运行态,表示进程正在运行或准备运行。
- S (Sleeping): 睡眠态,分为两种:
- 可中断睡眠态(Interruptible Sleep,S):进程正在等待某个事件或资源,收到信号后可被唤醒。
- 不可中断睡眠态(Uninterruptible Sleep,D):进程正等待设备I/O等系统资源,不能被信号中断唤醒。
- T (Stopped): 停止态,进程被暂停执行,如收到
SIGSTOP
信号或在调试过程中被暂停。 - Z (Zombie): 僵尸态,进程已经终止,但其父进程尚未调用
wait()
系统调用来获取它的退出状态。 - X (Dead): 已终止,进程结束且系统已清理其所有资源。
2. 进程状态转换过程
(1)创建进程
- 初始状态:R(Running)
- 当调用
fork()
或clone()
系统调用时,一个新的子进程会被创建。新进程最初处于运行态(R
),并继承父进程的资源和执行环境。
- 当调用
(2)运行态和睡眠态的转换
-
R → S(可中断睡眠态)
- 进程在等待某些资源或事件时会进入可中断睡眠态。比如,当进程请求磁盘I/O或等待网络数据时,它会调用
sleep()
或wait()
系统调用,导致状态从运行态转换为睡眠态。 - 进程在此状态下可以被信号唤醒。
- 进程在等待某些资源或事件时会进入可中断睡眠态。比如,当进程请求磁盘I/O或等待网络数据时,它会调用
-
S(可中断睡眠态)→ R(Running)
- 当进程等待的事件发生时(如I/O操作完成),或当它收到信号时,进程将被唤醒,状态转换为运行态。
-
R → D(不可中断睡眠态)
- 进程在等待设备硬件响应或内核中的某些特定事件时,可能进入不可中断睡眠态(如等待磁盘I/O)。这种状态下的进程不能被信号唤醒。
-
D → R(Running)
- 当设备操作或资源可用时,不可中断睡眠态的进程会被内核唤醒,并返回运行态。
(3)运行态和停止态的转换
-
R → T(Stopped)
- 进程接收到
SIGSTOP
或SIGTSTP
信号时会暂停执行,进入停止态。调试过程中,进程被暂停也会处于此状态。
- 进程接收到
-
T → R(Running)
- 进程收到
SIGCONT
信号后可以恢复运行,状态从停止态转换为运行态。
- 进程收到
(4)僵尸进程的产生及清理
-
R → Z(Zombie)
- 当进程完成执行并调用
exit()
函数后,它的状态会变为僵尸态。进程已经终止,但它的进程表条目依然存在,直到父进程通过wait()
或waitpid()
系统调用收集其退出状态。
- 当进程完成执行并调用
-
Z → X(Dead)
- 当父进程通过
wait()
或waitpid()
获取到僵尸进程的退出状态后,内核会释放僵尸进程的所有资源,进程真正终止。
- 当父进程通过
3. 进程状态的影响因素
进程状态的转换主要受以下因素影响:
- 系统资源:进程可能因为等待系统资源(如I/O、内存、锁)而进入睡眠态。
- 调度器:进程何时运行取决于调度器的策略,如时间片的分配、优先级等。
- 信号:进程可以通过信号进入停止态或被唤醒等。
三、进程优先级
1. 是什么?获得某种资源的先后顺序
例:排队的本质就是为了确认优先级,资源从打饭的窗口获取
2. 为什么?
因为目标资源比较少
权限:能不能的问题;优先级:已经能了,谁先谁后的问题
3. 怎么办?
task_struct
➡️ 优先级属性 ➡️ 几个特定的int
类型变量来表示优先级
优先级数字越小代表优先级越高
用指令 ps -la
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 618246 618229 0 80 0 - 2585 do_wai pts/1 00:00:00 su
4 S 1000 618247 618246 0 80 0 - 2225 do_wai pts/1 00:00:00 bash
0 S 1000 619020 618247 1 80 0 - 694 wait_w pts/1 00:00:00 prio
4 R 0 619021 618979 0 80 0 - 2518 - pts/2 00:00:00 ps
PRI
:Linux进程的优先级
NI
:优先级的nice数据
最终优先级 = pri(默认/旧的)+ NI
(优先级的修正数据)
UID
即用户ID,揭示了进程是由谁启动的
- 文件会记录拥有者、所属组和对应的权限
- Linux 中一切皆文件
- 所有操作都是进程操作,进程会记录其启动者
1.
和3.
比对就是权限的基本控制原则
进程竞争的是CPU资源
3.1 修改优先级(非高频操作且不建议修改)
top
命令 ➡️ r
➡️ 按照提醒修改即可(注意OS禁止频繁修改和没有权限修改)
$ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 621113 621089 0 80 0 - 2585 - pts/1 00:00:00 su
4 S 1000 621114 621113 0 80 0 - 2199 do_wai pts/1 00:00:00 bash
4 S 0 621186 621170 0 80 0 - 2585 - pts/2 00:00:00 su
4 S 1000 621187 621186 0 80 0 - 2199 do_wai pts/2 00:00:00 bash
0 S 1000 621239 621114 0 80 0 - 694 hrtime pts/1 00:00:00 pri
0 R 1000 621240 621187 0 80 0 - 2518 - pts/2 00:00:00 ps
如果设定nice
值为100
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 621113 621089 0 80 0 - 2585 - pts/1 00:00:00 su
4 S 1000 621114 621113 0 80 0 - 2199 do_wai pts/1 00:00:00 bash
4 S 0 621186 621170 0 80 0 - 2585 - pts/2 00:00:00 su
4 S 1000 621187 621186 0 80 0 - 2199 do_wai pts/2 00:00:00 bash
0 S 1000 621239 621114 0 99 19 - 694 hrtime pts/1 00:00:00 pri
0 R 1000 621245 621187 0 80 0 - 2518 - pts/2 00:00:00 ps
结果说明 nice
值最大为19
如果设定nice
值为-100
root@hcss-ecs-2ff4:/home/usr1/mylinux/F0922# ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 621113 621089 0 80 0 - 2585 do_wai pts/1 00:00:00 su
4 S 1000 621114 621113 0 80 0 - 2199 do_wai pts/1 00:00:00 bash
4 S 0 621186 621170 0 80 0 - 2585 do_wai pts/2 00:00:00 su
4 S 1000 621187 621186 0 80 0 - 2199 do_wai pts/2 00:00:00 bash
0 S 1000 621247 621114 0 60 -20 - 694 hrtime pts/1 00:00:00 pri
4 S 0 621254 621187 0 80 0 - 2649 do_wai pts/2 00:00:00 su
4 S 0 621255 621254 0 80 0 - 1909 do_wai pts/2 00:00:00 bash
4 R 0 621263 621255 0 80 0 - 2518 - pts/2 00:00:00 ps
结果说明 nice
值最小为20
NI
取值范围为[-20, 19]
NI
在可控范围内:因为分时操作系统在进行进层调度时要做到尽可能公平
pri
(最终)= pri
(default 80)+ nice
修改实质上是重置
- 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行(任何时刻)
- 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
(在一个时间段内,多个进程代码在“同时”推进)
四、进程切换
1. 引入
(1)每个进程都有时间片,时间片到了,进程就该被切换
(2)Linux是基于时间片进行调度轮转的
(3)一个进程在时间片到了的时候并不一定跑完的,可在任何地方被重新调度切换
2. 进程切换
- 保存上下文数据:保留历史运行痕迹
- 恢复上下文数据:保留不是目的,而是手段,未来恢复才是目的
(1)进程在运行时会有很多的临时数据都在CPU的寄存器中保存
CPU怎么知道读到哪一行代码了?
eip(pc):当前执行指令的下一条指令的地址(pc = 当前地址 + 读进来的指令长度)
ir:指令寄存器,保存的是正在执行的指令
task_struct 里有程序计数器(pc 指针)
(2)CPU内部寄存器的数据是进程执行时的瞬时状态信息数据,该数据就是进程的上下文数据(随时随地都在高频变化)
(3)CPU内有很多寄存器(一套寄存器),但寄存器 ≠ 寄存器里面的数据(当前进程的上下文数据)
进程切换的核心就是上下文数据的保存和恢复
不做保护无法完成多进程的调度和切换
所有进程都要做如下工作
切走:将相关寄存器的内容保存起来
切回:将历史保存的寄存器数据恢复到寄存器中
意味着每次切换保存完上下文时,CPU都是全新的
3. 细节理解
- 每个进程都要有自己的上下文数据,但CPU内部只有一套寄存器,该套寄存器被多个进程共享使用
- 上下文数据保存在哪里?在内存里,进程的上下文寄存器数据,只要保存到当前进程的
PCB
中就可以了,早期Linux是存在tss_struct
中
五、调度
真实进程优先级[60, 99]
Linux真实的调度算法
数组queue[140]
:存放 struct task_struct* 指针,共140个元素
前100个下标对应的第0~99个元素给实时进程使用的
如果一个进程的优先级pri = 61
,应该怎么放?
挂队列在下标为pri - startpri(60) + 100
的位置(即 61 - 60 +100 = 101)
hash桶:相同优先级的进程会被挂接到一起