计算机中的进程状态与linux中如何管理进程

基础状态

计算机程序在运行过程中会不断"变化",这些变化就可以用程序的状态(state) 来描述。不同领域对"状态"的划分略有差别,但总体可以从 生命周期状态 和 运行时状态 两个角度来理解。这是最常见的一种"状态划分",从操作系统角度看一个程序/进程通常会经历:

1)新建状态(New / Created)

当用户启动一个程序时,操作系统会先创建一个进程,这时进程处于"新建状态"。在这个阶段,系统正在为它分配必要的资源,比如进程控制块(PCB)、进程号(PID)、初始内存空间等,但它还没有进入真正可执行的队列,也还没有开始占用 CPU 执行指令。


2)就绪状态(Ready)

当进程创建完成并且运行所需的基本条件都具备后,它会进入"就绪状态"。就绪状态的含义是:进程已经准备好运行了,但此刻 CPU 可能正在运行别的进程,所以它只能在就绪队列中等待调度,一旦轮到它,它就能马上开始执行。


3)运行状态(Running)

当操作系统把 CPU 分配给某个就绪进程后,这个进程就进入"运行状态"。运行状态表示进程正在 CPU 上执行自己的指令、进行计算或处理任务,是进程真正"在跑"的阶段。一个 CPU 核心同一时刻只能运行一个进程(更准确说是一个线程),其他进程则处于就绪或等待状态。


4)阻塞/等待状态(Blocked / Waiting)

如果进程在运行过程中遇到必须等待的事情,就会从运行状态转为"阻塞/等待状态"。常见情况包括等待磁盘读写、网络数据到达、键盘输入、或者等待某个锁资源等。处于阻塞状态的进程即使有 CPU 也无法继续执行,它必须等到等待的事件发生(比如 I/O 完成、锁被释放)才会重新回到就绪状态。


5)终止状态(Terminated / Exit)

当进程完成任务正常结束,或者因为错误崩溃退出,或者被用户/系统强制杀死时,它就进入"终止状态"。终止状态表示进程已经不再执行,操作系统会回收它占用的资源(内存、打开的文件、各种句柄等),最终彻底从系统中消失。

小结:这套状态是操作系统调度中最经典的:新建→就绪→运行→阻塞→终止。


linux中如何管理程序

在 Linux 内核中,task_struct 是进程/线程的核心描述结构 ,相当于内核里的"任务控制块",用于表示并管理一个正在运行的任务 ;它保存了该任务的关键信息,比如进程状态、调度信息、优先级、CPU 上下文、内存管理、打开的文件、信号处理以及父子进程关系等,因此 task_struct 承担着内核对进程(线程)进行调度与资源管理的主要责任。所以,linux中通过对task_struct的操作,就可以让进程处于各种位置,发挥出更好的效果。

1)task_struct 为什么能被"自由组织"

Linux 的链表实现是这样的:

  • 双链表的节点类型是 struct list_head { struct list_head *next, *prev; }

  • Linux 不搞"链表节点单独分配",而是把 list_head 直接作为字段嵌进业务结构体里 (这里就是 task_struct)

  • 需要把 task 放进哪个"集合/队列",就用对应的那个 list_head 字段去挂链

直觉上就是:task_struct 身上带了很多"挂钩",每个挂钩都能把它挂到一个链表/队列/树里,所以组织方式很灵活。


2)一个 task 可以同时在很多"链/队列"里

task_struct 里典型的"组织用字段"大致是这几类(名字可能随内核版本略有变化,但思想一致):

  • 全局任务链 :tasks(list_head)

    让系统能遍历所有进程(比如 for_each_process()),这更多用于管理/统计,而不是调度的主结构。

  • 父子关系与兄弟链 :children、sibling(list_head)

    父进程有孩子链表,每个孩子在"兄弟链"里排队,从而能实现进程树。

  • 线程组链 :thread_group(list_head)

    同一个线程组(同一个 TGID)下的多个线程可以串起来。

  • PID 相关的哈希/链 :常见是 pid_links[](内部会用到哈希链 hlist_node 等)

    用于通过 pid 快速查找任务,而不是全表扫描。

  • 调度相关结构 :通常不是简单"一个调度链表",而是按调度类(CFS/RT/DL)分别组织

    例如 CFS 更像是"红黑树/队列 + 辅助链",RT 更像是"按优先级的队列数组"等。也就是说:调度主要挂在每 CPU 的 runqueue 上,不是挂在全局 tasks 链上。

重点:同一个 task_struct 可以同时出现在多个集合里,因为它有多个不同的挂钩字段,互不冲突。


3)怎么从链表节点"访问回 task_struct",再访问里面的数据

用语言描述:为什么能"从成员指针回到结构体指针"

1)关键前提:成员在结构体里有固定"偏移量"

在 C 里,一个结构体 struct task_struct 的每个字段(比如 tasks 这个 struct list_head)在内存中的位置是固定的:

  • 结构体起始地址是 &p(p 是 task_struct 指针)

  • 成员 tasks 的地址是 &p->tasks

  • 二者之间相差一个固定的字节数,这个字节数叫 offset(偏移量) ,可以用宏 offsetof(struct task_struct, tasks) 算出来

也就是说永远成立:

&p->tasks == (char*)p + offsetof(struct task_struct, tasks)

2)你在遍历链表时拿到的是"成员地址"

Linux 的链表是侵入式的:链表节点 list_head 直接嵌在 task_struct 里。

所以链表里存的/连的是 &p->tasks 这种"成员节点地址",而不是 p 本人。

遍历 tasks 链表时,pos 通常是 struct list_head *,它实际上指向某个进程的 p->tasks。

3)如何从 &p->tasks 反推 p

既然:

  • member_ptr = (char*)p + offset

    那反过来就是:

  • p = (char*)member_ptr - offset

这就是 container_of 的本质:知道成员指针 + 知道成员在结构体里的偏移量 → 推回结构体起始地址。

4)为什么要 (char*) 强转

因为指针做加减,单位是"指向类型的大小"。

  • task_struct* + 1 会跳过一个 task_struct 的大小

  • 我们要按"字节"精确地减去 offset,所以把指针转成 char*(1 字节单位)最合适。


例子:用一个小结构体模拟 task_struct + 链表节点

假设我们有个"任务"结构体:

  • pid 是数据

  • tasks 是挂到全局链表用的钩子(list node)

    struct list_head {
    struct list_head *next, *prev;
    };

    struct my_task {
    int pid;
    struct list_head tasks; // 侵入式链表节点:嵌在结构体内部
    int state;
    };

内存布局可以想象成这样(简化):

复制代码
p (my_task 起始地址)
+0    pid
+4    (对齐/填充可能存在)
+8    tasks (list_head)
+...  state

如果你现在只有 &p->tasks,想回到 p,你就做:

复制代码
p = (char*)(&p->tasks) - offsetof(struct my_task, tasks)

为什么这对 Linux 的 task_struct 特别重要

因为 task_struct 里有很多"挂钩字段",比如(示意):

  • struct list_head tasks;(全局进程遍历)

  • struct list_head thread_group;(线程组)

  • 还有调度相关的 sched_entity(内部也会被挂进调度结构)

不同链表拿到的"节点指针"可能是不同字段的地址:

  • 从 tasks 链表遍历 → list_entry(pos, task_struct, tasks)

  • 从 thread_group 链表遍历 → list_entry(pos, task_struct, thread_group)

同一个 task 能被不同组织结构"同时管理",靠的就是:每个组织结构对应 task_struct 里的一个成员节点;需要哪个结构就用哪个成员节点反推出 task。


Linux 之所以用 container_of / list_entry 这种"从成员指针反推出外层结构体"的方式,本质原因是它采用了侵入式数据结构 :链表节点(list_head)不是单独分配的,而是直接嵌在 task_struct 这种业务对象内部。这样设计后,内核在管理进程时不需要额外创建"链表节点对象",而是让每个进程天然自带多个"挂钩",可以随时把它挂到不同的组织结构里(全局进程链、父子链、线程组链、调度队列等)。而遍历这些结构时拿到的只是挂钩指针,利用成员偏移量就能准确回到所属的 task_struct,从而访问进程的各种信息。

这样做的好处非常明显:效率高、内存省、结构灵活 。首先链表操作只改指针,不涉及频繁的动态内存分配/释放,减少碎片和开销,适合内核这种高性能场景;其次同一个 task_struct 可以同时挂到多种链/队列中,实现"自由组织",调度器和进程管理模块都能快速插入、删除、移动任务;最后 container_of 是纯编译期偏移计算,运行时成本极低,使得遍历链表时既能保持数据结构通用(只认 list_head),又能拿回具体类型(task_struct*)进行业务处理。

linux中如何管理设备

1)、struct device 是干嘛用的(一句话版)

它把"硬件设备"从各自为政,统一纳入一套内核可管理、可匹配、可观察的体系里

在它出现之前,Linux 是驱动自己找设备,设备关系混乱,没有统一生命周期

有了 struct device 之后:设备有"身份",驱动不再主动找设备,而是被动匹配,内核能统一管理设备

struct device 是 Linux 内核中对"一个设备"的统一抽象,它的作用不是描述寄存器细节,而是给每个硬件或逻辑设备一个被内核识别、管理和追踪的身份,使内核能够以一致的方式对待各种来源的设备(USB、PCI、SoC 内设等)。


2)、它改变了什么?(解决的核心问题)

1️⃣ 从"驱动中心" → "设备中心"

以前(老模型)

复制代码
驱动:我去扫描硬件
驱动:我决定怎么初始化

现在(device model)

复制代码
内核:发现一个设备
内核:给它建一个 device
内核:看看谁能驱动它

👉 struct device 把 Linux 从"驱动自己找硬件"的模式,转变为"内核先表示设备、再让驱动来匹配"的模式,设备成为系统中的一等公民,驱动不再主导流程,只负责在匹配成功后提供功能实现。


2️⃣ 不同总线,不同设备 → 统一规则

不管你是:

  • USB

  • PCI

  • I2C

  • SoC 内部外设

统一流程:

复制代码
发现设备
 → 创建 device
 → 挂到某条 bus
 → bus 负责匹配 driver
 → 调用 driver->probe()

驱动只关心:

"你是不是我能处理的 device?"

不管设备来自哪种总线或固件描述方式,Linux 都会先创建一个 struct device 并把它挂到对应的总线上,再由总线负责按照统一规则匹配驱动,这让 USB、PCI、platform 等子系统在宏观逻辑上高度一致,然后由总线尝试为它匹配合适的驱动,匹配成功后调用驱动的 probe,设备才真正进入可工作状态。


3️⃣ 设备生命周期被标准化

以前:

  • 设备什么时候生、什么时候死,全靠驱动自觉

现在:

  • 插入 → 注册

  • 拔出 → 注销

  • 统一引用计数

  • 统一释放时机

👉 当设备被移除或失效时,内核会先解除设备与驱动的绑定,调用驱动的移除逻辑,然后将该 struct device 从设备模型中移除,最终在引用计数归零后统一释放资源。减少内核崩溃和内存泄漏


三、运行逻辑(超简流程)

从设备出现到可用

复制代码
硬件出现(插入 / 枚举 / 启动时发现)
 ↓
内核识别(USB/PCI/DT/ACPI)
 ↓
创建 struct device
 ↓
加入设备模型(device_add)
 ↓
挂到 bus
 ↓
bus 尝试匹配 driver
 ↓
匹配成功 → driver->probe()
 ↓
设备开始工作

设备消失时

复制代码
设备移除
 ↓
解除 driver 绑定
 ↓
driver->remove()
 ↓
device 从模型中移除
 ↓
引用计数归零
 ↓
真正释放

四、你平时"看见"的变化

struct device 带来的直接可感知结果 :/sys/devices 能看到完整硬件拓扑,插 USB 不用重启,suspend / resume 自动级联,用户态能通过 udev 感知设备变化,驱动写法高度一致

👉 struct device 使 Linux 能够支持热插拔、自动加载驱动、统一的电源管理和清晰的硬件拓扑展示,这些能力并非单个驱动实现,而是设备模型集中提供的系统级能力。


五、和 task_struct 的本质对比(帮助理解)

维度 task_struct struct device
管理对象 执行实体 硬件实体
谁创建 fork 总线 / 枚举
核心能力 调度 发现、匹配、生命周期
面向谁 CPU 驱动

一句话类比:

task_struct 让 CPU 有秩序地"用人",
struct device 让内核有秩序地"用硬件"。

在内核整体中的定位

在内核架构上,struct device 扮演的是"硬件管理核心节点"的角色,所有设备相关的子系统都围绕它展开,而进程结构如 task_struct 只是设备的使用者,并不参与设备的创建和管理。


相关推荐
雯宝1 小时前
mnt_init 函数
linux
律宏阔7 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔7 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴7 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千7 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei377 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei377 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei377 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh7 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW7 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux