在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 协议》,转载必须注明作者和本文链接
推荐文章: