01 | 回到一切的起点(shell)
闪客
闪客·手把手带你写个最精简的 docker

你是不是以为第一章节要先介绍一下 docker 的发展史了?不,现在我需要你清空你的大脑,忘掉 docker,忘掉镜像,忘掉所有 namespace、cgroup 这些听过无数次但又说不清楚是什么的概念。跟着我一起进入一个沉浸式的场景中。
话不多说,我们开始吧~
从一个奸商开始做起
想象一下,你是一个卖云服务器的老板,现在有两个人找你来买,但你手里只有一台机器,怎么办呢?
最简单的办法,什么都不用做,直接给他们俩 root 账号和密码。他们通过 ssh 登录你的服务器,通过一个 shell 进程终端来操作服务器,都以为自己独占了这台机器。
两个用户和你自己都通过 shell 进程操作着主机,从进程视角看如下图所示。

这张图里的每一个红色的方框,都表示一个进程,并且箭头的指向代表进程之间的父子关系,它们形成了一个树状的结构。
具体说来,你的主机上运行着一个 sshd 的守护进程,每当一个用户通过 ssh 连接到主机时,这个 sshd 守护进程会创建一个 sshd 子进程,这个 sshd 子进程又会再创建一个 shell 子进程接收用户的指令。
我们可以通过 ps 命令查看主机上的这些进程信息(输出的列表中,第 2 列表示进程的 PID,第 3 列表示进程的 PPID,也就是父进程的 PID)
可以看到所有的 shell 进程(我的电脑上用的 zsh 就是具体的一个 shell 进程实现,后面可能用 bash 那查看到的就是 bash 了无所谓,都是 shell 的实现)都分别有一个 sshd 的父进程,而所有的 sshd 进程都有一个共同的 sshd 父进程。这和我们刚刚画的图是一样的。
最终这个 sshd 进程也有一个父进程,是神秘的 1 号进程,我们在这里先留个悬念。
熟悉而又陌生的 shell
你仅仅用了两个 shell 进程,就让这两个用户都以为自己掌控了这台主机,你可真是个天才呢。
那 shell 进程是什么呢?你可能会说它不就是个黑窗口么。
没错,shell 就是用户与操作系统之间的接口,用于解释用户命令并执行相关的程序。
shell 进程是一个通用的说法,具体有最早的老祖宗 sh,以及现在最常用的 bash,还有定制化能力较强的 zsh 等。

通过 SHELL 变量可以查看你当前会话的默认 shell 是什么,通过 ps -p $ 可以查看当前正在使用的 shell 是什么。
通过 PS1 变量可以查看并修改命令提示符前面的内容。
通过这些我们可以直观感受到,shell 其实也是个普通的程序而已,只不过它是系统启动后第一个和用户直接打交道的进程,并拥有直接执行其他进程的功能,所以看起来比较特殊罢了。
说了这么多,不如直接看一下 shell 进程的源码。这里我选择了比较简单的 xv6 源码中的 sh 实现,它精简地把所有 shell 程序都遵循的核心逻辑写出来了。
没错,shell 程序就是个死循环,它永远不会自己退出,除非我们手动终止了这个 shell 进程。
在死循环里面,就是持续不断地读取(getcmd)用户输入的命令(比如说 ls),创建一个新的进程(fork),在新进程里执行(exec)这个命令,最后等待(wait)进程退出,再次进入读取下一条命令的循环中。
这里的 fork + exec 是经典的 Linux 创建新进程并执行指定程序的方式,其中的细节你可以先不用管。
好了,shell 的原理现在就很清晰了,我们回到刚刚的进程树下看看会发生什么变化。
此时你的两个用户分别在自己的 shell 里执行自己的命令,每执行一个命令就会创建一个新的子进程。那么进程树就会如下图所示。

现在我们来总结一下:在你的主机上有一堆有着父子关系的进程们,其中有一个 sshd 进程监听着用户的远程登录。每当有用户连接过来时就创建一个 shell 子进程和用户交互,shell 不断读取用户的命令并创建出新的进程。
可以说 Linux 上所有运行着的东西,都可以总结成这样一张进程树的图。
神秘的一号进程
既然 Linux 上所有运行着的东西其实就是一堆进程,那最开始是由谁来一步一步创造出这么多进程的呢?也就是说所有进程的初始发动机是什么?

还记得刚刚我们说的神秘的 1 号进程吗?我们尝试从它的身上寻找答案,通过 ps 命令看看主机上的所有进程信息。
你执行出来的效果可能是这样的
也可能是这样的
前者是在我的一台 Linux 云主机上,后者是在我的 mac 电脑上。
我们以 Linux 为例,如果你继续查看所有的进程信息,你会发现这样一个规律。不论是哪个进程,它的父进程(PPID)一定要么是 1 号进程,要么它父进程的父进程 ... 最终也是 1 号进程(这里先不考虑 kthreadd 这个 2 号内核进程和它的子进程)。

这个 1 号进程就是 Linux 上的第一个进程,由最早的 Linux 版本中的 init 进程逐渐进化到现在的 systemd 进程,但本质上就还是第一个启动的一个普普通通的进程而已。
systemd 进程的细节比较多,但简单说其实就是扫描几个指定目录下的特定后缀名的文件,然后解析出里面要执行的程序,把它执行起来。Linux 上最初的一批由 systemd 启动的进程信息,都写在这些目录下。
比如刚刚我们看到的 sshd 进程,就写在了 /lib/systemd/system/sshd.service 这个文件中,可以看到这个文件中有一个 ExecStart 变量,后面就写着如何启动 sshd 服务。
所以说 Linux 上原本就只有 systemd 这一个进程,只不过这个进程的作用就是创造出超级多的子进程,这些子进程中功能比较复杂的可能又会创造出自己的子进程。
这样一来 Linux 上就充斥着各种各样的进程,整个系统就被 systemd 给盘活了。
好了,现在我们把整个 Linux 的进程发动机找到了,我们再把图补充一下。

现在我们有底气了,整个 Linux 就是一堆运行着的进程,并且它们之间以树的关系绑定在一起。那么之后实现的任何功能,归根结底也逃不开这个最底层的逻辑体系。
文件系统好像也是这样
除了这个动态的进程树之外,还有个相对静态的文件系统,也是以树的形式组织起来的。这个就很符合我们的直觉了,甚至 Linux 上有个 tree 命令可以直接以树状结构来查看。
这个 / 就表示根目录,是所有目录结构的起点位置,通常是通过读取某块硬盘里的数据,按照指定的文件系统格式(比如 ext4)解析,然后形成一个树状的目录结构。
而这个 / 根目录,就类似进程中的 1 号进程,是一切的起点。

你可能会说,我们不是要讲容器么,怎么一直在讲这些看似没什么关联的东西?而且这些不都是非常符合直觉的常识么。
别急,最后你会发现,容器被拆解之后的最深层,其实就是玩这些简单的不能再简单的功能而已。
奸商行为被发现
现在我们回到最初的需求,你正在用一台机器卖个两个人同时使用,并做到尽可能让他们以为自己独占了整个机器。那么现在做到了么?
自信点,你做到了!你通过这样的小把戏,的的确确把两个人骗了好长时间。
直到有一天,有一个用户通过 shell 命令在根目录上创建了一个文件,随便起了个名字。
根目录下赫然出现了个 hehehe 文件。
有一天另一个用户无意中登录了自己的 shell,查看了根目录的文件,发现了居然有其他人也在用这台机器,于是找你算账。
这可怎么办呢?这时你开始思考,如何才能让他们都以为自己独占了整个机器呢?
我们下讲再一起来探索。
公开
同步至部落
取消
完成
0/2000
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《手把手带你写个最精简的 docker》
《手把手带你写个最精简的 docker》
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
精选留言
由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。
收起评论