参考了:操作系统_哈尔滨工业大学_中国大学MOOC(慕课) (icourse163.org)
什么是操作系统
对于没有操作系统的直接在硬件上跑程序的我们一般称为裸机。但这并不方便管理资源,也不方便移植,同时安全性也不足,所以有了操作系统。
操作系统是管理计算机硬件与软件资源的计算机软件程序。本质上来说其就是一个跑在裸机上的程序,它提供了一个抽象层,使得应用程序和硬件之间的交互更为简单和高效。操作系统负责管理硬件资源、提供用户接口、执行文件系统等任务,以便用户和应用程序能够更方便地利用计算机系统的功能。
启动
操作系统的启动指在跑操作系统的硬件上电的那一刻开始先干了什么。
在下面的列子中bootsect主要是读入操作系统,而setup是设置操作系统。
bootsect.s
这里以x86的PC为例:
- x86 PC刚开机时CPU处于实模式
- 开机时, CS=0xFFFF; IP=0x0000,寻址0xFFFF0(ROM BIOS映射区)
- BIOS检查RAM,键盘,显示器,软硬磁盘
- 自动将磁盘0磁道0扇区(引导扇区)读入0x7c00处
- 设置cs=0x07c0, ip=0x0000,这里就是bootloader:bootsect.s
为什么使用汇编不用C:
C在编译后,将无法操作某些地址,如当程序执行的地址,转跳到目标地址;而汇编可以更精确的操控。
转移bootsect.s
上图中的BOOTSEG
是原始的boosect位置,INITSEG
是准备移动到的位置,SETUPSEG
是之后setup的位置。
下图是将bootsect转移,并跳入新的bootsect.s:
转移bootsect是为了防止操作系统过大,导致在读入操作系统时两者产生冲突,因此将bootsect尽量放后面。
载入setup.s
在转跳到新的bootsect的go
后cs=INITSEG ,ip=go
,go
是为了给之后call的调用做准备。
load_setup
调用BIOS的中断读入磁盘的四个扇区的setup,读入错误会置cf
位
载入system
在载入完setup后,首先获取磁盘信息,然后读光标并显示提示字符,再读入system,最后退出bootsect,转入setup。
在上图中我们将读入分为了下图的一个函数,这是因为读入的system比较大,通常要跨磁道读入;并且结合之后的setup来看,是将system读到从0x1000:0000
开始,不到0x9000:0000
处,同时每个段只放0x8000
字节。
最后对于引导扇区的bootsect要以特定的数据结尾,否则会出错。
以上bootsect就运行完了,接下来转跳到setupjmpi 0, SETUPSEG
(在0xAA55
前)。
setup.s
setup将弯插OS启动前的设置。
获取信息并移动system
setup中主要的工作之一就是将设备的信息提取保存,如光标位置,扩展内存数等,放在原bootsect的地方;其中扩展内存数可以简单看作给设备配备的内存大小。
在将设备信息全保存之后,就是将system从0x1000:0000
移到0x0000:0000
,使system从0x0000:0000
开始。
退出并进入system
在完成上一步后还有一些其他工作,在这不具体展开。之后就是退出setup。
在退出之前首先要进入保护模式,这要靠上图中标红的两句mov
,这是对cr0
这给寄存器进行设置,从而进入保护模式。
对于保护模式于,简单来看就是实模式的升级,为了解决在实模式中的问题而存在,如内存安全问题,访问内存数较小等;而保护模式中引入了GDT表就良好的解决这些问题。
由此可见,在保护模式中最重要的GDT表,而该表就是在setup中初始化的,如下图。
在上图中就是建立GDT表并初始化,同时由于0x0000:0000
已经变为system,不再是中断向量表了,因此有了和GDT类似的IDT,作为保护模式的中断向量表。
以上就是对GDT表的构建。最后就是退出setup进入system,这靠的是 jmpi 0, 8
,此时由于已经是保护模式,所以用的寻址方式不一样了。
~在下表中用GDT[cs]表示基址~
实模式 | 保护模式 | |
---|---|---|
寻址方式 | (cs << 4) + ip | GDT[cs] + ip |
cs | 16位 | 16位 |
基址 | (cs << 4) 16位 | GDT[cs] 32位 |
ip | 16位 | 32位 |
再前面说过GDT表可以扩大内存访问,这是因为不再使用16位cs
,而是将其作为选择子,用来选择GDT的表项,这样可以将基址扩展为32位,除此之外ip
再保护模式下变为32位。
此时再看jmpi 0, 8
会使cs=8,ip=0
,而GDT[cs]
所代表表项根据上面的初始化内容等于0x00C09A0000007FFF
,再根据上图得出基址是0x0000
,因此jmpi 0, 8
就是跳转到0x0000:0000
,进入system。
编译问题:
到此时我们会发现bootsect,setup,system都有严格的顺序,因此在使用makefile编译的时要注意:编译完成后各个文件放入
image
的顺序和image
要从0磁道0扇区的位置开始放。
head.s
head.s是system中第一段代码,也是操作系统的开始,主要是在进入操作系统的main.c之前对一些东西进行设置。
注意:和前面的两个汇编不同,head.s不再是使用实模式下16位的汇编,而是用保护模式下32位的汇编。
在上图就是head.s中的一部分关键设置:再次建立设置GDT和IDT表,开启地址线等。
在一系列操作后就要准备进入操作系统的main.c了:
在代码执行函数时会将函数参数依次压栈,最后还有压入返回地址。因此进入main要手动这么做,是为了配合最后的ret
。
在after_page_tables
中全压栈完后进行页表设置(这里的设置页表将来在内存管理中再具体讲解)最后进入操作系统的main函数(没有用参数,但还是压栈了所以全压了0)。
操作系统的main函数不应该返回,若返回则进入L6
死机。
到此就进入了操作系统。