进入32位模式并导入C语言
今天算是写一个真正意义上的操作系统,比昨天难了亿点。前两天的内容基本上都算是准备阶段。
制作真正的启动区
启动区也称启动引导程序、主引导记录,MBR(Master Boot Record),在启动设备的第一个扇区中。包含了装载操作系统的程序。昨天的的代码中没有包含,今天开始正式装载操作系统。
昨天的程序只让软加载了盘最开始的512字节启动区,接下来要装载下一个512字节的内容到内存
首先复制源码
把 \30天自制操作系统\projects\03_day\harib00a 复制到 \30天自制操作系统\tolset。
启动区源码文件为ipl.nas
; haribote-ipl
; TAB=4
ORG 0x7c00 ; 指名启动扇的装载地址,不能随便指定其他位置
; 以下代码为启动区
; 以下段落为标准FAT12格式软盘专用代码
JMP entry
DB 0x90 ; 此处没有上一节中的0xeb, 0x4e
DB "HARIBOTE" ; 启动区名称,可取任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇大小,(必需为1个扇区)
DW 1 ; FAT的起始位置(一般是从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录大小,(一般设置为224项)
DW 2880 ; 该硬盘大小(必须为2880扇区)
DB 0xf0 ; 硬盘种类(必须是0xf0)
DW 9 ; FAT长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是18个)
DW 2 ; 磁头数(软盘必须是2个)
DD 0 ; 不使用分区,此处为0
DD 2880 ; 重写一次,硬盘大小
DB 0,0,0x29 ; 不知道是干什么的,但必要写
DD 0xffffffff ; 可能是卷标号码
DB "HARIBOTEOS " ; 硬盘名称(11字节)
DB "FAT12 " ; 硬盘文件系统名称(8字节)
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
;下面的代码是本次新加的部分
; 读取硬盘
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV BX,0 ; 基址寄存器,指定基址的起始地址
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JC error ; 进位标志CF是1的话跳转
;上面的代码是本次新加的部分
;让CPU进入待机状态
fin:
HLT ; 让CPU待机,等待指令
JMP fin ; 循环
error:
MOV SI,msg ; 把msg的地址赋值给SI
putloop:
MOV AL,[SI] ; 把SI中地址所指的内存单元中的值赋值给AL
ADD SI,1 ; SI的内容+1
CMP AL,0 ; AL和0比较
JE fin ; AL等于0时跳转fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用BIOS中的显卡
JMP putloop ; 无条件跳转putloop
msg:
DB 0x0a, 0x0a ; 2个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 当前到0x1fe地址的位置全部填0
DB 0x55, 0xaa ; 启动区末尾标识,必须是0x55, 0xaa
; 启动区结束
新汇编指令
- JC 进位标志CF是1的话跳转。
吐槽下作者的代码顺序
;新加的这部分代码是从软盘中读取一部分数据,存放到内存中
;初始化内存地址的代码是这段
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
..........
MOV BX,0 ; 基址寄存器,指定基址的起始地址
;设置软盘读取位置的是这段
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
;剩下的是BIOS中断调用
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
;结果原作者偏偏把初始化内存地址的部分代码写到BIOS中断调用中。。。。。。。
硬盘中断
新中断INT 0x13
详细介绍连接:https://www.ctyme.com/intr/int-13.htm
AH=0x00;(重置硬盘)
AH=0x01;(获取上次操作的状态)
AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)
CH=柱面号 &0xff;(柱面号低八位)
CL=扇区号(0-5位)|(柱面号&0x300)>>2;(物理扇区号从1号开始)
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)
返回值:
FLACS.CF==0:没有错误,AH==0
FLAGS.CF==1:有错误,错误号码存入AH内(与重置(reset)功能一样)
FLACS.CF表示INT 0x13的返回值。使用INT 0x13后没有出现错误CF值为0,有错值为1。
硬盘结构介绍
硬盘/软盘的结构,这两款存储设备的结构十分相似。接下来以机械硬盘举例说明
DL 驱动器号是电脑中第几块硬盘
DH 磁头号=盘面号,一个磁头只能读取一个盘面。
图中为3盘片,共6个盘面,每个盘面上有一个磁头,共六个磁头。所以盘面/磁头编号是0——5
CH 柱面号=磁道号,硬盘中上下贯穿所有盘面的一个空芯圆柱,称之为柱面。
CL 扇区,每个盘面的磁道\柱面都会划分扇区,每个扇区都像扇形,故称扇区。
内存数据缓冲区
代码中新加的这部分表示我们把软盘中读到的数据加载到内存中的哪个地址中;BX只有16位,所以最多只有2^16(64K)个地址单元。为了解决此问题,书中采用了ES寄存器
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
。。。。。。。。
MOV BX,0 ; 基址寄存器,指定基址的起始地址
代码中AX的值为0x820,可注释中却说段的首地址为0x08200,这是因为用段寄存器时,以ES:BX即——0x0820:0这种方式来表示完整的地址,其中ES作为段地址,BX作为偏移地址;计算方法为ES×16+BX,ES、BX中的值以十进制进行计算。0x820转换为10进制是2080,2080×16+0=33280=0x08200。
所以软盘中的数据会加载到内存地址为0x8200到0x83ff的区域
注意:BX为16位寄存器,而表达512个地址只需要9位即可,所以务必保证BX中高7位全部为0!!!!!
无论指定什么内存地址时同时必须指定段地址寄存器,若省略的话会把DS作为默认的段地址寄存器。MOV CX,[1234]
其实就是MOV CX,[DS:1234]
。MOV AL,[SI]
其实是MOV AL,[DS:SI]
。本程序中DS直接设置为0。
错误处理
当软盘读取出错时,要进行重复读取,重复5次。
首先复制源码
把 \30天自制操作系统\projects\03_day\harib00b 复制到 \30天自制操作系统\tolset。
新代码
; haribote-ipl
; TAB=4
ORG 0x7c00 ; 指明程序装载地址,不能随便指定其他位置
; 以下代码为启动区
; 以下段落为标准FAT12格式软盘专用代码
JMP entry
DB 0x90
DB "HARIBOTE" ; 启动区名称,可取任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇大小,(必需为1个扇区)
DW 1 ; FAT的起始位置(一般是从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录大小,(一般设置为224项)
DW 2880 ; 该硬盘大小(必须为2880扇区)
DB 0xf0 ; 硬盘种类(必须是0xf0)
DW 9 ; FAT长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是18个)
DW 2 ; 磁头数(必须是2个)
DD 0 ; 不使用分区,此处为0
DD 2880 ; 重写一次,硬盘大小
DB 0,0,0x29 ; 不知道是干什么的,但必要写
DD 0xffffffff ; 可能是卷标号码
DB "HARIBOTEOS " ; 硬盘名称(11字节)
DB "FAT12 " ; 硬盘文件系统名称(8字节)
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取硬盘
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
;下面的代码是本次新加的部分
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV BX,0 ; 基址寄存器,指定基址的起始地址
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JNC fin ; 若没有出错,跳转fin
ADD SI,1 ; SI+1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5时跳转error
MOV AH,0x00 ; AH=0x00 : 重置硬盘
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JMP retry
;上面的代码是本次新加的部分
;让CPU进入待机状态
fin:
HLT ; 让CPU待机,等待指令
JMP fin ; 循环
error:
MOV SI,msg ; 把msg的地址赋值给SI
putloop:
MOV AL,[SI] ; 把SI中地址所指的内存单元中的值赋值给AL
ADD SI,1 ; SI的内容+1
CMP AL,0 ; AL和0比较
JE fin ; AL等于0时跳转fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用BIOS中的显卡
JMP putloop ; 无条件跳转putloop
msg:
DB 0x0a, 0x0a ; 2个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 当前到0x1fe地址的位置全部填0
DB 0x55, 0xaa ; 启动区末尾标识,必须是0x55, 0xaa
; 启动区结束
新汇编指令
- JNC 进位标志CF是0的话跳转。
- JAE 条件跳转,大于或等于时跳转,需要配合CMP使用
这段新加的程序挺简单的,用SI记录失败次数,若失败的话无条件跳转到retry重新执行,每次失败SI次数都会+1,直到5次后跳转error。
读取18个扇区
首先复制源码
把 \30天自制操作系统\projects\03_day\harib00c 复制到 \30天自制操作系统\tolset。
; haribote-ipl
; TAB=4
ORG 0x7c00 ; 指明程序装载地址,不能随便指定其他位置
; 以下代码为启动区
; 以下段落为标准FAT12格式软盘专用代码
JMP entry
DB 0x90
DB "HARIBOTE" ; 启动区名称,可取任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇大小,(必需为1个扇区)
DW 1 ; FAT的起始位置(一般是从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录大小,(一般设置为224项)
DW 2880 ; 该硬盘大小(必须为2880扇区)
DB 0xf0 ; 硬盘种类(必须是0xf0)
DW 9 ; FAT长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是18个)
DW 2 ; 磁头数(必须是2个)
DD 0 ; 不使用分区,此处为0
DD 2880 ; 重写一次,硬盘大小
DB 0,0,0x29 ; 不知道是干什么的,但必要写
DD 0xffffffff ; 可能是卷标号码
DB "HARIBOTEOS " ; 硬盘名称(11字节)
DB "FAT12 " ; 硬盘文件系统名称(8字节)
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取硬盘
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
;下面的代码是本次新加的部分
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV BX,0 ; 基址寄存器,指定基址的起始地址
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JNC next ; 若没有出错,跳转next
ADD SI,1 ; SI+1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5时跳转error
MOV AH,0x00 ; AH=0x00 : 重置硬盘
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JMP retry
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 没有ADD ES,0x020指令,所以只能绕个弯。
ADD CL,1 ; CL+1
CMP CL,18 ; CL和18相比较
JBE readloop ; CL <= 18时跳转readloop
;上面的代码是本次新加的部分
;让CPU进入待机状态
fin:
HLT ; 让CPU待机,等待指令
JMP fin ; 循环
error:
MOV SI,msg ; 把msg的地址赋值给SI
putloop:
MOV AL,[SI] ; 把SI中地址所指的内存单元中的值赋值给AL
ADD SI,1 ; SI的内容+1
CMP AL,0 ; AL和0比较
JE fin ; AL等于0时跳转fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用BIOS中的显卡
JMP putloop ; 无条件跳转putloop
msg:
DB 0x0a, 0x0a ; 2个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 当前到0x1fe地址的位置全部填0
DB 0x55, 0xaa ; 启动区末尾标识,必须是0x55, 0xaa
; 启动区结束
新汇编指令
- JBE 有条件跳转指令,小于等于则跳转
这段新加的代码没什么难的,就是循环读取18个扇区,CL中存放扇区号,每次循环时CL中的值+1;每个扇区512字节。十进制中512正好是0x200,故每次读取后都会把附加段地址寄存器ES增加0x20。由于汇编语言中没有ADD ES,0x020指令所以只能把ES的值赋值给AX,AX加0x20后再赋值给ES,这样绕个弯
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 没有ADD ES,0x020指令,所以只能绕个弯。
每次ES增加0x20,根据ES×16+BX计算0x0820+0x20=0x840=2112,2112×16+0=33792=0x8400,作为下个512字节的存储位置。
读取10个柱面
首先还是复制源码
把 \30天自制操作系统\projects\03_day\harib00d 复制到 \30天自制操作系统\tolset。
; haribote-ipl
; TAB=4
CYLS EQU 10 ; 声明常数CYLS=10
ORG 0x7c00 ; 指明程序装载地址,不能随便指定其他位置
; 以下代码为启动区
; 以下段落为标准FAT12格式软盘专用代码
JMP entry
DB 0x90
DB "HARIBOTE" ; 启动区名称,可取任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇大小,(必需为1个扇区)
DW 1 ; FAT的起始位置(一般是从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录大小,(一般设置为224项)
DW 2880 ; 该硬盘大小(必须为2880扇区)
DB 0xf0 ; 硬盘种类(必须是0xf0)
DW 9 ; FAT长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是18个)
DW 2 ; 磁头数(必须是2个)
DD 0 ; 不使用分区,此处为0
DD 2880 ; 重写一次,硬盘大小
DB 0,0,0x29 ; 不知道是干什么的,但必要写
DD 0xffffffff ; 可能是卷标号码
DB "HARIBOTEOS " ; 硬盘名称(11字节)
DB "FAT12 " ; 硬盘文件系统名称(8字节)
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取硬盘
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV BX,0 ; 基址寄存器,指定基址的起始地址
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JNC next ; 若没有出错,跳转next
ADD SI,1 ; SI+1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5时跳转error
MOV AH,0x00 ; AH=0x00 : 重置硬盘
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JMP retry
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 没有ADD ES,0x020指令所以要把ES的值赋值给AX,AX加0x20后再赋值给ES
ADD CL,1 ; CL+1
CMP CL,18 ; CL和18相比较
JBE readloop ; CL <= 18时跳转readloop
;下面的代码是本次新加的部分
MOV CL,1 ; 扇区1
ADD DH,1 ; DH+1,即选择下一个磁头
CMP DH,2
JB readloop ; 如果DH<2,跳转readloop
;开始读取下个柱面
MOV DH,0 ; 磁头归0
ADD CH,1 ; CH+1,选择下个柱面
CMP CH,CYLS ; 比较CH和CYLS的值
JB readloop ; 如果CH < CYLS 跳转readloop
;上面的代码是本次新加的部分
;让CPU进入待机状态
fin:
HLT ; 让CPU待机,等待指令
JMP fin ; 循环
error:
MOV SI,msg ; 把msg的地址赋值给SI
putloop:
MOV AL,[SI] ; 把SI中地址所指的内存单元中的值赋值给AL
ADD SI,1 ; SI的内容+1
CMP AL,0 ; AL和0比较
JE fin ; AL等于0时跳转fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用BIOS中的显卡
JMP putloop ; 无条件跳转putloop
msg:
DB 0x0a, 0x0a ; 2个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 当前到0x1fe地址的位置全部填0
DB 0x55, 0xaa ; 启动区末尾标识,必须是0x55, 0xaa
; 启动区结束
新汇编指令
- EQU 代码的开头添加了一行CYLS EQU 10,就是声明了一个常量CYLS=10,相当于C语言中的#define
新加的部分没什么难的,就是读取第第一个柱面(0号柱面)上下两个盘面(磁头)的数据后继续循环读取10个柱面,仅此。
开发操作系统
怎样进入操作系统?
到此为止,启动区算是彻底写完了,开始开发操作系统。
这一节就是告诉你向空软盘保存文件时,文件名会写在0x2600的位置,文件内容会写在0x4200的位置。之后从启动区启动这个文件就能运行操作系统。
至于编译、连接程序,作者自带的make命令可以直接搞定
harib00e的内容就是告诉你软盘文件的保存位置的
首先依旧复制源码
把 \30天自制操作系统\projects\03_day\harib00e 复制到 \30天自制操作系统\tolset。
这次源码中除了ipl.nas外多了个haribote.nas文件,文件中就3行代码,实现CPU待机指令。这其实已经算得上是一个最简单的操作系统了。
fin:
HLT
JMP fin
要把两个代码文件编辑成”ipl.bin“、“haribote.sys”。之后把这两个文件合并为”haribote.img“,用自带的make命令可直接完成。
二进制编辑器打开haribote.sys,可以看到文件就3字节的内容
同样用二进制编辑器打开haribote.img。在0x2600的地方可以看到文件名
在0x4200的地方可以看到F4 EB FD ——haribote.sys文件的内容
至于为什么会这样,这牵扯到软盘所用的FAT12 文件系统。
软盘每个扇区512字节,按字节编址。地址从0开始,扇区编号从1开始。本系统中:
- 扇区1 启动区(0x0000 – 0x01FF),包含Master Boot Record和Boot Sector
- 扇区2-10 FAT 表 1(0x0200 – 0x11FF)
- 扇区11-19 FAT 表 2(0x1200 – 0x23FF)
- 扇区20-33 根目录 (0x2400 – 0x43FF)
- 扇区34 及之后 (0x4200—)数据区
所以,文件名存错在根目录中,文件存放在数据区中。
harib00e结束
harib00f和harib00g关联性很强这里直接跳过harib00f
把 \30天自制操作系统\projects\03_day\harib00g 复制到 \30天自制操作系统\tolset。
ipl10.nas文件
; haribote-ipl
; TAB=4
CYLS EQU 10 ; 声明常数CYLS=10
ORG 0x7c00 ; 指明程序装载地址,不能随便指定其他位置
; 以下代码为启动区
; 以下段落为标准FAT12格式软盘专用代码
JMP entry
DB 0x90
DB "HARIBOTE" ; 启动区名称,可取任意字符串(8字节)
DW 512 ; 每个扇区的大小(必须为512字节)
DB 1 ; 簇大小,(必需为1个扇区)
DW 1 ; FAT的起始位置(一般是从第一个扇区开始)
DB 2 ; FAT的个数(必须为2)
DW 224 ; 根目录大小,(一般设置为224项)
DW 2880 ; 该硬盘大小(必须为2880扇区)
DB 0xf0 ; 硬盘种类(必须是0xf0)
DW 9 ; FAT长度(必须是9个扇区)
DW 18 ; 1个磁道有几个扇区(必须是18个)
DW 2 ; 磁头数(必须是2个)
DD 0 ; 不使用分区,此处为0
DD 2880 ; 重写一次,硬盘大小
DB 0,0,0x29 ; 不知道是干什么的,但必要写
DD 0xffffffff ; 可能是卷标号码
DB "HARIBOTEOS " ; 硬盘名称(11字节)
DB "FAT12 " ; 硬盘文件系统名称(8字节)
RESB 18 ; 空出18字节
; 程序主体
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
; 读取硬盘
MOV AX,0x0820
MOV ES,AX ; 附加段地址寄存器,指定内存中段的首地址为0x08200
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ; AH=0x02 : 读取硬盘
MOV AL,1 ; 处理对象的扇区数,此处为1个扇区
MOV BX,0 ; 基址寄存器,指定基址的起始地址
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JNC next ; 若没有出错,跳转next
ADD SI,1 ; SI+1
CMP SI,5 ; 比较SI和5
JAE error ; SI>=5时跳转error
MOV AH,0x00 ; AH=0x00 : 重置硬盘
MOV DL,0x00 ; 驱动器号,即第一个驱动器
INT 0x13 ; 调用BIOS中的硬盘中断进行读盘
JMP retry
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 没有ADD ES,0x020指令所以要把ES的值赋值给AX,AX加0x20后再赋值给ES
ADD CL,1 ; CL+1
CMP CL,18 ; CL和18相比较
JBE readloop ; CL <= 18时跳转readloop
MOV CL,1 ; 扇区1
ADD DH,1 ; DH+1,即选择下一个磁头
CMP DH,2
JB readloop ; 如果DH<2,跳转readloop
; 读取下个柱面
MOV DH,0 ; 磁头归0
ADD CH,1 ; CH+1,选择下个柱面
CMP CH,CYLS ; 比较CH和CYLS的值
JB readloop ; 如果CH < CYLS 跳转readloop
; 执行haribote.sys
; 同时把fin移动到putloop(错误处理的部分)后面
; 下面的代码是本次新加的部分
MOV [0x0ff0],CH ; 内存地址0ff0中记录下启动区读到了哪个柱面
JMP 0xc200
; 上面的代码是本次新加的部分
error:
MOV SI,msg ; 把msg的地址赋值给SI
putloop:
MOV AL,[SI] ; 把SI中地址所指的内存单元中的值赋值给AL
ADD SI,1 ; SI的内容+1
CMP AL,0 ; AL和0比较
JE fin ; AL等于0时跳转fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用BIOS中的显卡
JMP putloop ; 无条件跳转putloop
; 让CPU进入待机状态
fin:
HLT ; 让CPU待机,等待指令
JMP fin ; 循环
msg:
DB 0x0a, 0x0a ; 2个换行
DB "load error"
DB 0x0a ; 换行
DB 0
RESB 0x7dfe-$ ; 当前到0x1fe地址的位置全部填0
DB 0x55, 0xaa ; 启动区末尾标识,必须是0x55, 0xaa
; 启动区结束
haribote.nas文件
; haribote-os
; TAB=4
ORG 0xc200 ; 指明程序装载的内存地址
MOV AL,0x13 ; BIOS调用VGA显卡,320x200x8bit彩色
MOV AH,0x00
INT 0x10
fin:
HLT
JMP fin
haribote.nas文件中出现了新的BIOS显卡中断
MOV AL,0x13 ; BIOS调用VGA显卡,320x200x8bit彩色
MOV AH,0x00
INT 0x10
- AL=模式:(省略了一些不重要的画面模式)
- 0x03:16色字符模式,80 × 25
- 0x12:VGA 图形模式,640 × 480 × 4位彩色模式,独特的4面存储模式
- 0x13:VGA 图形模式,320 × 200 × 8位彩色模式,调色板模式
- ……
这段程序也很简单,电脑启动后便跳转到内存0xc200位置(JMP 0xc200
)正是haribote.nas文件的装载地址(ORG 0xc200
)。haribote.nas文件实现了两个功能:
- 调用BIOS显卡中断,显示黑屏
- 让CPU进入待机状态
模拟运行结果
关于0x8000的地址说明
先上结论:最可能的原因是翻译问题。
本章第六节说明:
那么,要怎样才能执行磁盘映像上位于0x004200号地址的程序呢?现在的程序是从启动区开
始,把磁盘上的内容装载到内存0x8000号地址,所以磁盘0x4200处的内容就应该位于内存
0x8000+0x4200=0xc200号地址。
可程序代码中启动区装载到内存0x7c00位置(ORG 0x7c00
)数据部分要装载到0x08200的位置(MOV AX,0x0820
)。
查阅资料,怎么解释的都有。研究了一番,应该就是翻译问题。。。。。
代码中是从软盘的第2个扇区存入0x8200位置,第一个扇区(启动区)存入了0x7c00
位置。0x8000——0x8200之间为空。软盘的0x4200位置包含了第一个扇区。更严谨的算法应该是0x8200+0x4200-0x0200=0xc200。所以看着像是把启动区放在了8000的位置。且光盘中自带的虚拟机程序qemu有着查看实际内存数据的方法。
模拟运行后按下Ctrl+Alt+2
切换界面,输入xp /92xw 0x8000
再输入xp /92xw 0x7c00
对比光盘镜像文件的二进制代码,明显可以看到,启动区被加载到7c00
的位置,8000
的位置全是空的。
32位模式准备,导入C语言开发
准备阶段:
BIOS是用16位机器语言写的。进入32位模式就不能调用BIOS中断所以需要把以后需要的BIOS中断调用写完
把 \30天自制操作系统\projects\03_day\harib00h 复制到 \30天自制操作系统\tolset。
本次对haribote.nas进行了修改
; haribote-os
; TAB=4
; BOOT信息有关的数据
CYLS EQU 0x0ff0 ; 设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 颜色位数
SCRNX EQU 0x0ff4 ; x分辨率
SCRNY EQU 0x0ff6 ; y分辨率
VRAM EQU 0x0ff8 ; 图像缓冲区(显存)开始地址
ORG 0xc200 ; 程序装载地址
MOV AL,0x13 ; VGA显卡调用、320x200x8bit
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000 ;
; 用BIOS获得键盘上的各种LED指示灯状态(大写锁定,小键盘开关等)
MOV AH,0x02
INT 0x16 ; BIOS中断键盘调用
MOV [LEDS],AL
fin:
HLT
JMP fin
代码很简单,把以后要用到的BIOS中断参数,保存到内存中。方便以后调用。新的BIOS中断就不介绍了,可自行查看:https://www.ctyme.com/intr/rb-1756.htm
harib00h结束
编写第一个C语言程序
继续对haribote.nas进行扩增
harib00i的介绍合并到harib00j中
把 \30天自制操作系统\projects\03_day\harib00j 复制到 \30天自制操作系统\tolset。
haribote.nas中的代码增加到100多行,并重命名为asmhead.nas
asmhead.nas中的代码会在后面的章节中一一解释,此处就不放源码了。
bootpack.c
/* 函数声明 */
void io_hlt(void);
void HariMain(void)
{
fin:
io_hlt(); /* 调用naskfunc.nas文件中的_io_hlt函数 */
goto fin;
}
第一个C语言代码,没什么好说的,HariMain函数循环执行io_hlt仅此,io_hlt函数在本节新加的naskfunc.nas文件中。
naskfunc.nas
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 指定生成的目标文件格式为WCOFF
[BITS 32] ; 声明代码是为32位模式编写
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 指定源文件名信息
GLOBAL _io_hlt ; 声明_io_hlt是全局函数
; 以下为实际函数
[SECTION .text] ; 定义接下来的代码属于 .text 段
_io_hlt: ; void io_hlt(void)
HLT
RET
naskfunc.nas大部分代码主要用在编译、连接程序中。_io_hlt本身没什么好讲的,执行HLT然后返回RET。
需要注意下函数名称要用下划线开头,但C语言调用时省略开头的下划线。函数需要先用GLOBAL声明。
第三天结束。
注意!第三天正式进入操作系统后haribote.img文件写入U盘后便无法运行了,会跳转error
harib00j中的代码编译成haribote.img文件,make run运行后会正常显示黑屏。
但写入U盘在虚拟机或物理机中运行会显示load error。这是正常现象。因为这个操作系统目前只能在软盘中运行。