bootsect.s读入setup.s
首先编写一个 setup.s,该 setup.s 可以就直接拷贝前面的 bootsect.s(还需要简单的调整),然后将其中的显示的信息改为:“Now we are in SETUP”。
和前面基本一样,就不注释了。
entry _start _start: mov ah,#0x03 xor bh,bh int 0x10 mov cx,#25 mov bx,#0x0007 mov bp,#msg2 mov ax,cs ! 这里的cs其实就是这段代码的段地址 mov es,ax mov ax,#0x1301 int 0x10 inf_loop: jmp inf_loop msg2: .byte 13,10 .ascii "Now we are in SETUP" .byte 13,10,13,10 .org 510 boot_flag: .word 0xAA55
接下来需要编写 bootsect.s 中载入 setup.s 的关键代码
所有需要的功能在原版 bootsect.s 中都是存在的,我们要做的仅仅是将这些代码添加到新的 bootsect.s 中去。
除了新增代码,我们还需要去掉在 bootsect.s 添加的无限循环。
SETUOLEN=2 ! 读入的扇区数 SETUPSEG=0x07e0 ! setup代码的段地址 entry _start _start: mov ah,#0x03 ! 设置功能号 xor bh,bh ! 将bh置0 int 0x10 ! 返回行号和列号,供显示串用 mov cx,#52 !要显示的字符串长度 mov bx,#0x0007 ! bh=0,bl=07(正常的黑底白字) mov bp,#msg1 ! es:bp 要显示的字符串物理地址 mov ax,#0x07c0 ! 将es段寄存器置为#0x07c0 mov es,ax mov ax,#0x1301 ! ah=13(设置功能号),al=01(目标字符串仅仅包含字符,属性在BL中包含,光标停在字符串结尾处) int 0x10 ! 显示字符串 ! 将setup模块从磁盘的第二个扇区开始读到0x7e00 load_setup: mov dx,#0x0000 ! 磁头=0;驱动器号=0 mov cx,#0x0002 ! 磁道=0;扇区=2 mov bx,#0x0200 ! 偏移地址 mov ax,#0x0200+SETUPLEN ! 设置功能号;需要读出的扇区数量 int 0x13 ! 读磁盘扇区到内存 jnc ok_load_setup ! CF=0(读入成功)跳转到ok_load_setup mov dx,#0x0000 ! 如果读入失败,使用功能号ah=0x00————磁盘系统复位 mov ax,#0x0000 int 0x13 jmp load_setup ! 尝试重新读入 ok_load_setup: jmpi 0,SETUPSEG ! 段间跳转指令,跳转到setup模块处(0x07e0:0000) ! 字符串信息 msg1: .byte 13,10 ! 换行+回车 .ascii "Welcome to the world without assembly language" .byte 13,10,13,10 ! 换行+回车 ! 将 .org 510 ! 启动盘具有有效引导扇区的标志。仅供BIOS中的程序加载引导扇区时识别使用。它必须位于引导扇区的最后两个字节中 boot_flag: .word 0xAA55
再次编译
$ make BootImage
有 Error!这是因为 make 根据 Makefile 的指引执行了 tools/build.c,它是为生成整个内核的镜像文件而设计的,没考虑我们只需要 bootsect.s 和 setup.s 的情况。它在向我们要 “系统” 的核心代码。为完成实验,接下来给它打个小补丁。c
build.c 从命令行参数得到 bootsect、setup 和 system 内核的文件名,将三者做简单的整理后一起写入 Image。其中 system 是第三个参数(argv[3])。当 “make all” 或者 “makeall” 的时候,这个参数传过来的是正确的文件名,build.c 会打开它,将内容写入 Image。而 “make BootImage” 时,传过来的是字符串 “none”。所以,改造 build.c 的思路就是当 argv[3] 是"none"的时候,只写 bootsect 和 setup,忽略所有与 system 有关的工作,或者在该写 system 的位置都写上 “0”。
修改工作主要集中在 build.c 的尾部,可长度以参考下面的方式,将圈起来的部分注释掉。
重新编译
$ cd ~/oslab/linux-0.11 $ make BootImage $ ../run
setup.s获取基本硬件参数
这里把一些难以理解的代码单独列出来
1.获得磁盘参数
这里花了我很长时间,原因是概念没有搞清楚,我觉得老师在实验指导书上写的也不是很清楚,CSDN上都只是草草复制的代码,感觉他们可以压根没有理解这一段。
先来回顾一下上文的一个概念:int 0x41
在PC机中BIOS设定的中断向量表中int 0x41的中断向量位置 (4 ∗ 0 x 41 = 0 x 0000 : 0 x 0104 4*0x41 = 0x0000:0x01044∗0x41=0x0000:0x0104)存放的并不是中断程序的地址,而是第一个硬盘的基本参数表。对于100%兼容的BIOS来说,这里存放着硬盘参数表阵列的首地址0xF000:0E401,第二个硬盘的基本参数表入口地址存于int 0x46中断向量位置处.每个硬盘参数表有16个字节大小.
这段话是重点,我之前误理解为磁盘参数就存放在以0x0000:0x0104为首地址的单元中,总共占16个字节,但实际上,只存了4个字节,里面存放的是磁盘参数表的偏移地址和段地址,也就是上文所说这里存放着硬盘参数表阵列的首地址0xF000:0E401。
lds si,[4*0x41]
再看这行代码就可以理解了,这里是把0x0000:0x0104单元存放的值(表示硬盘参数表阵列的首地址的偏移地址)赋给si寄存器,把0x0000:0x0106单元存放的值(表示硬盘参数表阵列的首地址的段地址)赋给ds寄存器。
2.参数以十六进制方式显示
先说说浪费我很长时间的我的错误:我想的是一个ASCII码8位,为什么答案里是4位4位输出,这里是搞清楚显示的目的。显示的是存在内存单元里的16进制数,例如某个字(2个字节)中的数值为 019 A 019A019A,我所要显示的不是01和9A表示的ASCII码,而是显示019A本身,所以要4位4位显示。
以十六进制方式显示比较简单。这是因为十六进制与二进制有很好的对应关系(每 4 位二进制数和 1 位十六进制数存在一一对应关系),显示时只需将原二进制数每 4 位划成一组,按组求对应的 ASCII 码送显示器即可。ASCII 码与十六进制数字的对应关系为:0x30 ~ 0x39 对应数字 0 ~ 9,0x41 ~ 0x46 对应数字 a ~ f。从数字 9 到 a,其 ASCII 码间隔了 7h,这一点在转换时要特别注意。为使一个十六进制数能按高位到低位依次显示,实际编程中,需对 bx 中的数每次循环左移一组(4 位二进制),然后屏蔽掉当前高 12 位,对当前余下的 4 位(即 1 位十六进制数)求其 ASCII 码,要判断它是 0 ~ 9 还是 a ~ f,是前者则加 0x30 得对应的 ASCII 码,后者则要加 0x37 才行,最后送显示器输出。以上步骤重复 4 次,就可以完成 bx 中数以 4 位十六进制的形式显示出来。
下面是提供的参考代码
INITSEG = 0x9000 ! 参数存放位置的段地址 entry _start _start: ! 打印 "NOW we are in SETUP" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#25 mov bx,#0x0007 mov bp,#msg2 mov ax,cs mov es,ax mov ax,#0x1301 int 0x10 ! 获取光标位置 mov ax,#INITSEG mov ds,ax mov ah,#0x03 xor bh,bh int 0x10 ! 返回:dh = 行号;dl = 列号 mov [0],dx ! 存储到内存0x9000:0处 ! 获取内存大小 mov ah,#0x88 int 0x15 ! 返回:ax = 从0x100000(1M)处开始的扩展内存大小(KB) mov [2],ax ! 将扩展内存数值存放在0x90002处(1个字) ! 读第一个磁盘参数表复制到0x90004处 mov ax,#0x0000 mov ds,ax lds si,[4*0x41] ! 把低字(2B)置为偏移地址,高字(2B)置为段地址 mov ax,#INITSEG mov es,ax mov di,#0x0004 mov cx,#0x10 ! 重复16次,即传送16B rep movsb ! 按字节传送 ! 打印前的准备 mov ax,cs mov es,ax mov ax,#INITSEG mov ds,ax ! 打印"Cursor position:" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#18 mov bx,#0x0007 mov bp,#msg_cursor mov ax,#0x1301 int 0x10 ! 打印光标位置 mov dx,[0] call print_hex ! 打印"Memory Size:" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#14 mov bx,#0x0007 mov bp,#msg_memory mov ax,#0x1301 int 0x10 ! 打印内存大小 mov dx,[2] call print_hex ! 打印"KB" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#2 mov bx,#0x0007 mov bp,#msg_kb mov ax,#0x1301 int 0x10 ! 打印"Cyls:" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#7 mov bx,#0x0007 mov bp,#msg_cyles mov ax,#0x1301 int 0x10 ! 打印柱面数 mov dx,[4] call print_hex ! 打印"Heads:" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#8 mov bx,#0x0007 mov bp,#msg_heads mov ax,#0x1301 int 0x10 ! 打印磁头数 mov dx,[6] call print_hex ! 打印"Sectors:" mov ah,#0x03 xor bh,bh int 0x10 mov cx,#10 mov bx,#0x0007 mov bp,#msg_sectors mov ax,#0x1301 int 0x10 mov dx,[18] call print_hex inf_loop: jmp inf_loop ! 上面的call都转到这里 print_hex: mov cx,#4 ! dx(16位)可以显示4个十六进制数字 print_digit: rol dx,#4 ! 取 dx 的高4比特移到低4比特处 mov ax,#0xe0f ! ah = 请求的功能值(显示单个字符),al = 半字节(4个比特)掩码 and al,dl ! 前4位会被置为0 add al,#0x30 ! 给 al 数字加上十六进制 0x30 cmp al,#0x3a ! 比较看是否大于数字十 jl outp ! 是一个不大于十的数字则跳转 add al,#0x07 ! 否则就是a~f,要多加7 outp: int 0x10 ! 显示单个字符 loop print_digit ! 重复4次 ret ! 打印换行回车 print_nl: mov ax,#0xe0d ! CR int 0x10 mov al,#0xa ! LF int 0x10 ret msg2: .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_cyles: .byte 13,10 .ascii "Cyls:" msg_heads: .byte 13,10 .ascii "Heads:" msg_sectors: .byte 13,10 .ascii "Sectors:" msg_kb: .ascii "KB" .org 510 boot_flag: .word 0xAA55
经过漫长的调试,得到如下结果
Memory Size 是 0x3C00KB,算一算刚好是 15MB(扩展内存),加上 1MB 正好是 16MB,看看 Bochs 配置文件 bochs/bochsrc.bxrc:
这些都和上面打出的参数吻合,表示此次实验是成功的。
天道酬勤
实验二总共花费25小时,因为没有汇编基础,花费了大量时间在理解代码上,希望下面的实验可以越做越快吧。