慧聪芯城 | 慧聪智能硬件网 | 慧聪新能源网 | 慧聪LED网 | 慧聪电气网 | 慧聪电源网 | 慧聪IT网 | 慧聪变频器网
特惠新品微信
投稿
热门推荐:传感器专栏 | 半导体专栏 | 热门展会 | CES 2018 | 商营通 | 买芯片 采购 | 汽车电子 | 2017年活动回顾 | 中国电子展 | 2017品牌盛会TOP10榜单

慧聪电子网首页 > 技术文章 > 首页技术 > 正文

分享到

关于 fork 和 exec 是如何在 Unix 上工作的

http://www.ec.hc360.com2018年01月22日10:09 来源:电子发烧友T|T

    本文是关于fork和exec是如何在Unix上工作的。你或许已经知道,也有人还不知道。几年前当我了解到这些时,我惊叹不已。

关于 fork 和 exec 是如何在 Unix 上工作的

关于 fork 和 exec 是如何在 Unix 上工作的

    我们要做的是启动一个进程。我们已经在博客上讨论了很多关于系统调用的问题,每当你启动一个进程或者打开一个文件,这都是一个系统调用。所以你可能会认为有这样的系统调用:

    start_process(["ls","-l","my_cool_directory"])

    这是一个合理的想法,显然这是它在DOS或Windows中的工作原理。我想说的是,这并不是Linux上的工作原理。但是,我查阅了文档,确实有一个posix_spawn的系统调用基本上是这样做的,不过这不在本文的讨论范围内。

    fork和exec

    Linux上的posix_spawn是通过两个系统调用实现的,分别是fork和exec(实际上是execve),这些都是人们常常使用的。尽管在OSX上,人们使用posix_spawn,而fork和exec是不提倡的,但我们将讨论的是Linux。

    关于fork和exec是如何在Unix上工作的

    Linux中的每个进程都存在于“进程树”中。你可以通过运行pstree命令查看进程树。树的根是init,进程号是1。每个进程(init除外)都有一个父进程,一个进程都可以有很多子进程。

    所以,假设我要启动一个名为ls的进程来列出一个目录。我是不是只要发起一个进程ls就好了呢?不是的。

    我要做的是,创建一个子进程,这个子进程是我(me)本身的一个克隆,然后这个子进程的“脑子”被吃掉了,变成ls。

    开始是这样的:

    myparent

    |-me

    然后运行fork(),生成一个子进程,是我(me)自己的一份克隆:

    myparent

    |-me

    |--cloneofme

    然后我让该子进程运行exec("ls"),变成这样:

    myparent

    |-me

    |--ls

    当ls命令结束后,我几乎又变回了我自己:

    myparent

    |-me

    |--ls(zombie)

    在这时ls其实是一个僵尸进程。这意味着它已经死了,但它还在等我,以防我需要检查它的返回值(使用wait系统调用)。一旦我获得了它的返回值,我将再次恢复独自一人的状态。

    myparent

    |-me

    fork和exec的代码实现

    如果你要编写一个shell,这是你必须做的一个练习。

    事实证明,有了C或Python的技能,你可以在几个小时内编写一个非常简单的shell,像bash一样。(至少如果你旁边能有个人多少懂一点,如果没有的话用时会久一点。)我已经完成啦,真的很棒。

    这就是fork和exec在程序中的实现。我写了一段C的伪代码。请记住,fork也可能会失败哦。

    intpid=fork();

    //我要分身啦

    //“我”是谁呢?可能是子进程也可能是父进程

    if(pid==0){

    //我现在是子进程

    //“ls”吃掉了我脑子,然后变成一个完全不一样的进程

    exec(["ls"])

    }elseif(pid==-1){

    //天啊,fork失败了,简直是灾难!

    }else{

    //我是父进程耶

    //继续做一个酷酷的美男子吧

    //需要的话,我可以等待子进程结束

    }

    上文提到的“脑子被吃掉”是什么意思呢?

    进程有很多属性:

    打开的文件(包括打开的网络连接)

    环境变量

    信号处理程序(在程序上运行Ctrl+C时会发生什么?)

    内存(你的“地址空间”)

    寄存器

    可执行文件(/proc/$pid/exe)

    cgroups和命名空间(与Linux容器相关)

    当前的工作目录

    运行程序的用户

    其他我还没想到的

    当你运行execve并让另一个程序吃掉你的脑子的时候,实际上几乎所有东西都是相同的!你们有相同的环境变量、信号处理程序和打开的文件等等。

    唯一改变的是,内存、寄存器以及正在运行的程序,这可是件大事。

    为何fork并非那么耗费资源(写入时复制)

    你可能会问:“如果我有一个使用了2GB内存的进程,这是否意味着每次我启动一个子进程,所有2GB的内存都要被复制一次?这听起来要耗费很多资源!”

    事实上,Linux为fork()调用实现了写时复制copyonwrite,对于新进程的2GB内存来说,就像是“看看旧的进程就好了,是一样的!”。然后,当如果任一进程试图写入内存,此时系统才真正地复制一个内存的副本给该进程。如果两个进程的内存是相同的,就不需要复制了。

    为什么你需要知道这么多

    你可能会说,好吧,这些细节听起来很厉害,但为什么这么重要?关于信号处理程序或环境变量的细节会被继承吗?这对我的日常编程有什么实际影响呢?

    有可能哦!比如说,在Kamal的博客上有一个很有意思的bug。它讨论了Python如何使信号处理程序忽略了SIGPIPE。也就是说,如果你从Python里运行一个程序,默认情况下它会忽略SIGPIPE!这意味着,程序从Python脚本和从shell启动的表现会有所不同。在这种情况下,它会造成一个奇怪的问题。

    所以,你的程序的环境(环境变量、信号处理程序等)可能很重要,都是从父进程继承来的。知道这些,在调试时是很有用的。

责任编辑:陈彩霞

声明:本网站中,来源标明为“ 慧聪电子网”的文章,转载请标明出处。

欢迎投稿,邮箱:lijia03@hc360.com

活动推荐

更多

2017年电子产业品牌盛会暨采购经理人年会

2017年12月22日

深圳会展中心5F簕杜鹃厅

精彩现场

友情链接

申请友情链接

赛迪网 RFID世界网电子信息产业网畅享网与非网电子产品世界慧聪智能硬件网慧聪电气网慧聪电源网慧聪IT网慧聪变频器网慧聪LED网慧聪芯城慧聪新能源网

慧聪电子网总部

北京市海淀区海淀大街3号鼎好大厦B座7层

慧聪电子网分部

上海市普陀区中山北路3000号长城大厦5层

深圳市福田区深南中路2018号兴华大厦A座七楼

关于我们 | 加入我们 | 我要投稿
| 寻求报道 | 申请合作

Copyright?2000-2014 hc360.com. All Rights Reserved
京ICP证010051号 海淀公安局网络备案编号:11010802015485