My understanding

一大早起来,还沉侵在昨天成功的模拟构造了一个小操作系统的喜悦之中,可还是觉得那里不是让人很满意。想给自己一个交代,所以每次做完实验,喜悦之后总是在不断问自己为什么中睡去,在为什么中醒来,直到能用最通俗的计算机概念解释给我自己,让自己觉得满意为止。所以现在,说的最多的一句话就是“等等,还有一个问题是什么哪”,连走路的时候都在不停的说给自己听,就是想让自己理解的这种设计思路能说服自己的小知识圈。那天在公司吃早餐碰到女同事,人家向我打招呼,我发现我还在自言自语,真叫个尴尬啊,那时候真想自己是拿着手机。

昨天把android整个操作系统给拆了,然后从grub到内核,到整个系统启动,整个过度可是我按照自己的操作一步一步完成的,不过中间遇到的最大的困难是由于我切换运行环境,结果让执行终端shelll本身的环境也被我弄掉了,内核就会panic,不过最后还是搞定了,还实现了通过已安装的硬盘分区再把操作系统安装到别的硬盘分区上。大清早又回顾我以前曾问过自己关于内核启动的问题,不过发现这次又有了更近一步的认识。

 

程序执行的本质是什么?

计算机组成原理告诉我们,程序执行的根本在于硬件电路识别01序列的能力。硬件电路能识别的01序列又被划分为了指令和数据,划分其实也意味着一种抽象,而指令和数据继续抽象,他们有了各自的抽象系统,对应的为了寻找这些指令和数据,又派生出一个抽象的寻址系统。比如说序列10101010在某一个硬件平台上对应的是一个数据拷贝的电路功能,在没有其它判断的情况下(加入这个序列的执行还有赖于另一个开关的状态,那它有可能就是一个数据,或者是什么都不是,不被硬件电路所识别),我们可以暂且把这个序列归于指令阵营,我们可以继续抽象这个01序列,给它起个名字叫MOV,你懂得,继续抽象它可以是任何语言的任何符号,只要有编译器支持。计算机就这样不断抽象,再由抽象归结为电路是别的01序列完成以下几种功能。

数据移动:如将一个数值从存储单元A拷贝到存储单元B;

条件判断:如如果存储单元A的数值为100,则下一条指令地址为存储单元C;

算术逻辑运算:如计算存储单元A与存储单元B之和,结果存储到存储单元C中;

指令序列变换:如程序跳转,改变程序执行的序列。

 

为什么要用bootloader程序加载操作系统,而不用BIOS直接加载操作系统呢?

上面讲了既然是01代码,最终能表现出的所有能被硬件识别的01序列,对硬件来说都可以称之为执行实体,BIOS大名叫基本输入输出系统,可有多少人在乎过它的名字,也许大家都在乎了,只是我一直没在乎而已,计算机在加电之后,首先执行的是BIOS的代码,我个人理解,单从这一点上来说,抽象点它就是一个程序,更抽象就是一个内核(也许我这样理解太极端)。根据它的名字,它让计算机具备了输入输出的功能,所以从这一点出发,它已经是一个运行平台。BIOS会去加载bootloader程序,bootloader会加载所谓真正的内核,或所谓真正的操作系统。研究启动,好像大家就会想到Linux,Linux又好像会想起Grub。为什么要提起Grub呢?因为我觉得Grub在一定程度上误导了大家对bootloader的认识,前段时间我是花了很多的时间去研究Grub,因为它表现得实在是perfect,能识别文件系统,能执行shell脚本,它毫不掩饰,其实它可以称得上是一个独立的操作系统,只是它相比所谓真正的操作系统来说还是太单纯,可它又不是太单纯,因为真正单纯的bootloader从来不会忘记自己的职责,它真正的职责是加载操作系统,所以它的的职责决定了它的命运,勉强的可以给它起个好听的名字叫“加载操作系统的操作系统”。我们要问,就只是加载,为什么BIOS不直接加载操作系统,反正都是01代码,执行谁不是执行啊,问题就来了,BIOS中的代码首先会去读MBR中的数据,然后执行之(你不要问为什么,反正就是这样规定的),了解MBR的人都知道,MBR中可供存放执行代码的大小只有(512-64-2)bytes,不过在它后面预留了62(这个也是不一定的)个扇区。这个区域可以随你发挥,其实有些bootloader的多阶段启动就灵活的利用了这里的空间,而有的针对启动分区的加密也应用了这部分空间。 想必大家都看到了,什么多阶段引导,都是由于空间不足造成的,规定访问的空间放不下要引导的程序实体造成的。

 

实模式,保护模式的实质是什么?

为什么要说实模式和保护模式呢,我前面第一个问题说了,由于抽象,对指令和数据的访问就派生出一个抽象的寻址系统。寻址的真正目的,就是让我们访问指令和数据。

实模式,是指寻址采用和8086相同的16位段和偏移量,最大寻址空间1MB,最大分段64KB(这个是看0.11的内核代码看到的)。此模式下可以使用32位指令。

保护模式:寻址采用32位段和偏移量,最大寻址空间4GB,最大分段4GB (Pentium Pre及以后为64GB)。

无论是实模式还是保护模式,根本的问题还是程序如何在其中运行,指令和数据时如何获得。 因此我们在学习保护模式时应该时刻围绕这问题,和实模式下一样,保护模式下程序运行的实质仍是“CPU执行指令,操作相关数据”,因此实模式下的各种代码段、数据段、堆栈段、中断服务程序仍然存在,且功能、作用不变。 那么保护模式下最大的变化是什么呢?那就看代码在启动过程中做的那些事,反正我看到有两处在构造描述符表,当时在oldlinux上问赵博士,人家也懒得理我,我的一堆问题只能自己解决,实模式和保护模式下,“地址转换方式”发生了很大的变化,引起这种变化的原因是内存管理发生了彻底的变化。所以引起地址映射发生了很大变化,其实我更喜欢实模式,因为我一直都不喜欢虚的东西。

实模式,你看代码,就那些固定的地址,把什么放到什么什么地址,都是内存绝对地址,到了保护模式,什么全局描述符(GDT),局部描述符(LDT),段选择子等等,这个要单独讲一节才可以讲清楚,其实说白了就是一个间址寻址过程。

其实实模式和保护模式还有个很优趣的情况。 最早的时候我一直以为保护模式只是操作系统的专利呢,直到我接触80386之后的32位CPU之后的BIOS才纠正了自己的这种错误认识。其实在x86架构下的32位系统之后,BIOS一开始就是工作在32位的保护模式下,地址空间是4G;当把BIOS程序搬到1M空间之后,又切换到实模式下执行。

 

为什么要使用initrd,为什么android中会进行两次根文件系统的切换?

首先问,内核必须有initrd吗,普通的Linux,编译内核好像都有,但是我要告诉你其实是可以不用initrd的。initrd你去查,很多说是因为帮助内核加载模块的,可是我要问,initrd的实质是什么,放在硬盘上,它就是个压缩包,其实就是个文件夹,可它最终是要加载到内存中,放在内存中还要呈现出这种目录,我其实一直对这里很迷糊,怎么加载到内存,怎么挂载到根目录上,怎么样在内存中呈现出这种硬盘上表现出的文件夹结构。这就是我今天早晨所谓的喜。 我躺在床上梳理了下思路,看Grub启动菜单,最重要的有两个,一个Kernel,一个Initrd. 都给的是在磁盘上的文件名,通过文件名访问,哪是靠Grub支持文件系统,挂载启动分区,然后访问到相应的文件,并将他们载入内存,内核是可以执行的,可是单从initrd文件,就是个文件夹结构。好,我们开始加载内核,也就是说Grub把控制权交给了内核,其实有很多人对控制权交给内核不知道什么意思,其实你可以理解为函数调用,调用应用程序的动态库一样,因为动态库是可以独立模拟出一个操作系统的,我个人理解就是离散数学中描述的自包含的执行实体,开始执行内核程序,你要初始化中断调用表,加载设备驱动,挂载文件系统,最重要的是挂载根分区。这下问题来了:

 

假设如果这些内容都是自编译进内核的,哪程序就顺着执行下去,有了设备驱动,有了文件系统,就可以挂载根分区,都有运行环境了,你想想还要initrd干什么。initrd是干什么的,上面说了,就是加载模块的,提供运行环境,做运行环境切换的。不然把那么多目录结构放到内存里呢。

这里问题又来了,加载设备驱动,然后挂载文件系统,那设备驱动依赖的设备文件节点是谁提供的呢? 所以在编译内核的时候,内核编译选项中至少要内置一个内存文件系统,负责最初rootfs的安装, 至于initrd从理论上并不是必须的。 

问题是,内核提供了动态加载模块的功能,就让initrd的存在有了合理性。其实,从这点来说,内核可以被认为是不包含模块的通用执行体,这样就可以把内核做的更通用,功能更单一,好像挺符合现在的设计模式中一些思想的,松耦合,强内聚。一旦设备驱动和文件系统模块被单独拿出去,哪内核启动的时候就遇到了一个严重的问题:没有设备驱动和文件系统,它不知道怎么访问设备(Grub交给Kernel之后自己走了,BIOS是一直为人民服务,可内核觉得它提供的功能太低级,表面上没用),也无法按照要求的文件系统挂载设备。

所以,就搞出来了Initrd,还要Grub帮忙。Grub需要把initrd加载到内存,然后再把加载的位置,也就是initrd在内存的地址(start_initrd, end_initrd)告诉内核,其实这块的东西需要看一个数据结构,可以看看启动协议。

了解Linux的人,很多都知道Linux内核系统的第一个线程,可很少有人去关心内核启动阶段还启动了另一个重要性绝不亚于init线程的线程,去看看kthreadd,系统的第二个线程吧。

你可能感兴趣的:(My understanding)