30天自制操作系统——第三天画面显示黑屏和着手使用C语言开发

本文记录了制作操作系统的第三天进展,内容包括制作IPL,添加试错机制,读取软盘内容,解决画面显示黑屏问题,以及开始使用C语言进行开发。通过汇编和C语言的结合,逐步构建操作系统的基础功能。

今天的主要任务是让画面显示黑屏,嘿嘿。

内容比较多,包括制作能装载程序的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

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值