手写操作系统--主引导扇区以及内核加载器

在CPU上电后,会自动将cs:ip置为f000:fff0,下图就是一个计算机刚上电的模拟:

 ffff00这里开始的代码是BIOS自检,检查计算机的硬件完备性,做完这一切后将第一个扇区的内容复制到0x7c00的位置,并从0x7c00位置执行代码:

 0x7c00开始就放着我们的主引导扇区的代码。因为我的第一句代码是mov ax,0x0003,是占用3个字节,因此实际代码将会从0x7c02开始。

因此我们能接触的第一个操作系统代码就是编写这个主引导扇区。主引导扇区的功能主要是要将内核加载器加载到内存中。因此我们此次实验就模拟一个内核加载器,内核加载器的功能我们将慢慢完善。

;boot.asm

[org 0x7c00]   ;告诉编译器代码从0x7c00位置开始
xchg bx,bx
mov ax, 3           ;清空屏幕
int 0x10

; 初始化段寄存器
mov ax, 0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00

mov si, booting
call print

mov edi, 0x1000; 读取的目标内存
mov ecx, 2; 起始扇区
mov bl, 4; 扇区数量

call read_disk

cmp word [0x1000], 0x55aa
jnz error

jmp 0:0x1002  ; 没问题则跳转到内核加载器

; 阻塞
jmp $

read_disk:

    ; 设置读写扇区的数量
    mov dx, 0x1f2
    mov al, bl
    out dx, al

    inc dx; 0x1f3
    mov al, cl; 起始扇区的前八位
    out dx, al

    inc dx; 0x1f4
    shr ecx, 8
    mov al, cl; 起始扇区的中八位
    out dx, al

    inc dx; 0x1f5
    shr ecx, 8
    mov al, cl; 起始扇区的高八位
    out dx, al

    inc dx; 0x1f6
    shr ecx, 8
    and cl, 0b1111; 将高四位置为 0

    mov al, 0b1110_0000;
    or al, cl
    out dx, al; 主盘 - LBA 模式

    inc dx; 0x1f7
    mov al, 0x20; 读硬盘
    out dx, al

    xor ecx, ecx; 将 ecx 清空
    mov cl, bl; 得到读写扇区的数量

    .read:
        push cx; 保存 cx
        call .waits; 等待数据准备完毕
        call .reads; 读取一个扇区
        pop cx; 恢复 cx
        loop .read

    ret

    .waits:
        mov dx, 0x1f7
        .check:
            in al, dx
            jmp $+2; nop 直接跳转到下一行
            jmp $+2
            jmp $+2
            and al, 0b1000_1000
            cmp al, 0b0000_1000
            jnz .check
        ret

    .reads:
        mov dx, 0x1f0
        mov cx, 256; 一个扇区 256 字
        .readw:
            in ax, dx
            jmp $+2;  ;类似与nop空指令 只不过时钟周期会更长
            jmp $+2
            jmp $+2
            mov [edi], ax
            add edi, 2
            loop .readw
        ret

print:
    mov ah, 0x0e
.next:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10
    inc si
    jmp .next

.done:
    ret

booting:
    db "Loading XJC_OS", 10, 13, 0; \n\r

error:
    mov si, .msg
    call print
    hlt; 让 CPU 停止
    jmp $
    .msg db "Booting Error!!!", 10, 13, 0

; 填充 0
times 510 - ($ - $$) db 0

; 主引导扇区的最后两个字节必须是 0x55 0xaa
; dw 0xaa55
db 0x55, 0xaa

上述代码是一个完整的主引导扇区,它主要实现如下功能:

1,清空屏幕

2,在屏幕上输出一句加载内核

3,调用硬盘读写功能,将硬盘中的第二个扇区开始的内核加载器写入内存0x1000的位置

4,校检内核加载器的完备性,如果出现错误则跳转到输出错误语句

5,如果成功将控制权交给内核加载器。

这样主引导扇区的使命就完成了。接下来我们模拟实现一个小型的内核加载器

;loader.asm

[org 0x1000]   ;程序将从0x1000开始执行

dw 0x55aa

mov si,loading
call print1

jmp $

print1:
    mov ah, 0x0e
.next:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10
    inc si
    jmp .next
.done:
    ret

loading:
    db "os loading success!!!", 10, 13, 0; \n\r

这个内核加载器并没有实现很多功能,只是模拟主引导扇区是否成功加载内核加载器。如果成功加载将会在屏幕上输出一句“os loading success!!!”。我们编译这个asm看看二进制文件的样子:

 这是编译完的内核加载器的二进制文本,我们去验证一下是否加载到0x1000的内存位置:

屏幕输出了内核加载器的内容,并且确实加载进来了,因此实验成功。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值