在Linux上启动进程时会发生什么?

原文地址: What happens when you start a process on Linux?

在 Linux 上启动进程时会发生什么?#

这是关于 fork 和 exec 在 Unix 上的工作方式。你可能已经知道这一点,但有些人却不知道,并且在几年前当我知道的时候我感到很惊讶!

所以,你想打开一个进程。在此博客上,我们讨论了很多系统调用 –每当你启动一个进程或打开文件时,这就是系统调用。所以你可能会认为存在这样的系统调用

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

这是一个合理的考虑,显然这是它在 DOS / Windows 中的工作方式。我要说的是这不是 Linux 上的工作方式。我去看了看文档,表面上有一个叫 posix_spawn 的系统调用基本上可以做到这一点。这就是我所知道的。无论如何,我们不会谈论这个。

fork and exec#

Linux 上的 posix_spawn 幕后操作是通过称为 forkexec(实际上是 execve)的 2 个系统调用来实现的 ,这是人们通常实际使用的。在 OS X 上,显然不鼓励人们使用 posix_spawn 和 fork /exec!但是我们将讨论的是 Linux。

Linux 中的每个进程都位于 “进程树” 中。你可以通过运行 pstree 看到那棵树 。树的根是 PID 为 1 的 init。每个进程(init 除外)都有一个父进程,而任何进程都有很多子进程。

因此,假设我要启动一个名为 ls 的列出目录过程。我只有一个孩子 ls 吗?没有!

我要做的是代替你生一个孩子,而这个孩子是我自己的一个克隆人,然后这个孩子的大脑被吞噬并变成 ls

我们开始像这样:

my parent
    |- me

然后我跑 fork()。我有一个孩子,是我的克隆人。

my parent
    |- me
       |-- clone of me

然后我整理一下,以便我的孩子执行 exec("ls")。那让我们

my parent
    |- me
       |-- ls

当 ls 退出时,我将再次独自一人。大部分是这样的

my parent
    |- me
       |-- ls (zombie)

此时,ls 实际上是一个僵尸进程!这意味着它已经死了,但是它在等我,以防我想检查它的返回值(使用 wait 系统调用)。一旦获得了它的返回值,我真的将再次变得孤单。

my parent
    |- me

代码中的 fork 和 exec 看起来像什么#

如果你要编写一个 shell,这是你必须做的练习之一(这是一个非常有趣且有启发性的项目!Kamal 在 Github 上举办了一个很棒的讲习班,介绍了如何做的事情:https : //github.com/ kamalmarhubi / shell-workshop

事实证明,通过一些工作和一些 C 或 Python 技能,你可以在短短几个小时内(至少如果你旁边有一个知道在做什么的人,如果不知道时间会长一些)用 C 或 Python 编写一个非常简单的 shell(如 bash!)。他们正在做,如果没有的话,要更长的时间:))。我已经做到了,这太棒了。

无论如何,这是 fork 和 exec 在程序中的外观。我写了伪造的 C 伪代码。请记住, fork can fail!

int pid = fork();
// now i am split in two! augh!
// who am I? I could be either the child or the parent
if (pid == 0) {
    // ok I am the child process
    // ls will eat my brain and I'll be a totally different process 
    exec(["ls"])
} else if (pid == -1) {
    // omg fork failed this is a disaster 
} else {
    // ok i am the parent
    // continue my business being a cool program
    // I could wait for the child to finish if I want
}

好吧,朱莉娅,大脑被吃掉对你意味着什么#

流程有很多属性!

你有

  • 打开文件(包括打开的网络连接)
  • 环境变量
  • 信号处理程序(在程序上运行 Ctrl + C 会发生什么?)
  • 一堆内存(你的 “地址空间”)
  • 寄存器
  • 你运行的 “可执行文件”(/proc/ $ pid /exe)
  • cgroups 和名称空间(“linux 容器”)
  • 当前工作目录
  • 你的程序运行用户
  • 我忘记的一些其他东西

当你运行 execve 并有另一个程序吞噬了你的大脑时,实际上几乎所有内容都保持不变!你拥有相同的环境变量和信号处理程序以及打开的文件等等。

唯一改变的是,所有的内存和寄存器以及你正在运行的程序。这是一个很大的问题。

为什么 fork 不是超级昂贵(或:写时复制)#

你可能会问 “朱莉娅,如果我有一个使用 2GB 内存的进程该怎么办!这是否意味着每次启动子进程时,都会复制所有 2GB 的内存?听起来很贵!”

事实证明,Linux 为 fork()调用实现了 “写时复制”,因此对于新进程中的所有 2GB 内存,就像 “看看旧进程!一样的!”。然后,如果任何一个进程写入了任何内存,那么它将开始复制。但是,如果两个进程的内存都相同,则无需复制!

为什么你可能会关心这一切#

好的,茱莉亚,这很酷的琐事,但为什么重要呢?有关哪些信号处理程序或环境变量被继承的细节,或实际上对我的日常编程有什么影响的细节?

也许会!例如,Kamal 的博客中存在一个令人愉快的错误。它讨论了 Python 如何设置 SIGPIPE 忽略的信号处理程序。因此,如果你从 Python 内部运行程序,则默认情况下它将忽略 SIGPIPE!这意味着该程序的行为会有所不同,具体取决于你是从 Python 脚本还是从 Shell 启动它!在这种情况下,它导致了一个奇怪的错误!

因此,程序的环境(环境,信号处理程序等)可能很重要!它是从其父进程继承其环境的,无论那是什么!在调试时,有时这可能是有用的事情。

本作品采用《CC 协议》,转载必须注明作者和本文链接