第一个hello world例子

上代码先:

; hello-os
; TAB=4

		ORG		0x7c00			



		JMP		entry
		DB		0x90
		DB		"HELLOIPL"		
		DW		512				
		DB		1				
		DW		1				
		DB		2				
		DW		224				
		DW		2880			
		DB		0xf0			
		DW		9				
		DW		18				
		DW		2				
		DD		0				
		DD		2880			
		DB		0,0,0x29		
		DD		0xffffffff		
		DB		"HELLO-OS   "	
		DB		"FAT12   "		
		RESB	18				


entry:
		MOV		AX,0			
		MOV		SS,AX
		MOV		SP,0x7c00
		MOV		DS,AX
		MOV		ES,AX

		MOV		SI,msg
putloop:
		MOV		AL,[SI]
		ADD		SI,1			
		CMP		AL,0
		JE		fin
		MOV		AH,0x0e			
		MOV		BX,15			
		INT		0x10			
		JMP		putloop
fin:
		HLT						
		JMP		fin				

msg:
		DB		0x0a, 0x0a		
		DB		"hello, world"
		DB		0x0a			
		DB		0

		RESB	0x7dfe-$		

		DB		0x55, 0xaa

 ;这个程序有几个重要的地方需要讲解一下:

知识点一:为什么ORG要设置成0x7c00

这里指定这个很重要,ORG指令来源英文"origin",意思是"源头,起点"。它会告诉汇编器,程序要从指定的

这个地址开始,也就是要把程序装载到内存中的指定位置,那为什么这里就是要指定成地址是0x7c00,那么这里就要讲讲BIOS

BIOS就是基本输入输出系统了,Intel 8086可以访问1MB的内存空间,地址范围为0x00000到0xFFFFF。出于各方面的考虑,计算

机系统的设计者将这1MB的内存空间从物理上分为几个部分086有20根地址线,但并非全都用来访问DRAM,也就是内存条。事实上,

这些地址线经过分配,大部分用于访问DRAM,剩余的部分给了只读存储器ROM和外围的板卡与DRAM不同,只读存储器(Read Only 

Memory,ROM)不需要刷新,它的内容是预先写入的,即使掉电也不会消失,但也很难改变。这个特点很有用,比如,可以将一些

程序指令固化在ROM中,使处理器在每次加电时都自动执行。处理器醒来后不能饿着,这是很重要的。

在以Intel 8086为处理器的系统中,ROM占据着整个内存空间顶端的64KB,物理地址范围是0xF0000~0xFFFFF,里面固化了开机时要执行的指令;

DRAM占据着较低端的640KB,地址范围是0x00000~0x9FFFF;中间还有一部分,分给了其他外围设备,这个以后再说。因为8086加电或者复位时,

CS=0xFFFF,IP=0x0000,所以,它取的第一条指令位于物理地址0xFFFF0,正好位于ROM中,那里固化了开机时需要执行的指令。处理器取指令执

行的自然顺序是从内存的低地址往高低地址推进。如果从0xFFFF0开始执行,这个位置离1MB内存的顶端(物理地址0xFFFFF)只有16个字节的长度,

一旦IP寄存器的值超过0x000F,比如IP=0x0011,那么,它与CS一起形成的物理地址将因为溢出而变成0x00001,这将回绕到1MB内存的最低端。

所以,ROM中位于物理地址0xFFFF0的地方,通常是一个跳转指令,它通过改变CS和IP的内容,使处理器从ROM中的较低地址处开始取指令执行。

在NASM汇编语言里,一个典型的跳转指令像这样:

jmp 0xf000:0xe05b

在这里,“jmp”是跳转(jump)的简化形式;0xf000是要跳转到的段地址,用来改变CS寄存器的内容;0xe05b是目标代码段内的偏移地址,

用来改变IP寄存器的内容。一旦执行这条指令,处理器将开始从指定的“段: 偏移”处开始重新取指令执行。

这块ROM芯片中的内容包括很多部分,主要是进行硬件的诊断、检测和初始化。所谓初始化,就是让硬件处于一个正常的、默认的工作状态。最后,

它还负责提供一套软件例程,让人们在不必了解硬件细节的情况下从外围设备(比如键盘)获取输入数据,或者向外围设备(比如显示器)输出数

据。设备当然是很多的,所以这块ROM芯片只针对那些最基本的、对于使用计算机而言最重要的设备,而它所提供的软件例程,也只包含最基本、最

常规的功能。正因为如此,这块芯片又叫基本输入输出系统(Base Input & Output System,BIOS)ROM。在读者缺乏基础知识的情况下讲述ROM-BIOS

的工作只会越讲越糊涂,所以这些知识将会分散在各个章节里予以讲解。

ROM-BIOS的容量是有限的,当它完成自己的使命后,最后所要做的,就是从辅助存储设备读取指令数据,然后转到那里开始执行。基本上,这相当于接力赛中的交接棒。

,ROM-BIOS将读取硬盘主引导扇区的内容,将它加载到内存地址0x0000:0x7c00处(也就是物理地址0x07C00),然后用一个jmp指令跳到那里接着执行:

jmp 0x0000:0x7c00

第二:ROM-BIOS将读取硬盘主引导扇区是哪里?

硬盘的第一个扇区是0面0道1扇区,或者说是0头0柱1扇区,这个扇区称为主引导扇区

第三:这里的DS和ES寄存器为什么设置成0呢?

因为我们上面说过,ROM-BIOS读取硬盘主引导扇区后,会将内容加载到内存地址0x0000:0x7c00位置,那么很明显,这里的段地址是

0x0000,所以把DS,ES都设置成0

第四:这里为什么可以MOV SI,msg呢?这里的msg是一个标号,那么汇编器是怎么进行定位的?

估计有人会说,这还不简单啊,只要知道段地址和偏移量不就可以了吗?通过段地址:偏移量就可以准备的定位到msg的内存地址,但是

我们这里只是知道段地址是0x0000啊,并不知道msg的偏移量是多少啊,那怎么办,汇编器是怎么做到的啊?

下面就引入了一个汇编地址的概念。汇编地址是在程序编译期间,编译器为每条指令确定的汇编位置,也就是每条指令相对于整个

程序开头的偏移量,以字节计,当编译后的程序装入物理内存后,它又是该指令在内存段内的偏移地址。下面看个图你就明白了,从

图可以看出,当编译好的程序加载到物理内存后,它在段内的偏移地址和它在编译阶段的汇编地址相等的。编译后的程序是整体加载

到内存中某个段的,交叉箭头用于指示它们之间的映射关系。之所以箭头交叉,是因为远程序的编译是从上往下的,而内存地址的增长

是从下往上的(低地址往高地址方向增长),那么我们在编译期间就知道了msg的汇编地址,那不就很容易就可以得到其偏移地址吗

第五:看看几个比较重要的寄存器

AX---accumulator:累加寄存器

CX---counter:计数寄存器

DX---data:数据寄存器

BX---base:基址寄存器

SP---stack pointer:栈指针寄存器

BP---base pointer:基址指针寄存器

SI---source index:源变址寄存器

DI---destination index:目的变址寄存器

ES---extra segment:附加段寄存器

CS---code segment:代码段寄存器

SS---stack segment:栈段寄存器

DS---data segment:数据段寄存器

用这几个寄存器来进行编程,本人觉得确实觉得很难,估计是还不怎么熟练这种编程方式。

第六:这个程序中一些重要的指令:

mov 段寄存器,通用寄存器

mov 段寄存器,内存单元

MOV AI, [SI] 这里其实是设略的DS段,全称则是MOV AL, [DS:SI]

add指令需要两个操作数,目的操作数可以是8位或者16位的通用寄存器,或者指向8位或者16位

实际操作数的内存地址;源操作数可以是相同数据宽度的8位或者16位通用寄存器、指向8位或者

16位实际操作数的内存地址,或者立即数,但不允许两个操作数同时为内存单元。相加后,结果

保存在目的操作数中。(重点就是不允许目的操作数和源操作数都是内存单元)

cmp,它需要两个操作数,目的操作数可以是8位或者16位通用寄存器,也可以是8位或者16位内存单

元;源操作数可以是与目的操作数宽度一致的通用寄存器、内存单元或者立即数,但两个操作数同时

为内存单元的情况除外

第六:注意:

虽然我们可以用寄存器来指定内存地址,但可作此用途的寄存器非常有限,只有BX,BP,SI,DI这几个,剩下的

AX,CX,DX,SP不能用来指定内存地址。

MOV AX, [SP]这种写法是错误的

第七:显示文字

首先,既能是要显示文字,就应该看跟显卡有关的函数,这么看来,INT 0X10好像有点关系,这里有个网站可以

查询,不过是日文的 http://community.osdev.info/?(AT)BIOS,当然你也可以上官网去找。

显示一个字符

AH=0X0e;

AL=character code;

BH=0

BL=color code;

返回值:无  是不是看起来很看C的函数调用啊,指定传入函数的参数,就可以达到我们想要的效果了

注:beep,退格(back space),CR,LF都会被当做控制字符处理

所以,如果我们按照这里所写的步骤,往寄存器里代入各种值,再调用INT 0X10,就能顺利地在屏幕上显示一个

字符出来,是不是很简单啊,给变量代值总该会吧,我们要做的事情就是去文档查看要代的值是多少。

第八,HLT指令

HLT是让CPU停止动作的指令,不过并不是彻底停止(如果要彻底停止CPU的动作,只能切断电源),而是让CPU进入待机

状态,只要外部发生变化,比如按下键盘,或是移动鼠标,CPU就会醒过来,继续执行程序。这里的好处就是避免CPU

不停地权力去执行JMP指令,这会使CPU的负荷达到100%,非常的耗电,浪费。

JE指令的只能靠你上网搜索了,判断跳转的指令比较多,需要的时候查一查就OK了。

来张效果图:



 

后面就开始制作真正的IPL了以及开始导入C语言了,C语言是操作的重头戏了,汇编确实太枯燥了,不过后面的还是会继续大量的介绍汇编的一些用法。

猜你喜欢

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