实验二 操作系统的引导
一、实验目的
-
熟悉 hit-oslab 实验环境;
-
建立对操作系统引导过程的深入认识;
-
掌握操作系统的基本开发过程;
-
能对操作系统代码进行简单的控制,揭开操作系统的神秘面纱。
二、实验内容
- 阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解;
- 按照下面的要求改写 0.11 的引导程序 bootsect.s
- 有兴趣同学可以做做进入保护模式前的设置程序 setup.s。
改写 bootsect.s
主要完成如下功能:
- bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等
改写 setup.s
主要完成如下功能:
- bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
- setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
- setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。
相关代码文件
Linux 0.11 文件夹中的 boot/bootsect.s
、boot/setup.s
和 tools/build.c
是本实验会涉及到的源文件。它们的功能详见《注释》的 6.2、6.3 节和 16 章。
引导程序的运行环境
引导程序由 BIOS 加载并运行。它活动时,操作系统还不存在,整台计算机的所有资源都由它掌控,而能利用的功能只有 BIOS 中断调用。
实验中主要使用 BIOS 0x10 和 0x13 中断。
三、实验步骤
首先阅读赵炯博士的《Linux内核完全注释:基于0.11内核》第6章内容。
linux系统启动过程如下:
1.完成bootsect.s的屏幕输出功能
bootsect.s代码是磁盘引导块程序,驻留在磁盘的第一个扇区中(引导扇区,0磁道(柱面),0磁头,第一个扇区)。在PC机加电ROM BIOS自检后,ROM BIOS会把引导扇区代码bootsect加载到内存地址0x7C00开始处并执行之。在bootsect代码执行期间,它会将自己移动到内存绝对地址0x90000开始处并继续执行。该程序的主要作用是首先把从磁盘第二个扇区开始的4个扇区的setup模块(由setup.s编译而成)加载到内存紧接着bootsect后面位置处(0x90200),然后利用BIOS中断0x13取磁盘参数表中当前启动引导盘的参数,接着在屏幕上显示"Loading system…"字符串。再者把setup模块后面的system模块加载到内存0x10000开始的地方。随后确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数判别出盘的类型和种类(是1.44M A 盘吗?)并保存其设备号于root_dev(引导块的508地址处),最后长跳转到setup程序的开始处(0x90200)执行setup程序。
查看linux0.11屏幕显示的关键代码:
根据自己方式做出修改如下:
! 首先读入光标位置
mov ah,#0x03
xor bh,bh
int 0x10
! 显示字符串 “MOSS IS LOADING...”
! 要显示的字符串长度
mov cx,#24
mov bx,#0x0007
mov bp,#msg1
! es:bp 是显示字符串的地址
! 相比与 linux-0.11 中的代码,需要增加对 es 的处理,因为原代码中在输出之前已经处理了 es
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
! 设置一个无限循环
inf_loop:
jmp inf_loop
这里需要修改的是字符串长度,即用需要输出的字符串长度替换 mov cx,#24
中的 24。要注意:除了我们设置的字符串 msg1 之外,还有三个换行 + 回车,一共是 6 个字符。比如这里 Hello OS world, my name is LZJ
的长度是 30,加上 6 后是 36,所以代码应该修改为 mov cx,#36
。
接下来就是修改输出的字符串了:
! msg1 处放置字符串
msg1:
! 回车 + 换行
.byte 13,10
.ascii "MOSS IS LOADING..."
! 两对回车 + 换行
.byte 13,10,13,10
! boot_flag 必须在最后两个字节
.org 510
! 设置引导扇区标记 0xAA55
! 必须有它,才能引导
boot_flag:
.word 0xAA55
将 .org 508
修改为 .org 510
,是因为这里不需要 root_dev: .word ROOT_DEV
,为了保证 boot_flag
一定在最后两个字节,所以要修改 .org
。
完整代码如下:
entry _start
_start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007
mov bp,#msg1
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
inf_loop:
jmp inf_loop
msg1:
.byte 13,10
.ascii "MOSS IS LOADING..."
.byte 13,10,13,10
.org 510
boot_flag:
.word 0xAA55
接下来,编译和链接botsect.s
$ as86 -0 -a -o bootsect.o bootsect.s
$ ld86 -0 -s -o bootsect bootsect.o
其中 -0
(注意:这是数字 0,不是字母 O)表示生成 8086 的 16 位目标程序,-a
表示生成与 GNU as 和 ld 部分兼容的代码,-s
告诉链接器 ld86 去除最后生成的可执行文件中的符号信息。
如果这两个命令没有任何输出,说明编译与链接都通过了。
需要留意的文件是 bootsect 的文件大小是 544 字节,而引导程序必须要正好占用一个磁盘扇区,即 512 个字节。造成多了 32 个字节的原因是 ld86 产生的是 Minix 可执行文件格式,这样的可执行文件除了文本段、数据段等部分以外,还包括一个 Minix 可执行文件头部。
去掉该文件头部:
$ dd bs=1 if=bootsect of=Image skip=32
将生成的Image文件拷贝到linux-0.11目录下
# 当前的工作路径为 /home/shiyanlou/oslab/linux-0.11/boot/
# 将刚刚生成的 Image 复制到 linux-0.11 目录下
$ cp ./Image ../Image
# 执行 oslab 目录中的 run 脚本
$ ../../run
2. bootsect.s读入setup.s
首先编写一个setup.s,直接拷贝前面的bootsect.s,然后稍加修改
接下来需要编写 bootsect.s 中载入 setup.s 的关键代码。原版 bootsect.s
中下面的代码就是做这个的。
所有需要的功能在原版 bootsect.s 中都是存在的,我们要做的仅仅是将这些代码添加到新的 bootsect.s
中去,并去掉原bootsect.s中的无限循环。
SETUPLEN=4
SETUPSEG=0x07e0
entry _start
_start:
mov ah,#0x03
xor bh,bh
int 0x10
mov cx,#24
mov bx,#0x0007
mov bp,#msg1
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
load_setup:
mov dx,#0x0000
mov cx,#0x0002
mov bx,#0x0200
mov ax,#0x0200+SETUPLEN
int 0x13
jnc ok_load_setup
mov dx,#0x0000
mov ax,#0x0000
int 0x13
jmp load_setup
ok_load_setup:
jmpi 0,SETUPSEG
msg1:
.byte 13,10
.ascii "MOSS IS LOADING..."
.byte 13,10,13,10
.org 510
boot_flag:
.word 0xAA5
修改tools/buid.c
编译:
$ cd ~/oslab/linux-0.11
$ make BootImage
$ ../run
3.获取硬件参数
这一部分汇编代码没怎么看懂,建议参考李治军老师的在线实验教程。
4.总结
Linux操作系统启动部分主要执行流程:
- 开机上电后,CPU进入实模式,从0xFFFF0处开始执行,为ROM-BIOS的地址。
- BIOS执行系统检测、在地址0处初始化中断向量、把硬盘启动扇区的512字节读入内存绝对地址0x7C00处,并跳转至这个地方。
- bootsect.s把自己移动到0x90000继续执行,加载setup.s到0x90200处,显示开机字符串,再把system.s加载到0x10000处。随后跳转到setup.s部分执行
- setup.s利用BIOS终端读取系统参数,然后把system模块移动到0x00000处,设置IDT和GDT,进入保护模式
- head.s重新设置IDT和GDT,并设置页表,进入main.c
- 初始化内存和数据结构,创建第一个进程。