🍧前言
🍧每个进程并不是一直运行的,基于进程切换的分时操作系统,使得一个时间段内代码都得以推进。由此便可说明在不同的时刻,同一个进程可能有不同的状态。今天就一起来认识一下几种进程的不同状态吧。
🍧阻塞
🧁什么是阻塞
🍧进程因为等待某种条件就绪而导致的一种不推进的状态。
🍧我们都知道,磁盘中的数据要先被加载到内存之中才能被 cpu 调度,不仅如此,一个进程在被运行时可能还需要其他不同的资源。比如键盘,显示器等外设,若此刻一个进程需要使用这种资源且这个资源被其他进程占用,这个进程便可以理解成被卡住了,即此时这个进程处于阻塞状态。
🍧由此我们便可以推断,阻塞一定是在等待某种资源就绪。
🧁为什么阻塞
🍧进程要通过等待的方式,等具体的资源被别人使用后,再自己使用。
🍧在某个时刻,一定有多个进程需要使用 **cpu **这个资源。而 **cpu **一次只能处理一个进程,因此需要将这些进程管理起来,而之前我们便学过: 管理的本质就是先描述再组织。用于描述进程便是 **PCB **,也可以叫做 **task_struct **。若对这方面还有疑惑的可以参考【Linux】操作系统与进程的概念。
🍧cpu 便可以使用一个队列组织这些进程,按一定顺序进行处理。而这个性质也同样适用于所有的外设,即把进程的PCB链接到等待的设备队列的尾部,则称该进程在等待某种资源。
🍧不仅如此,**PCB **可以被维护在不同的队列中,且一定是某种 **OS **管理的资源。
**eg. **一个进程在进程中运行时缺乏网卡资源,因此将该进程的 **PCB **从 **cpu **的队列中拿出来,然后放到网卡队列的尾端,获得资源后再回到 **cpu **。
🍧挂起
🍧在资源紧张时,操作系统将闲置、不被调度的进程的代码和数据从内存交换到磁盘中。
🍧可以理解成一种特殊的阻塞状态,阻塞挂起状态。
- 因此此时内存中只存在该进程的PCB。
🍧进程状态
🍧一个进程从建立到结束都会经历如下过程,根据身处位置不同,本身的状态也不尽相同。不妨一起认识一下 **Linux **种有多少不同种进程状态。
🧁运行
🍧代表运行状态的字母是 ‘R’ ,+ 代表这个进程在前台运行。
🍧我们一般的认知都是:一个进程处于运行状态,那么它一定在运行。然而真的是这样吗?
🍧我们运行如下代码,再对该进程进行查询。此时代码明明在运行,我们多次查询却都显示是我们下面要讲的睡眠状态S。
#include<iostream>
#include<cstdio>
#include<unistd.h>
int main()
{
while(1)
{
printf("my pid is %d\n",getpid());
}
return 0;
}
🍧实际上,当 **PCB **在 **cpu **的队列上排队,此时的进程就是 **R **状态。
🍧那为何出现上面的情况呢?
- 在代码中我们使用了 **printf **函数,在显示器上进行打印,因此便需要访问外设,使用这个显示器的资源。
- **cpu **的运行速度十分的迅速,处理语句所花费的时间只需要几毫秒。
- 而大部分的时间都在显示器的队列中进行排队,因此不在 **cpu **的队列之中,便不处于运行状态。
🍧若只是死循环,而不访问外设的话,便可以一直处于 **R **状态。
🧁S休眠
🍧S休眠状态又叫可中断休眠,其本质上就是一种阻塞状态。
🍧此时的进程就在等待某种资源就绪,若是长时间没有进展的话,我们也可以使用 **ctrl+c **或 **kill -9 **手动将其关闭。
🧁D休眠
🍧D休眠又叫不可中断休眠,**Linux **中,当内存空间很紧张的时候,系统便拥有杀死一些未运行进程的权力,以缓解内存空间的压力。为了防止由于操作系统误删而导致数据丢失等问题。也正是如此,当这种进程状态出现时,基本上也代表这个机器快宕机了。
🍧在这种情况下,操作系统也无能为力,要么等待其自动结束,否则就只能切断电源了。
🧁暂停
🍧代表暂停状态的是 **T **,此时我们的代码正在循环打印,可以通过使用 kill -19 + pid 暂停这个进程。
[Alpaca@VM-12-9-centos text.4.22]$ kill -19 31593
[Alpaca@VM-12-9-centos text.4.22]$ ps -axj | head -1 && ps -axj | grep text | grep -v grep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29768 31593 31593 29768 pts/39 29768 T 1001 0:00 ./text
🍧此时这个进程就被暂停了,之后使用 kill -18 + pid 可以继续运行这个进程。
[Alpaca@VM-12-9-centos text.4.22]$ kill -18 31593
[Alpaca@VM-12-9-centos text.4.22]$ ps -axj | head -1 && ps -axj | grep text | grep -v grep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
29768 31593 31593 29768 pts/39 29768 S 1001 0:00 ./text
🍧我们也能够观察到,此时的进程由原来的 **S+ **转变成 **S **,表示这个程序此时是在后台运行。因此我们这时候使用 **ctrl+c **是无法杀死这个进程的,而 **kill -9 **仍然可以使用。
🧁死亡
🍧X代表死亡状态,这个状态只是一个返回状态,因此无法被查看到。
🧁僵尸
🍨为什么要创建进程
🍧我们创建进程的目的无非就是希望进程帮我们办事,即便同样是办事也还分作两种情况。
- 我关心结果。事情有办就行不管办的怎么样
- 我不关心结果。事情不仅要办结束后还要向我汇报
这一个性质便体现在代码 **main 函数的返回值中,main **可以返回 **int **也可以是 **void **不返回,我们可以使用 **echo $? **来显示上一个进程的退出码。
🍨僵尸进程
🍧Linux 中,当进程退出时,一般不会立即彻底退出,而是要维持一个状态叫做Z(僵尸状态)。方便后序父进程或OS读取该子进程退出的退出结果。如果父进程一直不读取其退出码,则子进程便会一直处于Z状态。
🍧运行以下代码,之后杀死子进程,进行僵尸进程的模拟。
#include<iostream>
#include<unistd.h>
#include<cstdio>
int main()
{
pid_t id = fork();
if(id==0) //子进程
{
while(1)
{
printf("我是子进程, pid: %d ppid: %d\n",getpid(),getppid());
sleep(1);
}
}
//父进程
while(1)
{
printf("我是父进程, pid: %d ppid: %d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
[Alpaca@VM-12-9-centos ~]$ kill -9 10785
[Alpaca@VM-12-9-centos ~]$ ps -axj | head -1 && ps -axj | grep myfile | grep -v grep
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
10170 10784 10784 10170 pts/44 10784 S+ 1001 0:00 ./myfile
10784 10785 10784 10170 pts/44 10784 Z+ 1001 0:00 [myfile] <defunct>
🍨僵尸进程的危害
🍧维护退出状态也属于进程基本信息,也需要数据维护,所以保存在 **PCB **中, 只要 Z 状态不退出, PCB就要一直维护。
🍧由此便会引发 **C/C++ **最常见的一种问题: 内存泄漏。
🍧孤儿进程
🍧孤儿进程并不是一种进程状态,而是一种现象,当父进程退出时,子进程却还存在时,为了有人能够回收子进程的退出码。因此该子进程会被 **1 **号进程收养,即子进程的父进程为 **1 **。
🍧还是使用上面的代码,不过这次杀死的是父进程。 之后我们便可以看到,此时进程的 **ppid **就已经更改成 **1 **了。
🍧好了,今天进程状态的讲解到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。
版权归原作者 LinAlpaca 所有, 如有侵权,请联系我们删除。