Unix/Linux fork隐藏的开销

目录
  • 一、fork的由来
  • 二、早期UNIX的覆盖(overlaying)技术
  • 三、fork引入UNIX前的表象
    • 1、UNIX fork的诞生
    • 2、UNIX fork-exec
    • 3、UNIX fork/exec/exit/wait

一、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内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!,希望大家以后多多支持我们!

(0)

相关推荐

  • 增强Linux和Unix服务器安全性的方法详解

    网络安全是一个十分主要的课题,而服务器是网络安全中最主要的环节.Linux被以为是一个比拟安全的Internet服务器,作为一种开放源代码操作系统,一旦Linux系统中觉察有安全漏洞,Internet上去自全球各地的意愿者会积极修补它.但是,系统维护员往往无法及时地得到信息并执行更正,这就给黑客以可乘之机.但是,相关于这些系统自身的安全漏洞,更多的安全疑问是由不当的配置形成的,可以议决适当的配置来防止.服务器上运转的服务越多,不当的配置呈现的时机也就越多,呈现安全疑问的能够性就越大. 众所周知,

  • UNIX/LINUX SHELL 正则表达式语法详解附使用方法

    几乎所有重要问题都需要从无用数据中过滤出有用数据.了解大量的 UNIX? 命令行实用工具如何使用正则表达式 来去芜取精. 非常奇怪,直到今天我仍然能重复周六早上的经典歌曲"Conjunction Junction".这是好事(看了太多电视)还是坏事(也许是我现在职业的先兆)仍然有待讨论.不管怎样,这首小调在欢快的节奏下传递了基本的信息. 我还没有为学习 UNIX 构想出与"Conjunction Junction"相似的作品,但是我会在未来的几个月里尝试亲手编写这样

  • 建议收藏:好用的 Unix/Linux 命令技巧

    1.删除一个大文件 我在生产服务器上有一个很大的200GB的日志文件需要删除.我的rm和ls命令已经崩溃,我担心这是由于巨大的磁盘IO造成的,要删除这个大文件,输入: > /path/to/file.log # 或使用如下格式 : > /path/to/file.log # 然后删除它 rm /path/to/file.log 2.如何记录终端输出? 试试使用script命令行工具来为你的终端输出创建输出记录. script my.terminal.sessio 输入命令: ls date s

  • Unix/Linux系统下的nobody用户与nologin详细介绍

    Unix/Linux系统下的nobody用户是什么? 1.Windows系统在安装后会自动建立一些用户帐户,在Linux系统中同样有一些用户帐户是在 系统安装后就有的,就像Windows系统中的内置帐户一样. 2.它们是用来完成特定任务的,比如nobody和ftp等,我们访问LinuxSir.Org的网页程序时,官网的服务器就是让客户以'nobody'身份登录的(相当于Windows系统中的匿名帐户); 我们匿名访问ftp时,会用到用户ftp或nobody. 3.首先,nobody是一个普通用户

  • 详解Supervisor安装与配置(Linux/Unix进程管理工具)

    Supervisor(http://supervisord.org/)是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具,不支持Windows系统.它可以很方便的监听.启动.停止.重启一个或多个进程.用Supervisor管理的进程,当一个进程意外被杀死,supervisort监听到进程死后,会自动将它重新拉起,很方便的做到进程自动恢复的功能,不再需要自己写shell脚本来控制. 因为Supervisor是Python开发的,安装前先检查一下

  • linux Shell入门:掌握Linux,OS X,Unix的Shell环境

    在Linux或类Unix系统中,每个用户和进程都运行在一个特定环境中.这个环境包含了变量.设置.别名.函数以及更多的东西.下面是对Shell环境下一些常用命令的简单介绍,包括每个命令如何使用的例子,以及在命令行下设定你自己的环境来提高效率. 找出你当前的shell 在终端应用中输入下面命令中的任意一个: ps $$ ps -p $$ 或者 echo "$0" 输出范例: 图1:找出当前的shell 找出所有已安装的shell 找到已安装shell的完整路径: type -a zsh t

  • Linux/Unix下安装Perl模块的两种方法分享

    方法一.手工安装的步骤 从CPAN下载了DBI模块1.13版的压缩文件DBI-1.13.tar.gz,假设放在/usr/local/src/下. cd /usr/local/src 解压缩这个文件: tar xvzf DBI-1.13.tar.gz 这时会新建一个DBI-1.13的目录. cd DBI-1.13 生成makefile: perl Makefile.PL 建立模块 make 测试模块 make test 如果测试结果报告"all test ok",您就可以放心地安装编译好

  • 在 Linux/Unix 中不重启 Vim 而重新加载 .vimrc 文件的流程

    我是一位新的 Vim 编辑器用户.我通常使用 :vs ~/.vimrc 来加载 ~/.vimrc 配置.而当我编辑 .vimrc 时,我需要不重启 Vim 会话而重新加载它.在 Linux 或者类 Unix 系统中,如何在编辑 .vimrc 后,重新加载它而不用重启 Vim 呢? Vim 是自由开源并且向上兼容 Vi 的编辑器.它可以用来编辑各种文本.它在编辑用 C/Perl/Python 编写的程序时特别有用.可以用它来编辑 Linux/Unix 配置文件. ~/.vimrc 是你个人的 Vi

  • Unix/Linux fork隐藏的开销

    目录 一.fork的由来 二.早期UNIX的覆盖(overlaying)技术 三.fork引入UNIX前的表象 1.UNIX fork的诞生 2.UNIX fork-exec 3.UNIX fork/exec/exit/wait 一.fork的由来 fork的思想在UNIX出现几年前就出现了,时间大概是1963年,这比UNIX在PDP-7上的第一个版本早了6年. 1963年,计算机科学家Melvin Conway(以Conway's Law闻名于世)写下一篇论文,正式提出了fork思想, for

  • 分享20个Unix/Linux 命令技巧

    让我们用这些Unix/Linux命令技巧开启新的一年,提高在终端下的生产力.我已经找了很久了,现在就与你们分享. > /path/to/file.log # 或使用如下格式 : > /path/to/file.log # 然后删除它 rm /path/to/file.log 如何记录终端输出? 试试使用script命令行工具来为你的终端输出创建输出记录. script my.terminal.sessio 输入命令: ls date sudo service foo stop 要退出(结束sc

  • 详解Unix/Linux中周期执行指令Crontab命令

    简介 crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于"crontab"文件中,以供之后读取和执行. 通常,crontab储存的指令被守护进程激活,crond常常在后台运行,每一分钟检查是否有预定的作业需要执行.这类作业一般称为cron jobs. cron 是 Unix/Linux 中提供定期执行 shell 命令的服务,包括 crond 和 crontab 两部分: crond: cron 服务的守护

  • 总结UNIX/LINUX下C++程序计时的方法

    前言 良好的计时器可帮助程序开发人员确定程序的性能瓶颈,或对不同算法进行性能比较.但要精确测量程序的运行时间并不容易,因为进程切换.中断.共享的多用户.网络流量.高速缓存访问及转移预测等因素都会对程序计时产生影响. 下面看看小编为大家整理几个计时方法 方法一: 如果是想统计某个程序的运行时间,那么可以使用 time ./a.out 方法二: 如果是想对某个函数或者语句进行计时,那么有别的方法.比如说,gettimeofday函数.直接贴示例代码: #include <sys/time.h> v

  • linux 下隐藏进程的一种方法及遇到的坑

    前言 1.本文所用到的工具在 https://github.com/gianlucaborello/libprocesshider 可以下载 2.思路就是利用 LD_PRELOAD 来实现系统函数的劫持 LD_PRELOAD是什么: LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库.这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数.通过这个环境变量,我们可以在主程序和其动态链

  • 收藏28个Unix/Linux的命令行神器

    dstat & sar iostat, vmstat, ifstat 三合一的工具,用来查看系统性能. 官方网站:http://dag.wieers.com/rpm/packages/dstat/ 你可以这样使用: alias dstat='dstat -cdlmnpsy' alias dstat='dstat -cdlmnpsy' slurm 查看网络流量的一个工具 官方网站:  Simple Linux Utility for Resource Management vim & ema

  • Python垃圾回收及Linux Fork

    目录 1.Linux fork简介 1.Copy-On-Write策略增加Python多进程内存占用的原因 3.解决办法 前言: 在口袋助理看到了其他部门的同事针对Python2内存占用做的一点优化工作,自己比较感兴趣,遂记录下. 1.Linux fork简介 fork是Linux提供的创建子进程的系统调用.为了优化创建进程速度,Linux内核使用了Copy-on-Write的方式去创建进程,所谓Copy-on-Write是指执行fork之后,内核并不立即给子进程分配物理内存空间,而是让子进程的

  • Python垃圾回收及Linux Fork

    目录 1.Linux fork简介 1.Copy-On-Write策略增加Python多进程内存占用的原因 3.解决办法 前言: 在口袋助理看到了其他部门的同事针对Python2内存占用做的一点优化工作,自己比较感兴趣,遂记录下. 1.Linux fork简介 fork是Linux提供的创建子进程的系统调用.为了优化创建进程速度,Linux内核使用了Copy-on-Write的方式去创建进程,所谓Copy-on-Write是指执行fork之后,内核并不立即给子进程分配物理内存空间,而是让子进程的

随机推荐