我也是自己一步一步学着别人的方法来编写一个操作系统。把一些重点记下来。教程在这里:http://www.brokenthorn.com/Resources/OSDevIndex.html
1、准备工作
一台32位Intel的电脑就可以了,使用Windows操作系统。然后下载一些软件安装上:
软件名 | 下载地址 | 说明 |
NASM | http://nasm.sourceforge.net/ | nasm –f bin Boot4.asm –o Boot4.bin |
PartCopy | http://www.brokenthorn.com/Resources/Programs/pcopy02.zip | partcopy Boot4.bin 0 200 -f0 |
VFD - Virtual Floppy Drive |
http://sourceforge.net/projects/vfd/ | |
Bochs Emulator |
http://bochs.sourceforge.net/ |
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中:
copy 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键,就可以结束这次模拟了。
好了,有关代码的具体介绍,请参看这里。