news 2026/11/2 6:32:22

揭秘Linux进程管理核心:从PCB到fork机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
揭秘Linux进程管理核心:从PCB到fork机制

目录

一、进程是什么

1.1 什么是进程

1.2 描述进程:PCB进程控制块

1.3 task_struct中存储什么

二、 组织进程

2.1进程管理

2.2 进程查看

三、fork创建进程

3.1 fork的作用

3.2 fork的原理

四、Linux内核中的进程状态

4.1 运行态

4.2 阻塞态

4.3 挂起态

4.4 Linux内核中的进程状态

4.5 前后台


本文系统阐述了进程的概念与管理机制:进程是运行的程序,由内核通过task_struct(PCB)描述和管理,包含PID、状态、上下文、资源指针等信息。操作系统用双向链表组织进程,通过/proc文件系统或ps命令查看进程。fork()创建子进程,采用写时拷贝(COW)优化内存使用,父子进程共享代码但独立数据。进程状态包括运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)、僵尸(Z)等,其中僵尸进程需父进程回收。内核使用list_head链表结构高效管理进程,结合container_of宏实现链表节点

一、进程是什么

1.1 什么是进程

课本概念:运行起来的程序叫做进程

内核观点:担当分配系统资源(CPU时间,内存)的实体

当前:进程=内核数据结构(struct task_struct)+自己的程序代码和数据

操作系统内部可以同时存在多个进程,那么OS对进程必须要做管理

1.2 描述进程:PCB进程控制块

既然OS要管理进程,首先要认识进程,在OS管理进程时,也需要一个信息记录表,这就是PCB(Process Control Block),在Linux系统中,PCB的具体实现就是task_struct 结构体,每个进程都有且仅有一个task_struct,它包含了进程的所有属性,OS通过操作task_struct来管理进程,而非直接操作进程本身。

task_struct就是 Linux 内核里的 PCB(进程控制块),内核用来描述、管理一个任务(进程 / 线程)的结构体,存在内核空间

1.3 task_struct中存储什么

关于 task_struct ,我们需要掌握以下信息:

  1. pid:进程 ID,身份标识
  2. state:进程状态(运行、可中断睡眠、不可中断睡眠、僵尸等)
  3. 上下文数据:进程执行时处理器的寄存器中的数据,切换时保存现场
  4. 调度信息:优先级、时间片
  5. 资源指针:内存(mm_struct)、文件描述符表(files_struct)
  6. 亲属关系:父进程、子进程、兄弟进程指针
  7. 程序计数器:程序中即将被执行的下一条指令的地址
  8. IO状态信息:包括显示的IO请求,分配给进程的IO设备和被进程使用的文件列表
  9. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记帐号等

二、 组织进程

2.1进程管理

我们在上文中有提到,OS是通过该结构体,用一些数据结构来进行进程的管理,那么在linux中,所有 task_struct 用双向链表串起来,内核遍历链表管理全部进程

2.2 进程查看

上文中提到进程的标识是pid,如果要查pid的话需要引入头文件

#include<sys/types.h>

PPID = Parent Process ID,父进程 IDPID 是自己的进程号;PPID 就是是谁把这个进程启动起来的,那个父进程的 PID。

完整的示例代码如下:

#include <stdio.h> #include<unistd.h> #include<sys/types.h> int main() { while(1) { printf("我是一个进程: pid: %d, ppid: %d\n", getpid(), getppid()); //ppid的第一个p的意思是parent sleep(1); } return 0; }

即可将进程的pid打印出:

我是一个进程: pid: 588, ppid: 30174

但是如果使用CTRL C中止进程后,在此启动,进程会变化


那么进程怎么查看呢?

/proc 是内核虚拟文件系统,用来查看内核和各个进程的运行信息

在proc文件夹下有许多数字目录,这些数字目录就是进程的pid,每个数字文件夹对应一个正在运行进程的信息,里面存这个进程的task_struct相关信息。

cwd:进程当前工作目录

另一种查看进程的方法是:

ps命令:查看一瞬间所有当前终端的进程信息,包括系统本身

[root@VM-0-4-centos ~]# ps PID TTY TIME CMD 23975 pts/0 00:00:00 ps 30174 pts/0 00:00:00 bash [root@VM-0-4-centos ~]#

ps axj :查看进程组 PGID、会话 SID、父子进程,专门用来学进程组、会话、作业控制

  • a:所有带终端的进程
  • x:包含无终端的后台进程(守护进程)
  • j:job 格式,作业控制,多出 PGID、SID 字段

ps axj | head -1:只打印 ps axj 的表头一行,先看懂字段含义,再过滤进程

[root@VM-0-4-centos ~]# ps axj PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 1 1 1 ? -1 Ss 0 1:51 /usr/lib/systemd/systemd --switched-root - 0 2 0 0 ? -1 S 0 0:00 [kthreadd] 2 4 0 0 ? -1 S< 0 0:00 [kworker/0:0H] 2 6 0 0 ? -1 S 0 0:08 [ksoftirqd/0] 2 7 0 0 ? -1 S 0 0:07 [migration/0] 2 8 0 0 ? -1 S 0 0:00 [rcu_bh] 2 9 0 0 ? -1 S 0 4:05 [rcu_sched] 2 10 0 0 ? -1 S< 0 0:00 [lru-add-drain] 2 11 0 0 ? -1 S 0 0:05 [watchdog/0] 2 12 0 0 ? -1 S 0 0:05 [watchdog/1] 2 13 0 0 ? -1 S 0 0:07 [migration/1]

三、fork创建进程

在上文中所提到的 getppid 是查看父进程,那么当命令行直接启动时,创建的对应的进程,父进程都是bash ! Bash 是 Linux 默认的 Shell(命令解释器),命令行解释器本身也是一个进程

[root@VM-0-4-centos ~]# ps ajx | grep bash 30145 30174 30174 30174 pts/0 32200 Ss 0 0:00 -bash #-bash表示远程登陆的bash 30174 32201 32200 30174 pts/0 32200 S+ 0 0:00 grep --color=auto bash

3.1 fork的作用

fork()的作用是复制父进程,生成一个子进程。子进程会继承父进程的代码,数据,文件描述符等资源,但拥有独立的PID和 task _struct,子进程的创建以父进程为模板,父子代码段一样,从fork之后的位置继续往下执行

#include <stdio.h> #include<unistd.h> #include<sys/types.h> int main() { printf("fork之前我是一个进程:pid: %d, ppid: %d\n", getpid(), getppid()); fork(); printf("fork之后我是一个进程:pid: %d, ppid: %d\n", getpid(), getppid()); return 0; }

结果:

[root@VM-0-4-centos lesson]# ./mycmd fork之前我是一个进程: pid: 3182, ppid: 623 //fork()调用一次返回两次 fork之后我是一个进程: pid: 3182, ppid: 623 fork之后我是一个进程: pid: 3183, ppid: 3182

返回两次:

  • 父进程:返回子进程 pid(>0)
  • 子进程:返回0
  • 失败:父进程返回 - 1,没有子进程

3.2 fork的原理

当父进程调用 fork() 时,内核会进行如下步骤:

  1. 核心:复制进程内核以调用 fork 的父进程为模板,新建一个子进程
    • 早期:完整复制父进程地址空间
    • Linux 现在:写时拷贝 COW(不是立刻复制,父子先共享同一份页表;任意一方修改内存时,才单独拷贝那一页)
  2. task_struct:新建子进程 PCB(task_struct),分配新 pid
  3. 返回两次:
    • 父进程:返回子进程 pid(>0)
    • 子进程:返回0
    • 失败:父进程返回 - 1,没有子进程
  4. 父子代码段一样,从 fork 之后的位置继续往下执行,不是从头重新跑 main

那么父子关于数据是怎么处理呢——进程是具有独立性的

写时拷贝的意义:避免创建进程时复制大量未修改的数据,节省内存,提高创建效率。

父子进程的执行顺序:不确定,由Linux调度器决定,父子进程会竞争CPU时间片

在这里我们看上述代码,C/C++ 中看到的都是虚拟地址(两个&flag为虚拟地址)。fork 之后父子进程虚拟地址一模一样;物理内存初始共享。当任意一方修改数据,操作系统才给修改的进程分配新物理页,实现分离,互不干扰。

fork 刚创建完、还没修改 flag 的时候: 父子的 flag 虚拟地址相同,物理地址是同一块,flag 的值也一样。 当父 / 子其中一个修改了 flag: 触发写时拷贝,OS 复制这一页,二者的 flag 不再是同一块物理地址,这时值才不一样。即:虚拟地址相同,物理地址已经分开了

四、Linux内核中的进程状态

状态:本质是task_struct内部的一个整型变量

进程状态:

运行:只要进程的pcb进入调度队列中,并且在CPU上执行,就处于运行状态

阻塞:进程在等资源,但是资源没有就绪,不在调度队列

挂起:PCB在调度队列,已经准备好了,等着 CPU

4.1 运行态

对上图的解释:

task_struct(PCB)里面嵌了一个小结构体list_head link

struct list_head { struct list_head *next; struct list_head *prev; }; struct task_struct { // 一大堆成员:pid、ppid、状态、优先级…… struct list_head link; // 就是图里红框这个! };

next、prev 只指向别的list_head,不是直接指向 task_struct!链表串起来的,只是一个个link节点。遍历链表,拿到了link的地址,但是我想要整个task_struct,拿到 pid 等进程信息!已知结构体内部成员(link)的地址,反求整个宿主结构体(task_struct)的起始地址

两个关键宏

  • offsetof:算出link在 task_struct 里面,距离结构体开头偏移多少字节
#define offsetof(type, member) ((size_t)&(((type*)0)->member))
  • container_of(内核灵魂宏):link地址 - link的偏移量 = task_struct首地址
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
  • ptr:拿到的 link 指针
  • type:宿主类型struct task_struct
  • member:结构体里面的成员名link

普通链表:节点里面存数据;Linux 内核链表:把链表头(list_head)塞到数据结构里面!好处:同一个 task_struct,可以同时放进多条不同链表(就绪队列、等待队列),只需要嵌多个 list_head。

内核为什么要这么实现链表?

4.2 阻塞态

阻塞态:进程等待资源,资源未就绪,进程不被调度

4.3 挂起态

交换(swap、进程挂起)

内存不够的时候,OS 把进程代码、数据从内存换到磁盘 swap 分区(换出);等内存空闲了,再读回内存(换入)

  • 本质:用时间换空间(磁盘远慢于内存,频繁 swap 会很卡)
  • 区分两种挂起:
    • 阻塞挂起:进程本身在等资源(阻塞),同时内存不够,被换到 swap
    • 运行 / 就绪挂起:进程本来就绪 / 可运行,只是内存紧张,被换到 swap

⚠️注意:PCB(task_struct)一直留在内存里,不会被换出去!只有进程的代码数据被换去 swap

swap 分区常规大小:一般设为内存的 1/2 或者等于内存大小;swap in/out 变多 = 系统卡顿

4.4 Linux内核中的进程状态

Linux内核中定义了进程状态的宏,共7种状态,下⾯的状态在kernel源代码⾥定义

/*--------------------------------------------------- *The task state array is a strange "bitmap" of *reasons to sleep. Thus "running" is zero, and *you can test for combinations of others with *simple bit tests. -----------------------------------------------------*/ 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:僵尸态 */ };
标记名称说明
Rrunning意味着进程要么在运行种要么在队列里
Ssleeping可中断休眠,意味着进程在等待事件完成(阻塞,可以被信号唤醒,比如 scanf 输入)
Ddisk sleep不可中断休眠(等待 IO 硬件,不能被信号打断,一般等磁盘)
Tstopped暂停(收到 SIGSTOP 信号,gdb 调试断点就是这个)
ttracing stop被追踪暂停(gdb 跟踪)
Xdead进程彻底释放,马上消失,几乎看不到
Zzombie僵尸进程:进程退出了,PCB 还在,等父进程回收退出信息

4.5 前后台

无论前台还是后台都能够向前台打印消息,在任何时刻,在Linux命令行操作时,有且仅有一个进程是前台进程,能够从键盘读取数据,拥有键盘文件的进程,就是前台进程;后台的作用是提高效率

前台进程(foreground)

  • 占用终端,能读取键盘输入、输出到屏幕
  • 可以直接用Ctrl+C终止、Ctrl+Z暂停
  • 前台跑起来之后,shell 卡住,不能敲新命令

后台进程(background)

  • 不占用终端输入,shell 提示符立刻回来,可以继续敲别的命令
  • 默认无法读取键盘,输出默认还会打印到屏幕
  • Ctrl+C对后台进程无效

这种Ss后面带加号的就是处于后台


以上就是本期全部内容

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 6:31:29

Agent 16. 如何设计多 Agent 的协作与动态切换机制?

16. 如何设计多 Agent 的协作与动态切换机制&#xff1f; &#x1f454;面试官&#xff1a;多 Agent 系统里&#xff0c;各个 Agent 之间怎么协作&#xff1f; &#x1f64b;‍♂️我&#xff1a;一个 Agent 做完之后把结果传给下一个 Agent&#xff0c;就像流水线一样&#xf…

作者头像 李华
网站建设 2026/11/2 6:29:35

mithril.js PWA 完整指南:用 8.93KB 框架搭出可安装的离线应用

mithril.js PWA 完整指南&#xff1a;用 8.93KB 框架搭出可安装的离线应用 【免费下载链接】mithril.js A JavaScript Framework for Building Brilliant Applications 项目地址: https://gitcode.com/gh_mirrors/mi/mithril.js mithril.js 是一个构建单页应用的 JavaSc…

作者头像 李华
网站建设 2026/11/2 6:29:23

AI3T 2026 | 人工智能基础设施与架构 Track 征稿通知

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 6:25:44

深入理解前端缓存策略完整指南(进阶篇)

本文深入探讨前端缓存策略完整指南&#xff08;进阶篇&#xff09;&#xff0c;涵盖背景分析、原理剖析、实战步骤、配置示例、优化建议和避坑指南。在前端性能优化领域&#xff0c;前端缓存策略完整指南&#xff08;进阶篇&#xff09;是开发者和技术负责人持续关注的核心议题…

作者头像 李华
网站建设 2026/11/2 6:22:53

若依整合vue教程

RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架|RuoYi开源|RuoYi视频|若依视频|RuoYi开发文档|若依开发文档|Java开源框架|Java|SpringBoot|SrpingBoot2.0…

作者头像 李华