Unix/Linux fork隐藏的开销

 更新时间:2021年08月23日 15:21:45   作者:Linux阅码场  
本文通过介绍Unix、fork的由来及早期状态,展开其隐藏的开销,对此感兴趣的小伙伴不要错过奥

一、fork的由来

fork的思想在UNIX出现几年前就出现了,时间大概是1963年,这比UNIX在PDP-7上的第一个版本早了6年。
1963年,计算机科学家Melvin Conway(以Conway's Law闻名于世)写下一篇论文,正式提出了fork思想,
fork的思想最初是Conway作为一种 多处理器并行 的方案提出来的,这个想法非常有意思。简而言之,fork思想来源于流程图。

我们看一个普通的流程图:

你看,流程图的分枝处,fork-叉子,多么形象!

一个流程图上的分支点分裂出来的分支显然是逻辑独立的,这便是可并行的前提,于是它们便可以表现为不同的 处理进程(process) 的形式,当时的表达还只是“process”这个术语,它还不是现代操作系统意义上的“进程”的概念。

join同步点表现为多个并行处理的进程由于某种原因不得不同步的点,也就是多个并行流程汇合的点,直到现在,在多线程编程中,这个点依然叫join。比如Java Thread的join方法以及pthread库的pthread_join函数。

广义来讲,join也表示诸如临界区等必须串行通过的点, 减少join点的数量将会提高并行的效率。

我们来看看Conway论文中关于fork的原始图示:

Conway在论文中的另一个创举是,他将处理进程(也就是后来操作系统中的process的概念)以及执行该进程的处理器(即CPU核)分离了开来,抽象出了schedule层。

大意是说、“只要满足系统中的活动处理器数量是总处理器数量和并行处理进程的最小值即可。” 这意味着调度程序可以将多处理器系统的所有处理器和系统所有处理进程分别看作是统一的资源池和消费者,执行统一调度:

在UNIX引入fork之后,这种多处理器并行的设计思想就深入到了UNIX的核心。这个思想最终也影响了UNIX以及后来的Linux,直到现在。
关于这个设计思想为什么可以影响UNIX这么久,我想和Conway本人的“Conway's law”不无关系,在这个law中,他提到:Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.

二、早期UNIX的覆盖(overlaying)技术

接下来看UNIX fork的另一个脉络,1969年最初的UNIX用一种在现在看来非常奇怪的方式运行。

一般的资料都是从UNIX v6版本开始讲起,那个版本已经是比较 “现代” 的版本了,所以很少有人能看到最初的UNIX是什么样子的。即便是能查阅到的1970年的PDP-7上运行的UNIX源码,也是引入fork之后的版本,在那之前的最原始版本几乎找不到了(你可能会说,那时的UNIX不叫UNIX,but who cares…)。

最初的UNIX是一个分时系统,它只有两个shell进程,分别属于两个终端:

分时系统最初并不是基于进程分时的,那时根本还没有完整的进程的概念,分时系统是针对终端分时的,而操作员坐在终端前,为了让每个操作员在操作过程中感觉上是在独占机器资源,每个终端享受一段时间的时间片,在该时间片内,该终端前的操作员完全享受机器,但是为了公平,超过了时间片,时间片就要给另一个终端。

就是这样,最初的UNIX为了体现分时特性,实现了最少的两个终端。注意,最初的UNIX没有fork,没有exec,甚至没有多进程的概念,为了实现分时,系统中仅有两个朴素的shell进程。

事实上,最初的UNIX用只有两个元素的表来容纳所有进程(显然,这看起来好笑…),当然,这里的 “表” 的概念也是抽象的朴素概念,因为当时的系统是用PDP-7的汇编写的,还没有后来C语言数据结构。

我们现在考虑其中一个终端的shell进程如何工作。马上问题就来了, 这个shell进程如何执行别的命令程序??

如果说系统中最多只能容纳两个进程,一个终端只有一个shell进程的话,当该终端的shell进程执行其它命令程序时,它自己怎么办?这个问题得思考一会儿…

注意:不要用现代的眼光去评价1969年的初版UNIX,按照现代的眼光,执行一个程序必然要生成一个新的进程,显然这在初版UNIX中并不正确。

答案是根本不用产生新的进程,直接将命令程序的代码载入内存并 覆盖 掉shell进程的代码即可!当命令执行完后,再用shell的代码覆盖掉命令程序的代码,针对单独的终端,系统其实一直在执行下面的覆盖循环(摘自论文的Process control 章节):

然而,在fork被引入UNIX之前,事实就是这样。一个终端上一直都是那一个进程,一会儿它执行shell的代码,一会儿它执行具体命令程序的代码,以下是一个覆盖程序的结构(图片来自《FreeBSD操作系统设计与实现》一书):

然而,当时毕竟还没有将这个逻辑封装成exec系统调用,这些都是每一个进程显式完成的:

  • 对于shell执行命令程序而言,shell自己执行disk IO来载入命令程序覆盖掉自身;
  • 对于命令程序执行结束时,exit调用内部执行disk IO载入shell程序。

exec逻辑是shell程序的一部分,由于它会被所有的命令程序所使用,该逻辑也被封装到了exit调用中。

三、fork引入UNIX前的表象

1963年Melvin Conway提出了fork思想,作为在多处理器中并行执行进程的一个手段。

1969年汤普森版UNIX仅有两个shell进程,使用覆盖(overlaying)技术执行命令。

截止目前,我们看到的表象是:

汤普森版UNIX没有fork,没有exec,没有wait,仅有的库函数般的exit也和现在的exit系统调用大相径庭,显然汤普森版UNIX并非一个多进程系统,而只是一个可以跑的简陋的两终端分时系统!

1、UNIX fork的诞生

fork是如何引入UNIX的呢?

这还要从采用覆盖技术的汤普森版UNIX所固有的问题说起,还是看论文原文:

若要解决这些问题,很简单的方案汤普森都想到了:

  • 保持shell进程的驻留而不是销毁。命令执行时,将其交换到磁盘便是了

很显然,命令程序是不能覆盖掉shell进程了。解决方案是使用 “交换” 技术。

交换技术和覆盖技术其实都是解决有限内存的多进程使用问题的,不同点在于方向不同:

  • 覆盖技术指的是用不同的进程磁盘映像覆盖当前的进程内存映像。
  • 交换技术指的是用将进程的内存映像交换到磁盘,载入一个别的进程磁盘映像。

使用交换技术解决覆盖的问题,意味着要创建新的进程:

  • 在新的进程中执行命令程序。

UNIX需要进行改动,两个配额的进程表显然不够用了。当然,解决方案也并不麻烦:

要讲效率,创造不如抄袭,创建新进程的最直接的就是copy当前shell进程,在copy的新进程中执行覆盖,命令程序覆盖copy的新进程,而当前的终端shell进程则被交换到磁盘保得全身。

覆盖和交换相结合了,UNIX离现代化更近了一步!

确定了copy当前进程的方案后,进一步的问题是如何来copy进程。

现在要说回fork了。

Conway提出fork思想后,马上就有了fork的实现原型(正如Conway自己所说,他只是提出了一个可能造就存在的想法,并没有实现它),Project Genie算是实现fork比较完善的系统之一了。

Project Genie系统的fork不仅仅是盲目地copy进程,它对fork的过程拥有精细的控制权,比如分配多大的内存空间,copy哪些必要的资源等等。显然,Project Genie的fork是冲着Conway的多处理器并行逻辑去的。

还是那句话,创造不如抄袭,UNIX若想实现进程copy,有一个现成的模版就是Project Genie,但是Project Genie的fork对于UNIX太过复杂,太过精细化了,UNIX显然用不到这些精细的控制, UNIX仅仅是想让fork出来的新进程被覆盖,而不是让它去执行什么多处理器上的并行逻辑。

换句话说,UNIX只是借用了fork的copy逻辑的实现,来完成一件别的事。

于是,UNIX非常粗暴的实现了fork!即完全copy父进程,这就是直到现在我们依然在使用的fork系统调用:

投机取巧:

  • fork本来就不是让你用来覆盖新进程的,不然为何多此一举。fork是让你来分解程序流程得以并行处理的。

UNIX fork就此诞生!

我们再次回顾一下UNIX fork诞生之前的景象:

再来看看fork诞生之后的景象:

于是UNIX正式迈开了现代化建设的步伐,一直走到了今天。

2、UNIX fork-exec

关于exec,故事没什么好讲的,它事实上就是关于上述覆盖逻辑的封装,此后程序员不必自己写覆盖逻辑了,直接调用exec系统调用即可。

于是经典的UNIX fork-exec序列便形成了。

3、UNIX fork/exec/exit/wait

值得一提的是,fork被引入UNIX后,exit的语义发生了巨大的改变。

在原始的1969年汤普森版UNIX中,由于每一个终端有且仅有一个进程,这意味着覆盖永远是在shell程序和某个命令程序之间进行的:

  • shell执行命令A:命令程序A覆盖内存中的shell代码。
  • 命令A执行结束:shell覆盖结束的命令A的内存代码。

然而,在fork被引入后,虽然shell执行某个命令依然是特定的命令程序覆盖fork出来的shell子进程,但是当命令执行完毕后,exit逻辑却不能再让shell覆盖当前命令程序了,因为shell从来就没有结束过,它作为父进程只是被交换到了磁盘而已(后来内存到了,可以容纳多个进程时,连交换都不需要了)。

那么exit将让谁来覆盖当前进程呢?

答案是不用覆盖,按照exit的字面意思,它只要结束自己就可以了。

本着 自己的资源自己管理的责任原则 exit只需要清理掉自己分配的资源即可。比如清理掉自己的内存空间以及一些其它的数据结构。

对于子进程本身而言,由于它是父进程生成的,所以它便由父进程来管理释放。于是经典的UNIX进程管理四件套正式形成:

到此这篇关于Unix/Linux fork隐藏的开销的文章就介绍到这了,更多相关Unix/Linux fork内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!,希望大家以后多多支持脚本之家!

相关文章

  • Centos 7.4服务器时间同步配置方法【基于NTP服务】

    Centos 7.4服务器时间同步配置方法【基于NTP服务】

    这篇文章主要介绍了Centos 7.4服务器时间同步配置方法,结合实例形式分析了NTP服务器安装、启动、设置时间同步等相关命令及问题解决方法,需要的朋友可以参考下
    2019-03-03
  • Ubuntu无法连接网络的解决办法

    Ubuntu无法连接网络的解决办法

    这篇文章主要为大家详细介绍了虚拟机中Ubuntu无法连接网络的有效解决办法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-03-03
  • Linux的路由表详细介绍

    Linux的路由表详细介绍

    这篇文章主要介绍了Linux的路由表详细介绍的相关资料,希望通过本文大家能彻底了解Linux 路由表的知识,需要的朋友可以参考下
    2017-08-08
  • Linux deb包解压、修改等操作方法代码示例

    Linux deb包解压、修改等操作方法代码示例

    这篇文章主要介绍了Linux deb包解压、修改等操作方法代码示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • linux如何查看nginx启动路径

    linux如何查看nginx启动路径

    这篇文章主要介绍了linux如何查看nginx启动路径问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 详解Linux文本编辑器Vim

    详解Linux文本编辑器Vim

    这篇文章主要介绍了Linux文本编辑器Vim,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • linux解决Tomcat内存溢出的问题

    linux解决Tomcat内存溢出的问题

    下面小编就为大家带来一篇linux解决Tomcat内存溢出的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • linux下vsftpd的安装及配置使用详细步骤(推荐)

    linux下vsftpd的安装及配置使用详细步骤(推荐)

    这篇文章主要介绍了linux下vsftpd的安装及配置使用详细步骤 ,需要的朋友可以参考下
    2018-01-01
  • 一步步教你配置并使用Xshell7免费版

    一步步教你配置并使用Xshell7免费版

    想要轻松掌握一款强大的终端仿真器吗?恭喜你找到了答案!本指南将一步步教你如何配置并使用 Xshell7 免费版,无论是新手还是资深用户,都能在这里找到实用技巧和惊喜,立即跟随我们的脚步,探索 Xshell7 的魅力吧!让我们一起开启高效运维之旅!
    2024-01-01
  • apache2服务器的搭建和配置步骤详解

    apache2服务器的搭建和配置步骤详解

    其实搭建apache2并不难,只是网上的资料有很多都过时了,之前被误导过好几次,走了很多弯路。趁着这次在本地搭建网站的时机重新捋一下思路,也方便以后有需求的时候查阅,需要的朋友可以参考下。
    2017-04-04

最新评论