有了前面的基础铺垫,下面就要开始写代码了。
开发环境是Ubuntu,理由是Win7下面没有类似于dd的好用的绝对扇区写入工具,Win7由于一些安全方面的限制,自己编写程序写U盘MBR总是失败,可能是我水平太菜了吧^_^
二、要写入U盘MBR的程序boot.asm
这段代码的功能就是加载kernel.bin到地址0x7e00处,然后跳转到0x7e00。写这段程序,还是乖乖的使用汇编吧。也就那么四百来个字节。
; boot.bin max size is 440
org 07c00h
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov ax, 0x7c00
mov sp, ax
call LoadKernel
jmp $
LoadKernel:
mov ax, [0x7c00+0x1c6] ;store first sector No of partition
mov word [PartitionStartSect], ax
mov cx, 1 ;read first sector of partition to 0x7e00
mov bx, 0x7e00
mov dx, 0
mov si, DiskAddressPacket
call ReadSects
mov al, byte [0x7e00+0x0d] ;store number of sectors per cluster
mov byte [SectorsPerCluster], al
mov ax, word [0x7e00+0x0e] ;store number of reserved sectors
mov word [ReservedSectors], ax
mov al, byte [0x7e00+0x10] ;store number of FAT table
mov byte [FatNumber], al
mov ax, word [0x7e00+0x24] ;store number of sectors per FAT table
mov word [SectorsPerFat], ax
mov bh, 0 ;calculate the sector No of data area
mov bl, [FatNumber]
mul bx
add ax, [ReservedSectors]
add ax, [PartitionStartSect]
mov word [SectIndexOfData], ax
mov bx, 0x7e00 ;read a cluster from data area to 0x7e00
mov ch, 0
mov cl, [SectorsPerCluster]
mov dx, 0
call ReadSects
call GetKernelFirstCluster ;search for kernel name and get the first cluster No in data area
cmp dx, 0 ;if or not success
jnz failed_to_load
sub ax, 2 ;if success, calculate the sector No of kernel and read it to RAM(only 4KB)
mov bh, 0
mov bl, [SectorsPerCluster]
mul bx
add ax, word [SectIndexOfData]
mov bx, 0x7e00
call ReadSects
jmp 0x7e00
failed_to_load:
mov bp, FailedMsg
mov cx, FailedMsgEnd-FailedMsg
call DispStr
ret
;read sectors function
;Input:
;1. Disk Address Packet pointer, ds:si
;2. Disk sector offset low 16 bits, ax
;3. Disk sector offset high 16 bits, dx
;4. number of sectors to read, cx
;5. buffer of data read, ds:bx
ReadSects:
push ax
push dx
mov word [si + 2], cx
mov word [si + 4], bx
mov word [si + 8], ax
mov word [si + 10],dx
mov ax, 0x4200
mov dx, 0x80
int 13h
pop dx
pop ax
ret
;Search for kernel
;Output:
;1. find kernel file or not(0 for success, 1 for failed), dx
;2. low 16 bits of first cluster, ax
GetKernelFirstCluster:
push bx
push si
push di
push cx
mov cx, 128 ;FAT32 Directory entrys of one cluster
travel_entry:
push cx
xor si, si
mov cx, 11 ;file name length
xor di, di
strcmp:
mov al, [bx+si]
cmp al, [KernelName+di]
jnz break
inc si
inc di
loop strcmp
mov dx, 0
add bx, 0x1a
mov ax, [bx]
pop cx
jmp complete
break:
add bx, 32 ;size of directory entry
pop cx
loop travel_entry
mov dx, 1
complete:
pop cx
pop di
pop si
pop bx
ret
;Display a character
;Input:
;1. Charater to display, al
DispChar:
push ax
push bx
mov ah, 0x0e
mov bx, 0x000c
int 10h
pop bx
pop ax
ret
;Display a string
;Input:
;1. Pointer to string, bp
;2. string length, cx
DispStr:
push ax
push cx
push bp
dc:
mov al, [bp]
call DispChar
inc bp
loop dc
pop bp
pop cx
pop ax
ret
PartitionStartSect dw 0
SectorsPerCluster db 0
ReservedSectors dw 0
FatNumber db 0
SectorsPerFat dd 0
SectIndexOfData dw 0
KernelName db "KERNEL BIN"
FailedMsg db "failed to load kernel..."
FailedMsgEnd:
DiskAddressPacket:
db 16 ;packet size
db 0 ;reserved
dw 0 ;block count(sector)
dw 0 ;buffer offset
dw 0 ;buffer segment address
dw 0 ;block number
dw 0
dw 0
dw 0
编译方式 nasm boot.asm -o boot.bin,编译出来的boot.bin不能超过440字节
写入MBR dd if=boot.bin of=/dev/sdb
然后附带一个调试时用的以16进制打印整数的函数
;Display a Integer in hex format
;Input:
;1. Low 16 bits of Integer, ax
;2. High 16 bits of Integer, dx
DispInt:
push ax
push bx
push cx
push dx
xor cx, cx
d:
mov bx, 16
div bx
cmp dx, 9
ja char
add dx, '0'
jmp over
char:
sub dx, 10
add dx, 'A'
over:
push dx
inc cx
cmp ax, 0
jz end
xor dx, dx
jmp d
end:
mov bx, 000ch
l:
pop ax
mov ah, 0x0e
int 10h
loop l
pop dx
pop cx
pop bx
pop ax
ret
三、编写kernel.bin
kernel.bin由两个文件编译连接而成,entry.asm和main.c,entry.asm仍旧使用汇编,作用是进入保护模式,然后调用main.c中的main函数。main.c中就是清了下屏然后打印了一句问候语。
entry.asm:
global begin
begin:
jmp start_e
[section .s16]
[bits 16]
start_e:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
lgdt [GdtPtr] ;load gdt
cli ;disable interrupt
in al, 0x92 ;enable A20
or al, 0x2
out 0x92, al
mov eax, cr0 ;enable protected mode
or al, 1
mov cr0, eax
jmp dword (gdt_code-GdtTable):enter_pm ;far jump to 32 bits code
[section .s32]
[bits 32]
extern main
enter_pm:
mov ax, 16
mov ds, ax
mov es, ax
mov ss, ax
mov ax, 0
mov fs, ax
mov gs, ax
call main
[section .gdt]
GdtTable:
dd 0
dd 0
gdt_code:
dw 0xffff
dw 0x0000
db 0x00
db 0x9a
db 0xcf
db 0x00
gdt_data:
dw 0xffff
dw 0x0000
db 0x00
db 0x92
db 0xcf
db 0x00
GdtPtr:
dw GdtPtr-GdtTable-1
dd GdtTable
main.c
void main()
{
char *str = "hello world from C";
char *p = (char *)(0xb8000);
int i;
for (i = 0; i < 25 * 80; i ++, p += 2)
*p = ' ';
for (p = (char *)(0xb8000); *str; str ++, p += 2)
*p = *str;
while (1);
}
kernel.bin:entry.o main.o
ld entry.o main.o -o kernel.bin -Ttext 0x7e00 -e begin --oformat binary
entry.o:entry.asm
nasm -f elf entry.asm -o entry.o
main.o:main.c
gcc -c main.c
cp:
sudo cp kernel.bin /media/tao/
u:
sudo umount /media/tao/
m:
sudo mount /dev/sdb1 /media/tao/
run:
sudo qemu -hda /dev/sdb -boot c
clean:
rm *.o *.bin
从Makefile中可以看出,每次得到kernel.bin后,先把它复制到U盘,然后要卸载U盘再挂载,然后使用qemu测试才能得到结果,如过不卸载再挂载,qemu运行的结果是上一次生成的kernel.bin。可能是因为操作系统没有及时写盘,所以要卸载一下。当然这个U盘也可以拿到真机上直接启动。
(完)