Linux:进程状态(进程二)

前篇博客我们了解到了进程的概念,这篇博客我们就来谈谈进程的状态,进一步加深对进程的了解

Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序,就必然会产生很多的进程,而每个进程会有不同的状态

下图是一个较为完整的状态图,现在还看不懂?没关系,当这篇博客结束肯定就会一目了然啦

1.运行&&阻塞&&挂起

在操作系统理论中,运行、阻塞、挂起是描述进程生命周期的核心状态,它们的划分基于进程是否占用 CPU、是否等待资源、以及进程映像(代码、数据)是否在内存中,下面我们将对这三者进行说明,方便我们更容易搞懂linux进程概念

1.运行

我们把pcb(也就是task_struct)在调度队列 (runqueue)(就绪状态,等待 CPU 调度)里面或者已经在cpu里面运行的状态统称为运行状态(running)

2.阻塞

进程因等待不可立即获得的资源 (如 I/O 完成、信号量释放、键盘输入等),主动放弃 CPU,其 PCB 从调度队列(runqueue)中移除,进入特定的等待队列(如 I/O 等待队列)的状态

比如scanf函数,进程要想继续运行下去,就必须使用键盘输入,如果键盘一直未输入(处于不活跃)就不会运行下去,那系统不可能让该进程一直占着调度队列吧,于是就把他链入键盘的等待队列中,当键盘输入内容时,就会先告诉操作系统,我输入内容啦,操作系统就会检查一下键盘的等待队列中有没有内容,有的话就把他重写链入调度队列里面继续运行

其实运行和阻塞是逻辑上反向的过程:

运行->阻塞:等待资源→资源未准备->进入等待队列→资源就绪→唤醒回调度队列

阻塞->运行:等待队列→资源就绪→回调度队列

3.挂起

当系统内存不足时,内核将进程的映像(代码、数据、堆栈)从内存换出到磁盘(swap 分区),仅保留 PCB 在内存中的状态。挂起状态的进程无法被直接调度,必须先将映像从磁盘换回内存(换入),才能转为运行或阻塞状态

磁盘会专门开一个swap交换分区,大概是内存的1~1.5倍,用于存放因为内存不足而交换的数据和代码

挂起与其他状态的转换:

  • 阻塞 → 挂起:内存不足时,内核将长期阻塞的进程映像换出到磁盘。
  • 挂起 → 阻塞:内存充足时,内核将进程映像从磁盘换入内存,恢复为阻塞状态(仍在等待资源)。
  • 挂起 → 运行:若进程映像换入内存,且等待的资源已就绪,则从挂起状态直接转为运行状态(进入 runqueue)

2.操作系统的无奈之举->挂起与OOM Killer 终止进程(丢弃进程)

假如内存已经超级满了,此时操作系统会直接将某些进程挂起,不然可能会影响所有进程,一般会将等待队列里面的资源swap,就如果这样还是满的,可能会直接将调度队列里面的进程swap,如果磁盘swap分区也满了呢,操作系统为了不影响所有进程,可能会直接将丢弃某些进程,内核会按 "先保核心、后牺牲非核心" 的顺序处理丢弃

3.Linux内核里面的进程状态

了解了基本的操作系统中描述进程生命周期的核心状态,我们来看看 Linux内核源代码怎么说
为了弄明⽩正在运⾏的进程是什么意思,我们需要知道进程的不同状态。⼀个进程可以有⼏个状
态(在Linux内核⾥,进程也叫做任务(翻译问题))
下⾯的状态在kernel源代码⾥定义:

/*
*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 */
};

  1. R 运行状态(running/runnable)并不意味着进程一定在运行中,它表明进程要么是在 CPU 上执行指令,要么是在运行队列(runqueue)中等待 CPU 调度,是唯一参与 CPU 竞争的状态。
  2. S 睡眠状态(sleeping/interruptible sleep):意味着进程在等待事件完成(如 I/O 就绪、键盘输入、信号量释放),此时进程会从运行队列移除,进入等待队列,可被信号(如 SIGINT)唤醒,是 Linux 中最常见的阻塞状态。
  3. D 磁盘休眠状态(Disk sleep/uninterruptible sleep):进程通常在等待关键 IO 操作完成(如磁盘读写、内存页交换),处于深度阻塞状态,无法被信号唤醒(包括 kill -9),避免关键操作被打断导致数据不一致。
  4. T 停止状态(stopped):可以通过发送 SIGSTOP 信号(强制暂停)或 SIGTSTP 信号(用户 Ctrl+Z 触发)让进程暂停,暂停后可通过 SIGCONT 信号恢复运行,暂停原因是外部信号干预,而非等待资源。
  5. t 被跟踪状态(traced):进程被调试器(如 gdb)跟踪,通常因触发断点而暂停,是 T 状态的特殊子集,需通过调试器命令(如 continue)恢复运行,状态标记为小写 t 以区分普通停止状态。
  6. Z 僵尸状态(Zombie):子进程已终止(代码执行完毕),但父进程未调用 wait ()/waitpid () 函数回收其 PCB(进程控制块),进程不再占用 CPU 和内存资源,仅残留 PCB 结构,过多会耗尽 PID 资源。
  7. X 死亡状态(dead):这个状态只是进程生命周期的最终返回状态,表明进程已完全终止,所有资源(包括 PCB)被内核彻底回收,你不会在任务列表(如 ps 命令输出)中看到这个状态。

下面是这7种状态与基本状态的关系图:

状态类型 包含状态 触发原因 是否等待资源 能否被信号唤醒 核心场景
运行 / 就绪 R 正在执行或等待 CPU 调度 -(本身就是就绪 / 运行) ls、循环程序、调试恢复后
阻塞状态 S、D 等待资源(I/O、信号量等) S 可唤醒,D 不可 等待键盘输入、磁盘 I/O、网络请求
暂停状态 T、t 外部信号(SIGSTOP)或调试 T 可被 SIGCONT 唤醒 Ctrl+Z暂停、gdb 断点
特殊状态 Z、X 子进程未回收(Z)、完全终止(X) Z 需回收父进程,X 无 子进程退出未回收、进程终止

4.通过实验来观察状态

1.R状态

运行状态

运行一下

为什么这里有S+与R+呢,那是因为printf这个过程太快了,于是大部分时间都处在等待状态,如果我们把printf删掉,那么就是一直R+状态

+是什么意思呢,说明该进程在前台 运行,没有+就说明说后台运行

2.S状态

其实就是阻塞状态,最简单的例子就是scanf

此时就是S+状态

3.D状态

这个比较有意思,举个例子,如果一个用户往一个银行打1000块钱,于是用户电脑正在磁盘查找是否有1000块钱,于是该进程就进入了阻塞状态,但是此时电脑内存已满,并且磁盘swap分区也满了,操作系统无奈只能丢弃某些进程,首当其冲的就是阻塞状态的进程,操作系统直接将这个进程丢掉,过了一段时间,磁盘知道了,有这1000块钱,于是告诉进程,可是这个进程已经被抛弃了,磁盘只能拿着这个1000块钱的数据无事可做,但是磁盘要干别的事情,只能也将这个数据抛弃,可是用户是不知道的,因为进程已经没了,没有进程可以告诉用户是否存在1000块钱,于是也就无法进行接下来存钱的操作

可是这个事件里面谁都没有错,操作系统为了一整个进程无奈丢弃S状态进程,进程需要服从操作系统,所以必须被丢弃,磁盘需要干自己的事情,不可能一直等进程

所以为了避免这种情况,于是将内存与磁盘之间的交互从S状态设置为D状态,告诉操作系统不要动这个进程

4.T状态

如果一个进程被我们使用ctrl+z停止,就是T状态

5.t状态

当使用gdb的时候就是t状态,因为此时打了一个断点,断点就是终止程序的嘛,所以就是t

我们用-g编译

打断点,然后r运行,就会发现t状态

6.X状态

由于一个进程结束时(且已被父进程知晓),会直接调用X状态,所以很难观察到,所以这里知道有这个状态就行

7.Z状态

如果一个子进程先退出,而父进程不退出也不接收子进程退出信息(比如告诉父进程我是因为什么退出的,时异常还是正常退出)的话,那么子进程就会进入僵尸状态,也就是Z状态,只有当父进程接收子进程退出信息,子进程才可以变成X状态

发现变成了Z状态

ctrl+Z于是双双变为X状态退出


僵⼫进程危害

进程的退出状态必须被维持下去,因为他要告诉关⼼它的进程(⽗进程),你交给我的任务,我
办的怎么样了。可⽗进程如果⼀直不读取,那⼦进程就⼀直处于Z状态?是的!

维护退出状态本⾝就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,
换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的!

那⼀个⽗进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数
据结构对象本⾝就要占⽤内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置
进⾏开辟空间!

内存泄漏?是的!

5.孤儿进程

与僵尸进程相反,孤儿进程顾名思义就是没有父母的进程,也就是父进程比子进程先结束,那次是怎么办呢,如果没人接管他,就会变成僵尸进程,造成内存泄露,所以此时pid为1的进程(可以简单理解为操作系统)接管他了,不仅接管了,而且还自动将其变为后台进程(也就是.cmd &),所以我们无法使用ctrl+c退出,只能使用kill -9 pid来杀死子进程

ctrl+c无法杀死子进程

用kill -9 pid就可以杀死

好啦,这就是有关进程状态的内容啦,我们下一篇博客见>.<

相关推荐
悟空空心3 分钟前
服务器长ping,traceroute
linux·服务器·网络·ssh·ip·ping++
Ghost Face...4 分钟前
Docker实战:从安装到多容器编排指南
运维·docker·容器
此生只爱蛋29 分钟前
【Linux】正/反向代理
linux·运维·服务器
qq_54702617936 分钟前
Linux 基础
linux·运维·arm开发
zfj32142 分钟前
sshd除了远程shell外还有哪些功能
linux·ssh·sftp·shell
废春啊1 小时前
前端工程化
运维·服务器·前端
我只会发热1 小时前
Ubuntu 20.04.6 根目录扩容(图文详解)
linux·运维·ubuntu
爱潜水的小L1 小时前
自学嵌入式day34,ipc进程间通信
linux·运维·服务器
保持低旋律节奏1 小时前
linux——进程状态
android·linux·php
zhuzewennamoamtf1 小时前
Linux I2C设备驱动
linux·运维·服务器