预备知识: 汇编语言、能读懂NASM语法、了解BIOS 中断INT0x10, INT 0x16,虚拟机的使用
涉及工具: NASM,一个文本编辑器(我用的是ConText + NASM语法高亮)
前言: 第一次写教程,不知道能拿多少分。
“怎样开始写自己的操作系统”可能是不少有“非分想法”的爱好者来说是一个难题,在我的大学课程《计算机操作系统教程》以教程的形式充分说明了操作系统的原理,这本书会告诉你各个环节的原语;所谓“原理”就指理论上的东西,实际上我们并不知道操作系统是如何工作的。为了不至于让有这个非分想法的朋友不至于放弃,这篇日志就带你走进操作系统开发的殿堂。
一、操作系统是如何启动的?
首先让我们来看看计算机的启动过程,这里我摘抄了一些东西:
“首先让我们来了解一些基本概念。第一个是大家非常熟悉的BIOS(基本输入输出系统),BIOS是直接与硬件打交道的底层代码,它为操作系统提供了控制硬件设备的基本功能。BIOS包括有系统BIOS(即常说的主板BIOS)、显卡BIOS和其它设备(例如IDE控制器、SCSI卡或网卡等)的BIOS,其中系统BIOS是本文要讨论的主角,因为计算机的启动过程正是在它的控制下进行的。BIOS一般被存放在ROM(只读存储芯片)之中,即使在关机或掉电以后,这些代码也不会消失。
“第二个基本概念是内存的地址,我们的机器中一般安装有32MB、64MB或128MB内存,这些内存的每一个字节都被赋予了一个地址,以便CPU访问内存。32MB的地址范围用十六进制数表示就是0~1FFFFFFH,其中0~FFFFFH的低端1MB内存非常特殊,因为最初的8086处理器能够访问的内存最大只有1MB,这1MB的低端640KB被称为基本内存,而A0000H~BFFFFH要保留给显示卡的显存使用,C0000H~FFFFFH则被保留给BIOS使用,其中系统BIOS一般占用了最后的64KB或更多一点的空间,显卡BIOS一般在C0000H~C7FFFH处,IDE控制器的BIOS在C8000H~CBFFFH处。”
(以上引自:http://article.pchome.net/2003/09/25/13022.htm)
二、进一步了解引导的关键所在
计算机启动的第一个过程由BIOS完成,它做了什么工作是不我们不必理会的,我们也管不了那它;关键在于:BIOS完成设备检查、初始化、更新之后,根据预先设定的引导设备顺序检查可以引导的设备,比如我们设置启动顺序为1:Floppy(软驱)2:HDD1(硬盘1) 3:CD-ROM(光驱),那么,BIOS会从软驱开始查找是否可以启动,如果软驱失败,则会去找HDD1,...... 那么,BIOS如何知道软盘或硬盘能不能引导呢?这个把戏的关键在于:每个可引导设备都有一个512字节的设备头,如果这个设备可引导,最后两个字节必须为0x55(低),0xAA(高),如果BIOS找到这个标志,就会将这512字节(注意:只有512字节)读到内存的0x0000:0x7C00,然后将控制权转交给(跳到)0x0000:0x7C00的程序,这就意味着:如果我们将软盘的第一个扇区最后两个字节打上"0x55,0xAA"的标志,这个软盘就可以启动了!当然,BIOS不会知道你这个引导程序是不是有效的,它只认0x55,0xAA。
好了,我们知道写自己第一个引导程序的关键在于: 1. 一个两字节(一个字长)的引导标志 0x55,0xAA 2. 引导程序必须占512字节
三、看看实例
代码用NASM编译:nasmw -f bin boot.asm -o boot.bin 解释一下上面的命令:在M$命令提示行下编译,-f bin 表示编译为纯二进制文件(我们不需要任何文件头),-o boot.bin 编译。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; boot.asm ; A demo to show how bootsect works ; Last modified:2005-4-3 10:57:45 ; Copyright (c) 2005,E-mean X. ; ; This program is released under GPL,See document for details ; You can use this code anywhere you want in condition keep autor's info ; original ; ; Author: E-mean X. ; Contact: xemean@sina.com ; Website: http://www.xemean.net/ ; April,02,2005 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; bits 16 ; 伪指令,告诉编译器这是 16位代码 org 0x7C00 ; 伪指令,告诉编译器这段代码由0x0:0x7C00开始 ;=========================================================================== ; 程序执行的第一条指令必须是跳转(如果你想使用FAT12这类文件系统的磁盘) ; 必须占用3字节 ;=========================================================================== jmp SHORT main ; 2 bits,跳转到主程序执行 nop ; 1 bit ;=========================================================================== ; FAT12 文件系统头,从NYAOS 借过来的,可以参考相关的文档以获得更多细节 ; 这个块会让 Winimage 认出编译后的二进制文件为有效的引导文件 ; 如果不使用这个块,Winimage将不会将其作为引导程序处理 ; 但我们可以借助其它方法和工具处理,比如DEBUG ;=========================================================================== bsOEM db "ExOS0.01" ; OEM String,任意你喜欢的8字节ASCII码 bsSectSize dw 512 ; Bytes per sector bsClustSize db 1 ; Sectors per cluster bsRessect dw 1 ; # of reserved sectors bsFatCnt db 2 ; # of fat copies bsRootSize dw 224 ; size of root directory bsTotalSect dw 2880 ; total # of sectors if < 32 meg bsMedia db 0xF0 ; Media Descriptor bsFatSize dw 9 ; Size of each FAT bsTrackSect dw 18 ; Sectors per track bsHeadCnt dw 2 ; number of read-write heads bsHidenSect dd 0 ; number of hidden sectors bsHugeSect dd 0 ; if bsTotalSect is 0 this value is ; the number of sectors bsBootDrv db 0 ; holds drive that the bs came from bsReserv db 0 ; not used for anything bsBootSign db 29h ; boot signature 29h bsVolID dd 0 ; Disk volume ID also used for temp ; sector # / # sectors to load bsVoLabel db "NO NAME " ; Volume Label bsFSType db "FAT12 " ; File System type <- FAT 12文件系统 ;=========================================================================== ; Main start here ;=========================================================================== main: cli ; 关闭可屏蔽中断,以备我们接下来初始化寄存器的工作 mov ax,cs ; 将代码段传给ax,实模式下,代码段与数据段没什么分别 mov ds,ax ; 数据段寄存器,实际上都是0 mov es,ax ; 附加段 mov ax,ss ; 堆栈段 mov sp,0x7C00-1 ; 堆栈指针,指向0x7BFF sti ; 基本工作已经完成,开放中断
; mov si,msgHello ; 使 si指向 "Hello World!"字符串 call printStr ; 调用显示子程序 mov si,fixLine ; 回车换行 call printStr mov si,msgMore ; 显示更多信息 call printStr
; Mission complete,take a break .loop: xor ax,ax ; ax 清0 int 0x16 ; 调用int 16h 读取键盘输入
; 重启计算机 ; mov ax,0x40 ; mov ds,ax ; xor ax,ax ; mov [0x0072],ax ; 向 0x40:0x72写0后再跳到_ ; jmp 0xFFFF:0x0 ; 0xFFFF:0x0 可以实现重启
jmp .loop ; 跳转到 .loop,什么也不做了
;------- END OF MAIN ----------------
;=========================================================================== ; printStr ; sub function for print a string to screen by INT 10H ; 入口:ds:si = 指向目标字符串 ; 返回:无 ;=========================================================================== printStr: push si ; 保护寄存器 push ax push bx
cld ; 清除进位标志位,这个标志位会影响 si 的递增方向 mov ah,0x0E ; int 0x10 子功能号,显示字符,参看相关资料以获得细节 mov bx,0x0007 ; 页号0,字符前景色 7,浅灰色,试着改变这个数值 ; 会给你的文字增添色彩 .nextChar: lodsb ; [si] -> al,取一个字节码 or al,al ; 如果取得的字节是0,则表示字符串结束 jz .OK ; 退出 int 0x10 ; 调用BIOS int 10h 中断 jmp .nextChar ; 继续下一个字符,直到遇到0 .OK: pop bx ; 恢复寄存器 pop ax pop si ret ; 返回调用程序 ;------- END OF printStr --------------
; data area
msgHello db 'Hello World!',0 ; 以物理 0结束 msgMore db 'Wow,my FIRST boot sector!',0 fixLine db 13,10,0 ; 回车,换行的ASCII码
; 引导程序必须为512字节,不用的地方以0填充 times 510-($-$$) db 0 ; $表示程序当前位置,$$表示程序开始位置,由编译器自动计算 BOOT_SIGN DW 0xAA55 ; 最后两个字节为引导标志55AA
;--------------------------- End of this programme --------------------------------------
编译成功之后,会生成一个512字节的boot.bin,用Winimage创建一个新的软盘,在“Options(选项)”菜单里,"Winimage mode selection ..." 选择“WinImage Professional mode(专家模式)”(如果不选专家模式,将不能进行引导扇区的编辑),之后,"Image-> Boot Sector Properties"(映像->引导扇区属性)中,将引导扇区改为你刚才编译的程序,保存,用Vmware/Qemu/Bochs之类的虚拟机工具试试你的启动盘吧!
发挥你的想像力,让你的引导程序实现更多的功能!
-----------------------------------------------------------
E-mean X. April,03,2005
转载请说明作者及出处 |