在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
幕后操作是通过称为 fork
和 exec
(实际上是 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 协议》,转载必须注明作者和本文链接
推荐文章: