操作系统实现-boot.asm实现

233 阅读6分钟

博客网址: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 操作
0x1F00x170DataData
0x1F10x171ErrorFeatures
0x1F20x172Sector countSector count
0x1F30x173LBA lowLBA low
0x1F40x174LBA midLBA mid
0x1F50x175LBA highLBA high
0x1F60x176DeviceDevice
0x1F70x177StatusCommand
  • 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个起始扇区的寄存器 :0x1F30x1F40x1F50x1F6,假如此时的起始扇区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

再来看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所需要的功能,下次见啦。。。