今天的主要任务是让画面显示黑屏,嘿嘿。
内容比较多,包括制作能装载程序的IPL,读取软盘内容,画面显示黑屏,开始运行C语言编写的程序。
由于我们在同一个文件上做了多次修改,因此今天的资源分了多个文件夹,里面用到的文件括号里的内容对应文末资源里的文件夹名。
事不宜迟,那么现在就开始吧——
制作IPL
上一篇我们知道,磁盘最初的512字节是启动区,现在我们要装载下一个512字节的内容。
我们在上一版ipl.nas中添加了如下代码,由于该部分内容后面需要经过多次修改,这里给每次修改添加了版本,ipl.nas(version_a)内容修改如下:
添加部分version_a版:
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
老规矩,看新指令——
JC指令:是“jump if carry”的缩写,意思是如果进位标识是1的话就跳转,进位标识是一个只能存储1位信息的寄存器。
这里用到了几个新的寄存器:CH=柱面号,CL=扇区号,DH=磁头号,DL=驱动器号。
在有多个软盘驱动器的时候,用驱动器号来指定从哪个驱动器的软盘上读取数据。
这里有些小伙伴可能会问柱面、扇区、磁头这些都是什么呢?不用着急,我们先把软盘拆开,看一下软盘的内部结构——

柱面(cylinder)就是上图中 深黑色的一圈一圈圆环状的区域,从外向内分别为柱面0、柱面1、…、柱面79,一共80个柱面。
磁头是一种针状的磁性设备,它可以从正反两面接触磁盘。软盘磁盘正反两面都能记录数据,因此我们有正反两个磁头,分别是磁头0号和磁头1号。
扇区(sector)就是把一个圆环(柱面),均匀分成几等份,每一份称为一个扇区。一个圆环有18个扇区,分别称为扇区1、扇区2、…扇区18。
综上,一张软盘有80个柱面、2个磁头,18个扇区,一个扇区可以存储512字节,所以一张软盘的容量为:
80×2×18×512 = 1 474 560B = 1440KB。
我们制作的IPL启动区位于C0-H0-S1(柱面0,磁头0,扇区1),下一个扇区是C0-H0-S1,接下来我们就装载这个扇区。
添加试错
读取软盘的时候很容易出错,这时候可能多读几次就能读出来了。
因此这里我们添加试错程序,让它重试5次,如果依然读不出来我们就放弃。在ipl.nas(version_b)文件中添加试错部分程序——
添加部分version_b版:
; 读磁盘
MOV AX,0x0820
MOV ES,AX
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 ; 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
新指令——
JNC指令:是"jump if not carry"的缩写,条件跳转指令,也就是说进位标志是0的话就跳转。
JAE指令:是"jump if above or equal"的缩写,条件跳转指令,意思是大于或等于时跳转。
AH = 0x00;DL = 0x00;INT 0x13的作用是复位软盘状态,再读一次。
读取软盘内容
读取18个扇区
这里我们往后继续读,把一个圆环18扇区读完,依然在ipl.nas(version_c)文件中,我们添加如下程序——
添加部分version_c版:
; 读磁盘
MOV AX,0x0820
MOV ES,AX
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 ; 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
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x20指令,所以这里稍微绕个弯
ADD CL,1 ; 往CL里加1
CMP CL,18 ; 比较CL与18
JBE readloop ; 如果CL <= 18 跳转至readloop
新指令——
JBE指令:是"jump if below or equal",条件跳转指令,意思是小于等于则跳转。
这里我们已经把磁盘上C0-H0-S2到C0-H0-S18的512×17=8704字节的内容装载到了内存0x8200~0xa3ff处。
读入10个柱面
继续往下读,读入10个柱面。C0-H0-S18扇区的下一个扇区,是磁盘反面的C0-H1-S1。从0xa400开始读入,按顺序C0-H0-S18->C0-H1-S1…,一直读到C9-H1-S18。ipl.nas(version_d)文件修改内容如下——
添加部分version_d版
; 读磁盘
MOV AX,0x0820
MOV ES,AX
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 ; 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
next:
MOV AX,ES ; 把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ; 因为没有ADD ES,0x20指令,所以这里稍微绕个弯
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
JB readloop ; 如果CH < CYLS 则跳转到readloop
新指令——
JB指令:是"jump if below"的缩写,意思是如果小于的话就跳转。
EQU指令:是"equal"的缩写,相当于C语言中的#define命令,用来声明常数。“CYLS EQU 10"意思是"CYLS = 10”。
现在我们程序已经可以把软盘最初的10×2×18×512=184320byte=180KB内容装载到内存里了,即这个程序可以用从软盘读取的数据填满内存0x08200~0x34fff的地方。
最简单的操作系统
制作一个特别短小的程序,只让它HLT(让CPU处于暂停状态)。
haribote.nas(version_e)文件内容:
fin:
HLT
JMP fin
用nask编译,生成 haribote.sys文件。
将haribote.sys的内容保存到映像文件haribote.img中,使用如下make指令制作映像文件,当然要保证Makefile里提到的文件都可用。

用二进制编辑器Bz打开haribote.img,注意到0x002600附近,保存着文件名。

再往下看,找到0x004200位置,可用看到“F4 EB FD”,这里是文件内容写入的地方。

画面显示黑屏
这里我们将ipl.nas改名为ipl10.nas(version_g),使用ipl10.nas生成haribote.img,在命令行输入make img命令:

用VMware Workstation启动我们制作的haribote.img,就成功显示黑屏啦~~

汇编语言的开发就到此结束啦,后面要开始以C语言为主的开发了。
开始使用C语言开发
下面我们就开始用C语言进行开发了,先做一个准备工作。
我们需要对画面模式进行设置,并从BIOS得到键盘状态,键盘状态是指NumLock是ON还是OFF等这些状态。
添加键盘状态的代码,本次的haribote.nas(version_h)内容:
; haribote-os
; TAB=4
; 有关BOOT_INFO
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显卡、320x200x8位彩色
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
这段程序比较好理解,我们设置好画面模式后,画面的像素数、颜色数,以及从BIOS取得的键盘信息都保存了,保存的位置是在内存0x0ff0附近。
现在开始加入C语言部分,文件名是bootpack.c(version_h),内容很简单:
void HariMain(void)
{
fin:
/* 这里想写上HLT,但C语言中不能用HLT! */
goto fin;
}
熟悉C语言的小伙伴看这个函数会比较顺眼吧。
这个函数名字叫HariMain,无参函数(void),没有返回值。
新指令——
goto指令:相当于汇编语言中的JMP。
那么bootpack.c是怎样变成机器语言的呢?
它要经历如下长长的步骤——
1.使用cc1.exe,从bootpack.c生成bootpack.gas。
2.使用gas2nask.exe,从bootpack.gas生成bootpack.nas。
3.使用nask.exe,从bootpack.nas生成bootpack.obj。
4.使用obi2bim.exe,从bootpack.obj生成bootpack.bim。
5.使用bim2hrb.exe,从bootpack.bim生成bootpack.hrb。
6.最后使用copy指令,将 asmhead.bin与bootpack.hrb直接结合起来,生成haribote.sys。
这里使用了不少新的可执行程序,简单介绍一下——
cc1.exe是C编译器,可以将C语言程序编译成汇编语言源程序。
gas2nask.exe是把gas文件转换成nask文件的程序。
obi2bim.exe是将必要的目标文件全部链接(link)上的程序。
bim2hrb.exe就留着后面介绍吧~
最后实现HALT
夜已经深了,在睡觉之前,让计算机处于HALT状态(这种状态比较省电)吧。
添加naskfunc.nas(version_i)文件:
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机械语言
; 制作目标文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函数名
; 以下是实际的函数
[SECTION .text] ; 目标文件中写了这些之后再写程序
_io_hlt: ; void io_hlt(void);
HLT
RET
新指令——
RET指令:相当于C语言的return。
bootpack.c(version_i)文件内容:
/* 告诉C编译器,有一个函数在别的文件里 */
void io_hlt(void);
/* 是函数声明却不用{},而用;,这表示的意思是:函数是在别的文件中,你自己找一下吧! */
void HariMain(void)
{
fin:
io_hlt(); /* 执行naskfunc.nas里的_io_hlt */
goto fin;
}
"make run"一下,还是黑屏,不过程序是正常运行的。
好啦,今天的任务到此就完成了~
最后附上资源链接:
链接:https://pan.baidu.com/s/1svWXTH0LfX2zuf5AUxXhXg
提取码:lhz4
本文记录了制作操作系统的第三天进展,内容包括制作IPL,添加试错机制,读取软盘内容,解决画面显示黑屏问题,以及开始使用C语言进行开发。通过汇编和C语言的结合,逐步构建操作系统的基础功能。

321

被折叠的 条评论
为什么被折叠?



