38.操作系统启动完毕
书接上回,上回书咱们说到一个 shell 程序的执行原理,至此我们的操作系统终于将控制权转交给了 shell,由 shell 程序和我们人类进行友好的交互。
其实到这里,操作系统的使命就基本结束了。
此时我想到了之前有人问过我的一个问题,他说为什么现在的电脑开机后和操作系统启动前,还隔着好长一段时间,这段时间运行的代码是什么?
在我的继续追问下才知道,他说的操作系统的开始部分,是我们看到了诸如 Windows 登陆画面的时候。
这个登陆画面就和我们 Linux 0.11 里讲的这个 shell 程序一样,已经可以说标志着操作系统启动完毕了,通过 shell 不断接受用户命令并执行命令的死循环过程中。
甚至在 Linux 0.11 里根本都找不到 shell 的源代码,说明 Linux 0.11 并没有认为 shell 是操作系统的一部分,它只是个普通的用户程序,和你在操作系统里自己写个 hello world 编译成 a.out 执行一样。在执行这个 shell 程序前已经可以认为操作系统启动完毕了。
操作系统就是初始化了一堆数据结构进行管理,并且提供了一揽子系统调用接口供上层的应用程序调用,仅此而已。再多做点事就是提供一些常用的用户程序,但这不是必须的。
OK,上一回我留了一个问题,shell 程序执行了,操作系统就结束了么?
此时我们不妨从宏观视角来看一下当前的进度。
看最右边的蓝色部分的流程即可。
我们先是建立了操作系统的一些最基本的环境与管理结构,然后由进程 0 fork 出处于用户态执行的进程 1,进程 1 加载了文件系统并打开终端文件,紧接着就 fork 出了进程 2,进程 2 通过我们刚刚讲述的 execve 函数将自己替换成了 shell 程序。
如果看代码的话,其实我们此时处于一个以 rc 为标准输入的 shell 程序。
|
|
就是 open 了 /etc/rc, 然后 execve 了 /bin/sh 的这个程序,代码中标记为蓝色的部分。
shell 程序有个特点,就是如果标准输入为一个普通文件,比如 /etc/rc,那么文件读取后就会使得 shell 进程退出,
如果是字符设备文件,比如由我们键盘输入的 /dev/tty0,则不会使 shell 进程退出。
这就使得标准输入为 /etc/rc 文件的 shell 进程在读取完 /etc/rc 这个文件并执行这个文件里的命令后,就退出了。
所以,这个 /etc/rc 文件可以写一些你觉得在正式启动大死循环的 shell 程序之前,要做的一些事,比如启动一个登陆程序,让用户输入用户名和密码。
好了,那作为这个 shell 程序的父进程,也就是进程 0,在检测到 shell 进程退出后,就会继续往下走。
|
|
下面的 while(1) 死循环里,是和创建第一个 shell 进程的代码几乎一样。
|
|
只不过它的标准输入被替换成了 tty0,也就是接受我们键盘的输入。
这个 shell 程序不会退出,它会不断接受我们键盘输入的命令,然后通过 fork+execve 函数执行我们的命令,这在上一回讲过了。
当然,如果这个 shell 进程也退出了,那么操作系统也不会跳出这个大循环,而是继续重试。
整个操作系统到此为止,看起来就是这个样子。
|
|
当然,这只是表层的。
除此之外,这里所有的键盘输入、系统调用、进程调度,统统都需要中断来驱动,所以很久之前我说过,操作系统就是个中断驱动的死循环,就是这个道理。
OK!到此为止,操作系统终于启动完毕,达到了怠速的状态,它本身设置好了一堆中断处理程序,随时等待着中断的到来进行处理,同时它运行了一个 shell 程序用来接受我们普通用户的命令,以同人类友好的方式进行交互。
完美!