我也是自己一步一步学着别人的方法来编写一个操作系统。把一些重点记下来。教程在这里:http://www.brokenthorn.com/Resources/OSDevIndex.html
1、准备工作
一台32位Intel的电脑就可以了,使用Windows操作系统。然后下载一些软件安装上:
NASM和PartCopy需要设置一下环境变量,在Path中添加指向其.exe目录的文件夹即可。其它问题如果有什么不明白的,可以参看这里。
VFD启动时注意使用管理员权限启动,否则会报“没有权限”的错误。
2、Bootloader
好了,我们直接进入启动程序。整个程序如下:
Boot4.asm;*********************************************
; Boot1.asm
; - A Simple Bootloader
;*********************************************
org 0
bits 16
start:
jmp main
;*********************************************
; BIOS Parameter Block
;*********************************************
; BPB Begins 3 bytes from start. We do a far jump, which is 3 bytes in size.
; If you use a short jump, add a "nop" after it to offset the 3rd byte.
bpbOEM db "My OS " ; OEM identifier (Cannot exceed 8 bytes!)
bpbBytesPerSector: DW 512
bpbSectorsPerCluster: DB 1
bpbReservedSectors: DW 1
bpbNumberOfFATs: DB 2
bpbRootEntries: DW 224
bpbTotalSectors: DW 2880
bpbMedia: DB 0xf8 ;; 0xF1
bpbSectorsPerFAT: DW 9
bpbSectorsPerTrack: DW 18
bpbHeadsPerCylinder: DW 2
bpbHiddenSectors: DD 0
bpbTotalSectorsBig: DD 0
bsDriveNumber: DB 0
bsUnused: DB 0
bsExtBootSignature: DB 0x29
bsSerialNumber: DD 0xa0a1a2a3 ; will be overwritten
bsVolumeLabel: DB "MOS FLOPPY "
bsFileSystem: DB "FAT12 "
;*********************************************
; Prints a string
; DS=>SI: 0 terminated string
; Changed Register
; AX, SI
;*********************************************
Print:
lodsb
or al, al
jz PrintDone
mov ah, 0eh
int 10h
jmp Print
PrintDone:
ret
;************************************************;
; Reads a series of sectors
; Input:
; CX=>Number of sectors to read
; AX=>Starting sector (logical block addressing)
; ES:BX=>Buffer to read to
; Changed:
; DI, SI, AX, CX, BX
;************************************************;
ReadSectors:
.MAIN:
mov di, 0x0005 ; five retries for error
.SECTORLOOP:
push ax
push bx
push cx
call LBACHS ; compute absoluteTrack, absoluteSector, absoluteHead
mov ah, 0x02 ; BIOS read sector
mov al, 0x01 ; read one sector
mov ch, BYTE [absoluteTrack]
mov cl, BYTE [absoluteSector]
mov dh, BYTE [absoluteHead]
mov dl, BYTE [bsDriveNumber]
int 0x13 ; invoke BIOS
jnc .SUCCESS ; test for read error. CF=0 then jump
xor ax, ax ; BIOS reset disk
int 0x13
dec di
pop cx
pop bx
pop ax
jnz .SECTORLOOP
int 0x18
.SUCCESS:
mov si, msgProgress
call Print
pop cx
pop bx
pop ax
add bx, WORD [bpbBytesPerSector] ; queue next buffer
inc ax ; queue next sector
loop .MAIN ; read next sector. Controlled by CX, If CX=0, then stop
ret
;************************************************;
; Convert CHS to LBA
; Input:
; AX=>the cluster to be changed
; Changed:
; AX, CX
; Return:
; AX=>sector number
; LBA = (cluster - 2) * sectors per cluster
;************************************************;
ClusterLBA:
sub ax, 0x0002 ; zero base cluster number
xor cx, cx
mov cl, BYTE [bpbSectorsPerCluster] ; convert byte to word
mul cx
add ax, WORD [datasector] ; base data sector
ret
;************************************************;
; Convert LBA to CHS
; Input:
; AX=>LBA Address to convert
; Changed:
; DX, AX
; Return:
; BYTE [absoluteSector], BYTE [absoluteHead], BYTE [absoluteTrack]
;
; absolute sector = (logical sector % sectors per track) + 1
; absolute head = (logical sector / sectors per track) MOD number of heads
; absolute track = logical sector / (sectors per track * number of heads)
;
;************************************************;
LBACHS:
xor dx, dx ; prepare dx:ax for operation
div WORD [bpbSectorsPerTrack]
inc dl ; adjust for sector 0
mov BYTE [absoluteSector], dl
xor dx, dx
div WORD [bpbHeadsPerCylinder]
mov BYTE [absoluteHead], dl
mov BYTE [absoluteTrack], al
ret
;*********************************************
; Bootloader Entry Point
;*********************************************
main:
;-----------------------------------------------------
; code located at 0000:7c00, adjust segment registers
;-----------------------------------------------------
cli
mov ax, 0x07c0 ; setup registers to point to our segment. s*16+off = address
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
;-----------------------------------------------------
; create stack
;-----------------------------------------------------
mov ax, 0x0000 ; set the stack
mov ss, ax
mov sp, 0xffff
sti ; restore interrupts
;-----------------------------------------------------
; display loading message
;-----------------------------------------------------
mov si, msgLoading ; "Loading Boot Image "
call Print
;-----------------------------------------------------
; load root directory table
;-----------------------------------------------------
LOAD_ROOT:
; compute size of root directory and store in "cx"
xor cx, cx
xor dx, dx
mov ax, 0x0020 ; 32 bytes directory entry
mul WORD [bpbRootEntries] ; total size of directory. bpbTotalSectors = 2880
div WORD [bpbBytesPerSector] ; sectors used by directory. ax is the consult
xchg ax, cx ; now cx is the result, ax is 0x0000
; compute location of root directory and store in "ax"
mov al, BYTE [bpbNumberOfFATs]
mul WORD [bpbSectorsPerFAT]
add ax, WORD[bpbReservedSectors]
mov WORD [datasector], ax ; base of root directory
add WORD [datasector], cx ; ?
; read root directory into memory (7c00:0200)
mov bx, 0x0200
call ReadSectors
;------------------------------------------------
; Find stage 2
;------------------------------------------------
; browse root directory for binary image
mov cx, WORD [bpbRootEntries]
mov di, 0x0200
.LOOP:
push cx
mov cx, 0x000b ; eleven character name
mov si, ImageName ; image name to find
push di
rep cmpsb ; test for entry match
pop di
je LOAD_FAT ; if found, "DI" is the pointer to ImageName in the Root Directory
pop cx
add di, 0x0020 ; queue next directory entry. Each entry in Root Directory is 32 bytes (0x20)
loop .LOOP ; cx = bpbRootEntries, check "cx" times.
jmp FAILURE
;----------------------------------------------
; load FAT
;----------------------------------------------
LOAD_FAT:
; save starting cluster of boot image
mov si, msgCRLF
call Print
mov dx, WORD [di + 0x001a] ; di contains starting address of entry. Just refrence byte 26 (0x1A) of entry
mov WORD [cluster], dx ; file's first cluster
; compute size of FAT and store in "cx"
xor ax, ax
mov al, BYTE [bpbNumberOfFATs]
mul WORD [bpbSectorsPerFAT]
mov cx, ax
; compute location of FAT and store in "ax"
mov ax, WORD [bpbReservedSectors] ; adjust for bootsector
; read FAT into memory (7c00:0200)
mov bx, 0x0200
call ReadSectors
; read image file into memory (0050:0000)
mov si, msgCRLF
call Print
mov ax, 0x0050
mov es, ax
mov bx, 0x0000
push bx
;----------------------------------------------
; load stage 2
;----------------------------------------------
LOAD_IMAGE:
mov ax, WORD [cluster] ; cluster to read. File's first cluster
pop bx ; buffer to read into. ES:BX. es=0x0050
call ClusterLBA ; convert cluster to LBA
xor cx, cx
mov cl, BYTE [bpbSectorsPerCluster]
call ReadSectors
push bx
; compute next cluster
mov ax, WORD [cluster] ; identify current cluster
mov cx, ax ; copy current cluster
mov dx, ax
shr dx, 0x0001 ; divide by two
add cx, dx ; sum for (3/2)
mov bx, 0x0200 ; location of FAT in memory
add bx, cx ; index into FAT
mov dx, WORD [bx] ; read two bytes from FAT
test ax, 0x0001
jnz .ODD_CLUSTER
.EVEN_CLUSTER:
and dx, 0000111111111111b ; take low twelve bits
jmp .DONE
.ODD_CLUSTER:
shr dx, 0x0004 ; take high twelve bits
.DONE:
mov WORD [cluster], dx ; store new cluster
cmp dx, 0x0ff0 ; test for end of file
jb LOAD_IMAGE
DONE:
mov si, msgCRLF
call Print
push WORD 0x0050
push WORD 0x0000
retf ; jmp to 0x0050:0000 to excute (MAY BE)
FAILURE:
mov si, msgFailure
call Print
mov ah, 0x00
int 0x16 ; a wait keypress
int 0x19 ; warm boot computer
absoluteSector db 0x00
absoluteHead db 0x00
absoluteTrack db 0x00
datasector dw 0x0000
cluster dw 0x0000
ImageName db "KRNLDR SYS"
msgLoading db 0x0d, 0x0a, "Loading Boot Image ", 0x0d, 0x0a, 0x00
msgCRLF db 0x0d, 0x0a, 0x00
msgProgress db ".", 0x00
msgFailure db 0x0d, 0x0a, "ERROR : Press Any Key to Reboot", 0x0a, 0x00
TIMES 510-($-$$) db 0 ; confirm the compiled bin file is 512B
dw 0xaa55 ; the bootable special character
这个程序经过NASM编译之后会形成一个大小恰好为512B的文件,我们使用下面的命令来编译这个名为Boot4.asm的文件(为什么叫Boot4.asm?因为测试这个程序时正好是这个编号:)
nasm -f bin Boot4.asm -o Boot4.bin
启动VFD,创建一个虚拟软盘,命名为A盘。然后使用PartCopy把Boot4.bin这个文件拷贝到软盘的第一个扇区:
partcopy Boot4.bin 0 200 –f0
现在在软盘的第一个扇区就是我们的这个启动程序。计算机启动时会按顺序检查BIOS设定的所有启动设备(比如按照软驱、光驱、磁盘的顺序来检测是否在其中有可以启动的设备)。在这里,我们把Boot4.bin写入了磁盘的第一个扇区(磁盘的每个扇区为512B),并且这个文件的末尾为0xaa55,这个特殊的字节序列表示这是一个可以启动的文件。BIOS就把这个文件放到内存的 0x7c00:0 这个地址,去执行这个文件。大致过程可以参看这里(计算机按下电源后发生了什么)。
有关这个汇编程序Boot4.asm的详细解释我们后面再做。下面我们编写一个超级简单的操作系统Stage2.asm
3、一个超级简单的操作系统Stage2.asm
不用任何解释,直接给出这个操作系统的代码。它的主要功能就是在屏幕上打印出 “Preparing to load operating system...”这个字符串
Stage2.asm; Note: Here, we are executed like a normal COM program, but we are still in
; Ring 0. We will use this loader to set up 32 bit mode and basic exception
; handling
; This loaded program will be our 32 bit kernal.
; We do not have the limitation of 512 bytes here, so we can add anything we
; want here!
org 0x0 ; offset to 0, we will set segments later
bits 16 ; we are still in real mode
; we are loaded at linear address 0x10000
jmp main
;*********************************
; Prints a String
; DS=>SI: 0 terminated string
;*********************************
Print:
lodsb
or al, al
jz PrintDone
mov ah, 0eh
int 10h
jmp Print
PrintDone:
ret
;********************************
; Second Stage Loader Entry Point
;********************************
main:
cli
push cs
pop ds
mov si, Msg
call Print
cli
hlt
;********************************
; Data section
;********************************
Msg db "Preparing to load operating system...",13,10,0
之所以把这个文件叫做Stage2.asm,是因为这是系统启动的第二个阶段,这个操作系统是由Boot4.bin从磁盘中读取出来并且加载到内存中的这个文件会被加载到0x7c00:0x0200这个内存地址上。现在我们使用NASM把这个文件编译成一个二进制文件:
nasm -f bin Stage2.asm KRNLDR.SYS
之所以把它编译成为KRNLDR.SYS,是因为在Boot4.asm中,我们设定了 ImageName db "KRNLDR SYS" 这个语句。现在只要知道有这么回事就可以了。这个文件名不能随便改。
下面我们把KRNLDR.SYS拷贝到磁盘A中:
这时候,检查A盘,就会发现里面多出了一个KRNLDR.SYS这个文件。
4、设置模拟器
下面我们使用Bochs这个模拟器来模拟系统的启动。首先安装这个模拟器。然后建立一个文件,名字叫做bochsrc.bxrc,里面的内容为:
bochsrc.bxrc# ROM and VGA BIOS images ---------------------------------------------
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
# boot from floppy using our disk image -------------------------------
floppya: 1_44=a:, status=inserted # Boot from drive A
# logging and reporting -----------------------------------------------
#log: OSDev.log # All errors and info logs will output to OSDev.log
#error: action=report
#info: action=report
保存好后,运行这个文件,就可以看到模拟器启动了。最后稳定之后的界面应该是这个样子的:
好了,到现在为止,我们的操作系统就已经运行完成了,打印出了一个字符串。如果你忘了把KRNLDR.SYS文件拷贝到A盘,它还会提示你出错。
按一下这个界面上面的Power键,就可以结束这次模拟了。
好了,有关代码的具体介绍,请参看这里。