前置知识:
1.并行和并发:
CPU执行进程代码,不是把进程执行完毕,才开始进行下一个,而是给每一个进程预分配一个时间片,基于时间片,进行调度轮转(单CPU)
并发:多个进程在一个CPU下采用进程切换的方式,在一段时间内,让多个进程得以推进
并行:多个进程在多个CPU下分别同时运行
2.时间片
linux/windows民用级别的操作系统,分时操作系统,调度任务追求公平
每个进程在处理器上的执行时间是多少呢,此时就要引入一个时间片的概念,时间片代表了每个进程在处理器上连续执行的时间。通过将处理器的执行时间分割成小的时间片段,操作系统可以轮流地为每个就绪态的进程分配处理器时间,从而实现多任务并发执行。
在一般情况下,只有处于就绪态的进程才会被放入运行队列并被分配时间片,当进程编程阻塞态时就会被移出运行队列,不过在一些复杂的情况下有可能会被移到某种阻塞队列中等待资源可用。
当进程由运行队列被放到处理器中时,就有了运行态,处理器为其分配时间片,使其能够在规定的时间内执行指令,处理任务。这种状态下的进程可能会在一段时间后被操作系统挂起,以便为其他进程腾出空间。
3.等待的本质
连入外部设备,CPU不调度
操作系统的进程状态
操作系统中一个进程通常有三种状态
就绪状态(Ready):表示进程已经具备运行所需要的一切条件,只需要等待CPU的分配就可以运行。进程处于就绪状态时,通常会被添加到运行队列,等待调度器分配CPU资源。
运行状态(Running):表示进程正在被CPU执行。处于运行状态的进程正在使用CPU进行计算或其他操作。
阻塞状态(Blocked):表示进程因为某些原因暂时无法继续执行,需要等待一些特定条件的解除之后才能继续运行。例如,当进程等待I/O操作完成或者等待某个资源可用时,会转入阻塞状态。进程在阻塞状态时,通常会被移动到阻塞队列中,等待条件的满足
运行队列runqueue
运行队列是操作系统内核中的一个数据结构,用于存储处于就绪态的进程,即哪些已经准备好执行但尚未获得处理器的进程。其通常是一个先进先出队列,其中进程按照加入就绪态的顺序排列。
阻塞状态
关于进程:
① 一个进程使用资源的时候,可不仅仅是在申请 CPU 资源
② 进程可能会申请其它资源:磁盘、网卡、显卡,显示器资源……当访问某些资源(磁盘,网卡等),如果该资源暂时没有准备好,或者正在给其他进程提供服务,那么此时:
① 当前进程要从 runqueue 中退出。
② 将当前进程放入对应设备的描述结构体中的waitqueue 。
进程状态:看PCB在哪个队列
PCB处在runqueue中是就绪状态,处在waitqueue中,是阻塞状态
操作系统对进程管理的本质
操作系统对进程的管理,本质是对链表的管理,就绪队列和阻塞队列通常都是以链表的形式实现的,通过将进程控制块(PCB)链接在一起,当一个进程从就绪状态变为运行状态时,它会从就绪队列中被取出;当一个进程因等待资源而阻塞时,它会被插入到阻塞队列中。
挂起 (用时间换空间)
当内存资源严重不足时,操作系统会通过挂起进程的方式,把代码和数据换入换出到磁盘中
进程挂起是指将一个正在运行的进程暂时停止执行,使其暂时不占用处理器资源。当一个进程被挂起时,它的执行状态被保存,以便稍后可以恢复执行。挂起是主动将进程暂停,进程可能被换出到磁盘上(磁盘swap分区),以释放内存等资源。(swap 分区的使用会对系统性能产生一定影响。由于磁盘的读写速度远低于内存,频繁地进行内存与 swap 分区之间的数据交换会导致系统性能下降)
挂起进程与阻塞进程不同,因为阻塞进程通常是由于等待某些事件而无法继续执行,而挂起进程是由操作系统或用户显式地将其暂停执行。
Linux的进程状态
进程状态用整数表示,这些整数存储在进程的task_struct
结构体中。常见的进程状态包括:运行(R)、睡眠(S)、磁盘睡眠(D)、停止(T)、死亡(X)、僵尸(Z)和孤儿进程。
状态代码 | 状态名称 | 描述 |
R | 运行(Running) | 进程正在运行或在运行队列中等待 |
S | 睡眠(Sleeping) | 进程在等待某事件完成,可被信号唤醒 |
D | 磁盘睡眠(Disk Sleep) | 进程在等待I/O操作完成,不可被信号唤醒 |
T | 停止(Stopped) | 进程被暂停,可通过信号恢复 |
X | 死亡(Dead) | 进程已终止,从进程列表中移除 |
Z | 僵尸(Zombie) | 进程已退出,父进程尚未读取其状态 |
孤儿(Orphan) | 父进程已退出,被init进程收养 |
查看进程状态
运行下面代码
使用ps aux
或ps axj
命令可以查看系统中进程的状态
我们看到大部分时间都是处于休眠状态(阻塞等待状态),我们不是死循环一直运行的吗
原因是cpu太快了,print显示器等待的时间在他看来就是在sleep了
代码中去掉printf就会一直显示R,运行状态
深度睡眠状态 D
在操作系统中,进程的 D 状态通常指的是不可中断睡眠状态(Disk Sleep)
休眠状态可以被kill杀掉进程,是可中断睡眠,浅睡眠
disk(磁盘),阻塞等待状态的一种,是不可中断睡眠,深度睡眠
当进程需要从磁盘读取大量数据,如加载大型文件或数据库查询操作涉及大量磁盘 I/O 时,可能会进入 D 状态。这种状态的设计是为了确保在进行关键的 I/O 操作时,进程不会被意外中断,从而保证数据的完整性和系统的稳定性。
T 暂停状态
比如看视频,听音乐,下载,都会有暂停。当你点击暂停的时候下载对应的代码就不跑了,此时这个进程你就可以认为是暂停状态。
运行一下我们的程序
输入kill -19的指令
发现我们的程序被暂停了,输入kill -18指令,程序又运行起来了
这时候状态由之前的S+变成了S,不能用ctrl+c杀掉进程了,只能用kill -9
S+和S状态都是睡眠状态(可中断睡眠)。S+前台进程组中的进程正在被跟踪,S进程不属于前台进程组,或者不再被跟踪
我们的程序直接运行,就是前台运行,后面加一个&就变成后台运行,不能ctrl+c结束
下载任务(后台运行,不影响前台任务)
t状态
进程做了非法但是不致命的操作,被OS暂停了
当进程被追踪的时候,断点停下,进程状态就是t
X死亡状态
死亡状态通常指的是进程已经终止执行并退出,但其相关的进程描述信息仍然保留在系统中,直到其父进程对其进行清理。
进程为什么会被创建?进程创建出来是为了完成用户的任务,通过进程执行结果,告知父进程/操作系统,任务完成的怎么样
Z僵尸状态
僵尸状态:当一个 Linux 中的进程退出的时候,一般不会直接进入 X 状态(死亡,资源可以立马被回收),而是进入 Z 状态。
为什么呢~
进程为 Z 状态,就是为了维护退出信息,可以让父进程或者 OS 读取记录的,退出信息会写入 test_struct。
以下是创建僵尸进程的代码示例:
子进程退出,父进程还活着,但是父进程什么都不做
再另一个终端输入以下代码,写一个监控脚本,监测进程状态
可以看到子进程代码执行完毕后,状态由S变为Z状态
Z状态:
维护自己的task_struct,方便父进程读取退出状态。僵尸进程默认没人管,进程不退一直消耗内存
僵尸进程的危害
僵尸进程虽然不再运行,但它们仍然占用系统资源(如进程控制块task_struct
)。如果父进程不及时回收子进程,会导致系统资源浪费,甚至内存泄漏。
孤儿进程
孤儿进程是指在操作系统中,父进程先于子进程结束,从而导致子进程成为孤儿进程
看以下代码:
通过fork
函数创建了一个子进程。父进程在创建子进程后立即结束,而子进程会睡眠 5 秒,在这段时间内,子进程就成为了孤儿进程
父进程结束,子进程的ppid变成了1,说明子进程被操作系统领养了