0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

Linux爱好者 2018-01-22 09:09 次阅读

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

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

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

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

fork 和 exec

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

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

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

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

开始是这样的:

my parent

|- me

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

my parent

|- me

|-- cloneof me

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

my parent

|- me

|-- ls

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

my parent

|- me

|-- ls(zombie)

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

my parent

|- 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 内存的进程,这是否意味着每次我启动一个子进程,所有 2 GB 的内存都要被复制一次?这听起来要耗费很多资源!”

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

为什么你需要知道这么多

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

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

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10992

    浏览量

    206742
  • UNIX
    +关注

    关注

    0

    文章

    295

    浏览量

    41061
  • Fork
    +关注

    关注

    0

    文章

    14

    浏览量

    3235

原文标题:当你在 Linux 上启动一个进程时会发生什么?

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux下多进程编程之fork()函数语法

    ,因此执行速度是比较慢的。为了加快fork()的执行速度,很多UNIX系统设计者创建了vfork()。vfork()也能创建新进程,但它不产生父进程的副本。它是通过允许父子进程可访问相同物理内存,从而伪装
    发表于 08-19 09:28

    【Linux学习杂谈】之exec族函数

    fork之后调用exec函数来执行我们的可执行程序int execl(const char *path, const char *arg, ...);int execv(const char
    发表于 09-08 13:14

    使用fork/exec/wait/exit等函数去创建一个进程

    运行。本期课程首先将会带领大家了解什么是进程,通过编程,学习使用fork/exec/wait/exit等函数去创建一个进程、管理控制一个进程的运行、终止一个进程。接下来,会带领大家打通进程与终端之间的关系。...
    发表于 11-04 08:58

    最常见的fork用法是什么

    的进程一模一样,这两 个进程都会继续运行最常见的fork用法是创建一个新的进程,然后使用exec载入二进制映像,替换当前进程的映像。这种情况下,派生(for
    发表于 12-15 07:38

    esp32在MacBook M1上工作,无法调试的原因?

    我是 esp32 的新手。在 MacBook M1 上工作。安装了 ESP-IDF,可以构建、刷写、运行和监控应用程序。从 CLI 和使用 VS 代码扩展。但是我无法调试。无论我尝试从 CLI 还是
    发表于 04-13 06:31

    何在esp8266的LX6上工作

    个“NeoPixel ZeroDMA”库……但它是特定于 M0/M4 的。 有没有类似的东西可以在 esp8266 的 LX6 上工作
    发表于 05-04 06:58

    何在GitHub上更新Fork以及PullRequest给源项目

    这里有个小技巧:在默认状态下,会是Base源项目,Head我自己的Fork项目;这样选择任何一个时,会调到某个无法更新的页面;解决方法是先Base或Head一个其他人的账号下的Fork,接着选择
    的头像 发表于 01-08 09:38 4148次阅读
    如<b class='flag-5'>何在</b>GitHub上更新<b class='flag-5'>Fork</b>以及PullRequest给源项目

    对“Fork”做一个技术方面的简介

    Linux/Unix 中的进程,除了 init 进程本身之外,都是由 init 进程复刻fork出来的。关于服务器编程方面的复刻fork的使用,可以进一步参阅“搭个 Web 服务器(
    发表于 04-02 14:48 227次阅读

    何在Mac终端上使用UNIX命令

    这是在Macintosh计算机上运行的操作系统。 Mac OS是基于UNIX的Darwin内核,因此终端可以让您基本上直接将命令输入到UNIX环境中。
    的头像 发表于 08-05 10:00 9878次阅读

    信号通路如何在多层PCB上工作

    在印刷电路板设计中,为什么要尽可能使用接地平面?接地平面降低了信号返回路径的电感。这反过来又将瞬时接地电流产生的噪声降至最低。本文将讨论信号通路如何在多层PCB上工作以及返回通路电感的概念。
    的头像 发表于 11-19 17:36 1899次阅读

    SystemVerilog中的fork-join

    fork-join语句块中,每个语句都是并发进程。在这个语句块中,父进程一直被阻塞,直到所有由“fork-join”产生的子进程都执行完。
    的头像 发表于 12-09 11:58 1600次阅读

    通过一个脚本搞懂fork、source和exec

    Source模式下,子shell执行时获取的环境变量会会影响到父shell。与fork的区别在于,不会额外打开一个sub-shell来执行被调用的脚本,而是在同一个shell中执行。所以,被调用的脚本中声明的变量和环境变量, 都可以在主脚本中得到和使用。
    的头像 发表于 02-03 16:05 1133次阅读

    Qt中的三个exec之间有什么联系

    在Qt中,常见到三个exec,第一个是QApplication::exec(),第二个是QEventLoop::exec,第三个是QThread::exec()。本文从源码角度来看看这
    的头像 发表于 03-06 09:44 1231次阅读

    Linux中可怕的fork炸弹介绍

    Linux中的Fork炸弹(Fork Bomb)是一种拒绝服务攻击的形式,它利用了操作系统中的“fork()”系统调用。
    的头像 发表于 05-22 10:46 2023次阅读
    Linux中可怕的<b class='flag-5'>fork</b>炸弹介绍

    docker exec命令的使用方法

    Docker是一种开源的容器化平台,可以让开发人员在容器中打包和运行应用程序。它提供了一种快速、可靠和一致的方式来构建、部署和运行应用程序。Docker exec命令是Docker提供的一个非常
    的头像 发表于 11-23 09:33 759次阅读