【Linux】进程(3):进程状态

目录

[一 站在操作系统角度,解释进程状态:运行,阻塞,挂起](#一 站在操作系统角度,解释进程状态:运行,阻塞,挂起)

[1 task_struct不是属于双链表嘛?怎么能还属于调度队列呢?](#1 task_struct不是属于双链表嘛?怎么能还属于调度队列呢?)

[2 阻塞状态](#2 阻塞状态)

[3 挂起状态](#3 挂起状态)

[1. Swap 分区](#1. Swap 分区)

[2. 进程挂起的核心操作](#2. 进程挂起的核心操作)

[3. 挂起的分类](#3. 挂起的分类)

[4. 挂起的本质](#4. 挂起的本质)

[5. 内存极端不足的处理](#5. 内存极端不足的处理)

[二 Linux中的运行状态](#二 Linux中的运行状态)

s状态:休眠状态

前后台问题

T状态:暂停状态

kill常见信号

t状态:进程被追踪情况下暂停

D状态:磁盘休眠状态

X状态:进程死亡

Z状态:僵尸状态

孤儿进程


结论:进程状态,就是text_struct内部的一个整型变量

一 站在操作系统角度,解释进程状态:运行,阻塞,挂起

下面是在操作系统的角度的运行状态示意图

1 task_struct不是属于双链表嘛?怎么能还属于调度队列呢?

我们先来解释一下什么是调度队列:

调度队列是一个队列结构,是将所有的进程的形式连接起来。每个CPU都必须有调度队列)

task_struct在内核中,是用双链表实现的

内存地址是从低地址到高地址线性排列的,因此绝大多数连续存储的数据结构在内存中也遵循同样的方向布局。不同类型的存储区域会在这个线性空间中占据各自的位置。总体来说,大多数数据在内存中的组织方式都是 "低地址指向高地址"。

我们以整型为例:int是四个字节

cpp 复制代码
int a = 10;

CPU访问内存的基本单位---是字节

对a取地址,为什么只标识一个地址呢?按照一个字节一个字节访问不应该是四个地址吗?

因为实际取的是开辟空间数值最小的地址+类型

同样的,数组的地址和数组首元素的地址在数值上是一样的

那么结构体呢?

c语言对任何类型,开辟空间的的时候,变量的地址在数字上等于开辟的众多字节中地址最小的那个数字!

在Linux内核中,是把链式信息嵌入到text_struct中,而其中的链式信息和我们以前学习的链表有所不同:它里面只有两个指针,没有数据信息。两个指针分别是next指针(指向下一个link对象,而不是一整个节点),prev指针指向前一个link对象的prev指针

已知条件

结构体内部某一个成员变量的地址 该结构体的完整类型定义
核心目标

通过已知成员的地址和它在结构体中的偏移量,反推出整个结构体对象的起始地址,进而访问结构体的其他成员。
关键问题

如何计算该成员变量在结构体中的偏移量

我们假设起始地址是0号地址,把0号地址强转成struct obj ,就变成了整数,之后就能直接访问元素d,后取地址,得到偏移量

内核为什么要这么做?

2 阻塞状态

通俗来讲,阻塞状态就是执行某种工作时,导致进程卡住了,操作系统无法再调度它

我们来举一个最典型的情况:

在执行这个程序的时候,如果没有从键盘输入a的数据,那么程序就不会运行,就会一直卡住

键盘属于硬件,操作系统管理硬件也要先描述,再组织,硬件再底层也有对应描述的结构体(包含硬件的相关属性,进程PCB的队列)

键盘等待与进程阻塞

CPU 调度其他进程时,当前需要读取键盘数据的进程会进入键盘等待状态,被 CPU 移出调度队列,进入阻塞状态( 表现为程序 "卡住")。

用户按下键盘后,键盘状态更新,OS 识别到后将状态置为reading,并把该进程从键盘等待队列重新移回调度队列,进程解除阻塞。

运行和阻塞的本质,就看 task_struct 被放在了哪一个队列中!

3 挂起状态

1. Swap 分区

  • Linux/Windows 系统在安装时,会在磁盘中划分一个独立区域,称为Swap 分区(交换分区),是内存的 "扩展空间"。
  • 核心触发条件:内存资源不足时 ,操作系统会触发进程挂起机制。

2. 进程挂起的核心操作

  • 找到内存中处于阻塞状态 的进程,将其代码和数据换出到 Swap 分区 ,仅保留PCB(进程控制块)在内存中,这种状态称为 进程挂起
  • 操作效果:释放进程占用的内存空间,缓解内存资源紧张问题。

3. 挂起的分类

  1. 阻塞挂起
    • 进程本身处于阻塞状态,其代码和数据被换出到 Swap 分区;当设备就绪(触发进程唤醒的条件满足),PCB 重新回到调度队列,代码和数据再换入内存,进程解除阻塞 + 挂起。
  2. 运行挂起
    • 若所有阻塞进程的代码和数据都被换出后,内存仍不足,操作系统会将处于就绪队列的进程(等待运行的进程)的代码和数据也换入 Swap 分区,称为运行挂起;仅保留 PCB 在内存,CPU 仅调度就绪队列的首个进程,其余进程排队等待。

4. 挂起的本质

  • 时间换空间:牺牲进程换入 / 换出的 IO 时间,换取宝贵的内存空间。

5. 内存极端不足的处理

  • 若 Swap 分区也无法缓解内存紧张,操作系统会强制终止(杀掉)部分进程,这也是程序 / 系统出现闪退、崩溃的原因之一。

swap分区不能太大,防止操作系统依赖swap

注意:单个CPU只有一个进程再跑,其他进程并没有真正的运行,此时这些进程的代码和数据本身也要占内存


二 Linux中的运行状态

在Linux 内核源代码中,介绍了Linux的几种运行状态

• R 运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中,要么在运行队列里。

• S 睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。

• D 磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待 I/O 的结束。

• T 停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

• X 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

这个时候可能就会有同学要问了:为什么Linux中没有挂起状态呢?

因为挂起状态是操作系统自己维护的,在Linux中被隐藏起来了,因为Linux觉得没必要在用户面前展现,但是挂起状态是存在的

s状态:休眠状态

s状态属于操作系统中的阻塞状态(例如前面讲到的最典型的阻塞状态:代码中里有一行scanf输入代码, 不输入时就会卡住),s状态时,可以用crtl+c或者kill-9杀掉,所以把s状态叫做可中断休眠状态,且在休眠期间,会相应外部状态

相应的,也有不可中断休眠状态---->D状态,详细的后面介绍

前后台问题

我们在用grep指令时,发现有些运行状态后面,会跟一个'+'号

这个加号表示:在Linux中,该进程在前台工作。相应的,没有加号就表示在后台工作

两种运行的指令不同:

假设文件名是mgproc.c,如果是./mgproc后直接回车,那就是前台运行(前台进程运行时,没有办法在命令行执行其他指令,但是如果是后台进程运行就能执行)

如果是./myproc & 那就会变成后台运行(注意:后台运行,没法用crtl+c"杀掉",要用kill-9)

什么是前后台?

谁想获取键盘输入,谁就把自己变成前台程序

有且只有一个进程充当前台获取数据。如果你此时是后台,非要充当前台获取数据,操作系统会暂停你一下--->T状态

切回前台:fg 1

这里的这个1不是固定的,是在输入./myproc &后,下方出现的数字

只有前台才允许获取数据,除了前台,其他进程全是进程

为什么要有前后台?

为什么要有后台?

为了提高速率。

假如说你此时要安装一个软件,但是安装下载工作需要一些时间,这个时候就可以把它放到后台工作,就可以正常的进行命令行的输入

T状态:暂停状态

当一个进程处于非法状态,但是还不至于到操作系统"杀死"它,此时操作系统可能就会暂停这个进程

T状态也属于一种阻塞状态

kill常见信号

kill-l

查看所有的Linux中常见信号(没有0,32,33号信号) 其中:1-31号是我们要用的,34-64号叫做实时信号,不用管

目前我们只见过九号信号:kill-9我们在前面使用过,也可以将数字换成它的名称:kill-SIGKILL

认识两个信号:

18号 SIGCONT 让对应进程运行起来

19号 SIGSTOP 让对应进程暂停

如果前台进程被暂停了,就必须自动切换成后台,默认把bash设置为前台。除非我们主动唤醒它,否则一直处于T状态

t状态:进程被追踪情况下暂停

有的内核对于T,t已经不做区分

我们可以通过在gdb里面打断点 的方式来观察暂停状态

gdb调试时,是通过创建子进程。gdb遇到断点,就表示子进程暂停

D状态:磁盘休眠状态

也是一种阻塞状态

我们通过一个故事来解释一下D状态:

起因:一个进程(比如银行的账单程序)正在执行向磁盘写入1GB数据的操作。 这时系统内存不足,操作系统(OS)为了自保,直接把这个进程强制杀掉了。 2. 混乱发生: 磁盘还在慢悠悠地执行写入操作,当它发现写入出错(比如空间不足),想回头通知进程时, 却发现进程已经被OS"干掉"了。这直接导致写入操作彻底失控,银行系统因此丢失了一整天的账单数据, 造成上千万元的损失。 3. 追责与反思: 行长震怒,把OS、进程、磁盘都叫去问责。这次事故让大家意识到, 当进程在执行磁盘I/O这类关键操作时,被OS误删会引发严重的业务灾难。 4. 解决方案:D状态登场 为了避免这类问题,D状态(disk sleep,磁盘休眠状态)应运而生。 它是一种不可中断的阻塞状态

-当进程发起磁盘I/O后,就会进入D状态。 - 此时OS无法强制杀死或唤醒它,只能等磁盘I/O完成、进程自己醒来。 - 这就保护了正在执行关键I/O的进程,防止被OS误删,避免了类似银行账单丢失的事故。

如果有同学想看一下D状态,可以用AI生成dd指令,帮助实现。因为D状态是磁盘必须处于高IO,所有一般程序员其实很难见到D状态

X状态:进程死亡

X状态表示该进程死亡,它的代码和数据被操作系统释放,但是进程不能直接进入X状态,必须先进入Z状态

Z状态:僵尸状态

因为Z状态的全称是ZOMBIE,翻译过来就是僵尸的意思

我们还是通过一个故事了解一下Z状态:

一个进程退出,它的相关信息被记录在PCB中(具体看下图),被操作系统维护

僵尸进程危害:

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

  • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 task_struct (PCB) 中,换句话说,Z 状态一直不退出,PCB 一直都要维护?是的!
  • 那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想 C 中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!

几个问题:

(1)如果父进程一直不读取呢?

那么子进程的·PCB就会一直维护,内存就不会释放,就会造成内存泄漏

内存泄漏问题最怕的就是进程永不退出(死循环),这种1进程叫做常驻进程

(2)父进程如何读取?

我们先给出结论,具体到后面讲解(bash也有父进程)

(3)父进程读取了之后,能看到X状态吗?

X是瞬时状态,很难看到

(4)malloc new 堆空间,free delete,如果进程退出了,内存泄漏还在吗?

不存在。这些都是申请的堆空间,但是关闭了进程,就不存在内存泄漏

(5)如果想看到Z状态,该怎么办?

孤儿进程

如果父进程先退出,子进程还在,会怎么办?

我们先来模拟出现象:

因为父进程也有自己的父进程,父进程退出之后,子进程就会被一号进程领养 (一号进程叫做systemd),也就是被系统领养,这种进程就叫做孤儿进程

为什么要领养?

避免子进程终止后,没有父进程读取其退出状态,从而变成僵尸进程,造成内存泄漏。被领养后,init/systemd 会负责回收子进程的退出状态,释放 PCB 资源。

孤儿进程的特点

  • 孤儿进程默认转为后台进程 ,无法通过Ctrl+C杀死(Ctrl+C仅能终止前台进程);
  • 孤儿进程本身不是异常进程,只是失去了父进程,能正常执行、正常退出,由领养进程完成善后

孤儿进程的用途

常用来实现后台守护进程,具体的我们在网络部分讲解

Linux进程状态总结

相关推荐
Mr_liu_6662 小时前
ubuntu:beyond compare 4 This license key has been revoked——————转载拼接
linux·运维·ubuntu
无聊的小坏坏2 小时前
补充:使用 /etc/cron.d 管理定时任务
linux·定时任务
热爱生活的五柒2 小时前
linux/mac/wsl如何使用claude code,并配置免费的硅基流动API?(官方的需要付费订阅)
linux·运维·macos
wefg12 小时前
【Linux】动静态库
linux·运维·restful
Y1rong2 小时前
linux之线程池
linux
H Journey2 小时前
Docker swarm 集群搭建实战
运维·docker·容器
划水的code搬运工小李2 小时前
Ubuntu下挂载NTFS格式磁盘
linux·运维·ubuntu
炸裂狸花猫2 小时前
开源域名代理与流量限制方案 - Cloudflare + Ingress + 自签名证书
运维·云原生·容器·kubernetes·cloudflare·waf·免费域名证书
佑白雪乐2 小时前
<Linux基础12集>1-11集大复习Review
linux·运维·策略模式