zoukankan      html  css  js  c++  java
  • 《Orange‘s》Loader

    Loader

    作用

    引导扇区只有512个字节,能做的事情很少,局限性太大。所以需要一个程序,通过引导扇区加载入内存,然后将控制权交给它,这样就突破了512字节的限制。这个程序便是loader。

    加载过程

    首先我们需要把loader程序复制到软盘上,并让引导扇区找到并加载它。
    为了简单起见,就把loader放在根目录中。根目录区目录项格式:

    要加载一个文件到内存中,需要读取软盘,这时候就需要BIOS中断INT 13h。用法如下表:

    中断需要的参数不是从第0扇区开始的扇区号,而是柱面号,磁头号和当前柱面的扇区号。对于1.44MB的软盘,有2个面(磁头号0和1),每面80个磁道(磁道0~79),每个磁道有18个扇区(扇区号1~18)。所以软盘容量=2×80×18×512=1.44MB

    所以磁头号、柱面(磁道)号和起始扇区号可以用如图所示方法来计算:

    接下来分析一下代码:

    首先实现将给定扇区号转化为13号中断所需参数并从软盘读取数据的函数:
    扇区号放在ax中,读取扇区数目放在cl中,数据被读取到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 ;要读cl个扇区
    	push	bx			; 保存 bx
    	mov	bl, [BPB_SecPerTrk]	; bl: 除数  每个磁道的扇区数目
    	div	bl			; 商 在 al 中, 余数 在 ah 中
    	inc	ah			; 余数 ++
    	mov	cl, ah			; cl <- 起始扇区号
    	mov	dh, al			; dh <- 商Q
    	shr	al, 1			; y >> 1 (y/BPB_NumHeads)
    	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 个扇区,读到es:bx对应的缓冲区中
    	int	13h
    	jc	.GoOnReading		; 如果读取错误 CF 会被置为 1, 
    					; 这时就不停地读, 直到正确为止
    	;到了此处,代表读取正确
    	add	esp, 2
    	pop	bp
    
    	ret
    

    接下来就要在软盘根目录区寻找Loader.bin文件。

    LABEL_START:
    	mov	ax, cs
    	mov	ds, ax
    	mov	es, ax
    	mov	ss, ax
    	mov	sp, BaseOfStack
    
    	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		;如果读完表示没有找到 LOADER.BIN
    	dec	word [wRootDirSizeForLoop]	;循环次数,初始为根目录所占的扇区数目
    	mov	ax, BaseOfLoader
    	mov	es, ax			; es <- BaseOfLoader
    	mov	bx, OffsetOfLoader	; bx <- OffsetOfLoader
    	mov	ax, [wSectorNo]		; ax <- Root Directory 中的某 Sector 号
    	mov	cl, 1 ;读取一个扇区
    	call	ReadSector
    	;此时es:bx开始的缓冲区内容为读取扇区的内容
    	mov	si, LoaderFileName	; ds:si -> "LOADER  BIN"
    	mov	di, OffsetOfLoader	; es:di -> BaseOfLoader:0100
    	cld ;决定块传送方向
    	mov	dx, 10h ;一个扇区512B,一个文件条目32B,所以一个扇区中有16个条目
    LABEL_SEARCH_FOR_LOADERBIN:
    	cmp	dx, 0			
    	jz	LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完了一个 Sector,
    	dec	dx				   ; 就跳到下一个 Sector
    	mov	cx, 11 ;LOADER__BIN 的长度
    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		; 发现不同字符,不是要找的Loader.bin条目
    
    LABEL_GO_ON: ;开始比较下一个字符
    	inc	di
    	jmp	LABEL_CMP_FILENAME	; 继续循环
    
    LABEL_DIFFERENT: ;当前条目不是Loader.bin
    	and	di, 0FFE0h		; di &= E0 为了让它指向本条目开头
    	add	di, 20h			;  di += 20h  下一个目录条目
    	mov	si, LoaderFileName	
    	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 后便来到这里继续
    	jmp	$			; 代码暂时停在这里
    

    上述程序段共有2个循环,外层循环遍历根目录区的每个扇区,内层循环遍历扇区内的每个条目。通过逐字符比较文件名,寻找Loader.bin。如果找到,此时的di就为Loader.bin文件名最后一个字符的偏移,和0xffe0相与之后便可以得到Loader.bin的条目的地址es:di,进而可以得到Loader.bin的开始簇号。

    找到文件后,就开始将Loader.bin载入内存。现在我们已经获得了Loader.bin的开始簇号,可以计算得到Loader.bin的起始扇区号。同时可以通过开始簇号找到FAT表中对应的项目以获得下一个簇号,从而找到Loader占用的其余所有扇区。

    为了方便起见,先实现一个通过簇号来在FAT表中获取下一个簇号的函数。
    ax中存放簇号,返回值为下一个簇号也放在ax中。

    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 <- 余数
    					; 这里相当于簇号*1.5,因为一个fat项占12位即1.5字节
    	cmp	dx, 0
    	jz	LABEL_EVEN  ;簇号为偶数,偏移刚好是整字节
    	mov	byte [bOdd], 1 ;簇号为奇数,标志位置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] ;得到在扇区内的偏移,为16位
    	cmp	byte [bOdd], 1
    	jnz	LABEL_EVEN_2 ; 偶数
    	shr	ax, 4 ; 奇数,低4位为前一个FAT项的高4位,所以右移4位
    LABEL_EVEN_2:
    	and	ax, 0FFFh ; 只保留低12位
    
    LABEL_GET_FAT_ENRY_OK:
    
    	pop	bx
    	pop	es
    	ret
    

    要注意的是,如果簇号为奇数,取到的16位二进制数中低4位属于前一个FAT项,所以要右移去掉。

    现在可以加载Loader到内存了:

    LABEL_FILENAME_FOUND:			; 找到 LOADER.BIN 后便来到这里继续
    	mov	ax, RootDirSectors  ;根目录占用Sector数目
    	and	di, 0FFE0h		; di -> 当前条目的开始
    	add	di, 01Ah		; di -> 首 Sector DIR_FstClus 偏移为1ah,此条目对应的开始簇号
    	mov	cx, word [es:di]  ;cx为开始的簇号
    	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 ; 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:  ;loader已经被加载到内存中
    
    	mov	dh, 1			; "Ready."
    	call	DispStr			; 显示字符串
    
    ; *****************************************************************************************************
    	jmp	BaseOfLoader:OffsetOfLoader	; 这一句正式跳转到已加载到内
    						; 存中的 LOADER.BIN 的开始处,
    						; 开始执行 LOADER.BIN 的代码。
    						; Boot Sector 的使命到此结束
    ; *****************************************************************************************************
    

    终于看到这里了,不容易啊。。。

  • 相关阅读:
    ssh事务配置
    使用注解实现事务处理
    c3p0、dbcp<转>
    添加业务层和事务机制
    使用spring集成hibernate
    使用Adivisor配置增强处理
    aop注解
    Spring IoC实现解耦合
    python console的命令执行
    python格式化输出
  • 原文地址:https://www.cnblogs.com/cknightx/p/7887206.html
Copyright © 2011-2022 走看看