操作系统-哈工大-李治军老师-【实验项目1-控制系统启动】
课程的实验地址:
实验楼:https://www.lanqiao.cn/courses/115
(一) 完成【实验项目一:熟悉实验环境】
实验项目一,只是熟悉一下实验楼的实验环境,这里略过
(二) 完成【实验项目二:操作系统的引导】
1. 实验要求
2. 实验内容
(1) 改写之后的 bootsect.s 代码:
SETUPLEN = 2 ! 要读取的扇区数
SETUPSEG = 0x07e0 ! setup 读入内存后的起始地址,这里 bootsect 没有将自己挪动到 0x90000 处,所以setup=0x07e00
entry _start
_start:
mov ah, #0x03 ! 第 0x10 号中断例程中的 0x03 号子程序,功能为获取光标位置
xor bh, bh
int 0x10
mov cx, #23 ! 显示字符串的长度
mov bx, #0x0002 ! bh=第 0 页,bl=文字颜色属性 2
mov bp, #msg1
mov ax, #0x07c0
mov es, ax ! es:bp 是将要显示的字符串的地址
mov ax, #0x1301 ! ah=13h 写字符串,al=01 移动光标
int 0x10
load_setup:
mov dx, #0x0000 ! dh=磁头号或面号 dl=驱动器号,软驱从0开始,硬盘从80h开始
mov cx, #0x0002 ! ch=磁道号 cl=扇区号
mov bx, #0x0200 ! es:bx 指向接收从扇区读入数据的内存区
mov ax, #0x0200 + SETUPLEN ! ah=int 13h 的功能号(2 表示读扇区) al=读取的扇区数
int 0x13 ! int 13h 是 BIOS 提供的访问磁盘的中断例程
jnc ok_load_setup ! 读入成功则跳转
mov dx, #0x0000
mov ax, #0x0000 ! 软驱、硬盘有问题时,会复位软驱
int 0x13
jmp load_setup ! 重新循环,再次尝试读取
ok_load_setup:
jmpi 0, SETUPSEG ! 段间跳转指令 ip=0, cs=SETUPSEG
msg1: ! len = 3换行 + 3回车 + 字符串长度
.byte 13, 10 ! 换行 + 回车
.ascii "WHZ is booting..."
.byte 13, 10, 13, 10
.org 510
boot_flag:
.word 0xAA55 ! 设置引导扇区标记 0xAA55
(2) 改写之后的 setup.s 代码:
INITSEG = 0x9000 ! setup.s 将获得的硬件参数放在内存的 0x90000 处
entry _start
_start:
! 显示字符串 "Now we are in SETUP"
mov ah, #0x03 ! 第 0x10 号中断例程中的 0x03 号子程序,功能为获取光标位置
xor bh, bh
int 0x10
mov cx, #25 ! 显示字符串的长度
mov bx, #0x0002 ! bh=第 0 页,bl=文字颜色属性 2
mov bp, #msg2
mov ax, cs
mov es, ax ! es:bp 是将要显示的字符串的地址
mov ax, #0x1301 ! ah=13h 写字符串,al=01 移动光标
int 0x10
! 获取基本硬件参数
mov ax, #INITSEG
mov ds, ax ! 设置 ds = 0x9000
! 读取光标的位置并写入 0x90000 处
mov ah, #0x03 ! 读入光标位置
xor bh, bh
int 0x10
mov [0], dx ! 将获取的光标位置写入 ds:[0]=0x90000 处
! 读取内存大小并写入内存中
mov ah, #0x88
int 0x15
mov [2], ax ! 将内存大小写入 ds:[2]=0x90002 处
! 从 0x41 处拷贝 16 个字节(磁盘参数表)
! 在 PC 机中 BIOS 设定的中断向量表中 int 0x41 的中断向量位置(4*0x41 = 0x0000:0x0104)存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。
mov ax, #0x0000
mov ds, ax
lds si, [4 * 0x41]
mov ax, #INITSEG
mov es, ax
mov di, #0x0004
mov cx, #0x10 ! 重复 16 次,因为每个硬盘参数表有 16 个字节大小。
rep
movsb
! 准备打印参数
mov ax, cs
mov es, ax
mov ax, #INITSEG
mov ds, ax
mov ss, ax
mov sp, #0xFF00
! 打印光标的位置
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #18
mov bx, #0x0002
mov bp, #msg_cursor
mov ax, #0x1301
int 0x10
mov dx, [0]
call print_hex ! 调用 print_hex 显示信息
! 打印内存大小
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #14
mov bx, #0x0002
mov bp, #msg_memory
mov ax, #0x1301
int 0x10
mov dx, [2]
call print_hex ! 调用 print_hex 显示信息
! 添加内存单位 KB
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #2
mov bx, #0x0002
mov bp, #msg_kb
mov ax, #0x1301
int 0x10
! 打印柱面数
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #7
mov bx, #0x0002
mov bp, #msg_cyles
mov ax, #0x1301
int 0x10
mov dx, [0x4 + 0x0]
call print_hex ! 调用 print_hex 显示信息
! 打印磁头数
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #8
mov bx, #0x0002
mov bp, #msg_heads
mov ax, #0x1301
int 0x10
mov dx, [0x4 + 0x2]
call print_hex ! 调用 print_hex 显示信息
! 打印扇区
mov ah, #0x03
xor bh, bh
int 0x10
mov cx, #10
mov bx, #0x0002
mov bp, #msg_sectors
mov ax, #0x1301
int 0x10
mov dx, [0x4 + 0x0e]
call print_hex ! 调用 print_hex 显示信息
call print_nl ! 打印换行回车
inf_loop:
jmp inf_loop ! 设置一个无限循环
! 以 16 进制方式打印栈顶的 16 位数
print_hex:
mov cx, #4 ! 循环的次数,一个 dx 寄存器有 16 位,每 4 位显示一个 ASCII 字符,因此需要循环 4 次
print_digit:
rol dx, #4 ! 循环左移,将 dx 的高 4 位移到低 4 位处
mov ax, #0xe0f ! ah=0x0e为int 0x10的子程序0x0e(显示一个字符串) al=要显示字符的 ASCII 码
and al, dl ! 取 dl 的低 4 位,通过与运算放入 al 中
add al, #0x30 ! 数字 + 0x30 == 对应的 ASCII 码
cmp al, #0x3a ! 比较指令,仅对标志寄存器位有影响
jl outp ! jl 小于跳转
add al, #0x07 ! a~f 是 字符 + 0x37 == 对应的 ASCII 码
outp:
mov bx, #0x0002
int 0x10
loop print_digit
ret
print_nl:
mov ax, #0xe0d
int 0x10 ! 打印回车
mov al, #0xa
int 0x10 ! 打印换行
ret
! 提示信息
msg2: ! len = 3换行 + 3回车 + 字符串长度
.byte 13, 10 ! 换行 + 回车
.ascii "Now we are in SETUP"
.byte 13, 10, 13, 10
msg_cursor:
.byte 13, 10
.ascii "Cursor position:"
msg_memory:
.byte 13,10
.ascii "Memory Size:"
msg_kb:
.ascii "KB"
msg_cyles:
.byte 13,10
.ascii "Cyls:"
msg_heads:
.byte 13,10
.ascii "Heads:"
msg_sectors:
.byte 13,10
.ascii "Sectors:"
.org 510
boot_flag:
.word 0xAA55
(3) build.c 需要更改的内容
默认的 build.c 会将 bootsect、setup 和 system 内核的文件名,将三者做简单的整理后一起写入 Image 中(即生成一个操作系统的镜像文件 Image)。
由于我们目前只有 bootsect 和 setup,因此需要更改一部分 build.c 的代码,更改的部分如下(只需要注释代码即可):
(4) 使用 make 命令将 bootsect、setup 一起生成 Image 镜像文件 && 运行 Bochs
$ cd ~/oslab/linux-0.11
$ make BootImage
$ ../run
(5) 在 Bochs 中运行最新编译号的 Image 镜像文件的效果图
-
bochs/bochsrc.bxrc 文件中保存的硬件信息
-
运行 Image 镜像文件的效果图
3. 实验报告
x86 计算机为了向下兼容,导致启动过程比较复杂,启动过程被硬件强制,软件必须遵守的两个“多此一举”的步骤:
(1) bootsect.s 从硬盘中先被加载到内存物理地址 0x7c000 处,后又挪动到了物理地址 0x90000 处
原因:
由于 BIOS 的硬件限制,引导扇区(bootsect.s) 在计算机启动时,从硬盘中先被加载到内存物理地址 0x7c000 处,后面又为了给 system 模块腾出空间,引导程序又将自己移动到内存靠后的位置,也就是 0x90000 处。解决方案:
在保证可靠性的前提下尽量扩大实地址模式下 BIOS 可访问的内存的范围,这样就可以直接将引导扇区加载到 0x90000 或者更高的内存地址处,就避免了二次的转移。
(2) system 模块从硬盘中先被加载到内存物理地址 0x10000 处,后又挪动到了物理地址 0x00000 处
原因:
在计算机启动的时候,会执行 BIOS 中的初始化程序,初始化程序主要的功能是在内存物理地址 0 处建立 BIOS 所支持的中断向量,即将 BIOS 提供的中断例程的入口地址登记在中断向量表中。
linux 0.11 的 system 模块被 bootsect.s 程序加载到 0x10000 处,等后续 setup.s 程序执行的时候,又会将 system 模块移动到 0x00000 处,目的是避免 system 模块直接写到 0x00000 处覆盖了中断向量表。(因为在移动 system 模块之前,setup 代码还需要利用 ROM BIOS 中的中断向量表来获取机器的一些参数)解决方案:
初始化程序时,将 ROM BIOS 的中断向量表放到实模式下能寻址内存的其他地方,这样操作系统的 system 模块就可以直接存放到 0x00000 处,避免二次的转移。