当你在 Linux 上启动一个进程时会发生什么?

当你在 Linux 上启动一个进程时会发生什么?

本文是关于 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

|--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 的技能,你可以在几个小时内编写一个非常简单的 shell,像 bash 一样。(至少如果你旁边能有个人多少懂一点,如果没有的话用时会久一点。)我已经完成啦,真的很棒。

这就是 fork 和 exec 在程序中的实现。我写了一段 C 的伪代码。请记住,fork 也可能会失败哦。

int pid = fork();

// 我要分身啦

// “我”是谁呢?可能是子进程也可能是父进程

if(pid ==0){

// 我现在是子进程

// “ls” 吃掉了我脑子,然后变成一个完全不一样的进程

exec(["ls"])

}elseif(pid ==-1){

// 天啊,fork 失败了,简直是灾难!

}else{

// 我是父进程耶

// 继续做一个酷酷的美男子吧

// 需要的话,我可以等待子进程结束

}

上文提到的“脑子被吃掉”是什么意思呢?

进程有很多属性:

打开的文件(包括打开的网络连接)

环境变量

信号处理程序(在程序上运行 Ctrl + C 时会发生什么?)

内存(你的“地址空间”)

寄存器

可执行文件(/proc/$pid/exe)

cgroups 和命名空间(与 Linux 容器相关)

当前的工作目录

运行程序的用户

其他我还没想到的

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/13000.html