本章节开始我们进入mbr代码的编写(后面我们统称mbr为boot);
按照上章节的说明,我们的boot会大于512字节,而标准的boot是512字节,我们会运用分段加加载的方法来加载整个boot,然后再运行loader.我们先用如下代码进行分段加载测试,如果运行成功,则会显示loader尝试加载信息。
boot代码如下:
boot.asm
%include "base_phy.inc" section mbr align=16 vstart=MACRO_BOOT_ADDR jmp start ;数据区 bootmsg: db "MBR running..", 0ah,0dh; bootmsglen: equ $-bootmsg mbr_left_len dw mbr_end-loader_begin ;代码区 start: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, MACRO_BOOT_ADDR ;清除屏幕 call clearScreen ;显示mbr加载信息 mov cx, bootmsglen ; CX = 串长度 push cx mov cx, bootmsg push cx call showString pop cx pop cx ;计算mbr剩余内容占用的扇区数目,不足一个扇区的按照一个扇区计算 mov dx, 0 mov ax, [mbr_left_len] add ax, 511 mov bx, 512 ;512字节每扇区 div bx ;MBR只有当前这一个扇区,直接加载loader cmp ax, 0 jz load_loader ;依次压入起始内存地址,扇区数目,扇区起始号 mov cx, MACRO_BOOT_LEFT_START_ADDR push cx push ax mov si, MACRO_BOOT_LEFT_START_SECTOR ;mbr剩余内容的起始扇区 push si call getDataFromSectors pop si pop ax pop cx jmp load_loader ;clearScreen ;清除当前屏所有字符,同时光标设置为起始0,0处 clearScreen: pusha mov ax, 0x0600 ; AH = 6, AL = 0 mov bx, 0x0700 ; 黑底白字 mov cx, 0 ; 左上角: (0, 0) mov dx, 0x184f ; 右下角: (80, 50) int 0x10 ; int 0x10 mov ax, 0x0200 mov bx, 0 mov dx, 0 int 0x10 popa ret ;showString(字符串地址,字符串长度) ;在当前当前光标处显示字符串信息 ;字符串地址: bp+4 ;字符串长度: bp+6 showString: push bp mov bp, sp pusha mov ax, 0x0300 mov bx, 0 int 0x10 mov cx, [bp+6] ; CX = 串长度 mov bp, [bp+4] mov ax, 0x1301 ; AH = 0x13, AL = 0x1 mov bx, 0x7 ; 页号为0(BH = 0) 黑底白字(BL = 0x7) int 0x10 ; int 0x10 popa pop bp ret ;getDataFromSectors(起始扇区号,扇区数目,起始内存地址) ;从指定扇区位置处连续加载指定数目个扇区到指定的内存起始位置处 ;起始扇区号;bp+4 ;扇区数目; bp+6 ;起始内存地址: bp+8 getDataFromSectors: push bp mov bp, sp pusha push es ;加载的内存起始地址 mov cx, [bp+8] ;要读取的扇区数目 mov ax, [bp+6] ;起始扇区号 mov si, [bp+4] ;剩余内容加载 到es:di起始处 shr cx, 4 mov es, cx xor di, di mov dx,0x1f2 out dx,al ;读取的扇区数 inc dx ;0x1f3 mov ax,si out dx,al ;LBA地址7~0 inc dx ;0x1f4 mov al,ah out dx,al ;LBA地址15~8 xor ax, ax inc dx ;0x1f5 out dx,al ;LBA地址23~16 inc dx ;0x1f6 mov al,0xe0 ;LBA28模式,主盘 out dx,al inc dx ;0x1f7 mov al,0x20 ;读命令 out dx,al .waits: in al,dx and al,0x88 cmp al,0x08 jnz .waits ;不忙,且硬盘已准备好数据传输 ;计算要循环读取的字节数目,由于一次读取是2个字节,因此总字节除以2 mov ax, [bp+6] mov bx, 512 mul bx mov bx, 2 div bx mov cx, ax mov dx,0x1f0 .readw: in ax,dx mov [es:di],ax add di, 2 loop .readw pop es popa pop bp ret times 510-($-$$) db 0 db 0x55, 0xaa ;剩余内容 loader_begin: loadermsg: db "Try to search loader and load it..", 0ah,0dh; loadermsglen: equ $-loadermsg load_loader: ;显示mbr加载信息 mov cx, loadermsglen ; CX = 串长度 push cx mov cx, loadermsg push cx call showString pop cx pop cx jmp $ mbr_end:base_phy.inc
MACRO_BOOT_ADDR equ 0x7c00 MACRO_BOOT_LEFT_START_SECTOR equ 4096 MACRO_BOOT_LEFT_START_ADDR equ MACRO_BOOT_ADDR+512 MACRO_LOADER_ADDR equ 0x10000 MACRO_LOADER_LBA_START equ 1 MACRO_KERNEL_ADDR equ 0x500000 MACRO_KERNEL_START_SECTOR equ 100 MACRO_GDT_ADDR equ 0x8000 MACRO_PAGEDIR_ADDR equ 0x20000 MACRO_PAGETBL_ADDR equ 0x21000还记得我们之前说过的代码路径吗,如下:
其中boot.asm存放在boot目录中, base_phy.inc放在include中。
为了方便编译,我们在boot目录中增加一个makefile, 在根目录中增加两个 sh脚本:env.sh, build.sh, env.sh用于设置各种路径,build.sh主要用来编译代码及代码安装。
makeFile
.PHONY : all clean boot boot_clean all : boot boot : boot.asm ../include/base_phy.inc $(ASM) $(ASMFLAG) $< -o $(RELEASE_BOOT_NAME) clean : boot_clean boot_clean : rm $(RELEASE_BOOT_NAME)env.sh:设置各种路径
#!/bin/sh #对变量赋值: export ASM=nasm export ASMFLAG="-I `pwd`/include/" export RELEASE=`pwd`/release export IMG_NAME=`pwd`/os.img #boot export RELEASE_BOOT_DIR=`pwd`/release/boot export RELEASE_BOOT_NAME=`pwd`/release/boot/boot.bin #loader export RELEASE_LOADER_DIR=`pwd`/release/loader export RELEASE_LOADER_NAME=`pwd`/release/loader/loader.bin #kernel export RELEASE_KERNEL_DIR=`pwd`/release/kernel export RELEASE_KERNEL_NAME=`pwd`/release/kernel/kernel.bin build.sh:完成编译,主要是编译boot及将boot内容写入磁盘os.img中,写时分为两步操作:前512字节写入扇区0, 剩余字节写入偏移为2097152开始 处(2097152=4096*512) #!/bin/sh #对变量赋值: source ./env.sh function bulild_root() { if [ ! -d $RELEASE_BOOT_DIR ] then mkdir -p $RELEASE_BOOT_DIR else rm -rf $RELEASE_BOOT_DIR/* fi make -C boot if [ ! -f $RELEASE_BOOT_NAME ] then echo "Error in build root" return 1 fi dd if=/dev/zero of=$IMG_NAME bs=512 count=1 conv=notrunc dd if=$RELEASE_BOOT_NAME of=$IMG_NAME bs=512 count=1 conv=notrunc dd if=$RELEASE_BOOT_NAME of=$IMG_NAME bs=1 count=$((`du -b $RELEASE_BOOT_NAME | cut -f 1` - 512)) conv=notrunc skip=512 seek=2097152 return 0 } if [ $1 == boot ] then echo build boot bulild_root else echo "./build.sh boot" exit 0 fi 这样一来, 我们的文件组成如下(使用tree命令,未安装此工具时,使用sudo apt-get install tree即可 ):t
现在来编译下,看下编译的效果如何, 执行bui.d.sh boot:
看看release下有没有生成boot.bin, 从这里面可以看到文件大小是563字节,已经超出了512
在执行了buildsh boot时,实际 上文件已经写入了os.img中,那如何检查下文件写入是否正确呢。此时可以使用xxd命令(更复杂的用法 可自行参考man xxd)
先检查下boot.bin内容,注意划蓝线处,第一个mbr running在512字节内,“try to search loader and load it ”已经超出512字节后,下面如果运行后能显示这句话,表示我们的分段加载是没有问题的。
再检查下os.img中的情况,实际它占用扇区0及扇区4096,分两段显示,并和boot对比,
扇区0内容如下,-l 参数表示显示数目, 和boot.bin有前512字节完全一致。
再显示扇区4096处的内容,也与boot.bin512字节之后 的内容一样
测试效果,运行bochs
打印信息正常。同时也显示 了512字节之后 的内容
运行bochs需要一个配置文件,如下
bochsrc
############################################################### # Configuration file for Bochs ############################################################### # how much memory the emulated machine will have megs: 32 # filename of ROM images romimage: file=/usr/share/bochs/BIOS-bochs-latest vgaromimage: file=/usr/share/vgabios/vgabios.bin # what disk images will be used #floppya: 1_44=a.img, status=inserted # hard disk ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 # !! Remember to change these if the hd img is changed: # 1. include/sys/config.h::MINOR_BOOT # 2. boot/include/load.inc::ROOT_BASE # 3. Makefile::HD # 4. commands/Makefile::HD ata0-master: type=disk, path="os.img", mode=flat, cylinders=40, heads=16, spt=63 #ata0-master: type=disk, path="20m.img", mode=flat, cylinders=40, heads=16, spt=63 # choose the boot disk. boot: disk # where do we send log messages? # log: bochsout.txt # disable the mouse mouse: enabled=0 # enable key mapping, using US layout as default. keyboard_mapping: enabled=1, map=/usr/share/bochs/keymaps/x11-pc-us.map下一章节,我们会对这个boot.asm进行详细说明,来描述它运行的原理。