宁波网站设计相信荣胜网络,wordpress is sticky,平台类网站,做网站教程流程进程状态
一个进程在 系统当中有很多的状态#xff0c;比如#xff1a;一个进程正在被cpu执行#xff0c;那这个进程就是一个 r 状态#xff1b;一个进程已经准备好了#xff0c;但是其中的运行这个进程需要的资源没有准备好#xff0c;那么这个进程一人不能运行。
比如…进程状态
一个进程在 系统当中有很多的状态比如一个进程正在被cpu执行那这个进程就是一个 r 状态一个进程已经准备好了但是其中的运行这个进程需要的资源没有准备好那么这个进程一人不能运行。
比如 在一个程序当中有一个 scanf函数当你 执行这个程序之后那么这个程序就变成一个 进程了但是在当 scanf函数执行开始时候你一直没有输入数据那么这个进程就会一直等待你输入的数据那么此时这个进程有没有被 cpu 执行呢肯定是没有的因为这个进程一直等不到 你输入的数据。
运行状态
操作系统当中的各个 进程都是用 某一种数据结构来把 各个进程的 PCB 链接在一起从而只要找到 这个数据结构的 head头那么就可以找到 当前需要运行的进程 PCB从而调用到这个进程带 cpu的当中进行执行。
但是因为 系统当中的 想运行的进程有很多而 cpu 又只能运行一个进程cpu 又是少数的资源。所以这些像运行的进程就会去竞争这个cpu资源所以就有了调度器的存在来帮助 cpu 更好的合理的 运行进程而不是一个进程一直在运行其他的进程一个在等待这些我们后面再说。
cpu 当中为了能管理好 运行的进程就自己维护了一个数据结构来用于管理当前正在运行的程序。这个数据结构就是 --- 运行队列runqueue。
在这个队列当中包含了很多属性但是这些属性都不重要最重要的是这个维护这个队列的 头尾指针
struct runqueue
{//运行队列struct task_struct *head;struct task_struct *tail;} 那么当调度器告诉cpu 现在运行哪一个进程cpu 就可以直接从这个队列当中找到这个进程的 PCB 放到cpu 当中就可以调用这个 进程了。这过程就是 进程在cpu 上排队。 调度器说白就是一个函数接口他可以把 把cpu 当中运行队列作为参数传入到函数体内这样在函数体当中就可以找到这个 或者是利用算法计算出 当前应该要执行的进程。
而我们把处于 运行队列当中的 进程这些进程的状态就是 运行状态运行态 R。
在这个运行队列当中进程就代表的是这个进程已经准备好了随时可以被调度。 在将来如果某一个进程准备好了他想被cpu所执行那么他只需要 入队到 运行队列即可。 只需要等待操作系统的算法计算哪一个进程应该被调度当计算到 新入队的进程这个新进程就可以被执行了。
当然肯定会出现进程占用cpu不退的情况比如你写了一个死循环的程序那么当这个程序死循环之后就会一直占用这个 cpucpu 肯定不会一直让这个 进程占用这个cpu。所以给每一个进程都有一个时间片。 简单来说就是这个进程在cpu上执行的之间只能是 一或多个个时间片的时间。如果过了这个时间片这个进程还没有执行完毕的话就要在 入运行队列然后等待操作系统调度cpu执行。 所以在上述简单操作系统调度进程 在 cpu 上运行的描述之下在一段时间之内所有的在运行队列之上的代码都会被执行。这种情况我们称之为 --- 多个进程的并发执行。
当然在这段时间之内肯定不只是执行刚开始的那些进程在此之中就会大量的把进程从cpu 上拿下来再从输入设备当中拿上去cpu 的过程 这个过程称之为 --- 进程切换。 因为 cpu 处理速度很快所以在我们看来根本感受不到 进程 在切换的过程这些进程都像是在同时被执行一样所以看起来像是 这些个进程都是在一起运行一样。 阻塞状态 进程就是 我们写出来的程序软件被执行时就变成了进程那么程序怎么多功能也是千变万化那么这个程序所需要的 数据也是不同的。那么很多程序就是需要 某些数据才能运行数据也是 进程的组成的一部分所以数据是不可或缺的
那么进程也不是想拿到什么数据就可以拿到什么数据的就跟最开始我们举的例子一样程序当中有 scanf函数我们不在键盘上输入数据那么这个进程就一直不会就收到数据那这个进程是不能执行的。那这个进程就是没有就绪也就不能放在 运行队列当中。
我们把上述这种没有 就绪的进程这种进程的状态就是 阻塞状态。 当然在一个 操作系统当中他要管理软硬件资源就是使用 先描述在组织的方式来管理的比如他要管理键盘这个硬件那么就是把管理键盘需要的 属性值都构造成一个对象这个对象当中就存储了管理 键盘需要的 属性值。
在这些属性值当中也有一个队列这个队列也是像 cpu 当中的 运行队列一样是用来给要使用这个硬件的 进程来进行排队的。
比如当前某一个进程需要使用到 键盘那么好就去键盘的 队列当中排队去吧如下图所示 这些队列在 每一个可能被进程使用的硬件当中都存在作用也是一样的。
我们把这些队列称之为 --- 等待队列。 像上述 scanf函数进程的例子如果键盘硬件当中轮到 这个进程来执行了键盘当中有数据了那么这个进程就可以读取键盘当中数据然后如果这个进程没有数据需要接受了这个进程也处于就绪状态了那么这个进程就可以入到 cpu 的运行队列当中去等待 操作系统调度器执行就行了。 所以阻塞队列有 N 多个 有非常多个想象我们硬件有这么多而且不止 硬件有阻塞队列。 挂起状态 - 操作系统对于阻塞状态的优化换出 和 换入
那么这么多的进程在 怎么多的 阻塞队列当中排队那么操作系统所管理的内存资源总是会有不足之时。那么如果内存被占满了不就意味着系统要进行卡顿吗
所以操作系统在此处进行了优化本来是不优化的因为此时迫不得已进行了优化。
之前我们说过在队列当中排队是 PCB 在进行排队此处的优化就在此
操作系统管理的内存资源已经快满了就把 一些 进程的 数据和代码 这些 换出 到外设比如是磁盘当中当 排队轮到这个进程执行之时才把 外设 当中的 代码 和数据 换入 到 内存当中。 那么当某一个进程的代码和文件已经被 换出了那么这个进程此时就是 挂起状态。
所以说一个进程只要是 PCB 在内存当中被操作系统所管理那么我们就认为是当前进程正在被运行而 这个进程的 代码和数据 在不在 只是这个进程的不同 状态代码和数据在不在 不是这个进程在不在的判断条件PCB 在不在才是。 Linux 当中的任务管理器 -- top 使用 top 可以查看到 当前实时状态下所以的正在运行的进程的很多信息包括下述要说明的 Linux 当中 进程的各种状态 Linux 当中的进程状态是如何维护的
我们上述所描述的状态只是一个框架对于 操作系统实现的细节不同的操作系统所实现的细节是不一样的比如在 Linux 当中就实现了更多的 进程状态用于更好的管理 进程这些进程的状态是保存在一个 const 字符数组当中的
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 */
};
这就是课本上的操作系统 和 具体实现的操作系统之间差别。
接下来我们来一个一个描述这些个状态。
R(running运行状态 和 Ssleeping阻塞状态浅度睡眠 只要进程的PCB是在 cpu 的运行队列当中那么就说明这个当前这个进程的状态就是 运行状态。
同样在等待外设在外设的等待队列当中时候我们称之为 阻塞状态。
我们不能拿我们之间的认知去 理解操作系统 在运行时给进程赋予的状态因为现实在使用 操作系统时所涉及的很多情况。比如如下情况
此时我们编写下面这个死循环代码 此时我们在运行 有上述 源文件生成的 text 可执行程序时候我们来查看这个 text 程序运行状态 发现此时的 text 进程是 S 阻塞状态而不是 进程状态。
我们还发现上述在描述状态不只是描述 R 或者是 S而是 R S 来描述的那么此时的 “”是什么意思呢
这里的 其实代表的是这个进程是在 前台运行的。
什么是前台呢在上述我们运行程序之后我们再输入执行bash 就不会再解析这个指令了。
如果想让一个程序在后台运行的话需要在 运行之时加上一个 符号。这个符号表示的是 这个程序在后台运行。 当一个程序在后台一直运行话我们此时使用 ctrl C 是不能把这个进程给关闭的因为这个进程现在不受 前台管理了所以此时我们要是用 kiil -9 PID 来杀掉程序 如上述所示ctrl C 是不能结束进程的。 所以其实 bash 命令行解释器在我们输入命令回车之前都是阻塞状态sleeping在命令行当中等到我们输入命令确认输入然后 bash在进行解析创建子进程等等的操作。
如下所示bash 都是在 S 状态 Ddisk sleep-- 深度睡眠也是一种阻塞状态磁盘休眠状态
像上述的 S 状态也是一种阻塞状态S 的阻塞状态是 浅度睡眠处于 浅度睡眠的 进程可以被随时唤醒可以随时响应外部的变化而唤醒的人可能是 用户也可能是 操作系统。
而 D 状态是 深度睡眠或者称之为 -- 磁盘睡眠。 要了解 深度睡眠我们先来了解一个场景
我们之前谈到过挂起状态挂起状态就是 把 进程的 数据和代码部分 放到外设当中进程暂时的存储但是 PCB 还在内存当中进程排队这样的可以减轻 内存的压力。那么这种情况比如是把 数据和代码 放在 磁盘当中存储的那么此时进程就肯定要拜托 磁盘让磁盘帮忙把 数据写入磁盘当中。
或者说此时某一个进程需要对磁盘当中写入数据那么这两种情况都导致一个结果就是 进程需要等到 磁盘写入数据。
如果磁盘写入数据要考虑很多的情况比如找到一个合适空间来存储判断是否能存储等等情况所以磁盘写入是需要耗费时间的。 那么进程在等待的途中就没事做优哉游哉的等待 磁盘写好数据给他回应进程才好把数据是否成功写入的结果返回给上层。
如果在磁盘等待的途中操作系统所管理的 内存资源已经严重不足了那么操作系统就会去想办法优化内存资源比如该挂起的挂起该释放掉释放等等操作。如果是是在没有办法的情况下操作系统为了保证自己的程序和 内存资源不会挂掉那么他就是会去杀后台也就是杀进程。
如果操作系统选择杀进程的话就是以当前能力的操作系统实在没办法优化了只能杀掉他认为不重要的进程了。
如果此时因为 上述在等待 磁盘回应的 进程在操作系统看来就非常的“闲”那么他有可能就会直接杀掉这个进程。
那么问题就来了假设这个进程是在写入一些非常重要的数据而又恰好磁盘在写入过程当中出现了问题此时磁盘就要给 进程报错但是又发现进程已经不在了。
那么这个重要数据就已经丢失了如果为这个事情开庭的话你是不是会认为是操作系统的问题觉得因为是操作系统“杀的”啊就是他的错。
但是实际不然可能 操作系统 磁盘 进程三者都没错。
操作系统只是在完整自己的本职工作他的本职就是要让用户一个稳定的不会时不时就卡顿的平台供用户使用如果到了上述那种非常极端的情况由不让它杀掉这个进程的话那么可能就会导致整个操作系统的瘫痪那么此时出问题的就不会这一个进程了而且所以的进程都会崩掉。
磁盘当然也不是他因为它就只是一个跑腿的只是帮助 进程来在外设当中写入数据而且事先 磁盘就会告诉进程此次写入数据操作是会写入失败的。
进程就更不是了进程本来就是受害者。
所以在进程等待磁盘写入数据这么关键的事情之上在磁盘写入数据完毕之前我们要让进程不被任何“人“ ”“杀掉”。
我们把上述 这个进程 所处的状态 称之为 --- Ddisk sleep。
D 状态的进程不响应 操作系统的任何请求所以才不会被操作系统杀掉。在这个状态的进程通常会等待IO的结束。 当进程 从 磁盘上得到的了 数据被写入成功或者失败的 信息之后进程才会从 D 状态解除到其他状态。 如果此时在内存当中出现了很多的 D 状态的进程操作系统是没有资格杀掉这些进程的而且只要是 磁盘没有给 进程返回写入信息那么就算是把 操作系统重启都是没有办法解决的。 所以此时要么是 直接重启断电要么是等待磁盘 返回写入信息。 因为cpu 处理都是毫秒级别的速度所以用户是感受不到 进程切换 挂起等等操作的过程的所以如果当用户已经查找了 哪怕是 一个 D 状态的进程了说明这个进程已经存在很长时间了那么就说明操作系统已经在奔溃的边缘了。
一般情况下D 状态的进程 非常少而且持续时间很短。 因为 D 状态的进程不好演示演示的话有可能会弄坏操作系统所以不建议演示但是可以使用 dd 命令来操作具体请看下述
Linux dd 命令 | 菜鸟教程 (runoob.com)
T (stopped) 停止状态
这指的停止状态不是 这个进程直接被杀掉了而是 这个进程暂停了 所以暂停代表的就是只是现在停止了但是后序可以继续执行。
Linux 当中我们可以使用 kill -l 来查看 kill 指令 能给进程发出的各种信号的信息 在这么多信息当中有 18 和 19 这两个信号18 信号是 SIGCONT 就是继续执行进程19 信号 是 SIGSTOP 就是暂停执行进程。
但我们使用 19 信号暂停某一个 进程之后这个进程当前就处于 T (stopped) 停止状态。 像上述例子当中的 text 就被暂停掉了当前 text 进程所处的状态就是 暂停状态。
此时可以使用 kill -18 PID 来恢复上述 text 进程但是注意此时恢复的进程是在后台运行的在 不受前台的控制。如果此时向停止这个 进程那么就要使用 kill -9 PID 给这个进程发出9号停止命令。 T 状态和 S 状态的区别 S 状态是阻塞状态进程的PCB 在各个硬件或者其他外设等等设备的 阻塞队列当中排队的进程此时的状态就是 阻塞状态。也就是说处于阻塞状态的进程是在等待某一个设备给予数据或者是给出信号才能继续运行。
而T状态是 不一定需要等待 某一个设备的 数据 或者 信号就像上述的 text 进程这个例子我在停止之后我想什么时候继续执行这个进程就 使用命令 让他继续执行即可。不一定需要某些硬件的数据才能执行。
也就是说处于 T 状态的 进程不一定是在排队等待而是只是单纯的暂停而已。只是目前想要 这个进程等待一下暂停一下可能是等待其他的事件发生。那么我们就可以把这个进程 设置为 T 状态。
当进程 处于 T 状态之后T 进程的代码就完全的暂停执行了一般而言进程就不再接受各种信号或数据了。而 S 状态就是要等待某种资源信号。
其实你也可以把 T 理解成一种阻塞状态但是不同的是T 状态的 进程不一定是在 等待某些资源可能就只是停止了可以被用户所控制。但是 S 状态是一定要等待某种资源的这点你要清楚。 gdb调试器 举例 T 状态的运用场景
当我们在使用 gdb 调试某一个程序之时我们可能会 逐步调试可能会 逐块调试或者是 直接运行到 断点处运行到条件断点处等等调试操作。
那么这些操作在执行 的之间时刻我们是要分析这个代码在当前状态下的各个信息是否正确。以此来逐步推断出 可能的错误所在。
那么其中判断推断的过程肯定是需要时间的在此期间代码是停止运行的那么如何让正在运行的进程停止下来那么其实也是一种阻塞但是其中不会只有S状态的阻塞还有T状态的阻塞。
如下所示当我们开始执行 text 的gdb 调试时就是 gdb 是处于 S 阻塞状态下的但是 text 是处于 T 停止运行状态下的 所以T 状态有自己的运用场景和 S 状态是有区分的。
X (dead死亡状态 - Z (zombie) 僵尸进程 - 孤儿进程 这个状态就顾名思义了一个进程运行完毕了那么这个进程就是退出内存释放资源那么这个进程所处的状态就是死亡状态了。
Linux 当中的 X 状态就和 很多教科书当中所说的 终止态是一样的。 但是你会很好奇为什么一个进程已经死亡了那么为什么要有这种状态呢
其实你想得没错X 状态只是一个返回状态你不会在任务列表里看到这个状态。
所以在程序死亡之前处于 X 状态之前还有一个状态叫做 Z 僵尸状态。操作系统要在 处于僵尸状态的 进程给 做检查此时操作系统不是直接就把这个进程的 资源等等信息直接释放清除了而是还有维持一段时间当都检查确认之后再进行释放。
那么这个信息维持给谁看呢就是这个僵尸进程的父进程因为此时最关心当前这个僵尸进程的就是父进程因为父进程 创造这个 子进程是需要耗费资源的同时也可能是 互相之间是有 交互的。
当父进程拿到 这个 僵尸状态的子进程的信息之时才会把这个子进程的状态 变成 死亡状态释放空间 。
把这种 已经死亡的课程但是需要有父进程来收集子进程的信息操作系统为这个子进程维持的状态就叫做 Z (zombie) 僵尸 状态。 进程一般在推出时如果父进程没有主动回收子进程的信息子进程就会一直处在Z的状态。而进程相关的资源尤其像是 task_struct 结构体对象不能被释放。 僵尸进程在死亡时候 如果父类没有做出回应接收信息那么这个僵尸进程就会一直占用 内存资源我们把这种由于僵尸进程占用内存资源的情况称之为 --- 因为僵尸进程而引发的 内存泄漏 问题。 当然如果你想看某一个进程的 僵尸状态话可以使用一下代码
#include stdio.h
#include stdlib.h
int main()
{pid_t id fork();if (id 0) {perror(fork);return 1;}else if (id 0) { //parentprintf(parent[%d] is sleeping...\n, getpid());sleep(30);}else {printf(child[%d] is begin Z...\n, getpid());sleep(5);exit(EXIT_SUCCESS);}return 0;
}
因为cpu 处理的速度很快假设你是使用使用 ./ 来运行的某个程序的话是查看不了 这个进程的 僵尸状态的。因为 此时这个进程是 bash 这个命令行解释器进程的 子进程当 这个子进程结束运行时就会立马被 bash 父进程接受信息从而转变为 死亡状态释放资源。 孤儿进程 如果在一对父子进程当中父进程先死亡了但是此时子进程还在运行没有挂掉在这种情况下这个子进程的 PPID 会变为1也就是此时这个子进程的父进程变为了 PID 为1 的这个进程。 使用 top 命令 来查看 PID 为1 的进程是什么
发现是 systemd 这个进程。 我们来查看 systemd 这个进程的详细信息 其实 1 号进程也就是这个 systemd 进程其实就是操作系统本身。有些操作系统当中和可能不是 systemd 也可能是 inti都是代表的是 操作系统进程 我们把上述的父进程先退出子进程 的 父进程就是 1 操作系统进程了这个子进程 就称之为 -- “孤儿进程”。这个子进程就被 操作系统给领养了。
为什么要被领养呢原因很简单当一个进程在结束之时要先变成僵尸状态就要被父进程回收信息总是要有一个为这个子进程回收信息的那么就让操作系统帮忙了。 其实如果这个子进程是在父进程 运行之时才创建的就是我们上述写的代码查看僵尸进程的例子那么不是 bash 不想管这个子进程是因为 这个子进程不是 bash 进程所创建的是有 bash 创建的这个子进程的 父进程 所创建的。而 爷爷不管孙子 而。操作系统不一样不需要使用一些接口来转移关系直接在内核层面就可以直接 修改父子关系做上“收尸者”。 僵尸进程的危害
其实关于僵尸进程的危害我们在上述已经阐述过一次了就是当父进程如果一直没有接收 子进程僵尸状态的信息的话那么子进程就会一直处于僵尸状态操作系统就会一直帮助 子进程来维持在内存当中等待父进程来接收信息
那么就会造成因为 僵尸进程 带来的 内存泄漏问题。 父进程之所以要接收子进程返回的信息是因为父进程花了资源和时间去创建了子进程目的就是为了让子进程帮忙父进程来办事那么子进程现在要 死亡了称为僵尸进程了那么子进程现在把这个事情办得怎么样了父进程是需要知道的。
像操作系统要维护 僵尸状态的子进程的话就要维护这个子进程的 task_struct 结构体对象而且如果父进程创建了多个子进程的话那么如果此时 变成僵尸状态的不止 一个 子进程的话那么所造成的内存泄漏问题就更严重了。 关于 Linux操作系统当中的 进程执行状态的关系图