Linux的进程状态
概述
在linux下具体的进程状态如图所示有以下几种,进程的状态用state整形表示(state是什么)
此处使用了位掩码(bitmask)的技术,这里每一个状态的值都是2的幂,则可以用state的各个二进制位来表示一个当前状态,为0则不处于,为1则处于该状态
RunningState(运行状态)
什么是运行状态
int main(){
while(1){}
return 0;
}
通过运行结果可以看到,死循环执行时,myprocess程序的STAT为R+,此处的R就为运行状态(+号跳转),并且由于有时间片的限制(时间片),该程序并不会一直在CPU上跑,但是STAT一直为R+状态,则说明此处的R表示进程正在CPU上被运行或是正处于CPU的运行队列里
前台与后台进程
前台
当使用./可执行程序名字
,是把当前程序放到前台运行,可以直接使用Ctrl+c来终止程序,并且在进程状态后会有一个+号表示是前台进程,上图所示,myprocess为R+,即处于前台Running(运行)状态
当输入命令时bash没有反应,因为前台正在被myprocess进程占据
后台
使用./程序名 &
则把当前程序运行到后台上,此时bash依然占据前台,则输入的命令是有效的
当前的myprocess处于R状态没有+号,表明+号被用来区分前后台,有+号前台,无+号后台
在后台运行的程序不能够使用Ctrl+c来杀掉进程,需要使用kill -9 pid
SleepingState(睡眠状态)
什么是睡眠状态
S也被称为可中断睡眠\浅度睡眠(interruptible sleep)
此状态表示正在等待软硬件资源的就绪,下面以键盘输入为例
int main(){
int a;
scanf("%d",&a);
printf("%d\n",a);
return 0;
}
此时myprocess运行到scanf正在等待键盘输入资源,STAT为S+,由于正在等待资源的就绪此时进程在键盘的等待队列中,则S为操作系统维度下的堵塞状态的一种(堵塞状态)
该状态可使用Ctrl+C来进行中断
sleep和printf状态分析
int main(){
while(1){
sleep(1);
}
return 0;
}
此时myprocess的状态变为了S,并非是R运行状态,因为此处的sleep(把进程从CPU上调度到sleep设置的等待队列)睡眠1秒而在CPU上运行的while判断速度执行的非常快,所以睡眠的时间占百分之95甚至更多,所以当查看进程状态时,很大概率都为S
#include<unistd.h>
int main(){
while(1){
printf("I am a process!pid: %d",getpid());
}
return 0;
}
printf也需要等待显式器打印完成,同样需要在显式器的等待队列中等待资源的就绪,而CPU的速度远远快于显式器的打印速度,所以大多数时候都保持为S睡眠状态,这里也检测到了R状态,只不过此状态占比非常小
disk sleep(磁盘休眠)
什么是磁盘休眠
disk sleep也被称为不可中断睡眠
当内存资源严重不足时(通过swap分区唤入唤出等等的操作都用了),操作系统为了维持正常的运行,则会杀掉一部分能够杀掉的进程(OOM Killer),而有些进程正在执行非常重要的与磁盘I/O操作(例如向磁盘写入用户的重要数据),正在等待硬件资源就绪,如果被杀掉可能导致一系列非常严重的问题,从而为了使当前正在等待资源就绪的进程不能被杀掉,则引入了disk_sleep状态即不可中断睡眠,处于此状态的进程不能被杀掉
此状态也是操作系统维度下的堵塞状态的一种,一样是在等待资源的就绪,但是不能被OOM Killer杀掉
OOM Killer (内存溢出杀手)
当内存压缩与回收和swap机制依然无法解决内存不足的问题时,Linux内核会启动OOM Killer.这是一个内核级别的功能,用于在物理内存和swap都耗尽时,通过一定的选择判断来决定终止哪些进程以释放内存空间
stopped(暂停状态)
什么是暂停状态
在暂停状态下的进程不会使用CPU资源,该进程的执行已被暂时中断,但它仍然保留在系统的进程表中,并且在内存中保留所有的代码和数据
该状态可以防止进程的未被授权的操作,例如一些进程在某个时段不允许访问显式器来打印数据,此时不能够进入显式器的等待队列也不能够被CPU运行(显示器的资源未就绪),那么此时该进程就会被暂停
使用kill暂停/重启进程与前后台状态细节
int main(){
while(1){
printf("I am a process!pid: %d",getpid());
sleep(1);
}
return 0;
}
kill的19号信号为stop信号即暂停信号使进程处于暂停状态
进程原为S+状态即前台的睡眠状态,当被暂停后状态变为T状态即暂停状态,同时去掉了+号,即当前进程从前台转到后台,这里进程被暂停后什么都不会做,肯定需要把前台让出来此时bash进程将重新在前台执行,此时输入命令有效
kill -18 pid
把暂停的状态重新运行起来,此时状态从T变为S,则从后台启动的进程会在后台运行,不会重新回到前台,要终止进程,只能使用kill -9 pid
trace stop(跟踪暂停)
什么是trace stop
trace stop是在使用跟踪工具对进程进行跟踪时进程所处的状态,进程会被暂停等待调试器的下一个信号来执行具体的操作,便于调试或监控
此处以gdb调试为例
可以看到,当在gdb中按r运行时,myprocess进程才被执行, 状态为t状态,因为此时正在被gbd追踪调试
总结暂停状态
stopped暂停等待硬件资源条件的就绪或者软件资源(例如启动信号)的就绪,而trace stop也同样是暂停等待软件资源的就绪(下一步的调试信号),则两种暂停状态也都可以归类到操作系统维度下的堵塞状态
zombie(僵尸状态)
什么是僵尸状态
当一个进程死亡时,它的代码和数据会被直接释放,但是它的task_struct(PCB)并不会立即被释放,因为一个进程执行完所有的任务(代码)后,系统需要知道进程是否是正常死亡退出的,还是在执行过程中出现错误而导致异常退出,就需要让上层从子进程的PCB中拿到死亡(退出)信息,从而判断子进程代码完成的结果数据
如果父进程一直都不去进行读取子进程退出信息(结果数据),那么处于僵尸状态的子进程会一直存在,子进程的PCB会一直占据内存空间,就会造成严重的内存泄漏!
int main(){
pid_t id = fork();
if(id == 0){
exit(0);
}
else{
sleep(1000);
}
return 0;
}
此时myprocess的子进程被exit退出此时处于僵尸Z状态,+号表示在前台,此处的defunct意思为失效的,不再存在的
使用wait拿取子进程的退出信息
int main(){
pid_t id = fork();
if(id == 0){
exit(0);
}
else{
sleep(10);
wait(NULL);
sleep(1000);
}
return 0;
}
使用wait使得处于Z+状态的子进程被父进程所回收,最后子进程从僵尸变为死亡
bash回收
所有bash的子进程在进入僵尸状态后都会被bash直接回收释放,即bash会自动回收,则不能直接捕捉到则没有示例
dead(死亡状态)
真正的死亡状态,进程的所有数据都被释放
孤儿进程
- 父进程在未回收子进程的结果数据时提前退出,子进程此时的父进程还是原来的父进程吗?
- 如果子进程处于Z状态,那么它会被回收吗,会被谁回收?
int main(){
7 int cnt = 5;
8 pid_t id = fork();
9 if(id == 0){
10 int cnt = 20;
11 while(cnt--){
12 printf("child process exist!\n");
13 sleep(1);
14 }
15 exit(0);
16 }
17 else{
18 // wait(NULL);
19 while(cnt--){
20 printf("parent process exist!\n");
21 sleep(1);
22 }
23 }
24 printf("parent process dead\n");
25 return 0;
26 }
当父进程退出被bash回收,但子进程还在运行时,那么id为1的进程为当前子进程新的父进程
id为1的进程为init进程,所有用户空间的进程均派生自该进程
init进程也会自动回收僵尸状态的子进程
总结:孤儿进程为父进程提前退出的进程,此时孤儿进程的父进程变为init进程,并且处于僵尸状态会被init自动回收