在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, 0x7c00mov si, booting
call printmov edi, 0x1000; 读取的目标内存
mov ecx, 2; 起始扇区
mov bl, 4; 扇区数量call read_diskcmp word [0x1000], 0x55aa
jnz errorjmp 0:0x1002 ; 没问题则跳转到内核加载器; 阻塞
jmp $read_disk:; 设置读写扇区的数量mov dx, 0x1f2mov al, blout dx, alinc dx; 0x1f3mov al, cl; 起始扇区的前八位out dx, alinc dx; 0x1f4shr ecx, 8mov al, cl; 起始扇区的中八位out dx, alinc dx; 0x1f5shr ecx, 8mov al, cl; 起始扇区的高八位out dx, alinc dx; 0x1f6shr ecx, 8and cl, 0b1111; 将高四位置为 0mov al, 0b1110_0000;or al, clout dx, al; 主盘 - LBA 模式inc dx; 0x1f7mov al, 0x20; 读硬盘out dx, alxor ecx, ecx; 将 ecx 清空mov cl, bl; 得到读写扇区的数量.read:push cx; 保存 cxcall .waits; 等待数据准备完毕call .reads; 读取一个扇区pop cx; 恢复 cxloop .readret.waits:mov dx, 0x1f7.check:in al, dxjmp $+2; nop 直接跳转到下一行jmp $+2jmp $+2and al, 0b1000_1000cmp al, 0b0000_1000jnz .checkret.reads:mov dx, 0x1f0mov cx, 256; 一个扇区 256 字.readw:in ax, dxjmp $+2; ;类似与nop空指令 只不过时钟周期会更长jmp $+2jmp $+2mov [edi], axadd edi, 2loop .readwretprint:mov ah, 0x0e
.next:mov al, [si]cmp al, 0jz .doneint 0x10inc sijmp .next.done:retbooting:db "Loading XJC_OS", 10, 13, 0; \n\rerror:mov si, .msgcall printhlt; 让 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 0x55aamov si,loading
call print1jmp $print1:mov ah, 0x0e
.next:mov al, [si]cmp al, 0jz .doneint 0x10inc sijmp .next
.done:retloading:db "os loading success!!!", 10, 13, 0; \n\r
这个内核加载器并没有实现很多功能,只是模拟主引导扇区是否成功加载内核加载器。如果成功加载将会在屏幕上输出一句“os loading success!!!”。我们编译这个asm看看二进制文件的样子:
这是编译完的内核加载器的二进制文本,我们去验证一下是否加载到0x1000的内存位置:
屏幕输出了内核加载器的内容,并且确实加载进来了,因此实验成功。