工具准备以及引导程序的编写
第一版的电子书(PDF) 自己动手写操作系统
1.Virtual PC安装
2.DOS 6.3安装
(1)安装DOS
执行命令:c:\>a:
执行命令:a:\>dosadd
然后就可以看到Successful了.呵呵.
然后就是重启,进入dos系统后, 菜单 -> 编辑 ->属性设置 这时共享文件夹就可以添加了.添加一个,设置成盘符Y:
然后执行命令 Y:
就可以看到共享的文件了.
VMware中只需要第一步,就能设置共享文件夹了.不用安装附加模块!
3.开机启动过程
计算机加电后,由BIOS完成一系列的检测工作,如果所有设备都工作正常,则接下来BIOS开始检测启动设备;计算机会在启动设备第一个扇区偏移量为 510的地方寻找一个魔力数字(Magic Number)0xAA55,如果没有这个魔力数字则不是启动设备。每个启动设备的第一个扇区偏移量510byte处都会有这个数字,那么一个计算机如果有多个启动设备(软盘,硬盘,光盘等),则PC就要按照一定的顺序依次检查这些启动设备;这个次序就是我们在BIOS中设置的boot sequence。我们将启动设备前512个字节称作MBR(全称是Master Boot Record);通常MBR指的就是硬盘的第一个扇区,BIOS负责将找到的第一个启动设备的MBR导入内存中的0x7C00开始的一段空间,之后将控制权交给这段MBR;接下来CPU开始执行MBR中的内容;由于MBR只有512个字节,这么短小的一段程序通常被用来导入真正的操作系统程序。(参考MBR代码分析 )
4.BootSector的引导程序编写
4.1 准备软盘
准备一个用于的启动的软盘,不过现在软盘已经是古董级的东西了,想找一个软盘都不是件容易的事,且软盘易坏读写速度不快,我们做一个虚拟软盘。具体步骤如下:
Virtual PC -> File -> Virtual Disk Wizard ->
/ WriteFlopy.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "windows.h" int _tmain(int argc, _TCHAR* argv[]) { // 下面读文件 ------------------------------------------------ unsigned char uchBootData[512]; char szFile[512] = "Boot.bin"; // buffer for file name HANDLE hf = ::CreateFile(szFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwFileSizeHigh = 0; DWORD dwFilesize = ::GetFileSize(hf, &dwFileSizeHigh); if (dwFilesize <= 0) { ::MessageBox(0, "文件错误!", "Error", MB_OK); return 0; } DWORD dwRead = 0; if (!ReadFile(hf, uchBootData, dwFilesize, &dwRead, NULL)) { int iErr; char szError[128]; iErr = GetLastError(); ::sprintf(szError, "文件读取错误!\n错误代码: %d", iErr); ::MessageBox(0, szError, "Error", MB_OK); return 0; } ::CloseHandle(hf); // 下面写文件 -------------------------------------------------------- ::strcpy(szFile, "Tinix.img"); HANDLE hfImage = ::CreateFile(szFile, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); ::SetFilePointer(hfImage, 0, 0, FILE_BEGIN); DWORD dwBytesWritten; if (!::WriteFile(hfImage, uchBootData, 512, &dwBytesWritten, NULL) ) { int err; char error[10]; err=GetLastError (); sprintf(error,"%d",err); //itoa (err, error, 10); //MessageBox (0, "Writing sectors ...Failed "); return 0; } ::CloseHandle(hfImage); ::MessageBox(0, "成功!", "Floppy writer", MB_OK); return 0; }
boot.asm | 引导程 序的源代码 |
WirteFlopy.exe | 将编译后的boot.bin文件写进虚拟软盘TINIX.IMG |
TINIX.IMG | 虚拟软盘 |
include | 用于放置一些常量和FAT12文件格式的定义的文件夹 |
compile.bat | 实现自动编译并带哦用WriteFlopy.exe将编译好的boot.bin文件写进虚拟软盘 |
compile.bat的内容如下:
nasm -I .\include\ boot.asm -o boot.bin
WriteFlopy.exe
4.4 编写boot.asm程序
;*******************************************************************************80 ;**boot.asm 软盘引导分区 ;******************************************************************************* org 07c00h ;BIOS将把Boot Sector开机加载到 ;地址0000:7c00 并执行 ;==宏=========================================================================== BaseOfStack equ 07c00h ;boot状态下的堆栈基地址(注意堆栈是向下 ;生长的) ;Loader address BaseOfLoader equ 09000h ;LOADER.BIN 被加载到的位置 ---- 段地址 OffsetOfLoader equ 0100h ;LOADER.BIN 被加载到的位置 ---- 偏移地 ;址 (共63K的大小) ;FAT12 SectorNoOfRootDirectory equ 19 ;Root Directory 的第一个扇区号= ;BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) RootDirSectors equ 14 ; 根目录占用空间: RootDirSectors = ;((BPB_RootEntCnt * 32) + (BPB_BytsPerSec ; – 1)) / BPB_BytsPerSec; 但如果按照此公 ;式代码过长 ;=============================================================================== jmp short LABEL_START nop ;必不可少的 ;**以下是FAT12的磁盘头的定义************ BS_OEMName DB 'SongYang' ;OEM String, 必须 8 个字节 BSB_BytePerSec DW 512 ;每扇区字节数 BSP_SecPerClus Db 1 ;每簇多少扇区 BPB_RsvdSecCnt DW 1 ;Boot 记录占用多少扇区 BSP_NumFats DB 2 ;共有多少 FAT 表 BSP_RootEntCnt DW 224 ;根目录文件数最大值 BSP_TotSec16 DW 2880 ;逻辑扇区总数 BPB_Media DB 0xF0 ;媒体描述符 BPB_FATSz16 DW 9 ;每FAT扇区数 BPB_SecPerTrk DW 18 ;每磁道扇区数 BPB_NumHeads DW 2 ;磁头数(面数) BPB_HiddSec DD 0 ;隐藏扇区数 BPB_TotSec32 DD 0 ;如果 wTotalSectorCount 是0由这个值记录 ;扇区数 BS_DrvNum DB 0 ;中断 13 的驱动器号 BS_Reserved1 DB 0 ;未使用 BS_BootSig DB 29h ;扩展引导标记 (29h) BS_VolID DD 0 ;卷序列号 BS_Volab DB 'Tinix0.01 ' ;卷标, 必须 11 个字节 BS_FileSysType DB 'FAT12 ' ;文件系统类型, 必须 8个字节 LABEL_START: ;**初始化寄存器************************* mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack ;**清屏********************************* mov ax, 0600h ; AH = 6, AL = 0h mov bx, 0700h ; 黑底白字(BL = 07h) mov cx, 0 ; 左上角: (0, 0) mov dx, 0184fh ; 右下角: (80, 50) int 10h ; int 10h ;**显示提示字符串*********************** mov dh, 0 call DispStr ;**软驱复位***************************** xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ ;**在软盘中寻找Loader.bin(FAT12)******** ;**将根目录整个的都读到es:bx处 mov ax, BaseOfLoader mov es, ax ; es <- BaseOfLoader mov bx, OffsetOfLoader ; bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader mov ax, SectorNoOfRootDirectory ; ax <- Root Directory 中的某 Sector 号 mov cl, RootDirSectors ;将根目录都读出来 call ReadSector ;mov ax, BaseOfLoader ;mov es, ax ;mov ax, cs ;mov ds, ax mov si, LoaderFileName ; ds:si -> "LOADER BIN" mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100 cld ;设置方向标志 mov cx, [BSP_RootEntCnt] mov [RootDirectItemNum], cx ;根目录的条数 LABEL_SEARCH_FOR_LOADERBIN: cmp word [RootDirectItemNum], 0 jz NOLOADER dec word [RootDirectItemNum] mov cx, 11 LABEL_CMP_FILENAME: cmp cx, 0 jz LABEL_FILENAME_FOUND ; 如果比较了 11 个字符都相等, 表示找到 dec cx lodsb ; ds:si -> al cmp al, byte [es:di] jz LABEL_GO_ON jmp LABEL_DIFFERENT ; 只要发现不一样的字符就表明本 DirectoryEntry 不是 LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME LABEL_DIFFERENT: and di, 0FFE0h add di, 020h and si, LoaderFileName jmp LABEL_SEARCH_FOR_LOADERBIN NOLOADER: mov dh, 2 call DispStr jmp $ LABEL_FILENAME_FOUND: and di, 0FFE0h add di, 01Ah ; di -> 首 Sector(偏移) mov cx, word [es:di] push cx ;cx里面放的是Loader的第一个扇区好(数据区的从2开始) ;****************************** ;**读取Fat表 mov ax, BaseOfLoader sub ax, 140h ;分配5k内存(512*10/1024) mov es, ax mov bx, 0 ;将Fat读到es:bx mov ax, [BPB_RsvdSecCnt];Fat开始扇区 1 mov cl, [BPB_FATSz16] call ReadSector ;**读取Loader到内存******* mov ax, BaseOfLoader mov es, ax mov bx, OffsetOfLoader pop ax LABEL_GOON_LOADING_FILE: push ax ; ┓ push bx ; ┃ mov ah, 0Eh ; ┃ 每读一个扇区就在 "Booting " 后面打一个点, 形成这样的效果: mov al, '.' ; ┃ mov bl, 0Fh ; ┃ Booting ...... int 10h ; ┃ pop bx ; ┃ pop ax ; ┛ push ax ;保存ax add ax, 17 + 14 mov cl, 1 ;一个扇区 call ReadSector ; pop ax ;回复ax call GetFATEntry;获得下个号码 cmp ax, 0FFFh jz LABEL_FILE_LOADED add bx, [BSB_BytePerSec] jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: mov dh, 1 ; "Ready." call DispStr ; 显示字符串 ; ***************************************************************************************************** jmp BaseOfLoader:OffsetOfLoader ; 这一句正式跳转到已加载到内存中的 LOADER.BIN 的开始处 ; 开始执行 LOADER.BIN 的代码 ; Boot Sector 的使命到此结束 ; ***************************************************************************************************** ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- LoaderFileName db "LOADER BIN", 0 ; LOADER.BIN 之文件名 RootDirectItemNum DW 0 ; 为简化代码, 下面每个字符串的长度均为 MessageLength MessageLength equ 9 BootMessage: db "Booting " ; 7字节 Message1 db "Ready. " ; 9字节 Message2 db "No LOADER" ; 9字节 ;============================================================================ ;---------------------------------------------------------------------------- ; 函数名: DispStr ;---------------------------------------------------------------------------- ; 作用: ; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) DispStr: push ax push bx push cx push dx push es mov ax, MessageLength mul dh add ax, BootMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0006h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 int 10h ; int 10h pop es pop dx pop cx pop bx pop ax ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) ; ----------------------------------------------------------------------- ; 设扇区号为 x ; ┌ 柱面号 = y >> 1 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁头号 = y & 1 ; 每磁道扇区数 │ ; └ 余 z => 起始扇区号 = z + 1 push bp mov bp, sp sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl push bx ; 保存 bx mov bl, [BPB_SecPerTrk] ; bl: 除数 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ax ; GetFATEntry: push bp mov bp, sp sub esp, 2 mov word [bp-2], 0 push es push bx push dx mov bx, 3 mul bx ;ax = ax * 3 mov bx, 2 div bx ;商在ax 余数在dx cmp dx, 0 jz LABEL_EVEN ;偶数 mov word [bp-2], 1 ;奇数 LABEL_EVEN: mov bx, ax mov ax, BaseOfLoader sub ax, 140h mov es, ax mov word ax, [es:bx] cmp word [bp-2], 0 jz LABEL_EVEN_2 shr ax, 4 LABEL_EVEN_2: and ax, 0FFFh ;低十二位 pop dx pop bx pop es add esp, 2 pop bp ret times 510-($-$$) db 0 ;填充剩余的空间,是生成的代码正好为512B dw 0xaa55 ;结束标记编译后的文件: boot.bin