码迷,mamicode.com
首页 > 其他好文 > 详细

一个操作系统的实现(11)-让操作系统进入保护模式

时间:2016-06-16 14:42:22      阅读:311      评论:0      收藏:0      [点我收藏+]

标签:

这节首先介绍了突破引导扇区只有512字节的原理,然后介绍了FAT12文件系统,最后通过实验加载loader并将控制权交给loader来实现突破512字节的束缚。

突破512字节的限制

前面所用的引导扇区只有512字节。然而实际上操作系统在启动过程需要做的事情是很多的。所以需要通过某种方法突破512字节的限制。

那么如何突破512字节的限制呢?一种方法是再建立一个文件,通过引导扇区把它加载到内存,然后把控制权教给它。这样,512字节的束缚就没有了。

这里被引导扇区加载进内存的并不是操作系统的内核。因为从开机到开始运行,操作系统经历了“引导→加载内核入内存→跳入保护模式→开始执行内核”这样一个过程。也就是说,才内核开始执行之前不但要加载内核,而且还有准备保护模式等一系列工作,如果全都交给引导扇区来做,512字节很可能是不够用的。因此,这里加载进内存的并不是内核,而是另外一个模块叫Loader。引导扇区把Loader加载进内存并把控制权交给它。上面所说的其他工作都交给Loader来做。Loader没有512字节的限制。所以会灵活很多。

接下来最主要的是如何找到Loader文件并加载进入内存。首先介绍FAT12文件系统

FAT12

FAT的全称是File Allocation Table。它是DOS时代就开始使用的文件系统(File System),现在的软盘上面仍旧使用此文件系统。FAT把磁盘划分成若干层次以方便组织和管理,这些层次如下:

  • 扇区(Sector):磁盘上的最小数据单元。
  • 簇(Cluster):一个或多个扇区。
  • 分区(Partition):通常指整个文件系统。

下面是FAT12格式的软盘的结构:

技术分享

引导扇区

首先是引导扇区,它位于第0个扇区。它的结构如下图

技术分享

引导扇区有一个很重要的数据结构叫做BPB(BIOS ParameterBlock),它以BPB_开头。以BS_开头的域不属于BPB,只是引导扇区(Boot Sector)的一部分。

FAT

可以看到有两个FAT表,FAT2可看作是FAT1的备份,他们通常是一样的。FAT有点像是一个位图。每12位称为一个FAT项(FATEntry),代表一个簇。

通常FAT项的值代表的是文件下一个簇号。从这里可以计算出FAT12中数据区的最大簇号是2^12=4K,如果每簇512字节,那么最大数据量是4K×512B=2MB

当FAT表项的值大于或等于0xFF8时,表示当前簇已经是文件的最后一个簇。如果值为0xFF7,表示它是一个坏簇

其中第0个和第1个FAT项始终不使用,从第2个FAT项开始表示数据区的每一个簇。也就是说,第二个FAT项表示数据区的第一个簇,所以数据区的第一个簇号是2

根目录区

根目录区位于第二个FAT表之后,开始的扇区号是19,它由若干个目录条目(Directory Entry)组成,条目最多有BPB_RootEntCnt个。由于根目录区的大小是依赖于BPB_RootEntCnt的,所以长度不固定。

根目录区的每一个条目占用32字节,格式如下:

技术分享

根目录区主要定义了名称属性时间开始簇号大小

数据区

数据区的簇号从2开始。这是因为上面所说的FAT表项从第二个开始。因为根目录区长度不是固定的。所以需要计算数据区的第一个簇号的位置。

如何读取某一文件

首先是进入根目录区根据文件名和属性来寻找文件。找到文件目录项后根据目录向中的开始簇号读取文件第一簇的信息,接下来查看FAT表项,找到文件的下一簇号是啥?如果小于0xFF7,则数据没读取完,如果大于或等于0xFF8则说明文件读取结束

接下来,实现一个最简单的loader并实现加载过程。主要有如下几步:

制作一个DOS可以识别的引导盘

引导扇区需要有BPB等头信息才能被微软识别,我们首先加上它,代码大致如下:

 30         ; 下面是 FAT12 磁盘的头
 31         BS_OEMName      DB ‘ForrestY‘   ; OEM String, 必须 8 个字节
 32         BPB_BytsPerSec  DW 512          ; 每扇区字节数
 33         BPB_SecPerClus  DB 1            ; 每簇多少扇区
 34         BPB_RsvdSecCnt  DW 1            ; Boot 记录占用多少扇区
 35         BPB_NumFATs     DB 2            ; 共有多少 FAT 表
 36         BPB_RootEntCnt  DW 224          ; 根目录文件数最大值
 37         BPB_TotSec16    DW 2880         ; 逻辑扇区总数
 38         BPB_Media       DB 0xF0         ; 媒体描述符
 39         BPB_FATSz16     DW 9            ; 每FAT扇区数
 40         BPB_SecPerTrk   DW 18           ; 每磁道扇区数
 41         BPB_NumHeads    DW 2            ; 磁头数(面数)
 42         BPB_HiddSec     DD 0            ; 隐藏扇区数
 43         BPB_TotSec32    DD 0            ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
 44         BS_DrvNum       DB 0            ; 中断 13 的驱动器号
 45         BS_Reserved1    DB 0            ; 未使用
 46         BS_BootSig      DB 29h          ; 扩展引导标记 (29h)
 47         BS_VolID        DD 0            ; 卷序列号
 48         BS_VolLab       DB ‘OrangeS0.02‘; 卷标, 必须 11 个字节
 49         BS_FileSysType  DB ‘FAT12   ‘   ; 文件系统类型, 必须 8个字节 

现在的软盘已经能够被DOS和Linux识别了,我们已经可以方便地往上添加或删除文件了。

编写一个简单的loader程序

要将Loader加载到内存中,首先需要有一个Loader。所以接下来就是写一个最简单的loader,代码如下:

  2 org     0100h
  3 
  4         mov     ax, 0B800h
  5         mov     gs, ax
  6         mov     ah, 0Fh                         ; 0000: 黑底    1111: 白字
  7         mov     al, ‘L‘
  8         mov     [gs:((80 * 0 + 39) * 2)], ax    ; 屏幕第 0 行, 第 39 列。
  9 
 10         jmp     $               ; Start

将此代码大保存在loader.asm文件中。这段代码被编译成.COM文件直接在DOS下执行,效果是在屏幕中央输出字符L,然后进入死循环。在这里,我们用下面的命令行来编译:

$ nasm loader.asm -o loader.bin

这里面编译出的二进制代码加载到内存的任意位置都可以正确执行,但是我们要扩展它,为了将来的执行不会出现问题,要保证把它放入某个段内偏移0x100的位置。

加载loader进入内存

int 13h

加载软盘上的一个文件进入内存,使用的是BIOS中断int 13h。它的用法如下图:

技术分享

从上图可以看出,中断需要的参数不是从第0扇区开始的扇区号,而是柱面号、磁头号以及在当前柱面上的扇区号三个分量。所以要通过下图方法来转换:

软盘相对扇区号的转换

技术分享

转换的原理如下:

首先,1.44M的软盘结构:一个软盘包括2个盘面(0和1),每个盘面有80条磁道(磁柱),每个磁道有18个扇区,每个扇区大小位512Byte。所以总容量:2×80×18×512Byte=1474569Byte=1.44MB

然后,从第0扇区开始一次编号叫做相对扇区,它与物理位置的关系如下:

0面,0道,1扇区             0 
0面,0道,2扇区             1 
0面,0道,3扇区             2 
...
0面,0道,18扇区           17 
1面,0道,1扇区            18 
...
1面,0道,18扇区           35 
0面,1道,1扇区            36 
...
0面,1道,18扇区           53 
1面,1道,1扇区            54

读软盘扇区

因为loader可能包含多个扇区,所以接下来写一个读软盘扇区的函数:

215 ;----------------------------------------------------------------------------
216 ; 函数名: ReadSector
217 ;----------------------------------------------------------------------------
218 ; 作用:
219 ;       从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
220 ReadSector:
221         ; -----------------------------------------------------------------------
222         ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
223         ; -----------------------------------------------------------------------
224         ; 设扇区号为 x
225         ;                           ┌ 柱面号 = y >> 1
226         ;       x           ┌ 商 y ┤
227         ; -------------- => ┤      └ 磁头号 = y & 1
228         ;  每磁道扇区数     │
229         ;                   └ 余 z => 起始扇区号 = z + 1
230         push    bp
231         mov     bp, sp
232         sub     esp, 2                  ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]
233 
234         mov     byte [bp-2], cl
235         push    bx                      ; 保存 bx
236         mov     bl, [BPB_SecPerTrk]     ; bl: 除数
237         div     bl                      ; y 在 al 中, z 在 ah 中
238         inc     ah                      ; z ++
239         mov     cl, ah                  ; cl <- 起始扇区号
240         mov     dh, al                  ; dh <- y
241         shr     al, 1                   ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
242         mov     ch, al                  ; ch <- 柱面号
243         and     dh, 1                   ; dh & 1 = 磁头号
244         pop     bx                      ; 恢复 bx
245         ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
246         mov     dl, [BS_DrvNum]         ; 驱动器号 (0 表示 A 盘)
247 .GoOnReading:
248         mov     ah, 2                   ; 读
249         mov     al, byte [bp-2]         ; 读 al 个扇区
250         int     13h
251         jc      .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
252 
253         add     esp, 2
254         pop     bp
255 
256         ret

上面的代码用到了堆栈,所以程序开头要初始化ssesp

 14 BaseOfStack             equ     07c00h  ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
 52         mov     ax, cs
 53         mov     ds, ax
 54         mov     es, ax
 55         mov     ss, ax
 56         mov     sp, BaseOfStack

读扇区的函数写好了,接下来就开始在软盘中寻找Loader.bin

寻找loader

主要包括两个寻找:

  1. 在根目录区寻找Loader的第一个扇区
  2. 在FAT表中寻找Loader的其余扇区

根目录区寻找loader.bin

 72 ; 下面在 A 盘的根目录寻找 LOADER.BIN
 73         mov     word [wSectorNo], SectorNoOfRootDirectory
 74 LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
 75         cmp     word [wRootDirSizeForLoop], 0   ; ┓
 76         jz      LABEL_NO_LOADERBIN              ; ┣ 判断根目录区是不是已经读完
 77         dec     word [wRootDirSizeForLoop]      ; ┛ 如果读完表示没有找到 LOADER.BIN
 78         mov     ax, BaseOfLoader
 79         mov     es, ax            ; es<-BaseOfLoader
 80         mov     bx, OffsetOfLoader; bx<-OffsetOfLoader于是,es:bx = BaseOfLoader:OffsetOfLoader
 81         mov     ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号
 82         mov     cl, 1
 83         call    ReadSector
 84 
 85         mov     si, LoaderFileName      ; ds:si -> "LOADER  BIN"
 86         mov     di, OffsetOfLoader      ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
 87         cld
 88         mov     dx, 10h
 89 LABEL_SEARCH_FOR_LOADERBIN:
 90         cmp     dx, 0                                   ; ┓循环次数控制,
 91         jz      LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR      ; ┣如果已经读完了一个 Sector,
 92         dec     dx                                      ; ┛就跳到下一个 Sector
 93         mov     cx, 11
 94 LABEL_CMP_FILENAME:
 95         cmp     cx, 0
 96         jz      LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到
 97 dec     cx
 98         lodsb                           ; ds:si -> al
 99         cmp     al, byte [es:di]
100         jz      LABEL_GO_ON
101         jmp     LABEL_DIFFERENT         ; 只要发现不一样的字符就表明本 DirectoryEntry 不是
102 ; 我们要找的 LOADER.BIN
103 LABEL_GO_ON:
104         inc     di
105         jmp     LABEL_CMP_FILENAME      ;       继续循环
106 
107 LABEL_DIFFERENT:
108         and     di, 0FFE0h                ; else ┓        di &= E0 为了让它指向本条目开头
109         add     di, 20h                   ;     ┃
110         mov     si, LoaderFileName        ;     ┣ di += 20h  下一个目录条目
111         jmp     LABEL_SEARCH_FOR_LOADERBIN;    ┛
112 
113 LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
114         add     word [wSectorNo], 1
115         jmp     LABEL_SEARCH_IN_ROOT_DIR_BEGIN
116 
117 LABEL_NO_LOADERBIN:
118         mov     dh, 2                   ; "No LOADER."
119         call    DispStr                 ; 显示字符串
120 %ifdef  _BOOT_DEBUG_
121         mov     ax, 4c00h               ; ┓
122         int     21h                     ; ┛没有找到 LOADER.BIN, 回到 DOS
123 %else
124         jmp     $                       ; 没有找到 LOADER.BIN, 死循环在这里
125 %endif
126 
127 LABEL_FILENAME_FOUND:                   ; 找到 LOADER.BIN 后便来到这里继续
128         mov     ax, RootDirSectors
129         and     di, 0FFE0h              ; di -> 当前条目的开始
130         add     di, 01Ah                ; di -> 首 Sector
131         mov     cx, word [es:di]
132         push    cx                      ; 保存此 Sector 在 FAT 中的序号
133         add     cx, ax
134         add     cx, DeltaSectorNo       ; cl <- LOADER.BIN的起始扇区号(0-based)
135         mov     ax, BaseOfLoader
136         mov     es, ax                  ; es <- BaseOfLoader
137         mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader
138         mov     ax, cx                  ; ax <- Sector 号

上面的代码的逻辑过程是:遍历根目录区所有的扇区,将每一个扇区加载入内存,然后从中寻找文件名为loader.bin的条目,指导找到为止。找到的那一刻,es:di是指向条目中字母N后面的哪个字符。其中有一些宏定义如下:

 17 BaseOfLoader            equ     09000h  ; LOADER.BIN 被加载到的位置 ----  段地址
 18 OffsetOfLoader          equ     0100h   ; LOADER.BIN 被加载到的位置 ---- 偏移地址
 19 
 20 RootDirSectors          equ     14      ; 根目录占用空间
 21 SectorNoOfRootDirectory equ     19      ; Root Directory 的第一个扇区号

还有一些变量和字符串的值定义如下:

176 ;============================================================================
177 ;变量
178 ;----------------------------------------------------------------------------
179 wRootDirSizeForLoop     dw      RootDirSectors  ; Root Directory 占用的扇区数, 在循环中会递减至零.
180 wSectorNo               dw      0               ; 要读取的扇区号
181 bOdd                    db      0               ; 奇数还是偶数
182 
183 ;============================================================================
184 ;字符串
185 ;----------------------------------------------------------------------------
186 LoaderFileName          db      "LOADER  BIN", 0        ; LOADER.BIN 之文件名
187 ; 为简化代码, 下面每个字符串的长度均为 MessageLength
188 MessageLength           equ     9
189 BootMessage:            db      "Booting  "; 9字节, 不够则用空格补齐. 序号 0
190 Message1                db      "Ready.   "; 9字节, 不够则用空格补齐. 序号 1
191 Message2                db      "No LOADER"; 9字节, 不够则用空格补齐. 序号 2
192 ;============================================================================

读取过程中会打印一些字符,打印字符串的函数如下:

195 ;----------------------------------------------------------------------------
196 ; 函数名: DispStr
197 ;----------------------------------------------------------------------------
198 ; 作用:
199 ;       显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
200 DispStr:
201         mov     ax, MessageLength
202         mul     dh
203         add     ax, BootMessage
204         mov     bp, ax                  ; ┓
205         mov     ax, ds                  ; ┣ ES:BP = 串地址
206         mov     es, ax                  ; ┛
207         mov     cx, MessageLength       ; CX = 串长度
208         mov     ax, 01301h              ; AH = 13,  AL = 01h
209         mov     bx, 0007h               ; 页号为0(BH = 0) 黑底白字(BL = 07h)
210         mov     dl, 0
211         int     10h                     ; int 10h
212         ret

loader的第一个扇区找到了,接下来寻找loader的剩下扇区,在FAT表项中寻找下一个扇区号。

由扇区号寻找FAT项的值

 22 SectorNoOfFAT1          equ     1       ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt
...
258 ;----------------------------------------------------------------------------
259 ; 函数名: GetFATEntry
260 ;----------------------------------------------------------------------------
261 ; 作用:
262 ;       找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
263 ;       需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
264 GetFATEntry:
265         push    es
266         push    bx
267         push    ax
268         mov     ax, BaseOfLoader; `.
269         sub     ax, 0100h       ;  | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
270         mov     es, ax          ; /
271         pop     ax
272         mov     byte [bOdd], 0
273         mov     bx, 3
274         mul     bx                      ; dx:ax = ax * 3
275         mov     bx, 2
276         div     bx                      ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
277         cmp     dx, 0
278         jz      LABEL_EVEN
279         mov     byte [bOdd], 1
280 LABEL_EVEN:;偶数
281         ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来
282         ; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
283         xor     dx, dx
284         mov     bx, [BPB_BytsPerSec]
285         div     bx ; dx:ax / BPB_BytsPerSec
286                    ;  ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号)
287                    ;  dx <- 余数 (FATEntry 在扇区内的偏移)。
288         push    dx
289         mov     bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00
290         add     ax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的扇区号
291         mov     cl, 2
292         call    ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界
293                            ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区
294         pop     dx
295         add     bx, dx
296         mov     ax, [es:bx]
297         cmp     byte [bOdd], 1
298         jnz     LABEL_EVEN_2
299         shr     ax, 4
300 LABEL_EVEN_2:
301         and     ax, 0FFFh
302 
303 LABEL_GET_FAT_ENRY_OK:

上面寻找loader的工作已经做完了,接下来加载loader:

127 LABEL_FILENAME_FOUND:                   ; 找到 LOADER.BIN 后便来到这里继续
128         mov     ax, RootDirSectors
129         and     di, 0FFE0h              ; di -> 当前条目的开始
130         add     di, 01Ah                ; di -> 首 Sector
131         mov     cx, word [es:di]
132         push    cx                      ; 保存此 Sector 在 FAT 中的序号
133         add     cx, ax
134         add     cx, DeltaSectorNo       ; cl <- LOADER.BIN的起始扇区号(0-based)
135         mov     ax, BaseOfLoader
136         mov     es, ax                  ; es <- BaseOfLoader
137         mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader
138         mov     ax, cx                  ; ax <- Sector 号
139 
140 LABEL_GOON_LOADING_FILE:
141         push    ax                      ; `.
142         push    bx                      ;  |
143         mov     ah, 0Eh                 ;  | 每读一个扇区就在 "Booting  " 后面
144         mov     al, ‘.‘                 ;  | 打一个点, 形成这样的效果:
145         mov     bl, 0Fh                 ;  | Booting ......
146         int     10h                     ;  |
147         pop     bx                      ;  |
148         pop     ax                      ; /
149 
150         mov     cl, 1
151         call    ReadSector
152         pop     ax                      ; 取出此 Sector 在 FAT 中的序号
153         call    GetFATEntry
154         cmp     ax, 0FFFh
155         jz      LABEL_FILE_LOADED
156         push    ax                      ; 保存 Sector 在 FAT 中的序号
157         mov     dx, RootDirSectors
158         add     ax, dx
159         add     ax, DeltaSectorNo
160         add     bx, [BPB_BytsPerSec]
161         jmp     LABEL_GOON_LOADING_FILE
162 LABEL_FILE_LOADED:
163 
164         mov     dh, 1                   ; "Ready."
165         call    DispStr                 ; 显示字符串

向loader交出控制权

万事具备,只差最后一步,向loader交出控制权,可以理解为直接跳转到loader所在的代码执行:

167 ; ****************************************************************************
168         jmp     BaseOfLoader:OffsetOfLoader     ; 这一句正式跳转到已加载到内
169                                                 ; 存中的 LOADER.BIN 的开始处,
170                                                 ; 开始执行 LOADER.BIN 的代码。
171                                                 ; Boot Sector 的使命到此结束。
172 ; ****************************************************************************

接下来看成果

bochs调试与运行

$ nasm boot.asm -o boot.bin
$ nasm loader.asm -o loader.bin
$ dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
$ sudo mount -o loop a.img /mnt/floppy
$ sudo cp loader.bin /mnt/floppy/ -v
$ sudo umount /mnt/floppy

运行结果如下:

技术分享

源代码


;%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 不可少

    ; 下面是 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            ; 显示字符串

    xor    ah, ah    ; ┓
    xor    dl, dl    ; ┣ 软驱复位
    int    13h    ; ┛

; 下面在 A 盘的根目录寻找 LOADER.BIN
    mov    word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp    word [wRootDirSizeForLoop], 0    ; ┓
    jz    LABEL_NO_LOADERBIN        ; ┣ 判断根目录区是不是已经读完
    dec    word [wRootDirSizeForLoop]    ; ┛ 如果读完表示没有找到 LOADER.BIN
    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

    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:
    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            ; 显示字符串
%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
    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                ; 结束标志

一个操作系统的实现(11)-让操作系统进入保护模式

标签:

原文地址:http://blog.csdn.net/friendley/article/details/51680627

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!