30天自制操作系统
Day 1
Binary Editor
nask 汇编
Binary Editor
RESB指令是“reserve byte”的略写,如果想要从现在的地址开始空出10个字节来,就可以写成RESB 10,意思是我们预约了这10个字节。填0。
RESB 0x1fe-$了。这个美元符号的意思如果不讲,恐怕谁也搞不明白,它是一个变量,可以告诉我们这一行现在的字节数(如果严格来说,有时候它还会有别的意思,关于这一点我们明天再讲)。在这个程序里,我们已经在前面输出了132字节,所以这里的$就是132。
Day 2
ORG 0x7c00 ; origin 0x7c00
首先是ORG指令。这个指令会告诉nask,在开始执行的时候,把这些机器语言指令装载到内存中的哪个地址。
Register 16bit
AX——accumulator,累加寄存器
CX——counter,计数寄存器
DX——data,数据寄存器
BX——base,基址寄存器
SP——stack pointer,栈指针寄存器
BP——base pointer,基址指针寄存器
SI——source index,源变址寄存器
DI——destination index,目的变址寄存器
Register low 8 bit
另一方面,CPU中还有8个8位寄存器。
AL——累加寄存器低位(accumulator low)
CL——计数寄存器低位(counter low)
DL——数据寄存器低位(data low)
BL——基址寄存器低位(base low)
AH——累加寄存器高位(accumulator high)
CH——计数寄存器高位(counter high)
DH——数据寄存器高位(data high)
BH——基址寄存器高位(base high)
名字看起来有点像,其实这是有原因的:AX寄存器共有16位,其中0位到7位的低8位称为AL,而8位到15位的高8位称为AH。所以,如果以为“再加上这8个8位寄存器,CPU就又可以多保存8个字节了”就大错特错了,CPU还是那个CPU,依然只能存储区区16个字节。CPU的存储能力实在是太有限了
那BP、SP、SI、DI怎么没分为“L”和“H”呢?能这么想,就说明大家已经做到举一反三了,但可惜的是这几个寄存器不能分为“L”和“H”。如果无论如何都要分别取高位或低位数据的话,就必须先用“MOV,AX,SI”将SI的值赋到AX中去,然后再用AL、AH来取值。这貌似是英特尔(Intel)的设计人员的思维模式。
register 32 bit
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI
这些就是32位寄存器。虽说EAX是个32位寄存器,但其实跟前面一样,它有一部分是与AX共用的,32位中的低16位就是AX,而高16位既没有名字,也没有寄存器编号。也就是说,虽然我们可以把EAX作为2个16位寄存器来用,但只有低16位用起来方便;如果我们要用高16位的话,就需要使用移位命令,把高16位移到低16位后才能用。
segment Register 16bit
ES——附加段寄存器(extra segment)
CS——代码段寄存器(code segment)
SS——栈段寄存器(stack segment)
DS——数据段寄存器(data segment)
FS——没有名称(segment part 2)
GS——没有名称(segment part 3)
CPU & memory
下面我们来看“MOV AL,[SI]”。如果这个命令是“MOV AL,SI”的话,不用多说大家也都能明白它的意思,可这里用方括号 把SI括了起来。如果在汇编语言中出现这个方括号,寄存器所代表的意思就完全不一样了
CPU与内存之间的电信号交换,并不仅仅是为了存取数据。因为从根本上讲,程序本身也是保存在内存里的。程序一般都大于44字节,不可能保存在寄存器中,所以规定程序必须放在内存里。CPU在执行机器语言时,必须从内存一个命令一个命令地读取程序,顺序执行
MOV指令的数据传送源和传送目的地不仅可以是寄存器或常数,也可以是内存地址。
MOV BYTE [678],123
这个指令是要用内存的“678”号地址来保存“123”这个数值。
至于内存地址的指定方法,我们不仅可以使用常数,还可以用寄存器。比如“BYTE [SI]”、“WORD [BX]”等等。如果SI中保存的是987的话,“BYTE [SI]”就会被解释成“BYTE [987]”,即指定地址为987的内存。
根据以上说明我们知道可以用下面这个指令将SI地址的1字节内容读入到AL。
MOV AL, BYTE [SI]
可是MOV指令有一个规则1,那就是源数据和目的数据必须位数相同。也就是说,能向AL里代入的就只有BYTE,这样一来就可以省略BYTE,即可以写成:
MOV AL, [SI]
JE
jump equre 如果相等就跳转
BIOS
电脑里有个名为BIOS的程序,出厂时就组装在电脑主板上的ROM2单元里。电脑厂家在BIOS中预先写入了操作系统开发人员经常会用到的一些程序,非常方便。BIOS是英文“basic input output system”的缩写,直译过来就是“基本输入输出系统(程序)”。
ORG 0x7c00
内存的0号地址,也就是最开始的部分,是BIOS程序用来实现各种不同功能的地方,如果我们随便使用的话,就会与BIOS发生冲突,结果不只是BIOS会出错,而且我们的程序也肯定会问题百出。另外,在内存的0xf0000号地址附近,还存放着BIOS程序本身,那里我们也不能使用。
内存里还有其他不少地方也是不能用的,所以我们作为操作系统开发者,不得不注意这一点。
0x00007c00-0x00007dff :启动区内容的装载地址
程序中ORG指令的值就是这个数字。而且正是因为我们使用的是这个同样的数字,所以程序才能正常运行。
http://community.osdev.info/?(AT)memorymap
Day3 进入32位模式并导入C语言
JC
所谓JC,是“jump if carry”的缩写,意思是如果进位标志(carry flag)是1的话,就跳转。这里突然冒出来“进位标志”这么个新词,不过大家不用担心,很快就会明白的。
INT 0x13
至于“INT 0x13”这个指令,我们虽然知道这是要调用BIOS的0x13号函数,但还不明白它到底是干什么用的,那就来查一下吧。当然还是来看下面这个(AT)BIOS网页,
http://community.osdev.info/?(AT)BIOS
内存地址的寄存器保存
一般说来,如果能用一个寄存器来表示内存地址的话,当然会很方便,但一个BX只能表示0~0xffff的值,也就是只有0~65535,最大才64K。大家的电脑起码也都有64M内存,或者更多,只用一个寄存器来表示内存地址的话,就只能用64K的内存,这太可惜了。
于是为了解决这个问题,就增加了一个叫EBX的寄存器,这样就能处理4G内存了。这是CPU能处理的最大内存量,没有任何问题。但EBX的导入是很久以后的事情,在设计BIOS的时代,CPU甚至还没有32位寄存器,所以当时只好设计了一个起辅助作用的段寄存器(segment register)。在指定内存地址的时候,可以使用这个段寄存器。
我们使用段寄存器时,以ES:BX这种方式来表示地址,写成“MOV AL,[ES:BX]”,它代表ES×16+BX的内存地址。我们可以把它理解成先用ES寄存器指定一个大致的地址,然后再用BX来指定其中一个具体地址。
32位系统0x8000启动区装载地址
0x8000~0x81ff这512字节是留给启动区的,要将 启动区的内容读到那里,所以就这样吧。
0x7c00~0x7dff用于启动区,0x7e00以后直到0x9fbff为止的区域都没有特别的用途,操作系统 可以随便使用。
段寄存器
到目前为止我们开发的程序完全没有考虑段寄存器,但事实上,不管我们要指定内存的什么地址,都必须同时指定段寄存器,这是规定。一般如果省略的话就会把“DS:”作为默认的段寄存器。
以前我们用的“MOV CX,[1234]”,其实是“MOV CX,[DS:1234]”的意思。“MOV AL,[SI]”,也就是“MOV AL,[DS:SI]”的意思。在汇编语言中,如果每回都这样写就太麻烦了,所以可以省略默认的段寄存器DS。
JBE
这也是个条件跳转指令,是“jump if below or equal”的缩写,意思是小于等于则跳转
JB。这也是条件跳转指令,是“jump if below”的缩写。翻译过来就是:“如果小于的话,就跳转。”
EQU
还有一个新指令,就是在程序开头使用的EQU指令。这相当于C语言的#define命令,用来声明常数。“CYLS EQU 10”意思是“CYLS = 10”。EQU是“equal”的缩写。
Floppy 寄存器读取设置
磁盘读、写,扇区校验(verify),以及寻道(seek)
AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 &0xff;
CL=扇区号(0-5位)|(柱面号&0x300)* * 2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:
FLACS.CF==0:没有错误,AH==0
FLAGS.CF==1:有错误,错误号码存入AH内(与重置(reset)功能一样)
Day 4
global(segment)descriptor table
IDT是“interrupt descriptor table”
的缩写,直 译过来就是“中断记录表”。当CPU遇到外部状况变化,或者是内部偶然发生某些错误时,会临时切换过去处理这种突发事件。这就是中断功能。
PIC
是“programmable interrupt controller”的缩写,意思是“可编程中断控制器”。
PIC是将8个中断信号1集合成一个中断信号的装置。PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知给CPU。IBM的大叔们想要通过增加PIC来处理更多的中断信号,他们认为电脑会有8个以上的外部设备,所以就把中断信号设计成了15个,并为此增设了2个PIC
与CPU直接相连的PIC称为主PIC(master PIC),与主PIC相连的PIC称为从PIC(slave PIC)。主PIC负责处理第0到第7号中断信号,从PIC负责处理第8到第15号中断信号。
ICW
ICW是“initial control word”的缩写 ICW有4个,分别编号为1~4,共有4个字节的数据。 ICW1和ICW4与PIC主板配线方式、中断信号的电气特性等有关,所以就不详细说明了。 电脑上设定的是上述程序所示的固定值,不会设定其他的值。如果故意改成别的什么值的话,早期的电脑说不定会烧断保险丝,或者器件冒烟2; 最近的电脑,对这种设定起反应的电路本身被省略了,所以不会有任何反应。