目录
一. Linux操作系统进程的状态
二. 进程状态的标识
2.1 运行状态R
2.2 睡眠状态S
2.3 前台进程和后台进程
2.4 磁盘睡眠状态D
2.5 暂停状态和调试状态T
2.6 僵尸状态Z
2.7 终止状态X
2.8 孤儿进程
三. 进程优先级
3.1 什么是优先级
3.2 进程优先级的修改和进程切换
四. 总结
一. Linux操作系统进程的状态
- 就绪:新进程被创建出来,PCB加入到运行队列等待运行的进程。
- 运行:进程正在使用CPU资源运行的状态。
- 阻塞:进程等待非CPU资源就绪的状态,如:文件的读写需要等待磁盘资源就绪。
操作系统中一定存在着各种资源,不仅仅是CPU,如磁盘、显卡、网卡等,都属于操作系统的资源。同时,操作系统中不仅仅有运行队列,还有阻塞队列。如图1.1所示,假设运行队列队头的进程A正在占用CPU资源,而某一时刻进程A需要从磁盘上读取数据,然而此时磁盘资源尚未就绪,这是进程A就会被添加到磁盘的阻塞队列去等待相关磁盘资源的就绪。当磁盘资源就绪后,进程A就会被再次加入到运行队列等待CPU运行。进程A在等待磁盘资源就绪的过程中,它的状态就是阻塞状态。
- 挂起状态:当开启的进程过多,内存资源几乎饱和使用的情况下,操作系统会将尝试见不允许的进程的代码和数据置换到磁盘的一块区域(注意进程的PCB不会被置换出去),被置换的进程的状态叫做挂起。
磁盘中有一块分区专门用于进行置换,在Windows下为pagefile.sys虚拟内存,在Linux/Centos下为Swap分区。
二. 进程状态的标识
2.1 运行状态R
写一段while(1)死循环代码,循环体内不进行任何工作,编译生成可执行文件并运行,另开终端使用ps指令查看进程状态,可见进程状态为R+,即为运行状态进程。
int main()
{while(1){ }return 0;
}
2.2 睡眠状态S
阻塞状态就是等待CPU资源就绪的状态,在代码死循环while(1)中不断使用printf向显示屏输出数据,通过脚本命令,间隔1s输出进程信息,可以看见进程状态为S+,即阻塞状态。
int main()
{ while(1) { printf("hello world\n"); sleep(1); } return 0;
}
这里基本上不会显示R状态,因为CPU的速度要远高于外部设备,且差别为数量级级别,导致程序运行期间99.99%以上的时间都在等待非CPU资源,所有我们观察到的进程状态一直为S+。
S睡眠状态的进程,可以强制中断睡眠。 睡眠状态,类似于阻塞状态。
2.3 前台进程和后台进程
我们注意到,2.1和2.2在查看进程状态时,都看到了R和S后面的+,+表明该进程是前台进程,后台进程不会有+,起点后台进程需要&符号做标识。
- 后台启动进程的语法:./XXX &
在后台启动2.2中程序的进程,监测进程状态,发现进程状态变为了S而非S+。
通过Ctrl + C无法强制终止后台进程的运行,需要通过9号信号终止。
- 后台进程终止命令:kill -9 进程pid
2.4 磁盘睡眠状态D
磁盘睡眠状态,可认为是一种深度睡眠状态,不能强制中断睡眠。如果D状态的睡眠被强制中断了,那么就会存在不可知的风险。
举个例子,假设某个进程在往磁盘中写数据,我们通过Ctrl+C或其他手段,企图强制终止睡眠,这种操作时不被运行的。因为如果进程还没写完就强制终止掉它,那么就无法给进程返回信息,确定文件读写是否成功,也有可能造成文件内容不全的问题。
通过dd指令,可以对D状态进程进行模拟。dd的功能为将一个文件的内容拷贝到另一个文件。
- dd的语法:dd if=输入文件名 of=输出文件名 bs=每块的大小 count=读取块数
输入和输出文件名在缺省时,为标准输入和输出。
2.5 暂停状态和调试状态T
写一段死循环代码,生成可执行程序myproc并运行,先通过ps指令查看进程pid,此时向进程发送19号暂停信号(SIGSTOP),此时输出进程属性信息,状态为T,再向进程发送18号继续运行信号(SIGCONT),此时进程状态变为S。
打开gdb调试程序程序,也能检测到T状态。
2.6 僵尸状态Z
- 是什么:一个进程已经退出,但是操作系统还不允许释放这个进程的资源,处于被检测的状态,此时这个进程就是僵尸状态进程。一般由父进程或操作系统进行检测。
- 为什么:子进程已经终止,但是父进程还在运行并且没有对子进程进行处理,那么这个子进程就处于僵尸状态。
编写如下所示的代码,通过fork创建子进程,在父子进程内死循环打印pid和ppid,通过9号信号终止子进程,检测进程属性信息,此时子进程状态为Z。
#include<iostream>
#include<unistd.h>
#include<stdlib.h> int main()
{ pid_t ret=fork(); //创建子进程 if(ret < 0) //进程创建失败 { perror("fork"); return 1; } else if(ret == 0) //子进程代码 { while(1) { printf("Child Process, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } } else //父进程代码 { while(1) { printf("Parent Process, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } } return 0;
}
僵尸进程的危害:
- 如果父进程一直不读取子进程状态,那么子进程就一直处于僵尸Z状态,操作系统就必须一直维护这样的僵尸状态。同时,僵尸状态的PCB必须一直被操作系统内置的数据结构和算法维护,会造成资源浪费。
- 僵尸进程会造成内存泄漏问题。
2.7 终止状态X
如果一个进程已经终止,但是操作系统还没来得及回收资源,那么这个进程就处于终止X状态。
2.8 孤儿进程
概念:如果一个进程的父进程终止了,但是这个进程(子进程)本身还在运行,那么这个进程就是孤儿进程。
处理方法:孤儿进程会被1号进程init领养。
下面的代码通过fork创建父子进程,父进程循环5次后退出,子进程不断运行,通过监测,我们发现,一开始子进程的ppid为其最开始父进程的pid,但后面变为了1,表明子进程在父进程终止后变为了孤儿进程,被1号进程收养。
#include<iostream>
#include<unistd.h>
#include<stdlib.h> int main()
{ pid_t ret=fork(); //创建子进程 if(ret < 0) //进程创建失败 { perror("fork"); return 1; } else if(ret == 0) //子进程代码 { while(1) { printf("Child Process, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } } else //父进程代码 { int cnt = 5; while(cnt--) { printf("Parent Process, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } } return 0;
}
三. 进程优先级
3.1 什么是优先级
- 优先级的概念:就是进程PCB中的一个属性参数,用来表示进程占用CPU资源的先后顺序,确定谁先获得CPU资源,谁后获得。
- 为什么存在优先级:因为CPU资源是有限的,进程需要通过某种方式竞争CPU资源。
- 优先级是调度器确定进程运行顺序的重要依据。
某一个进程的优先级,可以表示为:老的优先级 + nice值。即:PRI(new) = PRI(old) + NI
查看进程全部信息指令(包括优先级):ps -al
3.2 进程优先级的修改和进程切换
- 方法:top指令打开任务管理器 -> 输入r,输入要进行修改的进程的pid -> 输入新的nice值
- 优先级的数值越低,表示优先级越高,越先享用CPU资源。nice值为正数,表示降低进程优先级,nice为负数,表示提高进程优先级。
- 默认的PRI为80,NI为0。如果要将NI改为负数,需要管理员权限。
关于进程的切换,有以下概念和注意事项:
- 进程具有独立性,每个进程独占CPU资源,它们之间互不干扰。独立性包括父子进程,父进程和子进程之间也是独立的。
- 时间片:CPU并不是要将一个进程的代码全部运行结束后,才去运行下一个进程,而是给每个进程设置特定的时间片,时间片到了之后,就让出CPU资源,到运行队列中排队等待下一次运行。
- 抢占:高优先级进程抢占低优先级进程的CPU资源,即使低优先级进程的时间片还没到。
- 出让:某个进程在时间片还没到时,就因为各种原因,让出了它所占用的CPU资源。
- 假设进程A正在被CPU运行,那么CPU的寄存器中必须存储进程A的上下文信息,以便于确定进程A具体执行到了什么位置。当进程A的时间片结束,或者出让了CPU资源,那么要将进程A的上下文信息一并从CPU的寄存器中带走,以便下次运行进程A的时候能够继续之前的运行。
四. 总结
- Linux进程状态可以分为就绪、运行、阻塞、挂起等状态,就绪就是进程在运行队列中等待CPU执行,运行就是某个进程正在占用CPU资源运行,阻塞就是进程正在阻塞队列中等待非CPU资源,挂起就是内存资源接近饱和的时候,将某个进程的代码和数据暂时移动到磁盘中的状态。
- 进程的状态可以细分为运行状态R、睡眠状态S、磁盘睡眠状态D、僵尸状态Z、暂停和调试状态T、死亡状态D、孤儿进程。进程还分为前台进程和后台进程。睡眠状态S可以被强制中断,磁盘睡眠状态不能被强制中断。
- 进程的优先级用于调度器确定进程占用CPU资源的先后顺序,表示为PRI(new) = PRI(old) + NI。