操作系统 | 1.1操作系统的启动
参考来源:哈工大,李治军老师,操作系统公开课
预备知识:汇编
16位汇编(实模式使用)、32位汇编(保护模式使用)
段寄存器、段偏移...
1.什么是操作系统

- 操作系统是计算机硬件和应用之间的一层软件
- 管理那些硬件:
- CPU管理
- 内存管理
- 终端管理
- 磁盘管理
- 文件管理
- 网络管理
- 电源管理
- 多核管理
2.操作系统的启动
| 问题:打开你的windows电脑/Mac电脑/Linux电脑之后,发生了什么呢? |
(1) 计算机结构
- 伟大的 冯-诺伊曼存储程序思想
- 存储程序的主要思想:将程序和数据存放到计算机内部的存储器中,计算机在程序的控制下一步一步进行处理。
- 计算机由五大部件组成:输入设备、输出设备、存储器、运算器、执行器
- 计算机是怎么工作的呢?

- 把程序放到存储器中,然后用指针IP(PC)指向它,然后自动的取值执行。
- 计算机是怎么工作的 —— 取值执行
| 那么,当计算机按下开机键之后,计算机执行的第一句指令是什么呢? |
(2) boot模块

<1> 按下开机键之后,BIOS将操作系统的引导扇区读入内存
| 问题:当我们按下开机键时,IP指针等于多少? -->由硬件设计者决定! |
- 我们以 x86 PC 为例:

- 对于 x86 结构,刚一上电,内存中有一部分是固化的。称为ROM BIOS。开机如果内存里面什么也没有,那么一句程序也没有,CPU无法取值执行,因此,内存中必须固化一段程序,就是BIOS(基本输入输出系统)。
- 刚一上电,段寄存器CS = 0xFFFF, 偏移IP=0x0000;则寻址地址为 IP = CS * 16 + IP = 0xFFFF0(这个地址是 ROM BIOS映射区)。
- 检查RAM,键盘,显示器,软硬磁盘
- 将磁盘0磁道0扇区读入0x7c00处
- 常识1:磁盘的0磁道0扇区,就是操作系统的引导扇区,是操作系统的第一段代码。
- 常识2:磁盘的一个扇区512字节。
- 这个步骤就是将操作系统的第一段512字节的代码读入到内存0x7c00处
- 设置 段寄存器CS = 0x07c0, 段偏移IP=0x0000,则取值地址是 CS * 16 + IP = 0x7c00。也就是开始执行操作系统的第一段512字节的程序。
<2> 开始执行操作系统的引导扇区代码(512字节)
| 问题:BIOS将引导扇区读入内存之后,由CPU取值执行。那么引导扇区都做了哪些事情? |
- 操作系统(启动)的故事从这里开始...
- 引导扇区部分代码截取:bootsect.s
-

-
小问题:.s 是什么文件?为什么使用 .s 文件 - .s 是汇编文件。之所以使用汇编文件,是因为汇编能够精准的、完全的控制内存地址。
- 如果使用 c代码,那么编译之后,很可能会产生不可控制的东西。比如,
int i,我们无法控制 i 在内存中的位置。
-
- 现在来看这部分bootsect.s代码做了哪些事情
-
start部分 mov ax,#BOOTSEG mov ds,ax- 将0x07c0先赋值给ax,再通过ax将0x07c0赋值给ds。 ds=0x07c0
mov ax,#INITSEG mov es,ax- 将0x9000先赋值给ax,再通过ax将0x9000赋值给es。 es=0x9000
- mov cx,#256
- 在下下一行用到。256个字,等于512个字节。
sub si,si sub di,di- si = si - si = 0, di = di - di =0
rep movw- movw指令:将ds:si的内容送至es:di
- 此时,ds=0x07c0, si=0。那么,ds:si=ds*16+si=0x7c00。
- 此时,es=0x9000, es=0。那么,es:di=es*16+di=0x9000。
- rep指令:重复执行,直至 cx = 0。上面给出,共移动512字节。
- 因此,这条语句,就是把 引导扇区512字节的代码从 内存地址0x7c00 移动到 内存地址0x9000。
jmpi go,INITSEG-
jmpi语句:把go赋值给ip,把INITSEG赋值给CS
-
其中,go是一个标号,也就是从start开始之后的偏移。
-
因此,此时的执行指针:ip=cs:ip=INITSEG16+go=0x900016+go。因此,此时就是开始顺序执行go语句。
-
小问题:为什么顺序执行还需要jmpi? - 因为ip指针还在0x7c00后面,而(引导扇区的512字节程序)程序已经被赋值搬移到了0x9000处。这条指令,相当于把ip指针从0x7c00处,跟随着代码一起转移到了0x9000处。
-
-
问题:为什么要把引导扇区的512字节程序搬移走?这部分空间要用来干什么?(答案在后面)
-
go部分 && load_setup部分 - 只挑重要的读
- int 0x13 是BIOS的13号中断:读取磁盘扇区
- 寄存器ax=#0x0200+SETUPLEN=0x0204。其中,高八位ah=0x02,表示读磁盘;低八位al=0x04,表示总共读取4个扇区。
- 寄存器cx=#0x0002。其中,高八位ch=0x00,表示柱面号;低八位cl=0x02,表示从第二个扇区开始读。
- 寄存器dx=#0x0000。其中,高八位dh=0x00,表示磁头号;低八位0x00,表示驱动器号。
- 寄存器es=ax=cs=0x9000,寄存器bx=#0x0200。因此,程序开始的地址es:bx=es*16+bx=0x90200。(程序被搬移到了0x90000处,引导程序512个字节,转换成16进制,刚好是0x0200。因此,可以认为,把操作系统的第2~5个扇区,搬移到内存0x90200开始处,紧挨着引导扇区512字节的代码。)
- 在这之后,bootsect.s还有其他内容,只挑重要的介绍
-
- 引导扇区部分代码截取:bootsect.s

- 这部分做了两件事情:把system模块读入内存;把控制权交给setup.s
(3) setup模块

<1> setup将完成OS启动前的设置

- 挑出重要代码来说。start部分
int 0x15- 这个BIOS中断,要获取物理内存的大小,获取到的值赋值给ax。
mov [2],ax- 把获取到的物理内存大小(上一个代码保存在ax中)赋值给内存0x90002处。
- 之所以要获取并保存物理内存大小?是因为操作系统要管理内存,必须先知道内存有多大。
- 不仅要获取内存大小,还要知道光标位置、显卡参数、根设备号等等一些列内容。
- 挑出重要代码来说。do_move部分
- 这部分,把从0x90000部分开始,所有的操作系统代码,全部移动到了从内存地址0x0000开始处的位置。
- 回答了之前的问题:为什么把0x7c00处的512字节引导程序移动到0x90000处。就是为了能够把操作系统转移到0x0000处,如果不移动,操作系统的内容太大,会把正在执行的setup.s程序(搬移后setup.s在0x90200处,如果不搬移,它应该在0x7e00)进行覆盖,导致死机。
<2> 进入保护模式
| 为什么要从16位的实模式 转换到 32位的保护模式呢? |
-
回答:
- 在实模式中,段寄存器为16位,段偏移16位。段寄存器左移4位+段偏移,因此,这种模式下,最大的访问地址为1M。而现在,内存都较大,在32位的保护模式下,最大的访问地址已经可以达到4G.
-
问题:怎么进入到保护模式呢? 
- 上图第一行红色代码:cr0寄存器(32位)。最低位为0,为实模式;最低位为1,为保护模式。
- 保护模式下,CPU进入另一条解释执行的电路。
-
问题:寻址范围大大扩大,保护模式怎么寻址呢? - 这时,使用 cs左移4位+ip 获取地址不再满足需要

- GDT表
- GDT表写入硬件,就是为了计算机执行速度更快。
- 这个时候的寻址方式:cs里边放的是查表的索引,真正的地址,放在表项中。
- 初始化gdt表

- .ward 后边的内容,就是表项。
- 现在重新来看寻址方式
- 图13(怎么进入保护模式),有一句代码
jmpi 0,8 
- 一个GDT表项共 4*32位,GDT初始化的代码中,每一行是一个表项。
- 因此,
jmpi 0,8的含义:可以看到,8代表的是表项的第二行。段基址拼接完成后,cs=0x00000000,而ip=0x00000000,因此,jmpi到内存0x0000处,也就是system(system已经搬移到0x0000处)。
- 图13(怎么进入保护模式),有一句代码
(4) system模块的第一个模块head.s

<1> head.s部分代码

- 在head.s里面会重新设置idt表、gdt表(call setup_idt、call_setup_gdt),前面setup里面设置的gdt和idt都是临时的;这里会重新设置。还会开启A20地址线(je 1b),开启A20地址线之后寻址范围就是4G而不再是1M。
- IDT表是中断函数表,从此int n 不再是DOS中断了,而是在IDT表中找到中断函数的地址,执行
<2> head.s怎么跳出来,执行下一个模块main.c

(5) 进入main.c

<1> main.c做了哪些事情呢》

- main就是做一些初始化
(6) 以mem_init()为例

<1> 看一看mem_init做了什么

- mem_mep数组中,为0时,表示内存没有被使用。
3.本文总结
这篇文章,只要记录了操作系统启动的过程。
1. 打开电源之后,自动设置了cs和ip寄存器,地址指向固化的BIOS代码,并解释执行。
2. BIOS代码:
将 操作系统的引导扇区(0磁道0扇区)读入内存0x7c00处。然后cs:ip指向0x7c00。
3. bootsect.s代码
(1)先将0x7c00处的引导扇区的512字节代码移动到0x90000~0x901FF处;
(2)将cs:ip指向 0x90000+go 处
(3)将setup.s从ox90200处开始读入内存
(4)紧接着将system模块读入内存
(5)把控制权交给setpu.s
4. setup.s代码
(1)获取内存大小,还要知道光标位置、显卡参数、根设备号等等一些列内容。
(2)把操作系统从0x90000处开始的内容,移动到0x0000,即内存的0地址处。
(3)进入保护模式,临时初始化一个GDT表(注意理解GDT寻址方式)
(4)跳转到system模块
5.system模块
(1)根据Linux/Makefile,看到system依赖第一个head.s模块
<1> 重新设置idt表、gdt表,开启A20地址线等。
<2> 跳出来head.s,开始执行C代码:main.c
(2)main.c函数
<1> 对内存、中断、设备、时钟、CPU等进行初始化
(3)mem_map.c(根据这个初始化类比main.c中其他初始化)

浙公网安备 33010602011771号