于渊-动手写操作系统1

总感觉小日本的书有点敷衍的感觉,很多重要的知识点的没有讲,后面发现国内也有一本不错的操作系统书,于渊写的,还不错,理论知识讲解的也很周到。所以下面打算先看于渊的书先。好了,先贴代码,加注释分析

;%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
%include	"load.inc"

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

	jmp short LABEL_START		; Start to boot.
	nop				; 这个 nop 不可少

; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息
%include	"fat12hdr.inc"

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		;ah清0操作
	xor	dl, dl		;dl清0操作
	int	13h		;进行软驱复位
	
;; ------------------------------------------------------------------------
;; 功能介绍
;; 在A盘的根目录寻找LOADER.BIN
;; 根目录的结构信息如下
;; 名称		偏移		长度		描述
;; DIR_NAME	0		0XB		文件名8字节,扩展名3字节
;; DIR_Attr	0xB		1		文件属性
;; 保留位	0xC		10		保留位
;; DIR_WrtTime	0x16		2		最后一次写入时间
;; DIR_WrtDate	0x16		2		最后一次写入日期
;; DIR_FstClus	0x1A		2		此条目对应的开始簇号
;; DIR_FileSize	0x1C		4		文件大小
;; 这里最重要的信息就是DIR_FstClus,即文件开始簇号,它告诉我们文件存放在磁盘的什么位置,从而让我们可以找到它。
;; -------------------------------------------------------------------------
	mov	word [wSectorNo], SectorNoOfRootDirectory ;SectorNoOfRootDirectory=19,将此值存入到wSectorNo内存处

;; 开始进行遍历搜索了
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
	cmp	word [wRootDirSizeForLoop], 0 ;根目录占用空间,占用了14个扇区
	jz	LABEL_NO_LOADERBIN	      ;判断根目录区是不是已经读完
	dec	word [wRootDirSizeForLoop]    ;如果读完表示没找到LOADER.BIN
	mov	ax, BaseOfLoader	      ;09000h LOADER.BIN被加载到的位置--段地址
	mov	es, ax			      ;es <- BaseOfLoader
	mov	bx, OffsetOfLoader	      ;bx <- OffsetOfLoader 于是,es:bx=BaseOfLoader:OffsetOfLoader,0100h是LOADER.BIN被加载到的偏移地址
	mov	ax, [wSectorNo]		      ;ax <- Root Directory中的某Sector号
	mov	cl, 1			      ;读取一个磁盘
	call	ReadSector		      ;读磁盘函数

	mov	si, LoaderFileName ;di:si -> "LOADER BIN"
	mov	di, OffsetOfLoader ;es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
	cld			   ;标志位df=0,为后面的loasb服务
	mov	dx, 10h		   ;每隔根目录占了32个字节,所以这里每个Sector需要循环16次
LABEL_SEARCH_FOR_LOADERBIN:
	cmp	dx, 0		;循环次数控制
	jz	LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完一个Sector
	dec	dx				   ;就跳到下一个Sector
	mov	cx, 11				   ;根目录条目的前11个字节放着文件名
LABEL_CMP_FILENAME:
	cmp	cx, 0
	jz	LABEL_FILENAME_FOUND ;如果比较了11个字符都相等,表示找到了
	dec	cx
	lodsb
	cmp	al, byte [es:di] ;取出每个字节进行比较
	jz	LABEL_GO_ON
	jmp	LABEL_DIFFERENT	;只要发现一个不一样的字符就表示本DirectoryEntry不是
	
;;我们要找的LOADER.BIN
LABEL_GO_ON:
	inc	di		;di++,让它指向下一个字符
	jmp	LABEL_CMP_FILENAME ;递归循环

;; 发现不符合情况下面的处理
LABEL_DIFFERENT:
	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 ;根目录扇区+1
	jmp	LABEL_SEARCH_IN_ROOT_DIR_BEGIN ;继续读后续的扇区
	
;; 没有找到LAODER.BIN
LABEL_NO_LOADERBIN:
	;xchg    bx,bx
	mov	dh, 2		;"NO LOADER."
	call	DispStr		;显示字符串
	
%ifdef _BOOT_DEBUG_
	mov	ax, 4c00h	;没有找到LOADER.BIN,回到DOS
	int	21h
%else
	jmp	$
%endif

LABEL_FILENAME_FOUND:
	jmp	$
	
;; =========================================================================
;; 变量
;; =========================================================================
wRootDirSizeForLoop	dw	RootDirSectors ;Root Directory占用的扇区数,在循环中会递减为零
wSectorNo		dw	0	       ;要读取的扇区号
;; =========================================================================

;字符串
;----------------------------------------------------------------------------
LoaderFileName		db	"LOADER  BIN", 0	; LOADER.BIN 之文件名
MessageLength		equ	9
BootMessage:		db	"Booting  "; 9字节, 不够则用空格补齐. 序号 0
Message1		db	"Ready.   "; 9字节, 不够则用空格补齐. 序号 1
Message2		db	"No LOADER"; 9字节, 不够则用空格补齐. 序号 2
;============================================================================


;----------------------------------------------------------------------------
; 函数名: DispStr
;----------------------------------------------------------------------------
; 作用:
;	显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
        ;AH=13H
        ;BH=页码
        ;BL=属性(若AL=00H或01H)
        ;CX=显示字符串长度
        ;(DH、DL)=坐标(行、列)
        ;ES:BP=显示字符串的地址 AL= 显示输出方式 
        ;0—字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变 
        ;1—字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变 
        ;2—字符串中含显示字符和显示属性。显示后,光标位置不变 
        ;3—字符串中含显示字符和显示属性。显示后,光标位置改变 
DispStr:
	mov	ax, MessageLength       ;ax当前存储着字符串长度
	mul	dh                      ;dh显示字符串的序号(0*ax=0)
	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中
;; 	bp串首地址
ReadSector:
	; -----------------------------------------------------------------------
	; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
	; -----------------------------------------------------------------------
	; 设扇区号为 x
	;                           ┌ 柱面号 = y >> 1
	;       x           ┌ 商 y ┤
	; -------------- => ┤      └ 磁头号 = y & 1
	;  每磁道扇区数     │
	;                   └ 余 z => 起始扇区号 = z + 1
	push	bp
	mov	bp, sp		;sp堆栈地址
	sub	esp, 2		;开辟出两个字节的堆栈区域保存要读的扇区数:byte [bp-2]

	mov	byte [bp-2], cl	;cl要读的Sector大小
	push	bx		;保存bx OffsetOfLoader=0100h
	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

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

times 	510-($-$$)	db	0	; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 	0xaa55				; 结束标志

 上面的代码是在实模式下面进行的,要看懂代码,需要掌握两个知识点:

第一:FAT12结构,需要按照FAT12的结构来进行操作

第二:要懂的如何加载磁盘内容到内存中

上面的代码大致做了一下的工作:

首先从软盘的19扇区,也就是根目录区开始,把扇区的内容复制到09000h:0100h处,每当复制一个扇区,那么就对这个扇区的根目录区进行检查,因为根目录区是由每一条为32个字节的条目组成,一个扇区有512个字节,那么我们要连续检查512/32=16次,因为这些条目中有一个字段是用来文件名的,这个文件名由11个字节组成,那么我们只要拿目标文件名称跟这11个字节相比,如果相同,那么就表示该根目录扇区中含有我们的目标文件,如果找到了就跳到LABEL_FILENAME_FOUND,简单进行jmp$操作,如果没有找到,那么继续遍历下一个条目,知道16个条目遍历完了,如果没有找到,那么继续加载下一个扇区的内容到09000h:0100h处,重复上面的检测,一直到14个扇区(因为我们在FAT32结构中定义了根目录扇区为14个),那么就结束。

具体分析见代码。

运行效果:



 因为我们现在没有没有Loader.bin。这个bochs真不好用,一开起来我的linux就卡死住了。要不是为了调试我还真不想用你

下面给出调试的方法:

调试方式
1,首先要创建一个新的bm.img镜像文件,我们可以用bximage来进行创建
2,写入空白内容 dd if=/dev/null of=pm.img bs=512 count=1 conv=notrunc
3,使用 losetup 命令,将 pm.img.img 作为 loop device 使用:  sudo losetup /dev/loop0 pm.img
4,然后,格式化这个 loop device:  sudo mkfs.msdos /dev/loop0
5,检查文件系统 sudo fsck.msdos /dev/loop0
6,删除 loop device: sudo losetup -d /dev/loop0
7, sudo mount -o loop pm.img /mnt/floppy
8,sudo cp pmtest2.com /mnt/floppy/
9, sudo umount /mnt/floppy
10, bochsrc 末尾添加
#debug
magic_break: enabled=1
11,代码处在想调试的地方添加 xchg    bx,bx

 

猜你喜欢

转载自jjchen-lian201205235512.iteye.com/blog/1924179