首页 自制操作系统day1至day5
文章
取消

自制操作系统day1至day5

注:原书《三十天自制操作系统》比这份笔记生动易懂得多,这个笔记只是做一个总结。

day1

完全手写了程序系统镜像文件hellos.img,作者使用Bz软件,本质上就是一个二进制编辑器,如果你用vs code编写项目,使用插件Hex Editor也可以。

镜像文件hellos.img本来是要拷到软盘上的(所以大小为1440kb),不过作者也提供了虚拟环境QEMU,不用担心没有软盘。

编写了文件install.bat,负责将镜像写入软盘,如果不使用软盘,这个批处理文件是不会用到的。

编写了文件run.bat,用于将镜像helloos.img拷贝到虚拟环境QEMU,然后启动操作系统。对于没有软盘的我们,主要使用这个批处理文件。

其中,使用到原书作者编写的批处理文件!cons_nt.bat,也许你发现了,只是简单把cmd调出来而已。

至此,可以实现启动一个最简陋的“操作系统”,只能显示"hello world",其他什么也做不了。

使用汇编语言编写helloos.nas,使用原书作者提供的nask.exe编译器编译成helloos.img

day2

继续完善helloos.nas,补充并解释程序核心。本质上就是循环输出字符直到把"hello world"输出完,然后休眠。

优化流程,将helloos.nas主体制成ipl.nas,编译后就是我们的启动区,操作系统镜像剩余的部分会使用磁盘映像管理工具做。

修改批处理文件asm.bat,添加makeimg.bat。现在我们编译和调试的步骤是:

  1. 使用asm.bat编译汇编语言代码ipl.nas生成ipl.bin
  2. 使用makeimg.batipl.bin制作磁盘镜像文件helloos.img
  3. 使用run.bat拷贝镜像helloos.img到虚拟环境QEMU,启动操作系统。

之后,进一步优化流程,编写Makefilemake.bat将上面的三个命令进一步简化。

其中,make.bat只是将make命令引导至原书作者的工具箱,因为一般开发人员的电脑都会装有make程序,为了防止版本不兼容等错误,要引导至原书作者的工具箱。

现在,虽然核心仍是上面三步,有了Makefile的帮助下,只要使用命令make run就能一步做完所有工作并且启动操作系统。

此外,增添了两个功能命令:

  • make clean:删除所有中间产物,只保留源代码以及helloos.img
  • make src_only:清理,只保留源代码。

day3

制作真正的ipl,全称:InitialProgramLoader,中文意:首次装载系统,它就是我们的启动区,负责从磁盘读操作系统写入内存。

了解一个典型软盘的结构:80个柱面,2个磁头,18个扇区,一个扇区512字节。共 80 x 2 x 18 x 512Byte = 1440KB。

读盘顺序:C0-H0-S1(柱面0,磁头0,扇区1), C0-H0-S2 …… C0-H0-S18, C0-H1-S1 …… C0-H1-S18, C1-H0-S1 …… C1-H0-S18 ……C79-H1-S18。

当ipl启动时,它在内存中的启动区,即0x7c00,并且占用着缓冲区0x8000~0x8fff这512字节的内容,它的工作就是从磁盘读操作系统写到之后的缓冲区,即0x8200往后读10个柱面(暂定),每个柱面包含两个磁头(正反面),正面反面都有18个扇区。共 10 x 2 x 18 x 512Byte = 180KB。同时,读软盘可能出错,我们设置如果出错次数大于5则向用户报错。

改进了Makefile,引入了变量语法,让文件更加简洁,核心功能未变。

现在我们完成了启动区,是时候开始编写操作系统本体了。

使用汇编语言编写haribote.nas,之后编译成haribote.sys,即我们的操作系统主体,现在我们要将这个主体也保存进映像文件haribote.img(之前只有启动区),如果我们有软盘,操作流程为:

  1. 使用make install命令将磁盘映像文件写入磁盘。
  2. 在Windows中打开磁盘,把haribote.sys保存到磁盘上。
  3. 使用工具将磁盘备份为磁盘映像。

其实和我们现在用U盘保存文件的过程差不多。但是我们没有软盘,可以用edimg.exe直接写映像文件。我们在向映像文件haribote.img保存文件时,我们会发现:

  • 文件名会写在0x002600以后的地方。
  • 文件haribote.sys内容会写在0x004200以后的地方。

之前启动区的任务是将磁盘内容写到内存0x8000之后的缓冲区,所以要正式启动我们的操作系统,启动区在最后应该跳到 0x8000 + 0x4200 = 0xc200 号地址,执行操作系统的第一行代码。

继续完善操作系统主体haribote.nas,我们打开VGA显卡模式,并且准备进入32位模式。

现在我们开始导入C语言,大改了haribote.nas,改名为asmhead.nas,增添的代码原书作者打算之后解释,我们写下了我们在这个操作系统中的第一行C语言,即文件bootpack.c

从C语言文件bootpack.c到映像文件的处理过程如下:

graph LR
bootpack.c -->|ccl.exe| bootpack.gas
bootpack.gas -->|gas2nask.exe| bootpack.nas
bootpack.nas -->|nask.exe| bootpack.obj
bootpack.obj -->|obi2bim.exe| bootpack.bim
bootpack.bim -->|bim2hrb.exe| bootpack.hrb

asmhead.nas -->|nask.exe| asmhead.bin
asmhead.bin --> haribote.img
bootpack.hrb --> haribote.img

至于为什么流程这么多,不像汇编,C语言文件编译后还有链接的过程,而且在这个玩具操作系统上工具链并不完善,所以会有许多中间产物。Makefile也做了相应的更改来处理C语言文件。

因为C语言不直接支持HLT汇编指令,编写了文件naskfunc.nas定义了对应的函数,现在C语言文件可以引用本来是汇编编写的函数,实现了在C语言环境中执行汇编。

总结一下今天的工作:真正的启动区,操作系统主体,C语言与汇编混编,一个全黑屏界面。

day4

前一天界面还是全黑,现在我们让屏幕显示色彩。简单来说,就是向显存区域0xa0000~0xaffff写入字节。

设置了调色盘,复习一下,之前我们设置的VGA显卡模式色号只有8位,而一般的颜色表示是比如#ffffff一共24位表示,所以我们的操作系统只有有限的颜色(一共调色了16种颜色),需要使用调色板做颜色的映射。而使用调色板需要与显卡io端口做交互。

为了在C语言中使用汇编的INOUT指令与io做交互,而且因为使用INOUT前还要关中断,naskfunc.nas也定义了对应的函数。

现在有了颜色,我们要做的就是在界面上出一个系统桌面,为了方便写了一个绘制长方形的函数,然后绘制各种长方形,看起来就像一个桌面。

day5

为了规范与之后运行正确,优化bootpack.c,使用结构体(struct)从asmhead.nas接受启动信息并保存。具体从地址0x0ff0接收启动信息。

我们尝试开始显示字符,最简单的思路是定义一个数组,显示字符时按照数组的值一个一个画像素点。之后为了管理方便,在hankaku.txt定义了所以ASCII字符的点阵图,一共有256个字符,一个字符点阵占16字节大小,安装ASCII顺序排。将这个txt文本文件加入操作系统的流程为:

graph LR

hankaku.txt -->|makefont.exe| hankaku.bin
hankaku.bin -->|bin2obj.exe| hankaku.obj
hankaku.obj --> bootpack.bim
bootpack.obj --> bootpack.bim
naskfunc.obj --> bootpack.bim

这是新加入的流程,其余流程见day3的流程图。当然,Makefile也要做调整。

在处理成hankaku.obj后,与编写下面的汇编语句功能是一样的:

1
2
_hankaku:
	DB 各种数据(共4096字节)

之后我们可以在C语言程序中使用extern语法调用hankaku数组。

可以显示字符之后就简单了,编写了显示字符串的函数。之后,为了显示变量值以供调试,使用了stdio.h中的函数sprintf,这个函数与操作系统无关,只是将字符串写入内存。

现在我们为操作系统引入分段中断的功能,具体来说初始化了GDT全局段号记录表),GDTR寄存器,IDT中断记录表)。并且设定了两个段,段1是0x00000000开始的4GB,是CPU可用的所有内存。段2是0x280000开始的512KB,为bootpack.hrb准备。其他的设置细节day6会讲。

分段(不是分页):将4GB内存分成很多块,每一个块的起始地址都为0(相当于汇编语句的ORG 0)。为了表示一个段,需要:

  1. 段的大小。
  2. 段的起始地址。
  3. 段的管理属性(禁止使用,禁止执行,系统专用等等)

段寄存器是16位,而低三位不能使用,所以段号可以使用0~8191的数,每个段消息都有8字节,一共8192x8B=64KB,这64KB的信息叫做GDT全局段号记录表),整齐排列在内存中,其起始地址与有效设定个数放在CPU内部的GDTR特殊寄存器中。

中断:在外设完成工作或其他情况时引起中断通知CPU,就不需要CPU实时查询外设工作状态。中断号码可以使用0~255,这些中断号码与调用函数的对应信息被存在IDT中,也就是中断记录表

本文由作者按照 CC BY 4.0 进行授权

没用的C语言知识增加了

与小网站斗智斗勇