计算机中的进程状态与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 里典型的"组织用字段"大致是这几类(名字可能随内核版本略有变化,但思想一致):

  • 全局任务链taskslist_head

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

  • 父子关系与兄弟链childrensiblinglist_head

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

  • 线程组链thread_grouplist_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)在内存中的位置是固定的:

  • 结构体起始地址是 &pptask_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 只是设备的使用者,并不参与设备的创建和管理。


相关推荐
asaotomo5 小时前
一款 AI 驱动的新一代安全运维代理 —— DeepSentry(深哨)
运维·人工智能·安全·ai·go
坐怀不乱杯魂5 小时前
Linux网络 - UDP/TCP底层
linux·服务器·网络·c++·tcp/ip·udp
大胡子大叔5 小时前
docker pull命令拉取镜像失败的解决方案
运维·docker·容器
码农水水5 小时前
从 OpenFeign 到 RestClient:Spring Cloud 新时代的轻量化 HTTP 调用方案
java·运维·后端·spring·http·spring cloud·面试
Li_Zhi_Yao5 小时前
linux下qt快速搭建环境
linux·运维·qt
Hello.Reader5 小时前
Flink 弹性伸缩(Elastic Scaling)Adaptive Scheduler、Reactive Mode 与外部资源声明
服务器·网络·flink
Acrelhuang5 小时前
工厂配电升级优选 安科瑞智能断路器安全提效又节能-安科瑞黄安南
大数据·运维·开发语言·人工智能·物联网
Go_Zezhou5 小时前
render快速部署网站和常见问题解决
运维·服务器·开发语言·python·github·状态模式
小白电脑技术5 小时前
如何修改电脑名称及其实际作用
运维·网络·电脑