Day03
今天来学习制作IPL、显示一个最基础的图形界面(对操作系统进行基础的启动设置),以及导入c语言。
何为IPL
IPL(Initial Program Loader,启动程序装载器),在操作系统启动的时候,只会装载固定地址的很小字节(例如初始的512B),初始的部分即为IPL。在这里我们需要写一些汇编代码,来读取接下来的内容,或是启动操作系统的一些功能。
一些问题与感想
汇编语言中标签的顺序也很重要
类似c语言函数的声明顺序,汇编语言也是有前后依赖的关系的。在抄代码的时候,就随意把汇编语言中的函数放了个位置,结果意外地不能用了。
并不是从0开始导入C语言
如果说要真的从零开始导入C语言的话,那恐怕我们得先用汇编写一个编译器,之后再一步步导入各种库函数。本书直接把一些现成的函数给我们了,毕竟这也不是OS的主要部分,虽然有点遗憾,但也能理解吧。
下面是真正的书中内容
制作真正的IPL
harib00a
在程序核心部分内,我们加入了读取磁盘的操作。
当然,我们需要查阅相关说明后,发现 AH = 0x02时是读盘内容。对于其它关键字的意思,会在代码注释里给出。
; 读取磁盘
MOV AX, 0x0820
MOV ES, AX
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 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JC error
段寄存器表示地址
其实计组课上也有类似的知识点,用 ES << 4 + BX 来拼接成想要的地址。在汇编中的用法:
MOV AL, [ES:BX]
其实,平常写的地址也是默认的段寄存器。如
MOV CX, [1234] 其实是 MOV CX, [DS:1234]
当然要默认把DS设置为0
查看内存分布图
在我们想要把程序装载入内存的某些地址的时候,我们先查一下那个地方是否已经被用上了,不然冲突了就不好了。
makefile里的变量替换
说是变量,其实更像c语言里的宏定义,可以做到很方便的只需要在一个地方改动即可。
试错
harib00b
在读软盘的时候,经常容易出错。所以我们可以加入少量的尝试。
代码接在00a之后
MOV SI, 0 ; 记录失败次数的寄存器
retry:
MOV AH, 0x02 ; AH = 0x02 读盘
MOV AL, 1 ; 1个扇区
MOV BX, 0
MOV DL, 0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC fin ; 没出错的话跳转到fin
ADD SI, 1 ; 往SI加1
CMP SI, 5 ; 比较SI与5
JAE error ; SI >= 5 跳转到error
MOV AH, 0x00
MOV DL, 0x00 ; A驱动器
INT 0x13 ; 重置驱动器
JMP retry
读到18扇区
harib00c
我们可以设置一个循环,这样可以一直读18个扇区。主要更新的部分为next的标签。
前三行其实就是等价 ES += 20,只是这个寄存器不能直接加。。
next:
MOV AX, ES ; 把内存地址后移0x200
ADD AX, 0x0020
MOV ES, AX ; 因为没有ADD ES, 0x020指令,所以这里稍微绕个弯(把ES数放到AX里,加了之后再放回来)
ADD CL, 1 ; 往CL里加1
CMP CL, 18 ; 比较CL与18
JBE readloop ; 如果CL <= 18 跳转至readloop
读到10个柱面
harib00d
在next里继续添加一些东西,开始读下一个柱面。这样就能往内存里装载180KB了。
显然,CH在不断增加,读取下一个柱面。
next:
MOV AX, ES ; 把内存地址后移0x200
ADD AX, 0x0020
MOV ES, AX ; 因为没有ADD ES, 0x020指令,所以这里稍微绕个弯(把ES数放到AX里,加了之后再放回来)
ADD CL, 1 ; 往CL里加1
CMP CL, 18 ; 比较CL与18
JBE readloop ; 如果CL <= 18 跳转至readloop
MOV CL, 1
ADD DH, 1
CMP DH, 2
JB readloop ; 如果DH < 2 跳转到 readloop
MOV DH, 0
ADD CH, 1
CMP CH, CYLS ; 如果CH < CYLS, 跳转到readloop
JB readloop
另外,开头有一句
CYLS EQU 10 ; CYLS = 10
着手开发操作系统
harib00e
最简单的操作系统
目前还是一个最简单的操作系统,只有停机操作。
fin:
HLT
JMP fin
保存为nas文件后,编译成sys文件,然后通过edimg保存到磁盘映像img里。
向一个空软盘保存文件
文件名会写在 0x002600以后的地方
文件的内容会写在 0x004200以后的地方
所以接下来我们只要执行0x004200地方的东西,就可以执行这个程序了。
从启动区执行操作系统
harib00f
刚才说的情况是空磁盘,我们知道启动区从0x8000开始,所以0x4200的内容在0xc200的地址上。所以只要指明地址是0xc200就能执行想要的程序了。
更新一下haribote.nas
ORG 0xc200 ; 指定程序开始地址
fin:
HLT
JMP fin
更新一下ipl.nas
next:
MOV AX, ES ; 把内存地址后移0x200
ADD AX, 0x0020
MOV ES, AX ; 因为没有ADD ES, 0x020指令,所以这里稍微绕个弯(把ES数放到AX里,加了之后再放回来)
ADD CL, 1 ; 往CL里加1
CMP CL, 18 ; 比较CL与18
JBE readloop ; 如果CL <= 18 跳转至readloop
MOV CL, 1
ADD DH, 1
CMP DH, 2
JB readloop ; 如果DH < 2 跳转到 readloop
MOV DH, 0
ADD CH, 1
CMP CH, CYLS ; 如果CH < CYLS, 跳转到readloop
JB readloop
; 读取完毕后执行haribote.sys
JMP 0xc200
确认操作系统的执行情况
harib00g
通过调用显卡,我们可以看到一个漆黑的画面了。
PS:其实我写的这个代码有一个bug,所以看起来很奇怪(怎么这么长)。。到了day04才发现。。
haribote.nas
ORG 0xc200 ; 指定程序开始地址
MOV AL 0x13 ; VGA显卡,320*200*8位彩色
MOV AH, 0x00
INT 0x10
fin:
HLT
JMP fin
其它修改的地方
把ipl.nas 修改了为 ipl10.nas ,表示只能读取前10个柱面。还添加了磁盘读取的结束地址。
当然也要改makefile(作者应该觉得我们已经会了吧)。
32位模式前期准备
harib00h
BIOS需要16位机器语言,所以我们在16位下写好各种准备工作后,再进入32位模式。
BIOS准备了各种键盘状态、画面状态
haribote.nas
; haribote-os
; TAB=4
; 有关BOOT_INFO
CYLS EQU 0x0ff0 ; 设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色数目的信息。颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率的X(screen X)
SCRNY EQU 0x0ff6 ; 分辨率的Y(screen Y)
VRAM EQU 0x0ff8 ; 图像缓冲区的开始地址
ORG 0xc200 ; 指定程序开始地址
MOV AL 0x13 ; VGA显卡,320*200*8位彩色
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 ; KEYBOARD BIOS
MOV [LEDS], AL
fin:
HLT
JMP fin
其中,VRAM即为我们说的video RAM,用于显示每一个像素的颜色。这个地址当然也是设定好的,不能随便用。
开始导入C语言
harib00i
终于开始导入C语言了。当然C语言不是凭空不需要汇编就能写好的,所以作者直接给我们添加了些意义不明代码。没办法,只好把asmhead.nas复制过来好了。
来看C语言部分,还是很简单,很熟悉的。
void HariMain(void)
{
fin:
/* 写不了HLT */
goto fin;
}
C语言翻译成机器代码的过程
感觉这个过程要比平常直接.c .o .exe 更多啊。
翻译过程如下:
.c(C)->.gas(gas汇编)->.nas(nas汇编)->.obj(目标文件,需link)->.bim(二进制映像文件)->.hrb(对应的操作系统)->.sys
为了实现各层的指令,makefile也有了很多改动。到这里我就感觉梳理makefile的依赖关系已经比较麻烦了)。
实现HLT
harib00j
在这部我们初步尝试了c语言之后,现在可以尝试汇编(_func)与c(func)。就写一个简单的HLT指令。
naskfunc.nas
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机械语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函数名
; 以下是实际的函数
[SECTION .text] ; 目标文件中写了这些之后再写的程序
_io_hlt: ; void io_hlt(void)
HLT
RET
c程序中,只需要声明一下就好了
bootpack.c
void io_hlt(void);
void HariMain(void)
{
fin:
io_hlt(); /* 执行naskfunc.nas的_io_hlt */
goto fin;
}
在makefile里添加对应的naskfunc.nas 终于完成了。