`
jjchen_lian
  • 浏览: 87221 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

于渊-动手写操作系统3

 
阅读更多

我们之前已经通过boot.asm成功的把loader.bin加载内存0900h:0100处了,那么我们现在就要来写真正的loader.asm了。

loader.asm完成一下三件事:

第一件事与boot.asm一样,通过遍历根目录区条目找到我们后面自己写的操作系统内核,即我们的kernel

第二件事就是从实模式切换到保护模式

第三件事就是启动分页机制

第四件事就是把我们的kernel搬运到正确的运行内存地址中去

那么我们现在先来看第一件事:

下面的代码跟前面唯一一个不同的地方就是堆栈地址改变了,现在的堆栈地址是0900h:0100h处,并向地址减小的方向扩展。

代码如下:

org  0100h

	jmp	LABEL_START		; Start

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

BaseOfStack	equ	0100h
;实模式
LABEL_START:
        xchg    bx,bx			; <--- 从这里开始 *************
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	ss, ax
	mov	sp, BaseOfStack         ;BaseOfStack = 0100

	mov	dh, 0			; "Loading  "
	call	DispStrRealMode		; 显示字符串

        ;开始进行克勤克检用内存
        ;中断调用 int 15h
        ;int 15h可以完成许多工作,主要由ax的值决定,我们想要获取内存信息,需要将ax赋值为0E820h
        ;ebx 放置着"后续值",第一次调用时ebx必须为0
        ;ecx es:di所指向的地址范围描述符结构的大小,以字节为单位。无论es:di所指向的结构如何设置,BIOS最多将会填充ecx个字节。不过,通常情况下无论ecx为多大,BIOS只填充20个字节,有些BIOS忽略ecx的值,总是填充20个字节
        ;edx 0534D4150h('SAMP') BIOS将会使用此标志,对调用者将要请求的系统映像信息进行校验,这些信息会被BIOS放置到es:di所指向的结构中
        ;调用返回值
        ;CF CF=0表示没有错误,否则存在错误
        ;eax 0534D4150h('SAMP')
        ;es:di 返回的地址范围描述符结构指针,和输入值相同
        ;ecx BIOS填充在地址范围描述符中的字节数量,被BIOS所返回的最小值是20字节
        ;ebx 这里放置着为下一个地址描述符所需要的后续值,这个值的实际形势依赖于具体的BIOS的实现,调用者不用关心它的具体形式,只需在下次迭代时原封不动的放置到ebx中,就可以通过它获取下一个地址范围描述符。如果它的值为0,并且CF没有进位,表示它是最后一个地址范围描述符
	; 得到内存数
	mov	ebx, 0			; ebx = 后续值, 开始时需为 0
	mov	di, _MemChkBuf		; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure)
.MemChkLoop:
	mov	eax, 0E820h		; eax = 0000E820h
	mov	ecx, 20			; ecx = 地址范围描述符结构的大小
	mov	edx, 0534D4150h		; edx = 'SMAP'
	int	15h			; int 15h
	jc	.MemChkFail
	add	di, 20
	inc	dword [_dwMCRNumber]	; dwMCRNumber = ARDS 的个数
	cmp	ebx, 0
	jne	.MemChkLoop
	jmp	.MemChkOK               ;ebx=0,那么进行跳转
.MemChkFail:
	mov	dword [_dwMCRNumber], 0
.MemChkOK:

	; 下面在 A 盘的根目录寻找 KERNEL.BIN
	mov	word [wSectorNo], SectorNoOfRootDirectory	
	xor	ah, ah	; ┓
	xor	dl, dl	; ┣ 软驱复位
	int	13h	; ┛
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
	cmp	word [wRootDirSizeForLoop], 0	; ┓
	jz	LABEL_NO_KERNELBIN		; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN
	dec	word [wRootDirSizeForLoop]	; ┛
	mov	ax, BaseOfKernelFile    ;BaseOfKernelFile=08000h
	mov	es, ax			; es <- BaseOfKernelFile
	mov	bx, OffsetOfKernelFile	; bx <- OffsetOfKernelFile=0	于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
	mov	ax, [wSectorNo]		; ax <- Root Directory 中的某 Sector 号
	mov	cl, 1
	call	ReadSector

	mov	si, KernelFileName	; ds:si -> "KERNEL  BIN"
	mov	di, OffsetOfKernelFile	; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+????
	cld
	mov	dx, 10h
LABEL_SEARCH_FOR_KERNELBIN:
	cmp	dx, 0					; ┓
	jz	LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR	; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector
	dec	dx					; ┛
	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]	; if al == es:di
	jz	LABEL_GO_ON
	jmp	LABEL_DIFFERENT
LABEL_GO_ON:
	inc	di
	jmp	LABEL_CMP_FILENAME	;	继续循环

LABEL_DIFFERENT:
	and	di, 0FFE0h		; else┓	这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数
	add	di, 20h			;     ┃
	mov	si, KernelFileName	;     ┣ di += 20h  下一个目录条目
	jmp	LABEL_SEARCH_FOR_KERNELBIN;   ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
	add	word [wSectorNo], 1
	jmp	LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_KERNELBIN:
	mov	dh, 2			; "No KERNEL."
	call	DispStrRealMode		; 显示字符串
	jmp	$			; 没有找到 KERNEL.BIN, 死循环在这里

LABEL_FILENAME_FOUND:			; 找到 KERNEL.BIN 后便来到这里继续
	mov	ax, RootDirSectors
	and	di, 0FFF0h		; di -> 当前条目的开始

	push	eax
	mov	eax, [es : di + 01Ch]		; ┓
	mov	dword [dwKernelSize], eax	; ┛保存 KERNEL.BIN 文件大小
	pop	eax

	add	di, 01Ah		; di -> 首 Sector
	mov	cx, word [es:di]
	push	cx			; 保存此 Sector 在 FAT 中的序号
	add	cx, ax
	add	cx, DeltaSectorNo	; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
	mov	ax, BaseOfKernelFile
	mov	es, ax			; es <- BaseOfKernelFile
	mov	bx, OffsetOfKernelFile	; bx <- OffsetOfKernelFile	于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
	mov	ax, cx			; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
	push	ax			; ┓
	push	bx			; ┃
	mov	ah, 0Eh			; ┃ 每读一个扇区就在 "Loading  " 后面打一个点, 形成这样的效果:
	mov	al, '.'			; ┃
	mov	bl, 0Fh			; ┃ Loading ......
	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:

	call	KillMotor		; 关闭软驱马达

	mov	dh, 1			; "Ready."
	call	DispStrRealMode		; 显示字符串
	jmp     $

;============================================================================
;变量
;----------------------------------------------------------------------------
wRootDirSizeForLoop	dw	RootDirSectors	; Root Directory 占用的扇区数
wSectorNo		dw	0		; 要读取的扇区号
bOdd			db	0		; 奇数还是偶数
dwKernelSize		dd	0		; KERNEL.BIN 文件大小

;============================================================================
;字符串
;----------------------------------------------------------------------------
KernelFileName		db	"KERNEL  BIN", 0	; KERNEL.BIN 之文件名
; 为简化代码, 下面每个字符串的长度均为 MessageLength
MessageLength		equ	9
LoadMessage:		db	"Loading  "
Message1		db	"Ready.   "
Message2		db	"No KERNEL"
;============================================================================

;----------------------------------------------------------------------------
; 函数名: DispStrRealMode
;----------------------------------------------------------------------------
; 运行环境:
;	实模式(保护模式下显示字符串由函数 DispStr 完成)
; 作用:
;	显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
DispStrRealMode:
	mov	ax, MessageLength
	mul	dh
	add	ax, LoadMessage
	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
	add	dh, 3			; 从第 3 行往下显示
	int	10h			; int 10h
	ret
;----------------------------------------------------------------------------
; 函数名: ReadSector
;----------------------------------------------------------------------------
; 作用:
;	从序号(Directory Entry 中的 Sector 号)为 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, BaseOfKernelFile	; ┓
	sub	ax, 0100h		; ┣ 在 BaseOfKernelFile 后面留出 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:;偶数
	xor	dx, dx			; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
	mov	bx, [BPB_BytsPerSec]
	div	bx			; dx:ax / BPB_BytsPerSec  ==>	ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
					;				dx <- 余数 (FATEntry 在扇区内的偏移)。
	push	dx
	mov	bx, 0			; bx <- 0	于是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h
	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
;----------------------------------------------------------------------------
	
;----------------------------------------------------------------------------
; 函数名: KillMotor
;----------------------------------------------------------------------------
; 作用:
;	关闭软驱马达
KillMotor:
	push	dx
	mov	dx, 03F2h
	mov	al, 0
	out	dx, al
	pop	dx
	ret

;; 变量
_dwMCRNumber:			dd	0	; Memory Check Result
_MemChkBuf:	times	256	db	0

 运行效果如下



 因为现在还没开发自己的内核,所以这里就打印出了NO KERNEL了。

  • 大小: 116.3 KB
分享到:
评论

相关推荐

    自己动手写操作系统(于渊-含代码).rar

    本资源为《自己动手写操作系统》由于渊编写,它不仅为读者提供了一套完整的操作系统从无到有的开发教程,还附带了相应的源代码,使学习者能够在实践中深入理解操作系统的基本原理和开发过程。 首先,操作系统作为...

    Orange's一个操作系统的实现(于渊《自己动手写操作系统第二版》)随书光盘

    在《自己动手写操作系统第二版》中,于渊详细解释了这些概念并提供了实际的代码示例。通过分析和实践这些源代码,读者可以深入了解操作系统的内部运作机制,提升对计算机系统的理解,这对于想要深入学习操作系统原理...

    自己动手写操作系统_于渊_70M_高清(附源码).part3

    于渊的自己动手写操作系统真正高清版,原来csdn上有一个号称高清的版本,但是没有part1,太坑人了,后来自己花了好长时间才下到,不敢独享,特发出来与大家共同学习!

    《自己动手写操作系统》源码(0币)

    《自己动手写操作系统》是一本深受程序员喜爱的书籍,它以实践为导向,引导读者深入了解操作系统的工作原理,并通过编写操作系统来学习相关知识。源码的提供为读者提供了宝贵的实践机会,帮助他们将理论与实践相结合...

    orange's一个操作系统的实现-【自己动手写操作系统第二版】

    《orange's一个操作系统的实现-【自己动手写操作系统第二版】》是一本深入浅出的操作系统构建指南,作者为于渊。这本书旨在帮助读者通过实际操作来理解操作系统的原理和构造,适合对操作系统感兴趣的DIY爱好者和...

    自己动手写操作系统 -于渊 蓝光1080p

    非常清晰 非常清晰 非常清晰 非常清晰 非常清晰 非常清晰!

    自己动手写操作系统PDF 于渊 70M 超清晰版 part 4 of 5

    书名:自己动手写操作系统 作者:于渊 编著 来源:电子工业出版社 出版时间:2005年08月 ISBN:7-121-01577-3 定价:48元 内容介绍: 本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用...

    自己动手写操作系统(于渊著)

    《自己动手写操作系统》是由于渊编著的一本深入探讨操作系统原理与实现的书籍,它以实践为导向,引领读者从零开始构建一个简单的操作系统。这本书是对于操作系统内核设计和实现的绝佳学习资源,尤其适合计算机科学和...

    自己动手写操作系统PDF 于渊 70M 超清晰版 part 1 of 5

    书名:自己动手写操作系统 作者:于渊 编著 来源:电子工业出版社 出版时间:2005年08月 ISBN:7-121-01577-3 定价:48元 内容介绍: 本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用...

    自己动手写操作系统PDF 于渊 70M 超清晰版 part 3 of 5

    书名:自己动手写操作系统 作者:于渊 编著 来源:电子工业出版社 出版时间:2005年08月 ISBN:7-121-01577-3 定价:48元 内容介绍: 本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用...

    自己动手写操作系统PDF 于渊 70M 超清晰版 part 5 of 5

    书名:自己动手写操作系统 作者:于渊 编著 来源:电子工业出版社 出版时间:2005年08月 ISBN:7-121-01577-3 定价:48元 内容介绍: 本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用...

    自己动手写操作系统PDF 于渊 70M 超清晰版 part 2 of 5

    书名:自己动手写操作系统 作者:于渊 编著 来源:电子工业出版社 出版时间:2005年08月 ISBN:7-121-01577-3 定价:48元 内容介绍: 本书在详细分析操作系统原理的基础上,用丰富的实例代码,一步一步地指导读者用...

    自己动手写操作系统 于渊 随书光盘

    自己动手写操作系统 于渊 随书光盘。 优秀国内原创图书!本书用丰富的实例代码,一步一步地指导读者用C语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架,不同于其他的理论型书籍,本书提供给读者一个...

    于渊_自己动手写操作系统原代码

    《于渊_自己动手写操作系统原代码》可能是这样一本教程或书籍,旨在引导读者深入理解操作系统的原理,通过编写操作系统源码来实现这一目标。在这样的学习过程中,读者可以了解操作系统的主要组成部分和工作流程。 1...

    自己动手写操作系统 于渊 无水印 影印版

    自己动手写操作系统 于渊 无水印 影印版

    自己动手写操作系统光盘,自己动手写操作系统源代码

    在本文中,我们将深入探讨“自己动手写操作系统”的主题,这通常被看作是理解和学习操作系统原理的一种实践方法。这一项目由作者于渊发起,旨在帮助读者通过实际操作来理解操作系统的构建过程。 首先,我们要明白...

    自己动手写操作系统 于渊

    自己动手写操作系统 于渊 第一版 影印版

    自己动手写操作系统(于渊)工具和源码

    《自己动手写操作系统》是于渊先生撰写的一本经典教程,旨在帮助读者深入理解操作系统的原理和实现过程。这本书不仅包含了理论知识,还提供了实际的源代码和开发工具,使得学习更加直观和实践性强。 首先,我们要...

    自己动手编写操作系统(于渊)

    《自己动手编写操作系统》这本书由于渊所著,旨在帮助读者深入理解操作系统的原理,并通过实际编写代码来构建一个基本的操作系统框架。下面将详细探讨书中涉及的知识点。 首先,操作系统的基本概念和组成是学习的...

    自己动手写操作系统及光盘/于渊001

    于渊写的自己动手写操作系统,一本操作系统入门学习不错的书

Global site tag (gtag.js) - Google Analytics