(第一节)操作系统--第一个界面说起
写在最前面:由于视频集数较多、涉及到的内容也很多,所以此专题会分多篇文章进行记录。
揭开钢琴的盖子
钢琴本身就是一个操作系统,其内部是很复杂的。如果只会弹琴,而不去了解其内部结构,就只能弹而不会改。所以我们要揭开钢琴的盖子,去学习其内部复杂的构造。
上图是我们电脑开机后的第一个画面,那么这个画面背后正发生着什么呢
?
要了解其中发生的事情,需要我们结合计算机的工作原理和基本常识来进行。下面我们就来介绍一下计算机的发展流程和工作原理,再循着工作原理从汇编代码的层次来解析一下这个开机画面的底层实现。
计算机的发展与工作原理
从白纸到图灵机
从上图可知,计算机就是一个自动化的计算模型
,通过控制器实现一个具体的事件,比如图中所示的加法。
但是,上图所示的图灵机只可以进行单个特定的事件。
从图灵机到通用图灵机
通过设置控制器动作来指定操作器的实现,其实就是一个程序。
从通用图灵机到计算机
计算机相比于通用图灵机,一个很重要的突破:存储程序思想
。即将指令和数据存储到计算机内部设备,而后控制器读取、解码并执行相应指令。
前进一步却是质的飞跃。冯诺依曼用存储程序思想完美解释了图灵机的两个核心概念:运算规则、学会的含义
- 运算规则:一个指令序列
- 学会的含义:将指令序列中的指令逐条取出并解释执行
从汇编代码来始界看起面
前面我们已经知道,计算机的操作流程:取指、执行。起始界面也不例外。当然,界面只是计算机启动的一部分,在其背后还有很多无法直观看到的程序操作,下面我们就一一介绍。
计算机启动执行的第一个程序
第一部分程序就是引导扇区程序,占512字节,主要作用就是:
实现代码位置的移动
、将后续两个模块载入内存
并将启动界面打到屏幕上
。
ROM BIOS映射区
是通电后内存中唯一一个存在代码的地方,也是CPU执行命令的起点。(只有有代码才可以取指执行)
这段区域的代码主要有以下几个功能:
- 检查RAM、键盘、显示器、软硬磁盘等硬件设施。
- 将磁盘0磁道、0扇区读入内存0x7c00处。(共512字节,一个扇区,也叫引导扇区)
- 将CS=0x07c0,IP=0000。(设置下一步执行的指令位置,也就是引导扇区位置)
在执行完BIOS后会跳转到0x7c00位置,执行此处的指令。
上图就是引导扇区存储的指令,我们从汇编语言的角度来解析
:
1 |
|
上面详细解释了代码,请仔细观看。其作用就是:移动程序以腾出空间,位置跳转以顺序执行
。
下面我们继续解释代码,接着上一条代码去讲。具体代码如下图:
1 | >>> bootsect.s |
已将对应代码解释清楚,这一段代码的主要作用就是:将setup数据从磁盘载入内存
(使用13号中断,读取引导扇区后面的4个扇区的内容)并执行ok_load_setup
和 load_setup
.
注:setup载入的位置紧靠bootsect,也就是0x90200.通过ES:BX
给出此地址。
下面我们继续往下走,看一下
ok_load_setup
处的代码:(主要介绍一下重点部分)
这一部分代码主要涉及到页面显示
和后续跳转
两部分。
- 页面显示主要通过
int 0x10
中断进行,通过对应参数和数据来将启动界面打到屏幕上。(msg1存储的就是启动界面的数据,其位置在引导扇区的末尾)补充int 0x10的介绍 - 后续跳转就是调用
raed_it
实现
上面介绍了
ok_load_setup
部分代码,下面我们来介绍一下read_it
代码。注意执行完毕read_it后还要回到此处。
此部分代码将system模块
写入内存,引导扇区的代码就算执行完毕,接下来就算执行后续setup和system了.
bootsect程序总结
bootsect.s
的主要工作包括以下几个部分:
- 将磁盘上从第二到第五扇区即构成的
setuo模块
读到内存0x9200
处。 - 在显示器上输出操作系统标识
- 从磁盘第六个扇区之后读入
system模块
并将其放到0x10000
处
setup程序的执行
操作系统第一部分即引导扇区部分代码执行完毕,接下来就要执行setup程序即setup.s。
setup将完成os启动前的设置,具体包括:
读取并保存硬件参数参数
和模式切换
下图给出了setup模块的核心代码
,不同版本的操作系统代码或许不同,但是核心功能是差不多的。
上图代码可以分为两个部分:
start
:用于硬件信息的读取,主要涉及到int 0x15中断。这一模块的主要功能就是将操作系统建立起来,所以硬件信息特别是内存大小需要提前获取并保存在0x9000处。do_move
:进行数据的移动,其中ds:si
确定源地址;es:di
确定目的地址,即0x0000.也就是将操作系统的system模块代码移动到内存地址为0x0000
的位置。
注:我们前面介绍bootsect代码时提到,其中有一段代码用于数据的移动,将代码从0x7c00移动到0x9000,就是为了防止此处移动后照成数据的破坏,因为system模块内容很多。
有一个问题:内存地址为0的位置不是存储了中断向量表吗?移动数据到此不会破坏数据吗?
这个答案很简单,因为setup模块执行完毕后就要进入保护模式
,在这个模式下中断的调用与原来实模式不同了。不再使用原先的BIOS中断,而是建立新的IDT表并设置新的中断函数。
其实二者最本质的区别在于cpu对于指令的解析方式
不同。
上图就给出了setup进行模式切换的指令,通过cr0寄存器
进行。(涉及到硬件的很多主要控制都是通过设置CR0寄存器完成的,比如后面要学习的分页机制的启动等)
当计算机切换到保护模式,CPU在解析指令时就会使用区别于实模式的电路。(具体内容会在内存管理
中介绍)
在保护模式下,cpu的寻址方式发生了变化,不再使用cs直接存储内存地址,而是存储表项下标,而后通过查表获取具体地址。
既然要查表,首先要先有表,这里的表就是gdt表,也叫
全局描述符表
。关于更多GDT表的知识看一看一下
扩展知识篇
。
上图就给出了gdt表的初始化方式。在给定cs后就要根据其值进行查表,获取一个32位的地址。(此时IP也是32位寄存器)
与bootsect模块一样,setup模块也要进行指令跳转,以执行新的模块部分。
上图给出的 jmpi 0,8
就是一条保护模式下的跳转指令,其目的是跳转到0x0000
处执行system模块
。
system模块执行
system模块是setup模块之后要执行的模块。在具体介绍各个部分之前,先来看一下操作系统的设计。
Image
就是操作系统镜像,这个镜像的形成依赖于很多子模块,例如:bootsect、setup、system等,而这些子模块也依赖于更多的子模块,最终形成一个树结构
。
system模块的第一部分代码:
head.s
上图就是head.s的代码,主要关注一个部分:对gdt和idt表进行重新初始化。
注:在上图中,还要注意一下使用的汇编语言的不同。这里使用了32位汇编。对于汇编语言的具体介绍我们会单独介绍。
上图给出了跳转的流程:与函数调用机制类似,先传入参数和返回地址,之后使用ret进行跳转。
head.s需要在初始化之前进行一些准备工作:设置中断表
(不再使用BIOS中断)、设置GDT表
(进入system模块需要重新设置)、设置页表
(查询得到真正的物理地址)
如上图代码所示,IDT表和GDT表的本质就是两个内存空间,所以只需要设计两个包含连续8B内存的数组即可;之后再将这两个表的基址通过lidt
和lgdt
指令分别保存到IDTR
和GDTR
寄存器中。
对于两表的初始化:IDT表的值全0,表示中断不可用,在之后的各模块初始化时会设置对应的中断程序地址到表项里(前面我们使用的BIOS中断不再使用);而GDT表的初始化与前面setup模块设置一致,都是设置一段内存空间并将段基址传入,主要是内核代码段、数据段等。
上图很重要,实际上操作系统启动的所有流程都是为了形成这样一张内存图
。
上图并不是一个完整的内存视图,而是操作系统内核
在内存中的视图,也就是说内存拿出来1M大小的空间用于存储操作系统内核,其他部分为用户态区域。
通过观察此内存视图,可知:页表、页目录表、IDT、GDT表
都在这个区域内,并且其基址都保存在对应的寄存器中以便于寻址查询。而main.c
程序也在这个区域,此程序用来完成操作系统启动的最后一个步骤。同时,一些硬件信息也被存储在了这个区域0x9000
位置,这些硬件信息是setup模块
获取的,用于后续main.c中进行数据结构的初始化。
system模块的第二部分代码:
main.c
上图给出了main.c
的代码,其实就是很多初始化的函数,用于对内存、设备、cpu等进行初始化,之后启动一个shell执行指令。
我们以其中一个mem_init()
函数为例子:
通过mem_init
进行内存的初始化,借助参数进行内存的划分,以供后续使用。其中将管理的起始内存地址设置为4MB
,因为0-1MB为系统内核、1-4MB为磁盘高速缓存区;结束地址由0x9000
处存储的内存信息决定。
其他部分代码也是这样,这些初始化函数共同完成操作系统的初始化工作。
总结
前面我们从底层代码的角度和核心功能出发介绍了操作系统的各个模块:bootsect、setup、system
。
这三个模块实现的功能各不相同,但是归根结底还是为了实现这两个结果:操作系统的写入
、操作系统的建立
。
为什么要写入操作系统?
因为计算机的工作原理就是:取指执行
,只要内存中有指令才可以进行工作。所以第一步要将操作系统的源码写入内存
。
为什么要进行初始化?
因为操作系统是一个便于操作硬件的软件,所以要针对不同的硬件获取关键参数、初始化不同的数据结构,从而实现对硬件的管理。
写在最后:
到这里,对于操作系统学习的第一个部分就结束了。这一节详细结束了操作系统的启动流程
,要先理清这个流程,有一个大体的框架,而后再查缺补漏,将一些扩展的知识点进行记录学习。
下一节就要进入到操作系统接口的实现。