Orange's一个操作系统的实现学习(1)

工具准备以及引导程序的编写

Orange's一个操作系统的实现学习(1)_第1张图片

 

第一版的电子书(PDF) 自己动手写操作系统  

1.Virtual PC安装

 

 

      原书所说的Virtual PC已经下载不好下载了,经过几天的探索,终于发现了一个符合要求的版本。

 

      Virtual PC 5.0

2.DOS 6.3安装

 

      (1)安装DOS

 

 

      来这里下一个msdos6.22 的镜像.

 

 

 

      然后启动Virtual PC,新建一个PC,内存32m,硬盘50m,OK启动!

 

 

 

      菜单 -> 软驱 -> 载入镜像 选择622c.img

 

     

 

      OK,引导系统,进入了dos.然后对硬盘分区.

 

 

 

      执行命令:fdisk 一路回车到底 (注意:2007里需要,5.2版本里不需要).

 

 

 

      然后格式化c盘 format C: /s

 

 

 

      然后传输系统文件 sys C:

 

 

 

      然后拷贝软盘文件: copy a:\*.* c:\     (注意不要覆盖c盘已经存在的文件 )

 

     

 

      OK.菜单 -> 软驱 ->释放镜像"622c.img"

 

 

 

      重启 ,这样你就进了dos环境.

 

 

 

      (2)安装共享文件夹模块.

 

      重启进入dos后,菜单 -> 软驱 ->载入镜像选择dos附加模块(在Virtual PC的安装目录下\Virtual Machine Additions\DOS Virtual Machine Additions.vfd) 加载后,切换到软盘

 

执行命令: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 ->

 

Orange's一个操作系统的实现学习(1)_第2张图片

Orange's一个操作系统的实现学习(1)_第3张图片

Orange's一个操作系统的实现学习(1)_第4张图片

  Next->选择存放位置和文件名,选1.4M大小 -> Finish。

 

4.2准备写软盘的工具

 

      我们是要写一个软盘引导程序,但是我们写的程序如何放进软盘中(虚拟软盘)呢?如果放不经去我们如何测试我们的程序是否能运行呢?其实我们用二进制查看器打开已经存在的软盘映像发现,文件的的开始的512字节就是软盘的0磁盗0柱面1扇区,因此我们可以对映象文件进行绝对扇区的读写,将我们的512字节的引导程序写进映像中。原书的光盘中提供了一个工具,但是每次都手动添加实在是太麻烦了,(实际上如果亲自动手写的话估计光调试就得好几十遍每次都手动将编译的bin文件导入软盘的话,我估计你会疯的),所以我们将他的代码改造实现自动编译,编译后自动写软盘。

 

先看一下改造后的写软盘的代码(VS2005):

 

 编译好的可执行程序:WriteFlopy
/ 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;
}
 4.3 Boot程序的项目构建

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程序
该程序的主要功能是在FAT12文件格式的软盘中寻找Loder.bin文件并将其加载到内存中,然后将控制权交给Loader.bin。本程序和FAT12文件格式中的测试程序的功能类似,并且有关FAT12的格式问题同样可以参考FAT12文件格式。下面先给出实现后的代码:
;*******************************************************************************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

你可能感兴趣的:(操作系统)