【Linux】深度解析Linux进程管理:从进程PCB到创建子进程的全景指南

devtools/2025/3/19 6:09:26/

文章目录

  • 前言
  • 一、从任务管理器到内核源码
  • 二、进程控制块(task_struct)的体系化设计
    • 2.1 先描述
    • 2.2 再组织
    • 2.3 Linux系统下的PCB设计
  • 三、PID的奥秘:从用户空间到内核空间
    • 3.1 PID及相关指令
    • 3.2 PPID父进程
  • 四、通过系统调用fork()函数创建子进程
    • 4.1 为什么要给父进程返回PID,给子进程返回0
    • 4.2 一个函数如何做到返回两次值的?
    • 4.3 一个变量pid_d id为什么会有两个不同的内容?
  • 总结


前言

在Linux操作系统中,进程管理是操作系统核心功能模块之一,其本质是通过地址空间抽象与动态资源调度实现多任务并发执行。本文将从进程控制块(task_struct)、内存管理单元(MMU)到调度算法(CFS)的实现机制展开分析,揭示操作系统如何通过精妙的数据结构与算法设计,构建高效可靠的进程管理体系。
承上启下:
上篇最后提到了操作系统是通过先描述,再组织的方式管理资源,同样的,操作系统也是以同样的方式进行进程的管理。


一、从任务管理器到内核源码

一个已经加载到内存中的程序,就叫做进程。
在Windows系统下,当我们在Windows中按下 Ctrl+Shift+Esc 调出任务管理器时,就可以看到有着各种各样的进程:

在这里插入图片描述
在Linux系统下,我们使用指令ps axj,也能查看到当前系统中的进程,top指令来查看当前正在运行的进程
在这里插入图片描述
在这里插入图片描述

二、进程控制块(task_struct)的体系化设计

在计算机开机时,只需要先将操作系统加载进内存,再由操作系统运行其他多个进程就完成了计算机的开机工作。但此时由于每个进程的状态都不一样,操作系统是如何分辨哪些是需要被立即执行的或是不执行的进程呢?所以操作系统必须将进程管理起来。
于是,就很顺利地利用到了上一章内容提到的先描述,再组织的管理方法!

2.1 先描述

任何一个进程,在加载到内存并形成真正的进程时,操作系统都要先创建描述进程的结构体对象—— PCB(process contrl block,进程控制块) 包含该进程属性集合(进程编号、进程状态、进程优先级)的结构体,再将代码与数据加载到内存中。

每个进程都拥有两个核心要素:

  1. 有进程资源(学生个体)
  2. 有进程PCB(校教务管理系统有该学生个体信息)

PCB结构体内包含相关指针,指向了个人代码数据块。因此,操作系统管理进程并不需要管理真正的代码和数据,操作系统管理进程只需通过PCB即可找到对应的个人代码和数据进行执行。

结论: 进程 = 内核PCB结构体对象 + 个人代码数据

举例:就像校园保安虽在学校(个体),但因为没有学籍档案(PCB),永远无法成为正式学生(进程)。这解释了为什么僵尸进程会占用系统资源——它们失去了PCB却未被清理

2.2 再组织

有了对每一个进程的描述PCB块,就只需要把这些块链接起来的方法,我们可以将这些PCB都想象成一个一个的节点,再由某种算法组织,这一结构是不是很像数据结构中的链表?事实上,操作系统在这一过程中,充斥着大量的数据结构,不仅仅是某种单链表,甚至会出现各种数据结构复用的场景(这个节点即是某个单链表的节点,又是某个二叉树的节点)。

struct LinkNode
{int data;struct LinkNode* next;
};

于是,在操作系统中对进程进行管理,就变成了对某种数据结构的增删查改的操作。所谓增删查改就意味着该进程是否将要被执行、或者是否结束进程,对于不同状态的进程存在着不同的 “ 队列 ” ,按序执行的进程PCB需要去对应队列排队等待执行。

但是在不同的平台上,其PCB的实现方式也不同,那么Linux系统是怎么做的呢?

2.3 Linux系统下的PCB设计

在Linux内核中,最基本的进程描述块采用 task_struct 的方式(PCB),并使用双向链表组织。

注意,操作系统中的某个PCB节点并不只有一种数据结构类型,一个PCB节点可能即属于某个双向链表的节点,同时也属于某个二叉树的节点(内部实现其实是引入不同的数据结构的结构体指针),链入不同的结构体组织中,会进入不同的算法,从而影响着不同的应用背景。

task_struct是Linux内核的一种数据结构,是PCB的一种,它会被装载到RAM(内存)里并且包含着进程的信息。

// 精简版task_struct(基于Linux 6.x内核)
struct task_struct {volatile long state;        // 进程状态(运行/睡眠/停止)void *stack;                // 内核栈指针pid_t pid;                  // 进程唯一IDstruct mm_struct *mm;       // 内存管理信息struct files_struct *files; // 打开文件表struct list_head tasks;     // 进程链表节点// ...(更多字段见内核源码sched.h)
};

task_ struct更多内容分类

  • 标示符(PID等): 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等
  • 优先级: 相对于其他进程的优先级
  • 程序计数器(PC指针): 程序中即将被执行的下一条指令的地址
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指
  • 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
  • 其他信息(信号、网络等)

三、PID的奥秘:从用户空间到内核空间

Linux内核通过task_struct结构体实现进程的元数据管理,其核心字段包括:标识符体系:PID(进程唯一标识)、PPID(父进程ID)

3.1 PID及相关指令

每个程序拥有唯一的标识符PID,如何查看?

  • 方法一 ps pjx (使用grep与管道操作查询指定进程信息)
ps ajx | head -1 && ps ajx | grep proccess

grep本身也是一个过滤进程,所以也会被加载到进程中,并且它也有proc名称的任务
在这里插入图片描述
在这里插入图片描述

  • 方法二 ls /proc/[PID](查看指定PID的进程信息)
  1. exe(可执行文件存储地址)

在这里插入图片描述

  1. cwd(current work dir,当前工作目录)

在这里插入图片描述

c语言文件部分,我们知道fopen函数如果不指定路径,仅有文件名的情况下,会在当前目录下查找;touch指令创建一个文件时,也是默认在当前目录下创建,何为“当前目录”?
cwd存放的是当前进程的工作目录,操作(fopen、touch)默认都是在这个路径下展开的。

  • 补充

杀进程:kill -9 [PID]在这里插入图片描述循环查看进程
第一个系统调用接口:getpid()
man手册查看getpipd()函数介绍以及头文件包含

while :; do ps ajx | head -1 ; ps ajx | grep proc |grep - v grep ; echo "----------------------" ; sleep 1 ; done

3.2 PPID父进程

在这里插入图片描述

可以看到子进程的PID随着每次加载的过程也跟着变化,但父进程PPID不变,那么这个PPID是什么呢?
我们使用ps ajx | head -1; ps ajx | grep [PPID]查看一下COMMAND,我们发现是bash解释器进程。
在Linux当中,每次进入xshell环境,系统都会给分配命令行解释bash进程,作为后面所有可执行文件的父进程,换言之,所有的可执行文件的进程都是bash的子进程,执行过程中出现了问题只会影响到子进程。
在这里插入图片描述

四、通过系统调用fork()函数创建子进程

区别:
./ 创建子进程,指令层面

fork() 创建子进程,代码层面

  6 int main(){7     printf("进程,PID = %d, PPID = %d\n",getpid(),getppid());8     pid_t id = fork();9     if(id == 0){10         while(1)11         {12             printf("子进程:PID = %d, PPID = %d\n",13                     getpid(),getppid());14             sleep(1);15         }16     }else if(id > 0){17         while(1)18         {19             printf("父进程:PID = %d, PPID = %d\n",20                     getpid(),getppid());21             sleep(1);22         }23     }else{24         //25     }                                                         26     return 0;27 }

在这里插入图片描述

4.1 为什么要给父进程返回PID,给子进程返回0

返回不同的返回值,是为了让不同的执行流区分,以便执行不同的代码段。fork()执行完毕之后的代码共享。
在系统中,往往是单一父进程,诸多子进程的形式,父进程需要区分并控制子进程,所以需要给父进程返回子进程的pid,防止找不到属于自己的子进程;而子进程找父进程显然更容易,因此只需要返回0

4.2 一个函数如何做到返回两次值的?

从调用fork函数开始,就已经将子进程创建了出来(创建PCB、分配代码块与数据块),但此时fork函数还未执行到最后一行return语句,所以该行return语句会被父子进程执行两次,所以fork函数是可以返回两次甚至多次的返回值的。

4.3 一个变量pid_d id为什么会有两个不同的内容?

引入概念:在任何平台,进程在运行时是具有独立性的,a软件挂掉了不影响b软件的进程。

既然是独立的,那么父进程与子进程不会共用同一块数据(注意,只共享代码(以为代码一般不会被修改),但不能共享数据(防止互相被修改)),那么子进程的数据块从何而来呢?答案是将父进程的数据块给自己“拷贝”一份(但完全拷贝时,子进程会浪费父进程数据块资源,实际上刚开始的时候父子进程的数据块与代码块全是共享状态,只有当子进程需要修改数据块的数据时,系统会单独开辟一块空间用以存放子进程修改后的数据,而后子进程再次访问时,只会访问到自己修改的那一块数据而不会污染父进程原有的数据块。这一概念,被称之为 数据层面的写时拷贝),这样一来,父子进程的关系就是,代码块是共享的,数据块是割裂的。

所以在fork函数return返回值时,就已经发生了写时拷贝,所以 pid_t id的值在被父子进程分别调用时,看到的就是不同的值。

类比字符串的深浅拷贝:浅拷贝:两个指针指向同一块空间,深拷贝,两个指针指向不同空间,就是为了防止互相干扰。

至于接收返回值的pid_t id变量是如何存储两个值这个问题,会在后面的进程地址空间章节详细解答!


总结

本章涉及到的知识点比较多,具体指令验证细节并没有列出来,但文字上尽量做出了阐述说明,希望大家看完仍有所收获。
👍 ​感谢各位大佬观看。如果本文有帮助,请点赞收藏支持~


http://www.ppmy.cn/devtools/168260.html

相关文章

失败的面试经历(ʘ̥∧ʘ̥)

一.面向对象的三大特性 1.封装:将对象内部的属性私有化,外部对象不能够直接访问,但是可以提供一些可以使外部对象操作内部属性的方法。 2.继承:类与类之间会有一些相似之处,但也会有一些异处,使得他们与众…

C#设计模式之工厂模式

C#设计模式之工厂模式 工厂模式包含:简单工厂->工厂方法->抽象工厂,是设计模式中的创建型模式 1. 简单工厂模式:用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖已有代码(工厂类里的GetCalcultor…

Linux编辑器

Linux编辑器 yum yum是包管理器,类似于Linux的应用商店,安装和卸载工具需要使用yum,Linux系统中是预装了yum的 rzsz工具: Linux和window的文件互传工具 yum install lrzsz 查看所有的软件列表: yum list el代表centos,base代表软件的提供方是base 卸载软件 yum remove l…

微软OneNote无法同步解决方案

目录 前言原因UWP特性 解决方案C***h注册表 参考链接 前言 假设有多台Windows电脑,最方便且免费的多设备笔记同步方案就是微软自家的OneNote,使用OneDrive自带的5G云存储。 但是在国内大陆的OneNote,经常会出现无法同步、同步失败&#xff1…

基于SpringBoot+Vue的电商应用系统的设计与实现(代码+数据库+LW)

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本电商应用系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…

Java面试黄金宝典1

1. 8 种基本数据类型 整数类型 byte: 它是最小的整数类型,占用 1 个字节(8 位)。在一些对内存使用要求极高的场景,比如嵌入式系统开发、数据传输时对数据量有严格限制的情况,会使用 byte 类型。例如&#x…

工作记录 2017-02-04

工作记录 2017-02-04 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 郝 更新的问题 1、DataExport的设置中去掉了ListPayors,见DataExport\bin\dataexport.xml 2、“IPA/Group Name” 改为 “Insurance Name”。 3、修改了Payment Posted的E…

工作记录 2017-02-06

工作记录 2017-02-06 序号 工作 相关人员 1 修改Payment Detail的处理。 修改邮件上的问题。 更新RD服务器。 郝 更新的问题 1、View EOB Files的 “prior / next” 改为了 “PREV / NEXT” 2、Summary “Billed Date” 改为了 “Billing Date” 3、修改了Payment De…