博客网址:www.shicoder.top
微信:18223081347
欢迎加群聊天 :452380935
这一次我们进入操作系统实现的真实编码, 这一次主要是完善对boot.asm文件的全部实现,开始吧。。。
首先我们先来理一下boot.asm需要干什么
- 打印出
Booting System...
- 实现磁盘读写
- 将后续的
loader.asm
所在的区域读入到0x1000
处,然后跳转进入loader.asm
程序 - 开始执行
loader.asm
程序(这一节我们下次实现)
实模式下的print
在我们平时编写c语言时候,可以直接使用,但是在boot.asm中,完全就没有可以用库函数,因此为了在开始打印处start boot,我们需要自己实现print
先来看下代码把
mov si, booting
call print
print:
mov ah, 0x0e
.next:
mov al, [si]
cmp al, 0
jz .done
int 0x10
inc si
jmp .next
.done:
ret
booting:
db "Booting System...", 10, 13, 0; \n\r
这段程序主要使用使用BIOS的int 10h
来实现一个print
功能,al
寄存器存储要显示的字符串
磁盘读写
因为boot.asm
在主引导扇区,磁盘内存太小,不能在boot.asm
中实现loader.asm
的功能,因此我们将loader.asm
保存在磁盘的一个地方,在boot.asm
中利用磁盘读的方式,将代码读入到内存的一个区域,然后跳转到那个地方
先来看下磁盘读的功能实现
; 函数参数
; edi 将磁盘内容读到哪里
; ecx 从磁盘哪一个扇区开始
; bl 要读多少个扇区
read_disk:
; 设置读写扇区的数量
; 0x1f2 是硬盘控制端口,表示读写扇区的数量
mov dx, 0x1f2
mov al, bl
; 写端口用OUT指令 将al的值写入到dx端口
out dx, al
inc dx; 0x1f3 起始扇区前8位端口
; 因为ecx为起始扇区
; ecx中的cl就是0-7位
mov al, cl; 起始扇区前8位
out dx, al
inc dx; 0x1f4 起始扇区中8位端口
shr ecx, 8 ;右移8位
mov al, cl; 起始扇区中8位
out dx, al
inc dx; 0x1f5 起始扇区高8位端口
shr ecx, 8 ;右移8位
mov al, cl; 起始扇区高8位
out dx, al
inc dx ;0x1f6
shr ecx, 8
and cl, 0b1111 ;将高4位置为0,对应起始扇区的24-27位
mov al,0b1110_0000 ;第4位为0,表示主盘,第6位为1,表示LBA,5-7位必须为1
; 将al和cl合二为一,放在al中
or al, cl
out dx, al
inc dx ;0x1f7
mov al, 0x20 ;表示读硬盘
out dx, al
xor ecx, ecx ;清空ecx
mov cl, bl ;得到写扇区的数量
; loop指令会检查ecx是否为0 cl在ecx里面
.read:
push cx ;保存下,因为函数里面使用了
call .waits ;等待数据准备完毕
call .reads ;读取一个扇区
pop cx ;恢复
loop .read
ret
.waits:
mov dx, 0x1f7 ;读0x1f7端口
.check:
in al, dx ;将dx端口的值放入al中
jmp $+2 ;直接跳转到下一行 其实什么都没做,就是为了延迟一下
jmp $+2
jmp $+2
and al, 0b1000_1000 ;获得al的第3位和第7位
cmp al, 0b0000_1000 ;测试是否第7位为0,第3位为1 硬盘不繁忙,数据准备完毕
jnz .check ;数据没准备好
ret
.reads:
mov dx, 0x1f0 ;用于读写数据
mov cx, 256 ;一个扇区256字节
; loop指定会检查ecx cx在ecx里面
.readw:
in ax, dx
jmp $+2 ;直接跳转到下一行 其实什么都没做,就是为了延迟一下
jmp $+2
jmp $+2
; edi表示读取的目标内存
mov [edi], ax
; 因为ax是16bit,2个字节,所以edi+2
add edi, 2
loop .readw
ret
下面是磁盘的相关端口
Primary 通道 | Secondary 通道 | in 操作 | out 操作 |
---|---|---|---|
0x1F0 | 0x170 | Data | Data |
0x1F1 | 0x171 | Error | Features |
0x1F2 | 0x172 | Sector count | Sector count |
0x1F3 | 0x173 | LBA low | LBA low |
0x1F4 | 0x174 | LBA mid | LBA mid |
0x1F5 | 0x175 | LBA high | LBA high |
0x1F6 | 0x176 | Device | Device |
0x1F7 | 0x177 | Status | Command |
- 0x1F0:16bit 端口,用于读写数据
- 0x1F1:检测前一个指令的错误
- 0x1F2:读写扇区的数量
- 0x1F3:起始扇区的 0 ~ 7 位
- 0x1F4:起始扇区的 8 ~ 15 位
- 0x1F5:起始扇区的 16 ~ 23 位
- 0x1F6:
- 0 ~ 3:起始扇区的 24 ~ 27 位
- 4: 0 主盘, 1 从片
- 6: 0 CHS, 1 LBA
- 5 ~ 7:固定为1
- 0x1F7: out
- 0xEC: 识别硬盘
- 0x20: 读硬盘
- 0x30: 写硬盘
- 0x1F7: in / 8bit
- 0 ERR
- 3 DRQ 数据准备完毕
- 7 BSY 硬盘繁忙
注意上面的out和in指令
读端口用IN指令,写端口用OUT指令
out a,b 将b的值写入到a端口
in a,b 将b端口的值读到a中
先来看4个起始扇区的寄存器 :0x1F3
、0x1F4
、0x1F5
、0x1F6
,假如此时的起始扇区ecx=123456789
,即32位bit为00000111010110111100110100010101
-
0-7位:
00010101
=>0x1F3
-
8-15位:
11001101
=>0x1F4
-
16-23位:
01011011
=>0x1F5
-
24-31位:
00000111
- 24-27位:
0111
=>0x1F6
(0-3) -
mov al,0b1110_0000
-
0
=>0x1F6
(4) 表示主盘 -
111
=>ox1F6
(5-7) 固定为1
-
- 24-27位:
再来看0x1F7
,值为0x20
,表示读磁盘
然后通过mov cl,bl
,将扇区数量放在cl
中,后面进行循环,汇编中循环的次数和ecx
有关。因为是要读磁盘,因此需要先等待磁盘数据处理好,然后才进行读取,.wait
便是这个作用,其余的相关解析可以通过代码注释看懂,这里就不赘述了
jmp $+2
可以通过反汇编看到
0000:jmp $+2
0002:xxx所以这行代码就是跳到下一行,起到等待的作用
经过编写这个函数,我们就可以从磁盘中得到我们想要的代码啦,前面说过,我们本身就想将loader.asm
代码放在磁盘的一个地方,然后再读进来,那怎么放呢,这样,我们先简单写一个loader.asm
loader.asm
代码如下
[org 0x1000]
; 打印字符串
mov si, loading
call print
; 阻塞
jmp $
print:
mov ah, 0x0e
.next:
mov al, [si]
cmp al, 0
jz .done
int 0x10
inc si
jmp .next
.done:
ret
loading:
db "Loading System...", 10, 13, 0; \n\r
同样的,我们只是打印出一句话即可,那我们怎么将这些代码复制到磁盘中去呢,下面两行命令
nasm -f bin loader.bin loader.asm
dd if=loader.bin of=master.img bs=512 count=4 seek=2 conv=notrunc
利用dd命令,将bin文件从偏移为2的地方,写入4个到master.img中,这样就可以知道loader.bin
在磁盘哪里,就可以读入了
boot.asm
中代码如下
; 因为loader.bin是从第2个扇区开始写入,写了4个扇区
mov edi, 0x1000;读取的目标内存
mov ecx, 2 ;起始扇区
mov bl, 4 ;扇区数量
call read_disk
经过上面一番折腾,终于从boot
跳转到loader
中了,后续我们将对loader.asm
进行完善,实现loader
所需要的功能,下次见啦。。。
网友评论