Linux 0.11-操作系统启动完结篇-40
Posted 热爱编程的大忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 0.11-操作系统启动完结篇-40相关的知识,希望对你有一定的参考价值。
Linux 0.11-操作系统启动完结篇-40
操作系统启动完结篇
整个操作系统终于通过四个部分的讲解,完成了它的启动,达到了一个怠速状态,留下了一个 shell 程序等待用户指令的输入并执行。
具体来说。
通过 第一部分 | 进入内核前的苦力活 完成了执行 main 方法前的准备工作,如加载内核代码,开启保护模式,开启分页机制等工作,对应内核源码中 boot 文件夹里的三个汇编文件 bootsect.s setup.s head.s。
通过 第二部分 | 大战前期的初始化工作 完成了内核中各种管理结构的初始化,如内存管理结构初始化 mem_init,进程调度管理结构初始化 shed_init 等,对应 main 方法中的 xxx_init 系列方法。
通过 第三部分 | 一个新进程的诞生 讲述了 fork 函数的原理,也就是进程 0 创建进程 1 的过程,对应 main 方法中的 fork 函数。
通过 第四部分 | shell 程序的到来 讲述了从加载根文件系统到最终创建出与用户交互的 shell 进程的过程,对应 main 方法中的 init 函数。
至此操作系统启动完毕,达到怠速状态。
纵观整个操作系统的源码,前四部分对应的代码如下,这就是启动流程中的全部代码了。
--- 第一部分 进入内核前的苦力活 ---
bootsect.s
setup.s
head.s
main.c
void main(void)
--- 第二部分 大战前期的初始化工作 ---
mem_init(main_memory_start,memory_end);
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
sti();
--- 第三部分 一个新进程的诞生 ---
move_to_user_mode();
if (!fork())
--- 第四部分 shell程序的到来 ---
init();
for(;;) pause();
------
具体展开第四部分,我们首先通过 第31回 | 拿到硬盘信息 和 第32回 | 加载根文件系统 使得内核具有了以文件系统的形式管理硬盘中的数据的能力。
接下来 第33回 | 打开终端设备文件 使用刚刚建立好的文件系统能力,打开了 /dev/tty0 这个终端设备文件,此时内核便具有了与外设交互的能力,具体可以体现为调用 printf 函数可以往屏幕上打印字符串了。
再接下来,第34回 | 进程2的创建 利用刚刚建立好的文件系统,以及进程 1 的与外设交互的能力,创建出了进程 2,此时进程 2 与进程 1 一样也具有与外设交互的能力,这为后面 shell 程序的创建打好了基础。
然后,进程 2 此时摇身一变,在 第35回 | execve 加载并执行 shell 程序 利用 execve 函数使自己变成了 shell 程序,配合上一回 fork 的进程 2 的过程,这就是 Linux 里经典的 fork + execve 函数。
execve 函数摇身一变的关键,其实就是改变了栈空间中的 EIP 和 ESP 的值,使得中断返回后的地址被程序进行了魔改,改到了 shell 程序加载到的内存地址上。
此时,execve 系统调用的中断返回后,指向了 shell 程序所在的内存地址起始处,就要开始执行 shell 程序了。但此时 shell 程序还没有从硬盘中加载到内存呢,所以此时会触发缺页中断,将硬盘中的 shell 程序(除 exec 头部的其他部分)按需加载到内存,这就是 第36回 | 缺页中断 里讲述的过程。
这回,终于可以开始执行 shell 程序了,在 第37回 | shell 程序跑起来了 中我们以 xv6 源码中的超级简单的 shell 程序源码为例,讲解了 shell 程序的原理。
就是不断读取我们用户输入的命令,创建一个新的进程并执行刚刚读取到的命令,最后等待进程退出,再次进入读取下一条命令的循环中。
// xv6-public sh.c
int main(void)
static char buf[100];
// 读取命令
while(getcmd(buf, sizeof(buf)) >= 0)
// 创建新进程
if(fork() == 0)
// 执行命令
runcmd(parsecmd(buf));
// 等待进程退出
wait();
shell 程序是个死循环,我们再回过头来看操作系统的死循环。
在 第38回 | 操作系统启动完毕 中给出了整个操作系统启动代码的鸟瞰视角。
// main.c
void main()
// 初始化环境
...
// 外层操作系统大循环
while(1)
// 内层 shell 程序小循环
while(1)
// 读取命令 read
...
// 创建进程 fork
...
// 执行命令 execve
...
可以看出,不仅 shell 程序是个死循环,整个操作系统也是个死循环。
除此之外,这里所有的键盘输入、系统调用、进程调度,统统都需要中断来驱动,所以很久之前我说过,操作系统就是个中断驱动的死循环,就是这个道理。
OK!到此为止,操作系统终于启动完毕,达到了怠速的状态,它本身设置好了一堆中断处理程序,随时等待着中断的到来进行处理,同时它运行了一个 shell 程序用来接受我们普通用户的命令,以同人类友好的方式进行交互。
------
我们前四个部分,终于把整个操作系统的启动流程讲述清楚了,如果你头脑中已经有像过电影般把整个启动流程清晰地印在脑子里,相信你已经不再恐惧操作系统源码了。
但理解操作系统不单单是启动流程这个视角,还需要内存管理、文件系统、进程调度、设备管理、系统调用等操作系统提供的功能的视角看。
启动流程是一次性的,就这么来一下子,而这些功能是持续不断的,用户程序不断通过系统调用和操作系统提供的这些功能,完成自己想要让计算机帮忙做的事情。
所以接下来的第五部分,我打算用一条 shell 命令的执行过程,来把操作系统这些模块和所提供的功能讲述清楚。
因为一条 shell 命令的执行,包括了内存管理、文件系统、进程调度、设备管理、中断控制、特权级切换等等各方面的内容,实在是把它们都串起来的好办法。
那接下来就跟我一起,期待第五部分的到来吧!
欲知后事如何,且听下回分解。
转载
以上是关于Linux 0.11-操作系统启动完结篇-40的主要内容,如果未能解决你的问题,请参考以下文章