跳到主要内容
Linux 进程概念与 fork 函数详解 | 极客日志
C 算法
Linux 进程概念与 fork 函数详解 深入解析 Linux 下进程的核心概念。进程是资源分配的基本单位,由 PCB(task_struct)描述状态。通过 ps 命令和 /proc 目录可监控进程信息。getpid 和 getppid 系统调用用于获取进程标识符。重点讲解 fork 函数创建子进程的机制,包括返回值区分父子进程、写时拷贝原理及父子进程独立性。掌握这些知识有助于理解操作系统调度与并发编程基础。
栈溢出 发布于 2026/3/30 更新于 2026/4/27 3 浏览
进程是操作系统中资源分配和调度的核心单位,而 fork() 函数是创建子进程的关键工具。本节将简要介绍进程的概念,并通过 fork() 探讨其实际应用。
一、进程概念
进程 是计算机操作系统中的一个基本概念,指的是一个程序在计算机中的一次执行过程 。它是操作系统中资源分配和调度的基本单位。进程和程序的主要区别在于:程序是静态的代码,而进程是程序在运行时的动态实例。
1.1 进程理解
课本概念 :程序的一个执行实例,正在执行的程序等
内核观点 :已经加载到内存中的程序,担当分配系统资源 (CPU、内存) 的实体
当我们运行程序时,需要数据和代码加载到内存中,内存会对进程中数据和代码进行处理。其中操作系统中不单单只存在一个进程,而是同时存在多个进程,操作系统需要对这些进程进行管理,那么就需要用到"先描述,再组织",进行更好地管理。
二、如何描述和管理进程
2.1 进程本质
我们将磁盘中可执行程序的数据和代码被加载到内存,就可以说明"进程是可执行程序中数据和代码"吗?这里有一系列问题:占据内存多少空间、被调用多少时间、当前进程状态如何?
结果很显然,单从内存只有可执行程序的代码和数据完全不足描述上述需要的信息 。这说明了"操作系统将可执行程序加载到内存中,被加载到内存中的代码和数据,根本不是进程,只是进程对应的代码和数据"。
2.2 PCB 描述进程状态
操作系统为了管理已经被加载进来所有进程,需要创建用于描述进程的结构体对象PCB(进程控制块,Process Control Block) ,本质是对于进程属性进行描述。
2.2.1 task_struct 在 Linux 中描述进程的结构体是task_struct 。task_struct属于 Linux 中内核数据结构,它会被装载到 RAM(内存) 里并且包含着进程的信息。
2.2.2 task_struct 内容分类
PCB 是对操作系统学科的统称,其中struct task_struct对 Linux 进程块具体的称呼。
【标识符】 :描述本进程的唯一标识符,用来区别其他进程。
【状态】 :任务状态,退出代码,退出信号等。
【优先级】 :相对于其他进程的优先级。
【程序计数器】 :程序中即将被执行的下一条指令的地址。
【内存指针】 :包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 (可执行程序执行需要对应的数据和代码,所有 PCB 对象必须需要一个指针指向这块空间)。
【上下文数据】 :进程执行时处理器的寄存器中的数据。
【I/O 状态信息】 :包括显示的 I/O 请求,分配给进程的 I/O 设备和被进程使用的文件列表。
【记账信息】 :可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
【其他信息】
总结 :进程 = 内核数据结构对象 PCB + 对应代码和数据。
2.3 组织进程
操作系统做管理并不是把你的可执行程序加载进来做直接管理,而是对于 PCB 进行管理。
操作系统对进程进行管理,本质上是对 PCB 做管理,并不关心进程加载内存中代码和数据。只需要找到 PCB 对象,通过 PCB 内存指针找到对应的代码和数据,交给 CPU 处理就行。
每个进程都有属于自己的 PCB,为了更好地管理不同进程的 PCB,一般通过指针将 PCB 进行连接,形成链表 。根据不同进程的需求,会采用不同的数据结构进行存储,通过对应指针信息来将它们更好地管理。
其中在 Linux 中task_struct 主要是以双向链表形式组织 。
2.3.1 不同数据结构配合进程需求
进程管理的关键在于将其放入适合的组织数据结构中。不同的数据结构具有独特的特性,决定了其背后的算法选择,而算法的差异又对应于不同的应用场景。因此,合理的数据结构选择是优化进程管理的基础 。
【相关场景】 :调度运行进程,本质就是让进程控制块"task_struct"(简历) 进行排队,而不是这个代码和数据在排队,跟面试投简历一般,面试官查看每一份简历。
在操作系统中,可能存在存储进程指针的运行队列和等待队列。如果需要将某个进程移动到特定队列,可以通过修改队列指针的链接结构来实现,从而达到更灵活的进程管理目的。
三、查看进程状态
开机时操作系统(OS)从外部存储设备(如硬盘或固态硬盘)加载到内存中,这是启动过程的一个重要步骤。
3.1 查看所有进程指令 ps ajx 是用于查看系统中所有进程状态的命令。
具体参数含义如下:
ps:显示当前系统中的进程信息。
a:显示所有用户的进程(不仅仅是当前用户的进程)。
j:显示进程的控制终端、进程组等信息。
x:显示没有控制终端的进程(包括后台进程)。
【场景演示】 :这里想拿到头部属性信息和进程信息,可以使用指令级联。通过指令 ps axj | head -1 && ps axj | grep myprocess 查看可执行程序该进程状态信息。
如果指令需要被执行需要变成进程才能运行,grep 指令被执行本质上也是进程被执行,所以 grep 本身这个进程也被查出来。
3.2 /proc 目录
/proc 目录中的文件是内存级文件,关机时会消失,开机后会重新生成。它主要提供动态运行进程的可视化信息。
其中,蓝色表示目录文件,因为一个进程可能包含许多相关信息。我们可以尝试进入这些目录查看具体内容。
因为原则上一个程序要被变成进程调度,会在内存里面有存在一份。就算你将可执行程序从磁盘上删除了,由于内存存在备份,所以进程还是可以运行。
总结 :exe 表示当前进程对应的可执行文件路径,说明进程能够找到自己需要执行的代码(已被可视化展现)。cwd 表示当前进程的工作目录(Current Working Directory)。因此,当你使用 fopen 创建新文件时,默认路径就是当前目录,而这个默认位置正是由该进程的 cwd 决定的。说明这个启动的 myprocess 进程,是由这个指定的绝对路径下的这个程序加载形成的。
四、通过系统调用获取进程标识符 在内核结构体 task_struct 中,存在一个 pid 属性,用于表示进程的唯一标识符。
pid 是无符号整数类型(unsigned int pid),用于区分不同的进程。
它是操作系统封装的一种数据类型,实际对应无符号整数。
用户无法直接访问操作系统内核中与进程相关的 PCB(进程控制块)数据结构。如果用户需要获取进程的 pid、ppid,必须通过操作系统提供的系统调用**getpid()、getppid()**来完成。
4.1 父子进程 如果是 pid 属性表示进程的唯一标识符,那么 ppid 属性表示什么呢?
ppid 的含义:Parent Process ID 。
通过结果显示,ppid 属性表示父进程的唯一标识符。可以观察到,在我们的可执行程序中,父进程的 ppid 通常不会发生变化,但是 pid 会发生变化 ,因为它表示的是基础命令行进程(如 bash)的父进程标识。
4.1.1 父进程意义 在 Linux 权限管理相关内容中,以'王婆和实习生'的例子可以更好地理解为什么 bash 需要创建子进程。
bash 命令行的作用主要有两个方面:
解释命令 :将用户输入的指令解析为可执行操作。
阻止非法操作 :限制用户进行不符合权限的行为。
每一条指令或可执行程序其实都对应一个独立的进程。因此,bash 命令行会先创建一个子进程来执行对应的指令,而自己则保持运行,等待处理其他命令。
这种设计的优点是:即使子进程崩溃,也不会影响 bash 命令行本身的运行,从而确保可以继续处理其他指令。
4.2 使用系统调用接口 getpid、getppid 按照上述说法,进程每次启动对应 pid 都不一样,ppid 通常不会发生变化。但是当我们重新启动机器时,再次执行该程序,会发现 ppid 发生了变化,可以说明父进程或 bash 命令行在打开机器时候就创建好的进程 。
【实时监控脚本】 :while :; do ps ajx | head -1 ;ps ajx | grep mycode | grep -v grep;echo "----------------------------";sleep 1;done
如果不想显示与 grep 指令相关的进程信息,可以使用 -v 参数来排除,会过滤掉包含 grep 本身的进程信息。
五、系统调用创建进程
5.1 fork 函数(分叉/创建进程) 用户想创建进程,但是创建进程需要创建内核数据结构,但是用户是没有权限对内核数据结构进行任何的增删查改,用户没有权限在操作系统内新增一个 test_struct,所以操作系统也需要提供相对的系统调用 fork()。
5.2 fork 函数返回值
5.2.1 fork() 的返回值
成功创建子进程时 :
父进程中 :fork() 会返回子进程的 PID(正整数)。
子进程中 :fork() 会返回数值 0。
创建子进程失败时 :fork() 会返回 -1 给父进程,表示子进程创建失败。
总结:fork() 有两个返回值,分别传递给父进程和子进程,用于区分执行的上下文(父进程还是子进程),以及处理可能的错误情况。
5.2.2 为什么 fork 可以返回两次 由于子进程会继承父进程的代码,因此 return ret 这一段代码会被父子进程分别执行一次,从而导致两次返回结果。
5.2.3 变量同时接收两个返回值 由于子进程在修改数据发生了写时拷贝,导致了数据存在两份内容,进程具有独立性,父进程和子进程通过 if else 分流去执行共享的代码 。
【为父进程返回子进程 PID 和为子进程返回数值 0】
为父进程返回子进程的 PID :通过返回值,父进程可以知道子进程的唯一标识符(PID)。
为子进程返回数值 0 :子进程通过返回值知道自己是新创建的进程。
5.3 fork() 函数后进程关系
通过我们的实时监控,会发现 PID 和 PPID 是相同的,是存在父子关系。fork 之后,父进程和子进程会共享代码,但各自拥有独立的数据空间。
【创建一个进程的本质】 :系统中多了一个新的进程,对应一个新的内核 task_struct。子进程拥有自己的代码和数据空间,但默认情况下,继承了父进程的代码和数据。
【数据加载机制】 :
父进程的代码和数据是从磁盘加载的 ,子进程通过 fork 默认继承了这些内容,但有独立的内存空间。
【调用关系】 :
子进程会获取调用它的父进程的 id 信息 (导致子进程 PPID 为父进程 PID)。
5.4 父子进程运行顺序
当使用 fork() 函数创建出子进程,是先运行子进程还是父进程呢?这里先运行谁,是需要通过调度器去决定的 。一般电脑只有单个 CPU,调度器会根据当前进程中选择一个合适的进程放到 CPU 当中,进程之间会竞争 CPU 资源,所以调度器会遵循自己的一套原则来保证进程之间的公平性 (进程优先级、时间戳) 去决定。
5.5 创建子进程意义 如果父进程和子进程执行的后续代码完全相同,就没有实际意义。通常,父进程和子进程会各自执行代码的不同部分:父进程负责处理一部分逻辑,子进程负责处理另一部分逻辑。两个进程并发运行,从而提高程序的整体执行效率 。
【问题】 :问题在于 fork() 之后,父子进程执行后续代码完全相同,如何分开执行不同模块的功能呢?
fork 之后,父进程和子进程的代码虽然共享,但它们的执行路径可以通过 fork 的返回值来区分,从而保证父子进程执行不同的代码逻辑。
fork 的返回值 :在父进程中,fork 返回子进程的 PID(正整数)。在子进程中,fork 返回 0。如果 fork 失败,返回 -1 给父进程。
通过检查 fork 的返回值,可以让父进程和子进程执行不同的代码逻辑,例如:
pid_t pid = fork();
if (pid > 0 ) {
} else if (pid == 0 ) {
} else {
}
这种方式确保了父子进程可以执行各自的任务,实现功能分工。
5.6 进程具有独立性
任何平台上,进程都具有独立性,这意味着结束了某个进程不会影响其他进程。
在数据结构层面上,进程是并行的,你是你的我是我的;在逻辑上,它们是存在父子关系,但是仅仅是在数据结构指针层面上的关心。由于进程的代码和数据是从磁盘中加载进来的,所以子进程只能用父进程的代码,对于"数据"子进程也必须使用父进程的数据。
理论上,由于代码本身具有不可被修改属性,只有只读属性,所以父子进程共享代码。数据不一样,数据是可以被修改的,这样导致"子进程必须拷贝一份相同的数据"且独立出来。
问题在于父进程的数据可能非常多,但是可能子进程只是共享了其中一部分代码,不一定需要使用到父进程的所有数据。如果子进程无脑将这些数据拷贝过来,会存在大量资源浪费,效率也会大幅度减低。
5.7 Bash 父进程简介 在系统启动时,操作系统被加载到内存,同时负责启动对应的 bash 父进程 。当用户在命令行输入指令时,bash 会为每条指令创建一个子进程,由子进程负责执行该指令。即使指令执行失败,也不会影响 bash 父进程的正常运行,这种机制有效保护了 bash 父进程,使其能够专注于命令行解释的核心工作。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online