【Linux系统】07 进程状态

目录

​编辑

一、回顾:PCB(task_struct)

[二、Linux 内核的进程状态(内核源码定义)](#二、Linux 内核的进程状态(内核源码定义))

[1. R 运行状态(TASK_RUNNING)](#1. R 运行状态(TASK_RUNNING))

[2. S 睡眠状态(TASK_INTERRUPTIBLE)可中断睡眠](#2. S 睡眠状态(TASK_INTERRUPTIBLE)可中断睡眠)

[3. D 深度睡眠(TASK_UNINTERRUPTIBLE)不可中断睡眠](#3. D 深度睡眠(TASK_UNINTERRUPTIBLE)不可中断睡眠)

[4. T 停止状态(TASK_STOPPED)](#4. T 停止状态(TASK_STOPPED))

[5. Z 僵尸状态(TASK_ZOMBIE)【重中之重】](#5. Z 僵尸状态(TASK_ZOMBIE)【重中之重】)

[6. X 死亡状态(TASK_DEAD)](#6. X 死亡状态(TASK_DEAD))

三、孤儿进程

孤儿进程演示代码

四、僵尸进程演示代码

五、进程状态查看实操命令

六、进程优先级

查看优先级

七、进程的其他基础概念

[1. 进程的独立性](#1. 进程的独立性)

[2. 进程组 & 会话](#2. 进程组 & 会话)

八、面试高频问题

[Q1:R 状态代表进程一定正在 CPU 上运行吗?](#Q1:R 状态代表进程一定正在 CPU 上运行吗?)

[Q2:D 状态进程 kill -9 能否杀掉?](#Q2:D 状态进程 kill -9 能否杀掉?)

Q3:僵尸进程和孤儿进程区别?

Q4:如何处理僵尸进程?

[Q5:nice 值的范围和作用?](#Q5:nice 值的范围和作用?)

九、小结


一、回顾:PCB(task_struct)

Linux 下每一个进程都有一个 PCB,结构体task_struct,里面包含大量描述进程属性信息:

  • 进程 id:pid
  • 内存描述:虚拟地址空间信息
  • 寄存器上下文(进程切换时保存的硬件上下文)
  • 文件描述符表
  • 进程状态 state(本文核心成员)
  • 优先级、调度相关属性
  • 信号相关信息

操作系统通过 PCB 管理进程,状态就是用来判断这个进程是否可以被调度器调度到 CPU 上运行。

二、Linux 内核的进程状态(内核源码定义)

内核源码中,state 是整数,不同数字代表不同状态。

1. R 运行状态(TASK_RUNNING)

包含两种场景: ① 进程正在 CPU 上执行; ② 进程已经准备就绪,放到运行队列里,等待 CPU 调度(就绪状态)。

✅ 关键点: Linux 内核把就绪态 + 运行态统一归类为 R 状态,不像课本分开。只要进程准备好了,随时可以上 CPU 执行,就是 R 状态。

查看进程状态命令:

复制代码
ps aux

或者动态观察

复制代码
top

2. S 睡眠状态(TASK_INTERRUPTIBLE)可中断睡眠

进程等待某个事件,主动放弃 CPU,进入睡眠。 可中断 :可以被信号唤醒。 场景举例:cin/cout等待用户输入、进程等待读磁盘数据,此时进程不占用 CPU,挂在等待队列。

通俗理解:进程在等资源,等待期间可以收到信号被唤醒。

3. D 深度睡眠(TASK_UNINTERRUPTIBLE)不可中断睡眠

不可中断睡眠,不能被信号唤醒 ! 这个状态非常关键,面试高频考点。 什么时候出现:进程正在和硬件交互(比如读写磁盘),此时不能被打断。 如果此时收到 kill 信号,杀不掉 D 状态进程。

为什么设计 D 状态?防止进程在硬件 IO 操作中途被打断,造成数据损坏。 现象:kill -9 也无法杀死 D 状态进程,只能等 IO 完成,或者重启服务器。

4. T 停止状态(TASK_STOPPED)

进程被暂停执行,收到继续信号后可以恢复运行。 不是进程自己主动休眠,是外部信号让它停下。 典型场景:gdb 调试打断点,程序停下来,此时进程状态 T。

相关信号:

  • SIGSTOP:暂停进程
  • SIGCONT:唤醒继续运行

5. Z 僵尸状态(TASK_ZOMBIE)【重中之重】

子进程已经执行完毕、退出了,但是父进程没有读取子进程的退出信息。 子进程 PCB 不会被操作系统释放,保留退出状态信息,等待父进程回收。此时子进程就是僵尸进程,状态标记 Z。

核心特点:

  1. 子进程代码、资源全部释放,只剩 PCB 残留;
  2. 占用少量内存资源;
  3. 如果父进程长期不回收,大量僵尸进程会占用 pid 号,造成 pid 资源泄露;
  1. kill无法杀死僵尸进程,因为进程本身已经结束,只是 PCB 没回收。
    来⼀个创建维持30秒的僵死进程例⼦:

如何回收僵尸进程:

  1. 父进程调用wait() / waitpid(),等待并读取子进程退出码,操作系统释放子进程 PCB;
  2. 父进程先退出,子进程变成孤儿进程,被 1 号进程 init 收养,init 会自动回收僵尸子进程。

6. X 死亡状态(TASK_DEAD)

进程即将被系统彻底释放,PCB 马上销毁,这个状态非常短暂,几乎很难在 ps 命令中观察到。

三、孤儿进程

父进程提前退出,子进程还在运行,这个子进程叫做孤儿进程。 Linux 机制:孤儿进程会被init进程(pid=1)收养,变成 init 的子进程。 好处:当孤儿进程退出,init 进程会自动调用 wait 回收它,不会变成僵尸进程。

孤儿进程无害,系统自动兜底回收;僵尸进程是资源泄露问题,需要程序员处理。

孤儿进程演示代码

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程
        printf("子进程 pid:%d, 父进程pid:%d\n",getpid(),getppid());
        sleep(5);
        printf("子进程 pid:%d, 父进程pid:%d\n",getpid(),getppid());
    }
    else
    {
        //父进程,直接退出
        sleep(1);
        printf("父进程退出\n");
    }
    return 0;
}

运行现象:父进程先退出,5 秒后子进程打印 ppid 变成 1,被 init 收养。

四、僵尸进程演示代码

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //子进程,执行完直接退出
        printf("子进程执行完毕,准备退出\n");
        sleep(2);
    }
    else
    {
        //父进程一直循环,不调用wait回收子进程
        while(1)
        {
            printf("父进程正在运行,不回收子进程\n");
            sleep(1);
        }
    }
    return 0;
}

编译运行,新开终端执行ps aux | grep a.out,2 秒后子进程退出,状态变成 Z 僵尸。

五、进程状态查看实操命令

复制代码
# 查看所有进程,观察STAT那一列就是进程状态
ps aux

# 持续动态刷新观察进程状态
top

STAT 列除了字母,还会附带附加标记:

  • s:这个进程是会话首进程
  • +:前台进程组
  • l:多线程进程

六、进程优先级

操作系统不能平等对待所有进程,优先级决定谁优先使用 CPU。

优先级 = 谁优先被 CPU 调度。

查看优先级

复制代码
ps -al

字段解释:

  • PRI:进程优先级,内核调度使用,值越小优先级越高
  • NI:nice 值,用来修正 PRI。范围:-20 ~ 19
    • nice 越小,PRI 越小,优先级越高;
    • 默认 nice 值是 0。

修改 nice 值:

cpp 复制代码
# 启动程序的时候指定nice
nice -n -5 ./test

# 修改已经运行进程的nice
renice -5 -p 进程pid

root 用户可以设置负数,普通用户只能调高 nice(降低优先级),不能调高优先级。

七、进程的其他基础概念

1. 进程的独立性

每个进程有独立 PCB、独立虚拟地址空间。一个进程崩溃,不会直接影响其他进程。fork 创建子进程,采用写时拷贝,保证父子进程数据独立。

2. 进程组 & 会话

  • 进程组:多个关联进程放在同一个进程组,拥有同一个进程组 id。fork 出来的父子进程默认在同一个进程组。
  • 会话:一个会话包含多个进程组,一个会话对应一个终端。登录终端就创建一个会话。 前台进程组:直接占用终端,可以输入输出;后台进程不占用终端。 &把程序放到后台运行:
cpp 复制代码
./test &

八、面试高频问题

Q1:R 状态代表进程一定正在 CPU 上运行吗?

不是。R 包含两种:正在 CPU 运行,或者在运行队列就绪等待调度。

Q2:D 状态进程 kill -9 能否杀掉?

不能。D 是不可中断睡眠,无法被信号打断,只能等待 IO 结束或者重启机器。

Q3:僵尸进程和孤儿进程区别?

孤儿进程:父进程先退出,子进程被 1 号进程收养,无害 。 僵尸进程:子进程退出,父进程不回收子进程退出信息,PCB 残留,会造成资源泄漏。kill 无法清除僵尸进程。

Q4:如何处理僵尸进程?

方案 1:父进程使用 wait/waitpid 阻塞或者非阻塞等待回收子进程; 方案 2:父进程注册 SIGCHLD 信号,子进程退出会发送 SIGCHLD 信号,在信号处理函数内部回收子进程(工程最常用); 方案 3:杀死父进程,僵尸进程变成孤儿,由 init 回收。

Q5:nice 值的范围和作用?

nice 取值范围[-20,19],用于调整进程优先级。nice 越小,优先级越高。普通用户只能增大 nice,不能设置负数。

九、小结

进程状态保存在 PCB 结构体中,Linux 一共定义 R/S/D/T/Z/X 这几种核心状态。重点区分僵尸进程、孤儿进程。 僵尸进程是开发中需要重点规避的问题,本质是子进程退出后资源没有被父进程回收。 优先级、进程组、会话都是操作系统管理大量进程的辅助机制,理解这些,才能看懂进程调度行为。

相关推荐
敲代码的彭于晏1 小时前
2026 K8s 零基础入门:手把手教你一步步搭建 Kubernetes 集群
java·运维·全栈
土星云SaturnCloud1 小时前
土星云边缘计算+AI视觉+视频存管:连锁汽修数字化升级实践
服务器·人工智能·ai·边缘计算
ManageEngine卓豪1 小时前
智能运维如何降低 MTTR:告警聚类、因果分析与自动化修复的工程拆解
运维·智能运维·mttr
wangfenglei1234562 小时前
jenkins权限配置
运维·jenkins
ab1237682 小时前
Docker Compose 完整详解
运维·docker·容器
张洛闻Eren2 小时前
Ansible【第二课】:实施 Ansible Playbook
linux·运维·服务器·云原生·kubernetes·k8s·ansible
启博网络2 小时前
启博安企服务器与ERP加速服务器如何区分?一文讲透
运维·服务器
Zhu7582 小时前
【开源脚本】Ubuntu环境下载任意软件
linux·运维·ubuntu
吴声子夜歌2 小时前
Docker入门与实战——核心实现技术
运维·docker·容器