工具准备以及引导程序的编写
第一版的电子书(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