概念
cpu需要执行很多进程,有很多进程排在队列中,每个进程加载后运行一定的时间段,然后切换下一个进程。cpu如何判断进程需不需要加载,什么时候加载,依靠进程的状态和优先级属性来判断,进程调度,就变成了在task_struct的队列中选择一个进程的过程
内核源码
为了弄明白正在运行的进程是什么意思,需要知道进程的不同状态,一个进程有几个状态,有时候也叫任务
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
新建:新建进程,还没有运行的时候
运行
阻塞:当一个进程被cpu执行时,需要磁盘、网卡、显卡等设备资源就绪才可以,就会进入其他队列等待,等待磁盘等资源就绪的队列就是阻塞队列。**等待非cpu资源就绪就叫做阻塞态。**比如一个scanf函数一直不输入,等待键盘资源,就是阻塞。程序卡死有时候也是等待网卡资源,也有可能是cpu被占用,得不到调度,或操作系统卡死
挂起:当内存不足的时候,OS通过适当的置换进程的代码和数据到磁盘,进程的状态就叫做挂起
cpu内存不足的时候,会将长时间不执行,还需要等待很长时间资源的进程代码和数据换出到磁盘。也就是电脑的swap分区,是操作系统预先留好的,保证cpu正常的运行,这时候cpu会比较慢。再回来运行时需要把数据拿回来才能正常运行。压力过大电脑会宕机
写一份循环代码,用上节的循环命令不断查看进程状态
R+的状态就是运行中,接着往循环内加一个printf或者scanf函数,cpu的执行速度是非常快的,大部分时间都是往屏幕打印等待键盘输入等阻塞状态
S的状态就是阻塞
+的意思是前台程序
**前台进程:**当这个程序运行的时候,输入其他命令就没作用
运行程序后面加一个&符号,表示在后台运行,这个时候就没有+号了
后台进程停止需要发送kill信号,crtl+c没用
退出状态:
有了上面的状态描述,下面逐个解析源码对应的状态:
R运行状态(running):对应上面的运行态
S睡眠状态(sleeping):阻塞状态,可中断睡眠
什么是可中断睡眠,运行一个阻塞状态的程序,-l查看所有信号
发送19号停止信号
进程被强制停止了,也就是睡眠状态还会接受命令,可以被中断
D睡眠状态:深度睡眠,磁盘睡眠,不可被中断,不可以自动唤醒
这个和上面的S状态相反
当服务器压力过大的时候,操作系统会将一些长时间不就绪的进程杀掉,多数为等待硬盘资源就绪的进程,当正准备往硬盘写入数据被杀掉后,就会造成数据丢失。为了防止这种情况,就设置了一种D状态,操作系统遇到这种状态就不会误杀。常用于从硬盘读取数据的场景,只有当硬盘数据就绪完成后,才会改变状态。kill信号也不行,dd命令的进程就是这种状态
T暂停状态: 上面发送了19号信号的进程就是暂停状态
t调试状态: 暂停状态的一种,正在调试的进程显示的状态(需要程序是debug版才可以调试),打一个断点,运行到断点处
X终止状态: 瞬时性非常强, 因为有很多进程在运行,需要标注可以回收的进程,很难捕捉到,任务列表李看不到
Z僵尸状态: 一个进程已经退出,不允许被OS释放,处于一个被检测的状态。一般是为了父进程或OS来回收,需要检测运行的结果
#include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4
5 int main()
6 {
7 pid_t pid = fork();
8 if (pid == 0)
9 {
10 sleep(3);
11 }
12 else if(pid > 0)
13 {
14 while(1)
15 {
16 sleep(3);
17 }
18 }
19 }
上面的代码子进程三秒后会结束,父进程一直在运行,可以循环查看状态
上图是理论状态,字母是具体状态。创建就是进程没运行,fork的子进程如果还没运行就是创建
运行状态,退出或终止就是Z和X状态,阻塞就是S,挂起也是S,看数据在不在内存,不在的话,内存中是挂起,进程状态还是S。状态可以是在上面的很多之间不停转换
僵尸状态
一个比较特殊的状态,进程退出且父进程没有读取到返回代码就会产生僵尸进程
僵尸进程会以终止状态保存在进程表中,并且会一直等待父进程读取退出状态代码
所以,只要子进程退出,父进程还在运行,但父进程没有读取到子进程状态,子进程进入Z状态
僵尸进程危害
进程的推出态必须被维持下去,要告诉关心它的父进程任务完成情况,如果一直不读取,就处于Z状态。进程不退出,PCB一直要维护,一直不回收,就会造成内存资源浪费,后面会详细说明
孤儿状态
父进程提前退出,子进程还在运行,就叫孤儿进程
孤儿进程会被1号进程领养,就是系统的 init进程,系统本身,也会回收
int main()
6 {
7 pid_t pid = fork();
8 if (pid == 0)
9 {
10 while(1)
11 {
12
13 sleep(3);
14 }
15 }
16 else if(pid > 0)
17 {
18 sleep(3);
19 exit(0);
20
21 }
22 }
当3秒父进程结束后,子进程的父进程立马变成了1号进程,这时变为后台进程,可以kill杀掉
为什么要被领养,未来子进程退出的时候父进程不在,需要领养来回收资源
进程优先级
基本概念
cpu资源分配的先后顺序,进程的优先权
优先权高的进程有优先执行的权利,配置进程优先权对多任务环境的linux很有用,可以改善系统性能
还可以吧进程运行到 指定的cpu上,可以大大改善系统整体性能
优先级= 老优先级+nice值
运行一个进程,ps -la查看优先级
UID: 代表执行者的身份
PID: 进程的标识符
PPID: 哪个进程衍生来的,父进程
PRI: 进程的优先级,越小越早执行
NI: 代表nice值
NI是进程可执行优先级的修正数值,意思是可以通过根据这个值来调整优先级
加入nice值后,PRI会变为:PRI(new)=PRI(old)+nice
调整优先级,就是调整nice值
nice取值范围是-20至19,一共40个级别
进程的nice值不是优先级,但是会影响优先级,是修正数据
设置优先级
进入top,按r输入进程pid,回车后输入nice值
sudo模式设置nice为-100后,变为了60
这是因为每次设置nice值,老的优先级都是从80开始算,最小优先级是-20,80-20就是60
优先级的调整不会太大,nice值范围固定。是为了尽量不打破系统的调度安排,避免恶意占用资源,也为了系统更稳定
其他概念
竞争性:系统进程数目众多,cpu资源少量,所以进程有竞争性,为了高效合理分配资源,有了优先级
独立性:多进程运行,独享各种资源,一个进程崩溃不会影响其他进程。互相不干扰
并行:多个进程在多个cpu下同时运行
并发:进程切换的方式,一段时间内,多个进程都得以推进
每个进程在cpu中运行的时间叫时间片,而这个运行时间并不是固定的,如果运行完毕就会出去。同时,有时高优先级的进程会抢占cpu,低优先级的出让。每个进程都会较为均衡的占用cpu
一个进程在cpu运行的时候,寄存器内一定保存了进程的临时数据信息,寄存器有可显寄存器,还有电脑内的不可显。这些临时数据就是进程a的上下文数据,进程a被切下来时,需要带走保存自己的上下文数据。为了下次回来的时候,能恢复上去,就能按照之后的逻辑继续执行,就如同没有中断过一样。上下文数据可以有多份,分别对应不同的进程