从理论到实践:操作系统进程状态的核心逻辑与 Linux 实现

发布于:2025-07-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

前言

在操作系统的世界里,进程的 “生老病死” 并非随机无序,而是被一套精密的状态管理机制所调控。无论是程序从启动到运行的 “活跃期”,因等待资源而进入的 “阻塞态”,还是因内存紧张被暂时换出的 “挂起态”,这些状态的切换背后,藏着操作系统对资源调度、设备管理的底层逻辑。

本文将从理论与实践两个维度拆解进程状态:先从操作系统的通用模型出发,解析 “运行、阻塞、挂起” 三大基础状态的本质 —— 如何通过 PCB(进程控制块)在不同队列间的移动实现状态切换,以及进程调度与设备管理如何通过结构体和队列协同工作;再聚焦 Linux 系统,详细列举其特有的进程状态(如 R、S、D、Z、T 等),结合实例说明每种状态的触发场景、内核逻辑及观察方法。

无论你是想理解操作系统的调度原理,还是想搞懂 Linux 中 “僵尸进程”“不可中断睡眠” 等具体问题,这篇文章都将为你搭建起从抽象理论到具体实现的桥梁,让你对进程状态的认知从 “零散概念” 升华为 “体系化逻辑”。

目录

进程状态(操作系统)

运行&&阻塞&&挂起(概念)

✅ 1、进程调度与设备管理

本质:结构体与队列管理

🧠 2、PCB:进程控制块是调度核心

🏃‍♂️ 3、运行队列 runqueue

💡 4、状态切换的本质:PCB在不同队列之间移动

🖥️ 5、设备管理结构体 device 与等待队列

🔁 6、状态转换流程图

 Linux进程状态

状态列举

详细解释与实例


进程状态(操作系统)

        为了弄明⽩正在运⾏的进程是什么意思,我们需要知道进程的不同状态。⼀个进程可以有⼏个状态(在Linux内核⾥,进程有时候也叫做任务)。
       进程的状态决定了系统该如何处理进程。在操作系统里,可以用#define,宏定义来定义进程的状态,在task_struct结构体定义整形变量来对应状态。
进程状态就是task_struct中的一个整数。
        

运行&&阻塞&&挂起(概念)

CPU运行一个进程,先找到PCB,通过PCB找到进程对应的代码和数据。一个CPU。有一个调度队列。 调度队列就是进程状态为 ready 或 running 的“候选人名单”,它决定谁可以被 CPU 执行,而调度器就是挑选“下一个上场的选手”的裁判。
运行队列sruct struct*runqueue,里面有一个个PCB结点。所以PCB既在全局链表也在运行队列中。
运行:进程在调度队列中,进程的状态就是running。
阻塞:等待某种设备或者资源就绪。
操作系统对计算机的软硬件资源进行管理。像键盘,显示器,网卡等设备。操作系统像对这些硬件资源进行管理--先描述在组织。一个一个结构体device串联起来。每个device里面有个等待队列。

挂起:CPU,内存资源比较紧时,将进程的代码和数据放到外设磁盘交换分区中,腾出可用资源。(阻塞挂起和就绪挂起)

✅ 1、进程调度与设备管理

本质:结构体与队列管理

我们可以把操作系统的运行抽象为:

“通过维护多个队列和结构体,实现对 CPU、内存、设备等资源的调度与控制”


🧠 2、PCB:进程控制块是调度核心

struct task_struct {
    pid_t pid;
    long state;             // 进程状态:running、sleeping 等
    unsigned long flags;
    struct mm_struct *mm;   // 指向进程地址空间的指针
    struct task_struct *parent;
    struct list_head run_list;  // 运行队列用的链表节点
    ...
};

✳ 位置:

  • 所有 PCB 放在一个全局链表中,便于进程查找和管理

  • 同时活跃进程还会被挂入运行队列 runqueue中,供调度器选择运行


🏃‍♂️ 3、运行队列 runqueue

struct runqueue {
    struct task_struct *curr;     // 当前正在运行的任务
    struct list_head queue;       // 任务链表
    int nr_running;               // 正在运行的任务数
};
  • 操作系统调度器从 runqueue 里选出下一个运行进程,赋值给 CPU 执行。

  • 所以 “运行状态” = 在运行队列中 + 被 CPU 选中执行中


💡 4、状态切换的本质:PCB在不同队列之间移动

状态 描述 数据结构变化
running 占用 CPU 正在运行 PCB 在 runqueue 中,且被调度执行
ready 有资格运行但暂未被调度 PCB 在 runqueue 中等待 CPU
sleeping 等待 I/O(如键盘、网卡等) PCB 被挂到设备的 wait_queue
zombie 运行结束但父进程未回收资源 PCB 保留在进程表中
stopped 被暂停 被挂到某个暂停状态队列(如 ptrace)

🖥️ 5、设备管理结构体 device 与等待队列

struct device {
    int id;
    int status;                  // 是否就绪
    void *data;                  // 设备私有数据
    struct device *next;         // 串联所有设备
    int type;                    // 类型:键盘、网卡...
    struct task_struct *wait_queue;  // 等待此设备的进程队列
};

设备访问流程:

  1. 进程访问设备但设备未就绪

  2. 系统将当前 PCB 加入设备的 wait_queue

  3. 将进程状态设为 sleeping

  4. 当设备就绪,唤醒 wait_queue 上的进程 → 重新进入 runqueue


🔁 6、状态转换流程图

+----------+      block (I/O wait)     +------------+
| running  | ----------------------->  |  sleeping  |
+----------+                           +------------+
     |
     | preempt/yield
     v
+----------+      wakeup               +------------+
|  ready   | <----------------------   | waiting    |
+----------+                           +------------+
     |
     | schedule()
     v
+----------+
| running  |
+----------+

✅ 操作系统本质上是:

“通过结构体组织资源,通过队列调度进程,通过状态切换实现多任务并发。”

进程状态的变化表现之一就是要在不同的队列中进行流动,本质就是就是数据结构的增删查改。把PCB在两个队列中来回串。

 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 */
};
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这⾥的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个 状态的进程通常会等待IO的结束。
T停⽌状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停⽌(T)进程。这个被暂停的 进程可以通过发送 SIGCONT 信号让进程继续运⾏。
X死亡状态(dead):这个状态只是⼀个返回状态,你不会在任务列表⾥看到这个状态。

 

详细解释与实例

有+是进程是在前台出现,在命令行。没有+是在后台进行的。

 while :; do ps axj | head -n 1; ps axj | grep -v grep | grep code; sleep 1; done

 grep -v grep 是一个常用的过滤技巧,用于排除包含 grep 自身的进程

 

./code &(在后面加&) 

等待键盘输入,S阻塞sleeping   可中断休眠,浅度睡眠,可以杀掉

T,t暂停例子

debug下,gdb对程序断点调试,t进行追踪

 ctrl Z 暂停程序

 

 D 深度休眠,代表进程正在等待某个 I/O 操作(如磁盘读写、网络请求、外设交互等)完成,且在此期间无法被中断或杀死。该状态不做演示,一般都是高IO操作,若出现D,就要检查磁盘等设备了。

Z僵尸进程,为了获取退出信息。

在Linux系统里,所有的进程是某个进程的子进程,要么是bash的,要么是自己的,创建子进程的目的,是为了让子进程完成某种事情的。父进程就需要知道完成的结果的相关信息,子进程退出不能释放所有资源,会保留最小化的 PCB 信息,退出的信息暂时维持住,在子进程退出之后,父进程获取信息之前,就要有一个状态就是Z状态。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
    pid_t id=fork();
    if(id<0){
         perror("fork");
     }
    else if(id==0){
        int count=5;
       while(count--){
             printf("我是一个子进程,pid=%d,count:%d.\n",getpid(),count);
            sleep(1);
         }
    }

     else{
         while(1){
             printf("我是一个父进程,pid=%d\n",getpid());
            sleep(1);                                                                              
         }
    }
    
  return 0;
}

当子进程在 5 秒后退出时,由于父进程没有调用wait()waitpid()来回收子进程的退出状态,子进程会变成僵尸进程(Z 状态)。

如果父进程一直不管,不回收,不获取子进程的退出信息,那么Z一直存在,会引起内存泄漏问题。

 进程退出了,内存泄漏问题还在不在?自己代码引起的内存泄漏不存在了。

什么样的进程具有内存泄漏问题是比较麻烦的?常驻内存的进程 

结束语

进程状态的管理,本质上是操作系统对 “有限资源” 与 “无限需求” 的动态平衡艺术。从理论层面的 “运行、阻塞、挂起” 模型,到 Linux 系统中精细的状态划分(如 D 状态对 I/O 原子性的保护、Z 状态对父子进程通信的支持),每一种状态的设计都承载着特定的功能目标 —— 既要保证进程高效运行,又要避免资源竞争与数据不一致。

理解进程状态,不仅是掌握 “查看状态”“分析问题” 的实用技能,更能帮我们透过现象看本质:操作系统如何通过 PCB 和队列 “操控” 进程的生命周期?不同状态的转换如何体现 “调度优先” 与 “资源依赖” 的权衡?这些思考,将为我们深入学习进程调度算法、内存管理、设备驱动等知识打下坚实基础。

如果你在实践中遇到了特殊的进程状态问题(如长期 D 状态的排查、僵尸进程的清理),不妨回头再梳理这些理论逻辑 —— 答案往往就藏在状态转换的底层原理中。欢迎在评论区分享你的实践案例,让我们一起在 “理论指导实践,实践反哺理解” 的循环中,深化对操作系统的认知。


网站公告

今日签到

点亮在社区的每一天
去签到