关于for循环中调用fork()系统调用的执行原理解析

2024-08-21 13:32

本文主要是介绍关于for循环中调用fork()系统调用的执行原理解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

关于for循环中调用fork()系统调用的执行原理解析_fan1570285527的博客-CSDN博客

关于for循环中调用fork()系统调用的执行原理解析
该问题来源于操作系统概念(第九版)一书中的第三章的习题3.5,分析for循环中fork的执行原理
1、预备知识
2、题目解析
3、剖析原理
4、结论和意外发现
4.1、结论:
4.2、意外发现
该问题来源于操作系统概念(第九版)一书中的第三章的习题3.5,分析for循环中fork的执行原理
1、预备知识
fork()系统调用原理:
fork()系统调用用来创建新的进程,调用fork的进程为父进程,新创建的进程为子进程,新进程的地址空间复制了原来进程的地址空间,fork()函数执行完毕后,这两个进程(父和子)都继续执行处于系统调用fork()之后的指令,但有一点需要特别注意,fork()向新进程(子进程)的地址空间中返回0,而向父进程中返回的值为新进程的pid(进程标识符)。

解读:
关于 “新进程的地址空间复制了原来进程的地址空间” 这句话的话一开始我没太理解,后来搞清楚了,用大白话来讲的话就是,在fork执行完毕后,两个进程所拥有的所有东西都是一样的,书中也提到子进程就是父进程的一个copy,但是他们的区别是进程标识符----pid不同。

搞清楚了fork()系统调用的概念和原理后就可以接着来看这道题目了。

2、题目解析
题目描述:
请分析下列C程序创建了多少个进程?

#include <stdio.h>
#include <unistd.h>

int main() {
    int i;
    for (i = 0; i < 4; i++) {
        pid = fork();
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
在不影响最终执行结果的基础上,我对原始代码进行了一点改动,加了一些打印和同步的语句方便分析运行结果

补充说明:

getpid()用来获取当前进程的进程标识符,我们显示为pid;
getppid() 用来获取当前进程的父进程的进程标识符,我们取别名叫做ppid;
wait(NULL); 语句的作用是等待当前进程的子进程执行完毕;
#include <stdio.h>
#include <unistd.h>

int main() {
    int i;
    int n = 4;
    printf("main process pid is :%d\n", getpid());
    pid_t pid;
    for (i = 0; i < n; i++) {
        pid = fork();
        if (pid < 0) {
            printf("fork failed...\n");
            return 1;
        } else if (pid == 0) {
            printf("child process... pid=%d  ppid=%d current i=%d\n", getpid(), getppid(), i);
        } else {
            printf("parent process... pid=%d  ppid=%d  current i=%d\n", getpid(), getppid(), i);    
            wait(NULL);
            printf("parent process... pid=%d his child compeleted...\n", getpid());
        }
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
执行结果:

main process pid is :29574
parent process... pid=29574  ppid=22955  current i=0
child process... pid=29575  pid=29574 current i=0
parent process... pid=29575  ppid=29574  current i=1
child process... pid=29576  pid=29575 current i=1
parent process... pid=29576  ppid=29575  current i=2
child process... pid=29577  pid=29576 current i=2
parent process... pid=29576 his child compeleted...
parent process... pid=29575 his child compeleted...
parent process... pid=29575  ppid=29574  current i=2
child process... pid=29578  pid=29575 current i=2
parent process... pid=29575 his child compeleted...
parent process... pid=29574 his child compeleted...
parent process... pid=29574  ppid=22955  current i=1
child process... pid=29579  pid=29574 current i=1
parent process... pid=29579  ppid=29574  current i=2
child process... pid=29580  pid=29579 current i=2
parent process... pid=29579 his child compeleted...
parent process... pid=29574 his child compeleted...
parent process... pid=29574  ppid=22955  current i=2
child process... pid=29581  pid=29574 current i=2
parent process... pid=29574 his child compeleted...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
从执行结果中可以看出,加上我们原本的main进程,最终一共有16个进程。再仔细分析,我们可以发现他有点类似建树的过程。但是这样直接根据一次的结果来分析不太严谨。

3、剖析原理
我们将循环条件按照1 -> 5顺序依次调整,看执行结果。

当 n == 1 时,执行结果为:

main process pid is :516
parent process... pid=516  ppid=22955  current i=0
child process... pid=517  ppid=516 current i=0
parent process... pid=516 his child compeleted...
1
2
3
4
用树来表示: 图中方框内数字代表进程标识符pid,圆圈中数字表示当前进程中变量i的值


当 n == 2 时,执行结果为:

main process pid is :623
parent process... pid=623  ppid=22955  current i=0
child process... pid=624  ppid=623 current i=0
parent process... pid=624  ppid=623  current i=1
child process... pid=625  ppid=624 current i=1
parent process... pid=624 his child compeleted...
parent process... pid=623 his child compeleted...
parent process... pid=623  ppid=22955  current i=1
child process... pid=626  ppid=623 current i=1
parent process... pid=623 his child compeleted...
1
2
3
4
5
6
7
8
9
10
用树来表示:

当 n == 3 时,执行结果为:

main process pid is :1120
parent process... pid=1120  ppid=22955  current i=0
child process... pid=1121  ppid=1120 current i=0
parent process... pid=1121  ppid=1120  current i=1
child process... pid=1122  ppid=1121 current i=1
parent process... pid=1122  ppid=1121  current i=2
child process... pid=1123  ppid=1122 current i=2
parent process... pid=1122 his child compeleted...
parent process... pid=1121 his child compeleted...
parent process... pid=1121  ppid=1120  current i=2
child process... pid=1124  ppid=1121 current i=2
parent process... pid=1121 his child compeleted...
parent process... pid=1120 his child compeleted...
parent process... pid=1120  ppid=22955  current i=1
child process... pid=1125  ppid=1120 current i=1
parent process... pid=1125  ppid=1120  current i=2
child process... pid=1126  ppid=1125 current i=2
parent process... pid=1125 his child compeleted...
parent process... pid=1120 his child compeleted...
parent process... pid=1120  ppid=22955  current i=2
child process... pid=1127  ppid=1120 current i=2
parent process... pid=1120 his child compeleted...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
用树来表示:

当 n == 4 时,执行结果为:

main process pid is :1758
parent process... pid=1758  ppid=22955  current i=0
child process... pid=1759  ppid=1758 current i=0
parent process... pid=1759  ppid=1758  current i=1
child process... pid=1760  ppid=1759 current i=1
parent process... pid=1760  ppid=1759  current i=2
child process... pid=1761  ppid=1760 current i=2
parent process... pid=1761  ppid=1760  current i=3
child process... pid=1762  ppid=1761 current i=3
parent process... pid=1761 his child compeleted...
parent process... pid=1760 his child compeleted...
parent process... pid=1760  ppid=1759  current i=3
child process... pid=1763  ppid=1760 current i=3
parent process... pid=1760 his child compeleted...
parent process... pid=1759 his child compeleted...
parent process... pid=1759  ppid=1758  current i=2
child process... pid=1764  ppid=1759 current i=2
parent process... pid=1764  ppid=1759  current i=3
child process... pid=1765  ppid=1764 current i=3
parent process... pid=1764 his child compeleted...
parent process... pid=1759 his child compeleted...
parent process... pid=1759  ppid=1758  current i=3
child process... pid=1766  ppid=1759 current i=3
parent process... pid=1759 his child compeleted...
parent process... pid=1758 his child compeleted...
parent process... pid=1758  ppid=22955  current i=1
child process... pid=1767  ppid=1758 current i=1
parent process... pid=1767  ppid=1758  current i=2
child process... pid=1768  ppid=1767 current i=2
parent process... pid=1768  ppid=1767  current i=3
child process... pid=1769  ppid=1768 current i=3
parent process... pid=1768 his child compeleted...
parent process... pid=1767 his child compeleted...
parent process... pid=1767  ppid=1758  current i=3
child process... pid=1770  ppid=1767 current i=3
parent process... pid=1767 his child compeleted...
parent process... pid=1758 his child compeleted...
parent process... pid=1758  ppid=22955  current i=2
child process... pid=1771  ppid=1758 current i=2
parent process... pid=1771  ppid=1758  current i=3
child process... pid=1772  ppid=1771 current i=3
parent process... pid=1771 his child compeleted...
parent process... pid=1758 his child compeleted...
parent process... pid=1758  ppid=22955  current i=3
child process... pid=1773  ppid=1758 current i=3
parent process... pid=1758 his child compeleted...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
用树来表示:(为了方便,后面的图中省略了pid标识)

当 n == 5 时,由于篇幅限制,省略执行结果,直接上树图

几张树图应该表示的很清晰了,这里我们选取n == 5的树图进行解释:

首先main进程执行for循环,我们先不考虑子进程的执行,只考虑main进程的执行步骤,那么很简单就能知道他一定会循环5次,也就是说调用5次fork()创建了5个新的进程,在图中就是树的第二层,每个节点的数字代表当前变量i的值
接下来我们去分析第一个新进程,也就是第二层中的0号进程。因为fork()执行完毕后,新进程也会接着执行fork()系统调用后的代码,在这里就是继续执行for循环,首先进行i++操作,那么此时i == 1,只针对浅红色0号进程而言,for循环需要从i == 1开始执行,那么当前进程只会在循环4次,也就是创建出4个新的进程
剩下的以此类推,就可以得到完整的进程树
4、结论和意外发现
4.1、结论:
最朴素的计算方法就是按照上述图的解析的方法去画出进程树,最终统计出进程数中节点个数。

4.2、意外发现
如果我们从第一张图分析道第五张图,把关注点放在进程数的每层的节点数量上,我们可以很轻松的发现它每层的节点数量是关于中间层对称的,是不是超级像一个数学知识点 ------ 杨辉三角

先给出推论:

for循环中n的值和进程树的深度depth的关系为:depth = n + 1
for循环中n的值与杨辉三角的行line的值关系为:line = n + 1
结合上述两条,给定一个n,我们可以确定进程树中从上往下每层节点总数与杨辉三角中的第 line = n + 1行的数值一一对应。
当 n == 1 ,进程树深度为n + 1 = 2;从上到下每层节点总数分别为1、1;
当 n == 2 ,进程树深度为n + 1 = 3;从上到下每层节点总数分别为1、2、1;
当 n == 3 ,进程树深度为n + 1 = 4;从上到下每层节点总数分别为1、3、3、1;
当 n == 4 ,进程树深度为n + 1 = 5;从上到下每层节点总数分别为1、4、6、4、1;
当 n == 5 ,进程树深度为n + 1 = 6;从上到下每层节点总数分别为1、5、10、10、5、1;
个人结论: 给定循环边界n,可根据公式 全部进程数 = 杨辉三角第 n + 1 行的和; 计算出创建的所有进程数量。
————————————————
版权声明:本文为CSDN博主「fan1570285527」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fan1570285527/article/details/121041080

这篇关于关于for循环中调用fork()系统调用的执行原理解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093291

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

Java实现远程执行Shell指令

《Java实现远程执行Shell指令》文章介绍使用JSch在SpringBoot项目中实现远程Shell操作,涵盖环境配置、依赖引入及工具类编写,详解分号和双与号执行多指令的区别... 目录软硬件环境说明编写执行Shell指令的工具类总结jsch(Java Secure Channel)是SSH2的一个纯J

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Spring 依赖注入与循环依赖总结

《Spring依赖注入与循环依赖总结》这篇文章给大家介绍Spring依赖注入与循环依赖总结篇,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Spring 三级缓存解决循环依赖1. 创建UserService原始对象2. 将原始对象包装成工

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

Java MCP 的鉴权深度解析

《JavaMCP的鉴权深度解析》文章介绍JavaMCP鉴权的实现方式,指出客户端可通过queryString、header或env传递鉴权信息,服务器端支持工具单独鉴权、过滤器集中鉴权及启动时鉴权... 目录一、MCP Client 侧(负责传递,比较简单)(1)常见的 mcpServers json 配置