《Linux 是怎样工作的》笔记第三章——进程管理

122 阅读3分钟

简介

本章介绍进程创建与删除,事实上这部分与第 5 章的虚拟内存章节紧密相关,所以本章抛开虚拟内存简单介绍,第 5 章再完整讲述。

1. 创建进程

在 Linux 中创建进程通常有 2 种目的:

  • 将同一个程序分成多进程处理(例如 Web 服务器接收多个请求)
  • 创建另一个程序(例如从 bash 启动另一个程序)

Linux 通过 fork 和 execve 函数来实现这两个目的(实际底层对应的系统调用是 clone 和 execve)。

2. fork 函数

将同一个程序分成多进程处理,Linux 通过 fork 函数实现,具体执行流程如下:

image.png

a. 为子进程创建内存空间,并将父进程内存空间复制到子进程。 b. 父进程和子进程分裂成两个进程已执行不同的代码。这一点依赖于 fork() 在父子进程返回不同值来实现。

本书提供了非常经典的实验,编写一个 fork 程序,并打印父子进程的进程 id 来说明分裂多进程后的行为,可以看到父进程 fork 返回子进程 id。子进程则是返回 0.

3. execve 函数

启动另一个程序,在 Linux 中是通过 execve 实现。这一过程并没有新建进程,而是替换了当前进程。execve 执行流程如下:

image.png

a. 读取可执行文件,并读取创建进程的内存映像所需的信息。 b. 用新进程的数据覆盖当前进程的内存 c. 从最初的命令开始运行新的进程

理解 a 的关键点在于理解可执行文件。它除了包含代码和数据外,还包含了开始运行程序时所需要的数据,具体来说,包括:

  • 代码段关键信息:包含代码的代码段在文件中的偏移量、大小,以及内存映像的起始地址
  • 数据段关键信息:包含代码以外的变量等数据的数据段在文件中的偏移量、大小,以及内存映像的起始地址
  • 程序入口信息:程序执行的第一条指令的内存地址(入口点)

之所以需要内存映像的起始地址,是因为 CPU 访存需要具体内存地址。

可执行文件结构示例如下:

image.png

Linux 可执行文件遵循的是名为ELF(Executable and Linkable Format,可执行与可链接格式)的格式,实际比上图更复杂。

我们可以通过 readelf 获取可执行文件信息,比如:

# 获取起始地址等信息
readelf -h /bin/sleep
# 获取代码段、数据段等信息
readelf -S /bin/sleep
# 在程序运行时创建的进程的内存映像信息,可以从 /proc/进程id/maps这一文件中找到。
cat /proc/12345/maps


假设要另起一个别的进程,通常采用的是 fork and exec 方式。先 fork,然后在子进程调用 execve 替换成新程序执行。

image.png

在 C 语言之外,其他编程语言比如 Python 也可以通过 os.exec() 函数来请求 execve() 系统调用,本质是一样的。

4 结束进程

在 Linux 中可以通过 _exit() 函数(底层使用 exit_group() 系统调用)结束进程。进程结束后,所有分配给进程的内存都会被回收。

通常,我们很少直接使用 _exit() 函数,而是通过 C 标准库的 exit() 函数来结束进程的运行。C 标准库在调用完自身的终止处理后调用 _exit() 函数。