【转】OS | 从一道面试题谈 Linux 下 fork 的运行机制
今天一位朋友去一个不错的外企面试 Linux 开发职位,面试官出了一个如下的题目:
给出如下C程序,在 Linux 下使用 gcc 编译:
#include "stdio.h"
#include "sys/types.h"
#include "unistd.h"
int main()
{
pid_t pid1;
pid_t pid2;
pid1 = fork();
pid2 = fork();
printf("pid1:%d, pid2:%d\n", pid1, pid2);
}
要求如下:
已知从这个程序执行到这个程序的所有进程结束这个时间段内,没有其它新进程执行。
1、请说出执行这个程序后,将一共运行几个进程。
2、如果其中一个进程的输出结果是 “pid1:1001, pid2:1002”,写出其他进程的输出结果(不考虑进程执行顺序)。
明显这道题的目的是考察 Linux 下 fork 的执行机制。下面我们通过分析这个题目,谈谈 Linux 下fork 的运行机制。
预备知识
这里先列出一些必要的预备知识,对 Linux 下进程机制比较熟悉的朋友可以略过。
1、进程可以看做程序的一次执行过程。在 Linux 下,每个进程有唯一的 PID 标识进程。PID是一个从 \(1\) 到 \(32768\) 的正整数,其中1一般是特殊进程 init,其它进程从 \(2\) 开始依次编号。当用完 \(32768\) 后,从\(2\) 重新开始。
2、Linux 中有一个叫进程表的结构用来存储当前正在运行的进程。可以使用“ps aux”命令查看所有正在运行的进程。
3、进程在Linux 中呈树状结构,init 为根节点,其它进程均有父进程,某进程的父进程就是启动这个进程的进程,这个进程叫做父进程的子进程。
4、fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
解题的关键
有了上面的预备知识,我们再来看看解题的关键。我认为,解题的关键就是要认识到 fork 将程序切成两段。看下图:

上图表示一个含有 fork 的程序,而fork语句可以看成将程序切为 A、B两个部分。然后整个程序会如下运行:
step1、设由 shell 直接执行程序,生成了进程 \(P\) 。\(P\) 执行完Part. A的所有代码。
step2、当执行到 pid = fork(); 时,P启动一个进程 \(Q\),\(Q\) 是 \(P\) 的子进程,和 \(P\) 是同一个程序的进程。\(Q\) 继承 \(P\) 的所有变量、环境变量、程序计数器的当前值。
step3、在 \(P\) 进程中,fork() 将 \(Q\) 的 PID 返回给变量 pid,并继续执行 Part. B 的代码。
step4、在进程 \(Q\) 中,将 \(0\) 赋给 pid,并继续执行Part. B的代码。
这里有三个点非常关键:
1、\(P\) 执行了所有程序,而 \(Q\) 只执行了 Part. B,即 fork() 后面的程序。(这是因为 \(Q\) 继承了 \(P\) 的PC-程序计数器)
2、\(Q\) 继承了 fork() 语句执行时当前的环境,而不是程序的初始环境。
3、\(P\) 中 fork() 语句启动子进程 \(Q\),并将 \(Q\) 的 PID 返回,而 \(Q\) 中的 fork() 语句不启动新进程,仅将 \(0\) 返回。
解题
下面利用上文阐述的知识进行解题。这里我把两个问题放在一起进行分析。
1、从 shell 中执行此程序,启动了一个进程,我们设这个进程为 \(P0\),设其 PID为 XXX(解题过程不需知道其PID)。
2、当执行到 pid1 = fork(); 时,\(P0\) 启动一个子进程 \(P1\) ,由题目知 \(P1\) 的 PID 为 \(1001\)。我们暂且不管 \(P1\)。
3、\(P0\) 中的 fork 返回 \(1001\)给pid1,继续执行到 pid2 = fork();,此时启动另一个新进程,设为\(P2\),由题目知 \(P2\) 的 PID 为 \(1002\) 。同样暂且不管 \(P2\)。
4、\(P0\) 中的第二个 fork 返回 \(1002\) 给 pid2,继续执行完后续程序,结束。所以,\(P0\)的结果为“pid1:1001, pid2:1002 ”。
5、再看 \(P2\),\(P2\) 生成时,\(P0\) 中 \(pid1=1001\),所以 \(P2\) 中 pid1 继承 \(P0\) 的 \(1001\),而作为子进程\(pid2=0\)。\(P2\) 从第二个 fork 后开始执行,结束后输出 “pid1:1001, pid2:0”。
6、接着看 \(P1\),\(P1\) 中第一条 fork 返回 \(0\) 给 pid1,然后接着执行后面的语句。而后面接着的语句是pid2 = fork(); 执行到这里,\(P1\) 又产生了一个新进程,设为 \(P3\) 。先不管 \(P3\) 。
7、\(P1\) 中第二条 fork 将 \(P3\) 的 PID 返回给 pid2,由预备知识知 \(P3\) 的 PID为 \(1003\),所以 \(P1\) 的\(pid2=1003\)。\(P1\)继续执行后续程序,结束,输出“pid1:0, pid2:1003”。
8、\(P3\) 作为 \(P1\) 的子进程,继承 \(P1\) 中 \(pid1=0\),并且第二条 fork 将 \(0\) 返回给 pid2,所以 \(P3\) 最后输出“ pid1:0, pid2:0 ”。
9、至此,整个执行过程完毕。
所得答案:
1、一共执行了四个进程。(P0, P1, P2, P3)
2、另外几个进程的输出分别为:
pid1:1001, pid2:0
pid1:0, pid2:1003
pid1:0, pid2:0
进一步可以给出一个以 \(P0\) 为根的进程树:

验证
下面我们去linux下实际执行这个程序,来验证我们的答案。
程序如下图:

用 gcc 编译、执行后结果如下:

由于我们不太可能刚巧碰上 \(PID\) 分配到 \(1001\) 的情况,所以具体数值可能和答案有所差别。不过将这里的 \(2710\) 看做基数的话,结果和我们上面的解答是一致的。
总结
应该说这不是一道特别难或特别刁钻的题目,但是由于 \(fork\) 函数运行机制的复杂性,造就了当两个fork 并排时,问题就变得很复杂。解这个题的关键,一是要对 Linux 下进程的机制有一定认识,二是抓住上文提到的几个关于 fork 的关键点。朋友说,这个题给的时间是 \(5\) 分钟,应该说时间还算充裕,但是在面试的场合下,还是很考验一个人对进程、fork的掌握程度和现场推理能力。
希望本文能帮助朋友们对fork的执行机制有一个明晰的认识。
【转】OS | 从一道面试题谈 Linux 下 fork 的运行机制的更多相关文章
- 【Linux下进程机制】从一道面试题谈linux下fork的运行机制
今天一位朋友去一个不错的外企面试linux开发职位,面试官出了一个如下的题目: 给出如下C程序,在linux下使用gcc编译: #include "stdio.h" #includ ...
- 从一道面试题谈linux下fork的运行机制
http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html
- (转)linux下fork的运行机制
转载http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html 给出如下C程序,在linux下使用g ...
- []转帖] 浅谈Linux下的五种I/O模型
浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html 一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是 ...
- 浅谈Linux下/etc/passwd文件
浅谈Linux 下/etc/passwd文件 看过了很多渗透测试的文章,发现在很多文章中都会有/etc/passwd这个文件,那么,这个文件中到底有些什么内容呢?下面我们来详细的介绍一下. 在Linu ...
- 【转】Linux下Fork与Exec使用
Linux下Fork与Exec使用 转自 Linux下Fork与Exec使用 一.引言 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值.for ...
- Linux下Fork与Exec使用
Linux下Fork与Exec使用 一.引言 对于没有接触过Unix/Linux操作系统的人来说,fork是最难理解的概念之一:它执行一次却返回两个值.fork函数是Unix系统最杰出的成就之一, ...
- Linux下fork()、vfork()、clone()和exec()的区别
转自Linux下fork().vfork().clone()和exec()的区别 前三个和最后一个是两个类型.前三个主要是Linux用来创建新的进程(线程)而设计的,exec()系列函数则是用来用指定 ...
- [转帖]Linux下fork函数及pthread函数的总结
Linux下fork函数及pthread函数的总结 https://blog.csdn.net/wangdd_199326/article/details/76180514 fork Linux多进程 ...
- linux下,一个运行中的程序,究竟占用了多少内存
linux下,一个运行中的程序,究竟占用了多少内存 1. 在linux下,查看一个运行中的程序, 占用了多少内存, 一般的命令有 (1). ps aux: 其中 VSZ(或VSS)列 表示,程序占用 ...
随机推荐
- 聊聊分布式 SQL 数据库Doris(八)
稀疏索引 密集索引:文件中的每个搜索码值都对应一个索引值,就是叶子节点保存了整行. 稀疏索引:文件只为索引码的某些值建立索引项. 稀疏索引的创建过程包括将集合中的元素分段,并给每个分段中的最小元素创建 ...
- 从零开始的 dbt 入门教程 (dbt-core 基础篇)
最近一直在处理数据分析和数据建模的事情,所以接触了 dbt 等数据分析的工具,国内目前对于 dbt 比较详细的资料不多,所以打算写四道五篇 dbt 相关的文章,本文属于 dbt 系列的第一篇,本篇主要 ...
- 功能不够,SQL来凑,修改数据库的正确姿势?
修改数据库是一项关键任务,需要小心谨慎地执行,以确保数据的完整性和准确性.下面是一个详细的步骤指南,介绍了正确修改数据库的姿势. 第一步:备份数据库 在进行任何数据库修改之前,务必备份数据库.这样,如 ...
- keil 5 安装教程
一.下载 keil 官网 二.安装教程 1.开始安装 双击安装包,开始安装,直接下一步 2.勾选同意,下一步 3.选择软件安装路径,下一步 4.填写信息 可以随意填写,下一步. 5.等待安装 6.安装 ...
- Go笔记(5)-String详解
String详解 Go中的字符串是一个字节的切片,可以通过将其内容封装在双引号中来创建字符串,Go中的字符串Unicode兼容的,并且是UTF-8编码,字符串是一些字节的集合 (1)创建字符串 str ...
- 给祖传系统做了点 GC调优,暂停时间降低了 90%
问题描述 公司某规则引擎系统,在每次发版启动会手动预热,预热完成当流量切进来之后会偶发的出现一次长达1-2秒的Young GC(流量并不大,并且LB下的每个节点都会出现该情况) 在这次长暂停之后,每一 ...
- 火眼金睛破局ES伪慢查询
一.问题现象 服务现象 服务接口的TP99性能降低 ES现象 YGC:耗时极其不正常, 峰值200+次,耗时7s+ FULL GC:不正常,次数为1但是频繁,STW 5s 慢查询:存在慢查询5+ 二 ...
- Android SDK Manager 报错“加载 SDK 组件信息失败”。(Android SDK Manager complains with "Loading SDK component information failed."
[解决方案]: 将存储库设置更改为 Google .因此,在 android SDK 管理器上点击齿轮图标(设置),然后点击 Repository -> Google.
- P7112 【模板】行列式求值
学<高等代数>第二章的时候过来搜了搜模板,结果真搜到了.于是水一篇题解. 本文部分内容来自<高等代数>. 行列式定义 对于一个 \(n\) 阶行列式 \[A_{n \times ...
- Typora 掘金小册主题
主题说明 此主题样式基本来源于掘金小册学习界面 下载地址:https://github.com/easylee1996/typora-juejin-theme 主题预览 主题文档示例 如何使用 克隆仓 ...