读书人

(第4章 二)突破512字节的限制

发布时间: 2012-09-24 13:49:41 作者: rapoo

(第4章 2)突破512字节的限制

一、代码

?? ? 启动的过程中,引导扇区boot sector(boot.asm,07c00h开始,最多512bytes)负责把加载器Loader(loader.asm,长度不受限制)载入内存并且把控制权交给她。Loader再加载操作系统内核之前,还要作准备保护模式等一系列工作,就很可能超过512bytes了。

?

?? ? loader.asm代码在这里只是虚晃一枪,其具体功能编写放在“第五章 内核雏形”,不再赘述。

?

?? ??boot.asm中灰色部分为代码“主线”,基本思路为:

(1)LABEL_SEARCH_IN_ROOT_DIR_BEGIN~~~LABEL_FILENAME_FOUND:在FAT12文件系统的“根目录区”中找到"loader.bin"对应的条目。

?? 执行后[es:di]指向该条目内部的一个字节,而非条目的开始,根据P105“根目录分区的条目格式”可知,[es:di]指向条目中的位置如下图所示:

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?[DIR_NAME] [DIR_ATTR] [保 ? ? ? ? 留] ?[DIR_WrtTime] [DIR_WrtDate] [DIR_FstClus] [DIR_FileSize]

长度(单位:byte): ? ? ? ? 8+3 ? ? ? ? ? ? ? ? 1 ? ? ? ? ? ? ? ?10 ? ? ? ? ? ? ? ? ? ? ? ?2 ? ? ? ? ? ? ? ? ? ? ? ? ?2 ? ? ? ? ? ? ? ? ? ? ? ? ? 2 ? ? ? ? ? ? ? ? ? ? ?4 ? ? ? ? ? ? ? ? ?

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? LOADER_ _BIN ? ? ?* ? ? ? ? ? ? ? ********** ? ? ? ? ? ? ** ? ? ? ? ? ? ? ? ? ? ? ? ** ? ? ? ? ? ? ? ? ? ? ? ?** ? ? ? ? ? ? ? ? ? ? ****

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|

?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? di: 是字符'N'后面的那个字节

(2)LABEL_FILENAME_FOUND子程序:后面ReadSector中会用到int 13中断读磁盘,故在此准备3个寄存器内容——

ax <-- (全局)扇区号

es <-- ?BaseOfLoader (09000h)

bx <-- ?OffsetOfLoader (0100h)

?? 在ReadSector中,会将(全局)扇区号ax 转换成 “柱面号ch”,“磁头号(即盘面号)dh”,“起始扇区号cl”,从而定位到磁盘上的一个扇区。另外,在ReadSector中另行指定“cl为待读扇区数”。int 13h的2号功能(ah=2)会将磁盘上的这若干个扇区读到[es:bx]开始的内存中。

?

(3)LABEL_GOON_LOADING_FILE~~~jmp BaseOfLoader:OffsetOfLoader之前:注意到根据“根目录区条目”只能找到“loader.bin”在磁盘上的“第一个扇区”,因此本部分在FAT表中顺藤摸瓜地找到loader.bin在磁盘上的“后续所有扇区号(这里也是簇号)”,并通过call ReadSector载入内存。

?

(4)jmp BaseOfLoader:OffsetOfLoader这条指令:(前面已经将磁盘中若干个扇区上的loader.bin文件载入BaseOfLoader:OffsetOfLoader开始的连续内存中了,这里)跳转到loader.bin,将控制权交给loader.bin。在下一章中将分析loader完成的功能——加载OS内核+跳入保护模式。

?

loader.asm

org0100hmovax, 0B800hmovgs, axmovah, 0Fh; 0000: 黑底    1111: 白字moval, 'L'mov[gs:((80 * 0 + 39) * 2)], ax; 屏幕第 0 行, 第 39 列。jmp$; Start
???

?

boot.asm

?

?

;%define_BOOT_DEBUG_; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试

?

%ifdef_BOOT_DEBUG_

org ?0100h; 调试状态, 做成 .COM 文件, 可调试

%else

org ?07c00h; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行

%endif

?

;================================================================================================

%ifdef_BOOT_DEBUG_

BaseOfStackequ0100h; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)

%else

BaseOfStackequ07c00h; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)

%endif

?

BaseOfLoaderequ09000h; LOADER.BIN 被加载到的位置 ---- ?段地址

OffsetOfLoaderequ0100h; LOADER.BIN 被加载到的位置 ---- 偏移地址

?

RootDirSectorsequ14; 根目录占用空间

SectorNoOfRootDirectoryequ19; Root Directory 的第一个扇区号

SectorNoOfFAT1equ1; FAT1 的第一个扇区号 = BPB_RsvdSecCnt

DeltaSectorNoequ17; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2

; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo

;================================================================================================

?

jmp short LABEL_START; Start to boot.

nop; 这个 nop 不可少 ???1为何

?

; 下面是 FAT12 磁盘的头

BS_OEMNameDB 'ForrestY'; OEM String, 必须 8 个字节

BPB_BytsPerSecDW 512; 每扇区字节数

BPB_SecPerClusDB 1; 每簇多少扇区

BPB_RsvdSecCntDW 1; Boot 记录占用多少扇区

BPB_NumFATsDB 2; 共有多少 FAT 表

BPB_RootEntCntDW 224; 根目录文件数最大值

BPB_TotSec16DW 2880; 逻辑扇区总数

BPB_MediaDB 0xF0; 媒体描述符

BPB_FATSz16DW 9; 每FAT扇区数

BPB_SecPerTrkDW 18; 每磁道扇区数

BPB_NumHeadsDW 2; 磁头数(面数)

BPB_HiddSecDD 0; 隐藏扇区数

BPB_TotSec32DD 0; 如果 wTotalSectorCount 是 0 由这个值记录扇区数

BS_DrvNumDB 0; 中断 13 的驱动器号

BS_Reserved1DB 0; 未使用

BS_BootSigDB 29h; 扩展引导标记 (29h)

BS_VolIDDD 0; 卷序列号

BS_VolLabDB 'OrangeS0.02'; 卷标, 必须 11 个字节

BS_FileSysTypeDB 'FAT12 ? '; 文件系统类型, 必须 8个字节 ?

?

LABEL_START:

movax, cs

movds, ax

moves, ax

movss, ax

movsp, BaseOfStack


; 清屏

movax, 0600h; AH = 6, ?AL = 0h

movbx, 0700h; 黑底白字(BL = 07h)

movcx, 0; 左上角: (0, 0)

movdx, 0184fh; 右下角: (80, 50)

int10h; int 10h


movdh, 0; "Booting ?"

callDispStr; 显示字符串

?

?? ? ? ?; ?int 13h is a shorthand for BIOS interrupt call 13h, the 20th interrupt vector in an x86-based computer system. The BIOS typically sets up a real mode interrupt handler at this vector that provides sector-based hard disk and floppy disk read and write services using cylinder-head-sector(CHS) addressing.

?? ? ? ?; Drive Table:

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? DL=00h, ?1st floppy disk ("drive A:")

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? DL=01h, ?2st floppy disk ("drive B:")

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? DL=80h, ?1st hard disk?

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? DL=80h, ?2st hard disk

?? ? ? ?; Function Table:

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? AH=00h, Reset Disk Drives

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? AH=01h, Get Status of Last Drive Operation

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? AH=02h, Read Sectors From Drive

?? ? ? ?; ? ? ? ? ? ? ? ? ? ? ? ? ? AH=03h, Write Sectors To Drive

?? ? ? ?; Parameters ...

?? ? ? ?; An interrupt handler, also known as an interrupt service routine (ISR), is a callback subroutine in microcontroller firmware, operating system or device driver whose execution is triggered by the reception of an interrupt.

xorah, ah; ┓

xordl, dl; ┣ 软驱复位

int13h; ┛

?? ? ? ?; 下面在 A 盘的根目录寻找 LOADER.BIN

movword [wSectorNo], SectorNoOfRootDirectory;根目录分区的第一个扇区号SectorNoOfRootDirectory=19

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:

cmpword [wRootDirSizeForLoop], 0; ┓;根目录分区占用的扇区数wRootDirSizeForLoop

jzLABEL_NO_LOADERBIN; ┣ 判断根目录区是不是已经读完

decword [wRootDirSizeForLoop]; ┛ 如果读完表示没有找到 LOADER.BIN

?

?

?? ? ? ?; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中

?? ? ? ?; call ReadSector后,[BaseOfLoader:OffsetOfLoader] (即[es:bx]),开始存放根目录分区的一个扇区的数据

?

movax, BaseOfLoader

moves, ax; es <- BaseOfLoader

movbx, OffsetOfLoader; bx <- OffsetOfLoader于是, es:bx = BaseOfLoader:OffsetOfLoader

movax, [wSectorNo]; ax <- Root Directory 中的某 Sector 号

movcl, 1

callReadSector


?? ? ? ?; 比较[ds:si] (即"LOADER BIN")和 [es:di](即根目录区中每个条目中 文件的名称)

movsi, LoaderFileName; ds:si -> "LOADER ?BIN"

movdi, OffsetOfLoader; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100

cld

movdx, 10h

LABEL_SEARCH_FOR_LOADERBIN:

cmpdx, 0; ┓循环次数控制,

jzLABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR; ┣如果已经读完了一个 Sector,

decdx; ┛就跳到下一个 Sector

movcx, 11

LABEL_CMP_FILENAME:

cmpcx, 0

jzLABEL_FILENAME_FOUND; 如果比较了 11 个字符都相等, 表示找到

?? ? ? ?deccx

lodsb; ds:si -> al

cmpal, byte [es:di]

jzLABEL_GO_ON

jmpLABEL_DIFFERENT; 只要发现不一样的字符就表明本 DirectoryEntry 不是

; 我们要找的 LOADER.BIN

LABEL_GO_ON:

incdi

jmpLABEL_CMP_FILENAME;继续循环

?

LABEL_DIFFERENT:

?? ? ? ?; 一个根目录区中的条目长20h (bytes),详见P105

anddi, 0FFE0h; else ┓di &= E0 为了让它指向本条目开头

adddi, 20h; ? ? ┃

movsi, LoaderFileName; ? ? ┣ di += 20h ?下一个目录条目

jmpLABEL_SEARCH_FOR_LOADERBIN; ? ?┛

?

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:

addword [wSectorNo], 1

jmpLABEL_SEARCH_IN_ROOT_DIR_BEGIN

?

LABEL_NO_LOADERBIN:

movdh, 2; "No LOADER."

callDispStr; 显示字符串

?

; 由于上面有这样一句:“jzLABEL_FILENAME_FOUND”,所以程序不会执行如下的预编译,而直接跳到LABEL_FILENAME_FOUND去 !

%ifdef_BOOT_DEBUG_

movax, 4c00h; ┓

int21h; ┛没有找到 LOADER.BIN, 回到 DOS

%else

jmp$; 没有找到 LOADER.BIN, 死循环在这里

%endif

?

?

LABEL_FILENAME_FOUND:; 找到 LOADER.BIN 后便来到这里继续

movax, RootDirSectors

anddi, 0FFE0h; di -> 当前条目的开始

adddi, 01Ah; di -> 首 Sector

movcx, word [es:di]

pushcx; 保存此 Sector 在 FAT 中的序号

addcx, ax

addcx, DeltaSectorNo; cl <- LOADER.BIN的起始扇区号(0-based)

movax, BaseOfLoader

moves, ax; es <- BaseOfLoader

movbx, OffsetOfLoader; bx <- OffsetOfLoader

movax, cx; ax <- Sector 号


LABEL_GOON_LOADING_FILE:

pushax; `.

pushbx; ?|

movah, 0Eh; ?| 每读一个扇区就在 "Booting ?" 后面

moval, '.'; ?| 打一个点, 形成这样的效果:

movbl, 0Fh; ?| Booting ......

int10h; ?|

popbx; ?|

popax; /


movcl, 1

callReadSector

?

popax; 取出此 Sector 在 FAT 中的序号

callGetFATEntry

cmpax, 0FFFh

?

?? ? ? ?; 下面需要将“FAT中的扇区号/簇号“ax 转换为“在整个磁盘中的扇区号”ax

jzLABEL_FILE_LOADED

pushax; 保存 Sector 在 FAT 中的序号

movdx, RootDirSectors

addax, dx

addax, DeltaSectorNo

addbx, [BPB_BytsPerSec]

jmpLABEL_GOON_LOADING_FILE

LABEL_FILE_LOADED:


movdh, 1; "Ready."

callDispStr; 显示字符串

?

; *****************************************************************************************************

jmpBaseOfLoader:OffsetOfLoader; 这一句正式跳转到已加载到内

; 存中的 LOADER.BIN 的开始处,

; 开始执行 LOADER.BIN 的代码。

; Boot Sector 的使命到此结束

; *****************************************************************************************************

?

?

?

;============================================================================

;变量

;----------------------------------------

wRootDirSizeForLoopdwRootDirSectors; Root Directory 占用的扇区数, 在循环中会递减至零.

wSectorNodw0; 要读取的扇区号

bOdddb0; 奇数还是偶数

?

;============================================================================

;字符串

;----------------------------------------

LoaderFileNamedb"LOADER ?BIN", 0; LOADER.BIN 之文件名

; 为简化代码, 下面每个字符串的长度均为 MessageLength

MessageLengthequ9

BootMessage:db"Booting ?"; 9字节, 不够则用空格补齐. 序号 0

Message1db"Ready. ? "; 9字节, 不够则用空格补齐. 序号 1

Message2db"No LOADER"; 9字节, 不够则用空格补齐. 序号 2

;============================================================================

?

?

;----------------------------------------

; 函数名: DispStr

;----------------------------------------

; 作用:

;显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)

DispStr:

movax, MessageLength

muldh

addax, BootMessage

movbp, ax; ┓

movax, ds; ┣ ES:BP = 串地址

moves, ax; ┛

movcx, MessageLength; CX = 串长度

movax, 01301h; AH = 13, ?AL = 01h

movbx, 0007h; 页号为0(BH = 0) 黑底白字(BL = 07h)

movdl, 0

int10h; int 10h

ret

?

?

;----------------------------------------

; 函数名: ReadSector

;----------------------------------------

; 作用:

;从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中

ReadSector:

; -----------------------------------

; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)

; -----------------------------------

; 设扇区号为 x

; ? ? ? ? ? ? ? ? ? ? ? ? ? ┌ 柱面号 = y >> 1

; ? ? ? x ? ? ? ? ? ┌ 商 y ┤

; -------------- => ┤ ? ? ?└ 磁头号 = y & 1

; ?每磁道扇区数 ? ? │

; ? ? ? ? ? ? ? ? ? └ 余 z => 起始扇区号 = z + 1

pushbp

movbp, sp

subesp, 2; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

?

movbyte [bp-2], cl

pushbx; 保存 bx

movbl, [BPB_SecPerTrk]; bl: 除数

divbl; y 在 al 中, z 在 ah 中

incah; z ++

movcl, ah; cl <- 起始扇区号

movdh, al; dh <- y

shral, 1; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)

movch, al; ch <- 柱面号

anddh, 1; dh & 1 = 磁头号

popbx; 恢复 bx

; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^

movdl, [BS_DrvNum]; 驱动器号 (0 表示 A 盘)

.GoOnReading:

movah, 2; 读

moval, byte [bp-2]; 读 al 个扇区

int13h

jc.GoOnReading; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

?

addesp, 2

popbp

?

ret

?

;----------------------------------------

; 函数名: GetFATEntry

;----------------------------------------

; 作用:

;找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中

;需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx

GetFATEntry:

pushes

pushbx

pushax

movax, BaseOfLoader; `.

subax, 0100h; ?| 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT

moves, ax; /

popax

movbyte [bOdd], 0

movbx, 3

mulbx; dx:ax = ax * 3

movbx, 2

divbx; dx:ax / 2 ?==> ?ax <- 商, dx <- 余数

cmpdx, 0

jzLABEL_EVEN

movbyte [bOdd], 1

LABEL_EVEN:;偶数

; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来

; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)

xordx, dx

movbx, [BPB_BytsPerSec]

divbx ; dx:ax / BPB_BytsPerSec

? ; ?ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号)

? ; ?dx <- 余数 (FATEntry 在扇区内的偏移)

pushdx

movbx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00

addax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号

movcl, 2

callReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界

? ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区

popdx

addbx, dx

movax, [es:bx]

cmpbyte [bOdd], 1

jnzLABEL_EVEN_2

shrax, 4

LABEL_EVEN_2:

andax, 0FFFh

?

LABEL_GET_FAT_ENRY_OK:

?

popbx

popes

ret

;----------------------------------------

?

times 510-($-$$)db0; 填充剩下的空间,使生成的二进制代码恰好为512字节

dw 0xaa55; 结束标志


?

?

?

二、运行方法

?

1. 编译boot.asm和loader.asm

[hadoop@sam1 c]$ pwd

/home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter4/c

[hadoop@sam1 c]$ ls

a.img ?bochsrc ?boot.asm ?loader.asm ?Makefile

[hadoop@sam1 c]$ nasm boot.asm -o boot.bin

[hadoop@sam1 c]$ nasm loader.asm -o loader.bin

?

2. 创建a.img软盘镜像,并将boot.asm写入软盘第一个扇区作为“引导扇区”:

[hadoop@sam1 bochs-2.6]$ bximage

...

Do you want to create a floppy disk image or a hard disk image?

Please type hd or fd. [hd] fd

...

Please type 0.16, 0.18, 0.32, 0.36, 0.72, 1.2, 1.44, 1.68, 1.72, or 2.88.

?[1.44]

...

What should I name the image?

[a.img]?

...

[hadoop@sam1 bochs-2.6]$ dd if=/home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter4/c/boot.bin of=/home/hadoop/Desktop/OSImpl/bochs-2.6/a.img bs=512 count=1 conv=notrunc

?

?

注:必须要设置从软盘a.img启动,因此修改$bochs_home/.bochsrc为如下:

megs:32

romimage:file=/usr/share/bochs/BIOS-bochs-latest

vgaromimage:file=/usr/share/vgabios/VGABIOS-lgpl-latest.bin


#floppya: 1_44=freedos.img, status=inserted

#floppyb: 1_44=pm.img,status=inserted

floppya: 1_44=a.img, status=inserted


boot: a


#disable the mouse


#enable key mapping, using US layout as default.

keyboard_mapping: enabled=1, map=gui/keymaps/x11-pc-us.map


mouse: enabled=0


log:bochsout.txt

?

3. 把loader.bin作为一个普通的文件写入软盘a.img(当然也可以是其他盘)

[hadoop@sam1 bochs-2.6]$ sudo mount -o loop /home/hadoop/Desktop/OSImpl/bochs-2.6/a.img /mnt/floppy/

[sudo] password for hadoop:?

[hadoop@sam1 bochs-2.6]$ sudo cp /home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter4/c/loader.bin /mnt/floppy -v

`/home/hadoop/Desktop/OSImpl/一个操作系统的实现/chapter4/c/loader.bin' -> `/mnt/floppy/loader.bin'

[hadoop@sam1 bochs-2.6]$ sudo umount /mnt/floppy/

?

注:mount -o loop ——?

?? ? ? ?-o: option

?? ? ? ?loop: loop device. loop device is a pseudo('fake') device (actually just a file) that acts as a block-based device.

?? ? ? ?(1)loop device?区别于loopback device.?Sometimes, the loop device if erroneously referred to as 'loopback' device, but this term is reserved for a networking device in the Linux kernel (cf. loopback). The concept of the 'loop' device is distinct from 'loopback', although similar in name.

?? ? ? ?(2)为什么命名为loop呢:在使用之前,一个loop设备必须要和一个文件进行连接,这种结合方式给用户提供了一个代替块特殊文件的接口。如果一个文件包含一个完整的文件系统,那么这个文件就可以像一个磁盘设备一样被mount起来。

?? ? ? ? ? ? 对于第一层文件系统,它直接安装(环绕)在我们的计算机物理设备上;而对于这种被mount起来的镜像文件(它也包含有文件系统),则建立在第一层文件系统上,就好像是在第一层文件系统上再环绕一层文件系统似的,所以称为loop.

?

?

4. 开始模拟

[hadoop@sam1 bochs-2.6]$ ./bochs?


结果截图:

(第4章 二)突破512字节的限制

?

?

?

?

?

?

?

?

?

读书人网 >操作系统

热点推荐