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

(第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

org	0100h

	mov	ax, 0B800h
	mov	gs, ax
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	mov	al, '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_

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

%else

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

%endif

 

BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ----  段地址

OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址

 

RootDirSectors equ 14 ; 根目录占用空间

SectorNoOfRootDirectory equ 19 ; Root Directory 的第一个扇区号

SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt

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

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

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

 

jmp short LABEL_START ; Start to boot.

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

 

; 下面是 FAT12 磁盘的头

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

BPB_BytsPerSec DW 512 ; 每扇区字节数

BPB_SecPerClus DB 1 ; 每簇多少扇区

BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区

BPB_NumFATs DB 2 ; 共有多少 FAT 表

BPB_RootEntCnt DW 224 ; 根目录文件数最大值

BPB_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_VolLab DB 'OrangeS0.02'; 卷标, 必须 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 ; "Booting  "

call DispStr ; 显示字符串

 

        ;  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.

xor ah, ah ; ┓

xor dl, dl ; ┣ 软驱复位

int 13h ; ┛

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

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

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:

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

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

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

 

 

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

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

 

mov ax, BaseOfLoader

mov es, ax ; es <- BaseOfLoader

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

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

mov cl, 1

call ReadSector


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

mov si, LoaderFileName ; ds:si -> "LOADER  BIN"

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

cld

mov dx, 10h

LABEL_SEARCH_FOR_LOADERBIN:

cmp dx, 0 ; ┓循环次数控制,

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

dec dx ; ┛就跳到下一个 Sector

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 不是

; 我们要找的 LOADER.BIN

LABEL_GO_ON:

inc di

jmp LABEL_CMP_FILENAME ; 继续循环

 

LABEL_DIFFERENT:

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

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

add di, 20h ;     ┃

mov si, LoaderFileName ;     ┣ di += 20h  下一个目录条目

jmp LABEL_SEARCH_FOR_LOADERBIN;    ┛

 

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:

add word [wSectorNo], 1

jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

 

LABEL_NO_LOADERBIN:

mov dh, 2 ; "No LOADER."

call DispStr ; 显示字符串

 

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

%ifdef _BOOT_DEBUG_

mov ax, 4c00h ; ┓

int 21h ; ┛没有找到 LOADER.BIN, 回到 DOS

%else

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

%endif

 

 

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

mov ax, RootDirSectors

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

add di, 01Ah ; di -> 首 Sector

mov cx, word [es:di]

push cx ; 保存此 Sector 在 FAT 中的序号

add cx, ax

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

mov ax, BaseOfLoader

mov es, ax ; es <- BaseOfLoader

mov bx, OffsetOfLoader ; bx <- OffsetOfLoader

mov ax, cx ; ax <- Sector 号


LABEL_GOON_LOADING_FILE:

push ax ; `.

push bx ;  |

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

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

mov bl, 0Fh ;  | Booting ......

int 10h ;  |

pop bx ;  |

pop ax ; /


mov cl, 1

call ReadSector

 

pop ax ; 取出此 Sector 在 FAT 中的序号

call GetFATEntry

cmp ax, 0FFFh

 

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

jz LABEL_FILE_LOADED

push ax ; 保存 Sector 在 FAT 中的序号

mov dx, RootDirSectors

add ax, dx

add ax, DeltaSectorNo

add bx, [BPB_BytsPerSec]

jmp LABEL_GOON_LOADING_FILE

LABEL_FILE_LOADED:


mov dh, 1 ; "Ready."

call DispStr ; 显示字符串

 

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

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

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

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

; Boot Sector 的使命到此结束

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

 

 

 

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

;变量

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

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

wSectorNo dw 0 ; 要读取的扇区号

bOdd db 0 ; 奇数还是偶数

 

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

;字符串

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

LoaderFileName db "LOADER  BIN", 0 ; LOADER.BIN 之文件名

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

MessageLength equ 9

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

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

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

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

 

 

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

; 函数名: DispStr

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

; 作用:

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

DispStr:

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, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h)

mov dl, 0

int 10h ; int 10h

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 的 Sector 在 FAT 中的条目, 结果放在 ax 中

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

GetFATEntry:

push es

push bx

push ax

mov ax, BaseOfLoader; `.

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

mov es, ax ; /

pop ax

mov byte [bOdd], 0

mov bx, 3

mul bx ; dx:ax = ax * 3

mov bx, 2

div bx ; dx:ax / 2  ==>  ax <- 商, dx <- 余数

cmp dx, 0

jz LABEL_EVEN

mov byte [bOdd], 1

LABEL_EVEN:;偶数

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

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

xor dx, dx

mov bx, [BPB_BytsPerSec]

div bx ; dx:ax / BPB_BytsPerSec

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

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

push dx

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

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

mov cl, 2

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

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

pop dx

add bx, dx

mov ax, [es:bx]

cmp byte [bOdd], 1

jnz LABEL_EVEN_2

shr ax, 4

LABEL_EVEN_2:

and ax, 0FFFh

 

LABEL_GET_FAT_ENRY_OK:

 

pop bx

pop es

ret

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

 

times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为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字节的限制