21.一个新进程的诞生
一个新进程的诞生,从操作系统的源码角度来说,其实就两行代码。而关于创建进程的重点,其实就一行代码,就是大名鼎鼎的 fork 函数。
我们先来看一张图,看看操作系统从开机到怠速都做了些什么事情。
你看,第一部分和第二部分,为我们这个第三部分做了充足的铺垫工作。
到了第三部分,简单说就是从内核态切换到用户态,然后通过 fork 创建出一个新的进程,再之后老进程进入死循环。
|
|
至于 fork 出来的新进程做了什么事,就是 init 函数里的故事里,这个不在第三部分的讨论范畴。
所以你看,一共就两行代码,顶多再算上最后一行的死循环,三行,就把创建新进程这个事搞定了。
再加上新进程里要做的 init 函数,一共四行代码,就走到了 main 函数的结尾,也就标志着操作系统启动完毕!
但就是这没有多少个字母的四行代码,是整个操作系统的精髓所在,也是最难的四行代码。
理解了它们,你就会有原来操作系统就是这破玩意的感叹了~
今天我们就总览一下这四句,很轻松。
第一句是 move_to_user_mode
直译过来即可,就是转变为用户态模式。
因为 Linux 将操作系统特权级分为用户态与内核态两种,之前都处于内核态,现在要先转变为用户态,仅此而已。
一旦转变为了用户态,那么之后的代码将一直处于用户态的模式,除非发生了中断,比如用户发出了系统调用的中断指令,那么此时将会从用户态陷入内核态,不过当中断处理程序执行完之后,又会通过中断返回指令从内核态回到用户态。
整个过程被操作系统的机制拿捏的死死的,始终让用户进程处于用户态运行,必要的时候陷入一下内核态,但很快就会被返回而再次回到用户态,是不是非常无奈?
第二句是 fork
这是创建一个新进程的意思,而且所有用户进程想要创建新的进程,都需要调用这个函数。
原来操作系统只有一个执行流,就是我们一直看过来的所有代码,就是进程 0,只不过我们并没有意识到它也是一个进程。调用完 fork 之后,现在又多了一个进程,叫做进程 1。
当然,更准确的说法是,我们一路看过来的代码能够被我们自信地称作进程 0 的确切时刻,是我们在 第18回 | 进程调度初始化 sched_init 里为当前执行流添加了一个进程管理结构到 task 数组里,同时开启了定时器以及时钟中断的那一个时刻。
因为此时时钟中断到来之后,就可以执行到我们的进程调度程序,进程调度程序才会去这个 task 数组里挑选合适的进程进行切换。所以此时,我们当前执行的代码,才真正有了一个进程的身份,才勉强得到了一个可以被称为进程 0 的资格,毕竟还没有其他进程参与竞争。
如果你觉得这些话很困惑,就对了,在理解了整个这一块的细节之后,尤其是对于进程调度这种被人赋予了好多虚头巴脑的名词的地方,你会豁然开朗的。
第三句是 init
只有进程 1 会走到这个分支来执行。这里的代码可太多了,它本身需要完成如加载根文件系统的任务,同时这个方法将又会创建出一个新的进程 2,在进程 2 里又会加载与用户交互的 shell 程序,此时操作系统就正式成为了用户可用的一个状态了。
当然,当你知道了新进程诞生的过程之后,进程 2 的创建,就和进程 1 的创建一样了,在后面的章节中你将不会再困惑创建新进程的过程,减轻了学习负担。所以这一部分,又是作为下一部分的重要基础,环环相扣。
我们的教育,往往是强调知识点。但我认为,整个知识都是成体系的,没有哪个地方可以单点立起整个的理解大厦,必须一环扣一环。幸运的是,每一环都是十分简单且纯粹的。
第四句是 pause
当没有任何可运行的进程时,操作系统会悬停在这里,达到怠速状态。没啥好说的,我一直强调,操作系统就是由中断驱动的一个死循环。
一共四句话,切换到用户态,创建新进程,初始化,然后悬停怠速。
乍一看,是不是特别简单?是的,不过当你展开每一段代码的细节后你会发现,一个庞大的世界让你无从下手。但当你把全部细节都捋顺了之后你又会发现,不过如此。