本文节选自达人课《攻克 Linux 系统编程》
你写了一个多进程模型的服务器,但总感觉新进程启动地不干净,有时会有些父进程的东西掺和到子进程里来。
可如果让父进程在启动子进程之前做更多的计算,或者单纯多等一会,这种情况发生的概率便大大减少了,该系统的行为让人有点捉摸不透,其背后的原因是什么呢?
简单来讲,进程就是运行中的程序。更进一步,在用户空间中,进程是加载器根据程序头提供的信息将程序加载到内存并运行的实体。
在本文中,我们就来深挖进程在用户空间内的更多细节,主要包括以下几部分内容:
- 进程的虚拟空间排布
- 进程的启动
- 监控子进程的状态
- 进程的终止
01 进程的虚拟空间排布
1.1 虚拟空间及其功能
在理解虚拟空间排布之前,先要明确虚拟空间的概念。在《攻克 Linux 系统编程》中,我们解释了的 ELF 文件头中指定的程序入口地址,各个节区在程序运行时的内存排布地址等,指的都是在进程虚拟空间中的地址。
虚拟空间可以认为是操作系统给每个进程准备的沙盒,就像电影《黑客帝国》中 Matrix 给每个人准备的充满营养液的容器一样。
实际上,每个进程只存活在自己的虚拟世界里,却感觉自己独占了所有的系统资源(内存)。
当一个进程要使用某块内存时,它会将自己世界里的一个内存地址告诉操作系统,剩下的事情就由操作系统接管了。
操作系统中的内存管理策略将决定映射哪块真实的物理内存,供应用使用。操作系统会竭尽全力满足所有进程合法的内存访问请求。
一旦发现应用试图访问非法内存,它将会把进程杀死,防止它做“坏事”影响到系统或其他进程。
这样做,一方面为了安全,防止进程操作其他进程或者系统内核的数据;另一方面为了保证系统可同时运行多个进程,且单个进程使用的内存空间可以超过实际的物理内存容量。
该做法的另一结果则是降低了每个进程内存管理的复杂度,进程只需关心如何使用自己线性排列的虚拟地址,而不需关心物理内存的实际容量,以及如何使用真实的物理内存。
1.2 虚拟空间地址排布
在 32 位系统下,进程的虚拟地址空间有 4G,其中的 1G 分配给了内核空间,用户应用可以使用剩余的 3G。
在 64 位的 Linux 系统上,进程的虚拟地址空间可以达到 256TB,内核和应用分别占用 128TB。目前看来,这样的地址空间范围足够用了。
一个典型的内存排布结构如下图所示:
图中 #1 部分是《深入程序布局内部》中讨论过的内容,是按照 ELF 文件中的程序头信息,加载文件内容所得到的。除此之外,加载器还会为每个应用分配栈区(Stack)、堆区(Heap)和动态链接库加载区。栈和堆分别向相对的方向增长,系统会有相应的保护措施,阻止越界行为发生。
在 Linux 系统中,使用如下命令可查看一个运行中的进程的内存排布。
ca