(第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?
结果截图:
?
?
?
?
?
??
?
?