在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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!