操作系统(一)控制系统启动
实验内容
阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解; 按照下面的要求改写 0.11 的引导程序 bootsect.s 有兴趣同学可以做做进入保护模式前的设置程序 setup.s。
改写 bootsect.s 主要完成如下功能:
bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)
改写 setup.s 主要完成如下功能:
bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行”Now we are in SETUP”。 setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。 setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。
实验步骤
修改bootsect.s文件
entry _start
_start:
!使用 0x10 号中断读取光标位置
mov ah,#0x03
xor bh,bh
int 0x10
!显示字符串 “Hello OS world, my name is JJT”
!cx 为要显示字符串的长度(除字符串外还有3个换行+1个回车)
mov cx,#36
mov bx,#0x0007
! es:bp 为显示字符串的地址
mov bp,#msg1
mov ax,#0x07c0
mov es,ax
mov ax,#0x1301
int 0x10
!设置一个无限循环
inf_loop:
jmp inf_loop
!设置显示字符串
msg1:
!回车+换行
.byte 13,10
.ascii "Hello OS world, my name is JJT"
!两对 “回车+换行”
.byte 13,10,13,10
!boot_flag 必须在最后两个字节
.org 510
!设置引导扇区标记 0xAA55(必须设置,否则无法成功引导)
boot_flag:
.word 0xAA55
设置开机引导字符

#汇编 bootsect.s 为 16 位目标文件 bootsect.o
as86 -0 -a -o bootsect.o bootsect.s
#将目标文件 bootsect.o 链接成可执行文件 bootsect,这是引导扇区的最终可执行镜像
ld86 -0 -s -o bootsect bootsect.o
#将 bootsect 文件的一部分(跳过前 32 字节)复制到 Image 文件,创建一个最终可用的引导扇区镜像。
dd bs=1 if=bootsect of=Image skip=32
cp ./Image ../Image
../../run

修改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 "JJT is booting..."
.byte 13, 10, 13, 10
.org 510
boot_flag:
.word 0xAA55 ! 设置引导扇区标记 0xAA55
修改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
为tools/build.c添加一些注释
die("Write call failed");
i += c;
}
// if ((id=open(argv[3],O_RDONLY,0))<0)
// die("Unable to open 'system'");
// if (read(id,buf,GCC_HEADER) != GCC_HEADER)
// die("Unable to read header of 'system'");
// if (((long *) buf)[5] != 0)
// die("Non-GCC header of 'system'");
// for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
// if (write(1,buf,c)!=c)
// die("Write call failed");
// close(id);
// fprintf(stderr,"System is %d bytes.\n",i);
// if (i > SYS_SIZE*16)
// die("System is too big");
return(0);
}
cd path/oslab/linux-0.11
make BootImage
#确保出现sync
../run

ps: 实验完成后请恢复初始化,不然下个实验会受影响
# 删除原来的文件
cd path/oslab
sudo rm -rf ./*
# 重新解压确保代码初始化
tar -zxvf hit-oslab-linux-20110823.tar.gz
作于2024年09月08日