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

shilinkun
2022-05-08 / 0 评论 / 105 阅读 / 正在检测是否收录...
博客网址:www.shicoder.top
微信:kj11011029
欢迎加群聊天 :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) 固定为

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

0

评论 (0)

取消