WriteOS: 操作系统从软盘引导分区(Boot Sector)到加载Loader过程原理与实践总结

转载:http://sleepycat.org/tech/os/loader


目录:

  • 本文简介
  • 1 软盘磁头号, 磁道号, 起始扇区计算方法
  • 2 引导扇区到加载 Loader 程序到内存的跳转过程总结
  • 3 实践过程
  • 4 代码及注释
  • 5 参考资料

本文简介

  • 概要:WriteOS: 操作系统从软盘引导分区(Boot Sector)到加载Loader过程原理与实践总结, 代码及细节来自《自己动手写操作系统》一书。
  • 版本:Linux Mint 12, Bochs x86 Emulator 2.5.1, NASM version 2.09.08
  • 日期:2012-08-25
  • 永久链接:http://sleepycat.org/tech/os/loader

1 软盘磁头号, 磁道号, 起始扇区计算方法

书中提到的公式:
    ; 设扇区号为 x
    ;                              ┌ 磁道号 = y >> 1
    ;       x              ┌ 商 y ┤
    ; --------------    => ┤      └ 磁面号 = y & 1
    ;  每磁道扇区数        │
    ;                      └ 余 z => 起始扇区号 = z + 1
 
原理分析:

1.44M 软盘的存储结构:
	扇区地址: 00 ==>> 磁面: 0  磁道: 0 扇区: 1
	扇区地址: 01 ==>> 磁面: 0  磁道: 0 扇区: 2
	扇区地址: 02 ==>> 磁面: 0  磁道: 0 扇区: 3
	.......
	扇区地址: 17 ==>> 磁面: 0  磁道: 0 扇区: 18
	
	扇区地址: 18 ==>> 磁面: 1  磁道: 0 扇区: 1
	扇区地址: 19 ==>> 磁面: 1  磁道: 0 扇区: 2
	扇区地址: 20 ==>> 磁面: 1  磁道: 0 扇区: 3
	.......
	扇区地址: 35 ==>> 磁面: 1  磁道: 0 扇区: 18
	
	扇区地址: 36 ==>> 磁面: 0  磁道: 1 扇区: 1
	扇区地址: 37 ==>> 磁面: 0  磁道: 1 扇区: 2
	扇区地址: 38 ==>> 磁面: 0  磁道: 1 扇区: 3
	......
	扇区地址: 53 ==>> 磁面: 0  磁道: 1 扇区: 18


归纳总结公式如下:
    扇区地址 = (磁面号) * 18 + (磁道号) * 18 * 2 + (相对起始扇区号 - 1)


推算:
    从上面公式得出:
    
    (扇区地址/18) 的商(y) = 磁面号 + 磁道号 * 2
    (1). 磁面号只为 0 或 1, 而 (磁道号 * 2) 只可能是偶数. 故, 若 y 为偶数, 则 磁面号 = 0. 反之亦然.
        故只需 y & 1, 计算最后一位是 0 还是 1 即可.
    (2). 另, 显然 y >> 1, 结果为磁道号的值.
    
    (扇区地址/18) 的余数(z) = 相对起始扇区号 - 1
    故: 相对起始扇区号 = 余数(z) + 1 

2 引导扇区到加载 Loader 程序到内存的跳转过程总结

    (1) 开机, BIOS 找到 Boot Sector 并将其程序装载到 0000:7c00 处. 
    (2) 运行 Boot Sector 程序. 从软盘的根目录区按顺序读取其第一个扇区的第一个目录.
    (3) 对比此目录中 DIR_Name 内容是否为 LOADER BIN, 如果不是继续循环读下一个目录, 下一个扇区.  
    (4) 如果找到 LOADER BIN 的目录, 则读出其 DIR_FstClus 内容.
    (5) 根据其 DIR_FstClus 内容, 在 FAT1 中获取其第一个簇及其他关联的簇的地址.
    (6) 根据 FAT1 中的目录的 簇的地址, 获取 loader.bin 文件内容地址. 并跳转. Boot Sector 交出控制权给 loader.bin.

3 实践过程

(1) 通过 bximage 命令生成一个软盘镜像.
$ bximage 
========================================================================
                                bximage
                  Disk Image Creation Tool for Bochs
          $Id: bximage.c 10933 2012-01-04 19:34:08Z vruppert $
========================================================================

Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] fd

Choose the size of floppy disk image to create, in megabytes.
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] 
I will create a floppy image with
  cyl=80
  heads=2
  sectors per track=18
  total sectors=2880
  total bytes=1474560

What should I name the image?
[a.img] 

Writing: [] Done.

I wrote 1474560 bytes to a.img.

The following line should appear in your bochsrc:
  floppya: image="a.img", status=inserted

(2) 将引导扇区写入磁盘镜像.
$ nasm boot.asm -o boot.bin
$ dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

(3) 加载此磁盘, 然后将 loader.bin 文件复制到磁盘中.
$ nasm loader.asm -o loder.bin
$ sudo mount -o loop a.img /mnt/floppy/
$ sudo cp loader.bin /mnt/floppy/ -v
$ sudo umount /mnt/floppy/

(4) 通过 bochs 启动此磁盘镜像. 查看或调试结果.
$ bochs -f bochsrc


4 代码及注释

对原代码做了些微调, 并添加了部分自己理解的注释

只列了 boot.asm 代码. loader.bin 代码只是显示一个字符,故略过

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
; file boot.asm
; nasm boot.asm -o boot.bin
; dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
 
org  07c00h            ; Bios 将把本 Boot Sector 加载到 0:7C00 处并开始执行
 
;================================================================================================
BaseOfStack              equ    07c00h  ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
 
BaseOfLoader             equ    09000h  ; LOADER.BIN 被加载到的位置 ---- 段地址
OffsetOfLoader           equ    0100h   ; LOADER.BIN 被加载到的位置 ---- 偏移地址
 
RootDirSectors           equ    14      ; 根目录占用空间
SectorNoOfRootDirectory  equ    19      ; Root Directory 的第一个扇区号
SectorNoOfFAT1           equ    1       ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt (Boot 记录占用的扇区数. 此处为 1)
DeltaSectorNo            equ    17      ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
                                         ;               = 1 + (2 * 9) - 2
                                         ;               = 17
                                         ; 即: FAT1 + FAT2 的总扇区数.
 
                                         ; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
;================================================================================================
     
     ;BS_jmpBoot
     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  " (调用函数 DispStr , 显示其中序号为 0 的字符串)
     call   DispStr          ; 显示字符串
     
     xor    ah, ah           ; ┓
     xor    dl, dl           ; ┣ 软驱复位
     int    13h              ; ┛
     
     ; 下面开始在 A 盘的根目录寻找 LOADER.BIN
     ; 注: SectorNoOfRootDirectory = 19, 即根目录区的起始扇区号.
     mov    word [wSectorNo], SectorNoOfRootDirectory
     
     
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
     
     ; 注: wRootDirSizeForLoop 初始值为 RootDirSectors, 即根目录所有扇区数(=14).
     ;     在搜索过程中循环递减.
     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                          ; set df=0
     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:
     ; 如果不同, 则进入下一个目录比较(一个目录长度为 32 byte, 20h byte)
     and    di, 0FFE0h                        ;   ┓ 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             ; 显示字符串
     
     jmp    $                   ; 没有找到 LOADER.BIN, 死循环在这里
     
     
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                   ; 结束标志

5 参考资料

http://blog.csdn.net/jj9876jj/article/details/5252329

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