linux汇编学习(2)-----摆脱MBR大小的限制,加载stage2代码

完整代码已经归档到 : https://github.com/linzhanglong/mini_bootloader

         我们知道,系统启动会加载磁盘的MBR扇区到内存0x7c00那里去执行。但是毕竟MBR扇区只有512个字节,如果要实现设置GDT,读取内核,引导内核等功能,这512字节显得力不从心。所以我们这里第一步就是通过MBR去磁盘读取一块更大的空间到内存然后去执行这部分代码(称为 stage2)。这里我们尤其要注意一点就是,系统刚从BIOS启动执行磁盘MBR数据,CPU的工作模式还是实模式,寄存器都是16位的。通过段寄存器,最大可以支持的内存是[0xffff,0xffff] = 0xffff * 16 + 0xffff = 1M。总共只能索引1M的内存,而每一个段内只能索引64K范围。

      我们这里先规划我们的内存和磁盘布局:

linux汇编学习(2)-----摆脱MBR大小的限制,加载stage2代码_第1张图片


我们现在实现的代码就是通过MBR代码,把stage2的代码加载到内存0x9000执行。


首先我们要先实现一个读取磁盘的函数接口,对于磁盘的结构理解,网上很多资源:http://www.cnblogs.com/joydinghappy/articles/2511948.html


我们这里要借助BIOS的中断服务来实现磁盘的读操作:

我们看一下对应的BIOS如何读取磁盘:
使用的中断号:
INT 13h AH=02h: Read Sectors From Drive[edit]

传入的参数:
AH 02h
AL Sectors To Read Count
CH Cylinder
CL Sector
DH Head
DL Drive
ES:BX Buffer Address Pointer

结果的返回:
CF Set On Error, Clear If No Error
AH Return Code
AL Actual Sectors Read Count

数据的存放:
磁盘数据存放的内存地址: ES:BX

注意事项:
1. CX寄存器包含了圆柱面号和扇区号,其中同心圆号占10bit,扇区号占6bit【所以一个圆盘最大扇区数目就是64 * 1024】,所以CX[0-5]表示扇区号,CX[6-15]表示同心圆号
CX =       ---CH--- ---CL---
cylinder : 76543210 98
sector   :            543210
2. 根据第一点我们可以知道,这里我们最大索引的磁盘空间是: 256 Head * 1024  Cylinder * 64 Sector * 512 byte = 8G

3. 因为数据拷贝存放的地址是放在ES:BX起始地址,我们知道一个段可以索引的大小是1M(BX:0xFFFF),所以拷贝的数据大小+拷贝的其实地址BX最好小于1M。


现在开始实现我们的读磁盘函数(文件名字 read_disk.asm):

;
;@ brief 这里实现把磁盘的数据拷贝到内存 
;@ param dh:cx 从哪个扇区开始拷贝。al表示磁头号,bx决定哪个同心圆哪个扇区
;@ param al 拷贝多少个扇区数据
;@ return 如果成功,函数直接返回,数据存放到ES:BX的地址。
;         如果失败,打印一条错误日志,然后卡主
;@ notes 调用者需要先设置好数据的拷贝地址 : ES:BX的地址
;
read_diskdata:
    pusha

    mov [READ_SECTER_NR], al
    ;开始拷贝的扇区地址分为 (Head)8bit:(Cylinder)10bit:(Sector)6bit
    ;其中Head -> DH, Cylinder -> CH, Sector -> CL
    ;这里目前只支持拷贝drive0,也就是磁盘hda --> 0x80
    mov dl, 0x80
    ;开始拷贝磁盘数据
    mov ah, 0x02
    int 0x13
    ;开始判断磁盘拷贝结果,如果CF表示磁盘拷贝失败
    jc _READ_ERR
    ;判断拷贝的磁盘扇区数目是不是和我们要拷贝的一样,不是也报错
    mov dl, al ;保存实际的拷贝的扇区数目到dl
    mov al, [READ_SECTER_NR] ;我们想要拷贝的扇区数目al
    cmp dl, al
    jne _READ_ERR
    ;成功拷贝,打印一条日志,然后返回
    mov bx, DISK_READ_OK
    call print_string

    popa
    ret
    
_READ_ERR:
    mov bx, ax
    call print_hex
    mov bx, DISK_READ_ERR
    call print_string
    jmp $ ;卡主
    ret ;Never go there
    
;定义打印的字符串
DISK_READ_OK db 'Disk Read Ok', 0
DISK_READ_ERR db 'Disk Read Error', 0
;拷贝的扇区数目我们需要保存起来
READ_SECTER_NR equ 0x00

实现完磁盘的读取函数接口,现在我们就开始写MBR代码,MBR代码512字节大小,实现的功能,就是把磁盘地址512到512+32K(也就是第二个扇区开始,读取64个扇区)的数据加载到内存0x9000地址,然后跳过去执行。直接上代码-(文件名字 mbr.asm):

;
; Date 2017/11/28
; Authon: linzhanglong
; notes: 这里是MBR分区,用途:用于加载stage2代码到指定内存位置,然后执行

[org 0x7c00]
	;定义几个变量
	;根据https://www.kernel.org/doc/Documentation/x86/boot.txt,
	;定义stage2的代码长度,要求512字节对齐!
	;mbr工作在实模式,一个段内索引最大64k,这里定义stage2 32k大小
STAGE2_LEN equ 0x8000 ;32k
STAGE2_MAGIC equ 0x4433 ;用于校验我们是不是拷贝正常

	; step 1 先初始化号堆栈,免得出异常[xxxxx, 0x8000]
	mov bp, 0x8000
	mov sp, bp

	; step 2 先初始化stage2内存拷贝地址[0x9000, 0x9000 + 64k]
	mov ax, 0x900
	mov es, ax
	mov bx, 0

	; step 3 从磁盘位置[0x200, 0x200 + 64k] => [2 sector, 2 + 128 sector]
	;         拷贝stage2代码到内存
	mov dh, 0 ;(Head)8bit
	mov cx, 2 ;(Cylinder)10bit:(Sector)6bit
	mov ax, STAGE2_LEN
	shr ax, 9 ;al <----- setor num
	; step 4开始拷贝数据
	call read_diskdata

	;check load ok,直接读取stage2在内存最后两个字节,判断是不是魔数 STAGE2_MAGIC
	;是表示我们加载没有错误,不是表示我们加载有问题
	mov bx, STAGE2_LEN
	sub bx, 2
	mov ax, [es:bx]
	cmp ax, STAGE2_MAGIC
	jne _CHECK_ERR

	mov bx , MSG_LOAD_STAGE2_OK
	call print_string

	jmp 0x9000   ;跳到stage2代码执行

_CHECK_ERR:
	mov bx, MSG_LOAD_STAGE2_ERR
	call print_string
	jmp $ 

%include "print.asm"
%include "read_disk.asm"

MSG_LOAD_STAGE2_ERR db 'Load stage2 Err', 0
MSG_LOAD_STAGE2_OK db  'Load stage2 Ok', 0

times 510-($-$$) db 0
dw 0xaa55


最后就是stage2的代码了,目前这格代码只是打印一条日志,后面有时间在实现stage2的真正代码功能(文件名字 boot_kernel.asm):

;
; Date 2017/11/28
; Authon: linzhanglong
; notes: 这里是加载内核,目前先实现一个个打印功能。
[org 0x9000]
	;目前只是简单打印一条日志,后面在实现功能
	mov bx, MSG_BOOT_KERNEL_TEST
	call print_string
	jmp $

%include "print.asm"
%include "read_disk.asm"

MSG_BOOT_KERNEL_TEST db 'Boot kernel test', 0

times 32766-($-$$) db 1
dw 0x4433

最后,就是编译文件制作一个可以启动的磁盘:

# !/bin/bash
rm -rf ./raw_disk

nasm mbr.asm -o mbr
if [[ $? != 0 ]];then
    exit 1
fi

#写入MBR
dd if=./mbr of=./raw_disk bs=512 count=1
if [[ $? != 0 ]];then
    exit 1
fi

nasm boot_kernel.asm -o boot_kernel
#写入stage2
dd if=./boot_kernel of=./raw_disk bs=512 seek=1
if [[ $? != 0 ]];then
    exit 1
fi

#启动qemu
qemu-kvm raw_disk -vnc :6

exit 0


最终的结果图:

linux汇编学习(2)-----摆脱MBR大小的限制,加载stage2代码_第2张图片



你可能感兴趣的:(linux汇编学习(2)-----摆脱MBR大小的限制,加载stage2代码)