目录
(2)S状态(Linux中的阻塞状态--sleeping)D状态(深度睡眠状态--disk sleeping)
(5)X状态(死亡状态--dead)和 Z状态(僵尸状态--zombie)
一、进程状态(对于一般的操作系统)
对于一个操作系统,这里,我们先要理解重要的3种状态:就绪、阻塞、挂起
1.就绪
有些地方将就绪称为运行。
首先我们要认识就绪队列:
简单来说:所谓就绪队列,就是进程就绪了,在该队列中排队,等待被CPU执行。
展开一下:
就绪队列往往是由链表构成的队列,CPU需要从头向后通过指针逐步查看进程,并且通过进程的PCB将代码和数据加载到CPU中开始运行。
进程在就绪队列中的状态就叫做就绪状态。
2.阻塞
为什么会有阻塞?
根本上,阻塞的存在是为了避免进程浪费 CPU 时间在无意义的操作上。当进程遇到上述任何一种情况时,让它进入阻塞状态可以释放 CPU 给其他更需要的进程使用,从而提高系统的整体效率和响应速度。
阻塞(Blocked)状态是进程生命周期中的一个常见状态,它表示进程暂时无法继续执行,因为它在等待某个外部事件的发生。阻塞的主要原因是进程依赖的资源或条件尚未满足。
例如,当我编写一段带有scanf函数的c语言代码,运行该程序时,程序需要等待用户键盘输入才能继续往下执行。在等待用户键盘输入的过程中我们称为阻塞状态。
当然了,这只是阻塞的一种示例,还有其余示例,在我们之后深入学习Linux操作系统和网络的过程中还会遇到很多种。
3.挂起
可以说,这是极端场景下,操作系统为保障有限的资源分配的一种调度策略:
挂起状态意味着进程暂时被移出内存并存储到磁盘或其他持久性存储设备上,以减少内存使用。当系统资源变得紧张(如内存不足)或者进程长时间处于非活动状态时,操作系统可能会选择将一些进程挂起。
挂起的主要分为两种:
就绪挂起(Suspended Ready):
- 描述:进程原本在就绪队列中等待 CPU 资源,但因为内存不足等原因被挂起到磁盘上的挂起队列。
- 作用:释放内存空间给更活跃或更重要的进程使用。
- 转换:当内存资源再次可用时,这些进程可以从挂起状态恢复到就绪状态,重新参与 CPU 的竞争。
阻塞挂起(Suspended Blocked):
- 描述:进程原本在阻塞状态等待某个事件的发生(如 I/O 操作完成),但由于预计等待时间较长或内存压力大而被挂起。
- 作用:避免长时间阻塞的进程占用内存资源。
- 转换:当等待的事件发生并且内存资源足够时,这些进程可以从挂起状态恢复到阻塞状态,直到事件完成后再进入就绪状态。
二、Linux操作系统中的进程状态
1.从源码看状态:
/*
*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 */
};
本质上,Linux系统的状态定义成了一系列整数。
R, S, D, T, t, X, Z对应的数字分别是0, 1, 2, 4, 8, 16, 32。
状态 | 英文全名 | 对应数字 | 描述 |
R | running | 0 | 运行状态 |
S | sleeping | 1 | 休眠状态(可中断休眠,浅度睡眠) |
D | disk sleep | 2 | 休眠状态(不可中断睡眠) |
T | stopped | 4 | 暂停状态 |
t | tracing stop | 8 | 追踪状态 |
X | dead | 16 | 死亡状态 |
Z | zmbie | 32 | 僵尸状态 |
其中,S状态和D状态都可以看成是
2.Linux进程状态:
进程查看操作(ps命令):
ps -axj
ps -aux
- a: 显示一个终端所有的进程,包括其他用户的进进程
- x:显示没有控制终端的进程,例如后台运行的守护进程。
- j:显示进程归属的进程组ID、会话ID、 进程ID,以及与作业控制相关的信息
- u:以用户为中心的格式显 进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等
常用命令:
ps -axj | head -1; ps -axj | grep 进程名 | grep -v grep
这是一条复合命令,可以根据对应的进程名搜索出进程信息
(1)R状态(运行状态--running):
myprocess.c:
int main() {
while (1) {
}
return 0;
}
我们可以像这样创建一个进程,编译且执行编译后的二进制文件,我的二进制文件叫myprocess。
ps -axj | head -1; ps -axj | grep 进程名 | grep -v grep
STAT那一列对应的就是状态,可以看到我们的进程处于R状态。
PS: R后面的加号意味着进程处于前台进程组中。我们现在只需要明白进程与终端(命令行)相关联时会出现‘+’即可。
(2)S状态(Linux中的阻塞状态--sleeping)D状态(深度睡眠状态--disk sleeping)
我们可以修改myprocess.c的代码来查看该程序是否为S状态。
#include <stdio.h>
#include <unistd.h>
int main() {
printf("I am a process, pid: %d\n", getpid());
int x;
scanf("%d", &x);
return 0;
}
结果展示:
我们发现当程序处于scanf的阻塞状态时,我们查询到的进程状态是S。
D状态就是不可中断的S状态,它和S状态一样,也可以看成一种阻塞状态。
前面提到了操作系统在资源极度紧张的时候,有可能将阻塞的进程挂起,甚至杀死进程来环节内存资源紧张状态。但是,当我们处于一些重要的I/O时(比如往磁盘写入数据),如果操作系统调度策略杀死了这个进程,那么,轻则数据损失,重则磁盘损坏。
因此, Linux操作系统引入了D状态,处于D状态的进程无法被杀死,只能等待其运行完成。
(3)t状态(追踪状态--tracing stop)
当我们使用gdb调试程序设置断点的时候,我们可以查询到进程此时处于t状态,
(4)T状态(暂停状态--stopped)
如上图,当我们用crtl + z 按键暂停程序时可以发现程序处于T状态
(5)X状态(死亡状态--dead)和 Z状态(僵尸状态--zombie)
X状态是一个返回状态,无法在任务列表中看到。我们杀死(终止)一个进程之后,它会返回死亡状态给父进程,告知其已经被终止。
但是在杀死进程到返回死亡状态之前,其实还有一个过程,一种不恰当的叫法是“收尸”。也就是说,进程虽然被杀死了,但是要把自己的信息维持一段时间让父进程去回收。这个“收尸”的过程中进程处于Z状态(僵尸状态--zombie)。
为了模拟实现Z状态,我们编写如下代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t id = fork();
if (id == 0) {
// child
int count = 5;
while (count) {
printf("I am a child process, I am running. %d\n", count);
sleep(1);
count--;
}
} else {
// father
while (1) {
printf("I am a parent process, I am running. \n");
sleep(1);
}
}
return 0;
}
这段代码让子进程输出5次,然后子进程结束。父进程一直执行。当子进程结束之后,我们就能检查到Z状态。
3. 僵尸进程
倘若父进程一直运行,不对子进程加以处理,那么子进程就会一直处于Z状态。此时,我们称这种没有被“收尸”的进程为僵尸进程。
僵尸进程的危害--->内存泄漏!!!
至此,值得关注的进程状态都介绍完了,接下来介绍一种特殊进程--孤儿进程。
4.孤儿进程
父进程退出了,子进程如何呢?
接下来,我们就编写这样一段代码:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t id = fork();
if (id == 0) {
// child
while (1) {
printf("I am a child process, I am running. pid: %d\n", getpid());
sleep(1);
}
} else {
// father
int count = 5;
while (count) {
printf("I am a parent process, I am running. %d\n", count);
sleep(1);
count--;
}
}
return 0;
}
这一段代码和之前手动创造僵尸进程代码恰好相反,我们选择父进程执行5次就停止,而子进程一直执行。
父进程结束前:
这里,我们发现子进程的pid就是40155,而父进程的pid是40154
父进程结束后:
这里我们发现父进程的pid变成了38276,我们将此时的pid为40155的进程称作孤儿进程,它的父进程改变了,这个过程我们称作“领养”。
至于这个38276进程,因为我使用的是自己电脑上装的ubuntu双系统,38276是管理我这个用户的各种服务,包括桌面环境、应用程序等等。当我尝试kill -9 38276的时候,我就会推出到锁屏界面重新登陆用户,且我之前打开的应用程序都被关闭了(不要轻易尝试!!!)。
如果使用云服务器,云服务器的领养一般是由1号init进程来管理的。