目录
1.进程状态
1.1 阻塞
阻塞
:进程因为等待某种条件就绪,而导致的一种不推进的状态
通俗来说,阻塞就是进程卡住了,因为缺少了某种资源
所以阻塞一定是在等待某种资源
为什么阻塞?
因为进程要通过等待的方式,等具体的资源被别人用完之后,再被自己使用。
简单理解进程等待和资源
- 资源比如磁盘、网卡、显卡 等各种外设
- 例如,当我们下载游戏时,下载速度0kb,此时CPU无法继续正常的下载,需要等待网络资源,CPU就将这个进程设置为阻塞状态,此时进程就在等待。
具体理解进程等待
系统为了管理各种各样的进程,需要为进程先描述创建task_struct,然后再组织形成双链表形式的数据结构,同样,系统为了管理各种各样的硬件资源(磁盘、网卡、显卡 等各种外设),就需要为他们创建struct来对硬件资源进行描述,然后再组织形成数据结构
例如为了管理网卡操作系统创建了struct dev,其中包含了struct task_struct* queue的等待队列,当一个进程等待网卡资源时,cpu无法调度这个进程,这个进程就被维护在网卡struct dev结构体中的queue等待队列中。
例如当scanf等待用户输入时,该进程就是阻塞状态,这个进程被维护在键盘struct dev结构体中的queue等待队列中。
实际操作系统的实现要复杂的多,这只是一个基本的理解过程。
总的来说:
阻塞就是进程不被调度,一定是当前进程需要等待某种资源就绪,一定是进程的 task_struct 结构体需要在某种被 OS 管理的资源下排队
1.2 挂起
挂起:当 CPU 资源紧张时,将 进程的代码和数据交换至磁盘中挂起
,此时内存中只有 PCB
挂起可以看作一种特殊的阻塞状态,因此挂起的全称是阻塞挂起
2. 进程状态
进程和程序不相同,进程是活动的且有状态变化的
。一个进程是有多个状态的。
这里我们具体谈一下Linux操作系统中的进程状态,Linux操作系统的源代码当中对于进程状态有如下定义:
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 */
task_struct是一个结构体,内部会包含各种属性,其中就有状态
struct task_struct
{
int status;
…
}
2.1 运行状态-R
进程是R状态,不代表正在运行,代表可被调度。换句话说,进程只有是R状态才可被调度,其他状态要先转为R状态,才能被OS调度。
这表明处在运行状态的进程要么是在被OS调度中,要么在运行队列
里。
当操作系统需要切换进程运行时,就直接在运行队列中选取进程运行。
进一步理解运行状态
当我们运行下面这个简单的死循环,我们再来查看当前进程的状态
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("Hello\n");
}
return 0;
}
我们发现该进程并不是处在运行状态上,他的状态是S+(睡眠状态)( + 表示当前进程在前台运行中),并不是在运行状态,这和我们的认知相矛盾。
原因在于:
- 代码中存在printf,这个函数需要去访问外设资源。
- 我们知道,CPU的速度非常快,外设的速度非常慢。
- 当printf想要访问屏幕外设来打印时,这个外设并不一定准备就绪,因此进程就在这个外设的
等待队列
中等待。 - 外设准备就绪,进程被CPU调度,打印工作几乎一瞬间就运行完成,因此这个状态R很难被查询到。绝大多数的时间进程都在外设等待队列中排队,所以我们就查到S睡眠状态。
2.2 睡眠状态-S
睡眠 S 的本质就是 进程阻塞
,表示此时进程因等待某种资源而暂停运行。
睡眠 S也称作可中断睡眠
,我们可以强制将其关闭。
ctrl + c 关闭
注意:处在后台运行(也就是不带+号的)的进程无法使用ctrl + c来关闭。
- kill命令关闭
kill -9 pid
2.3 休眠状态-D
当一个进程处于休眠状态(disk sleep)时,表示该进程不会被杀掉,即便是kill命令和操作系统也不行
,只有该进程自动唤醒才可以恢复。
休眠也称为 不可中断睡眠
2.4 暂停状态-T
我们可以让进程处于暂停状态
通过kill -l命令来查看信号
我们可以使用19.SIGSTOP和18.SIGCONT来使进程暂停和恢复
kill -19 PID 暂停进程
kill -18 PID 恢复进程
暂停进程
恢复进程
注意:在 gdb 中调试代码时,打断点实际上就是使进程在指定行暂停运行,此时进程处于追踪暂停状态 t
2.5 僵尸状态-Z
Linux当进程退出时,一般进程不会立即退出,而是会维持一个状态------僵尸状态Z
目的是为了方便后续父进程或是OS读取子进程的退出结果
创建一个父子进程,并运行。
终止子进程,就可以看到子进程的状态变成了僵尸进程
僵尸状态是必要的,进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。 而任务完成的结果,可以用退出码
来体现。
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1)
{
printf("Hello\n");
sleep(1);
}
return 0;
}
这个返回值0返回给了操作系统,告诉他任务顺利完成。
在Linux操作系统,我们可以使用**echo $?**命令获取最近一次进程退出时的退出码。
echo $?
僵尸进程的危害
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护不能释放。
如果不能对僵尸进程进行回收,就会造成内存泄漏
的问题。
2.6 死亡状态-X
这个状态只是一个返回状态,我们不会在任务列表里看到这个状态。因为当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了。
2.7 孤儿进程
当创建一个父子进程,如果退出子进程,此时子进程就成了僵尸进程
。当先退出了父进程时,此时的子进程就被称为孤儿进程
当退出父进程后,父进程无法通过ps指令查询出来,说明此时的父进程已经被回收了。此外子进程的PPID变成了1,也就是操作系统。
- 父进程的父进程是bash,有回收机制,因此无法看到僵尸进程。
- 当终止父进程时,此时的子进程会被OS领养
- 被领养后,后续子进程退出,就能被回收了。这也就是OS领养的原因
以上就是我们对“进程状态”这一主题的全面探讨。通过此次学习,我们初步掌握了进程的不同状态,理解了何为阻塞状态及其产生的原因。同时,我们也深入了解了进程状态转换的各种情况,为今后更深入地学习和控制进程状态奠定了坚实的基础。