任务概述:
- 启动扇区的编写,读入10个柱面(每个柱面18个扇区,两个磁头所在的面也需要读入)
- 开始编写操作系统程序,只需要简单的让CPU休眠即可
- 从启动扇区处开始执行操作系统程序
- 更改第二步的操作系统程序,往显存写东西,是屏幕黑屏,然后再让CPU休眠
- 初步引入C语言,使用C语言编写操作系统
笔记
任务1:启动扇区的编写
思路:
需要从软盘中读出一个个扇区(前10个柱面两个磁头的所有扇区,共2 10 18 = 360个扇区,三重循环就行)到指定的内存区域:
- 0x08000 ~ 0x081ff,此部分为启动扇区(也就是0柱面,0磁头,1号扇区)。
- 0x08200 ~ 0x08200 + 360 * 512 (转换为16进制为2 D000) = 0x35200,根据内存分布图,这部分内存可以用,那就不用担心了
具体怎么读呢,通过下面的几个BIOS中断可以实现读软盘的操作。(具体参数直接看附录里面的参考吧,懒得写了)
示例1:
- 读取一个扇区(0磁头,0柱面的2号扇区)
```bash
init:
mov es, ax ; 不能直接给es赋值立即数 ; 下面这几个参数对应的寄存器不用背,查文档手册 mov ch, 0 ; 柱面号,0 mov dh, 0 ; 磁头号,0 mov cl, 2 ; 扇区号,2mov ax, 0x0820
; 开始读入扇区
readloop: mov si, 0 ; 错误计数,超过5次读取错误则直接退出
; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了 retry: mov ah, 0x02 ; 功能号,0x02 mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题) mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗 mov dl, 0x00 ; 驱动器号,0 int 0x13 ; 调用中断 jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败 add si, 1 ; 错误数加1 cmp si, 5 jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读 mov ax, 0x00 ; 0x00功能号,驱动器复位 mov dl, 0x00 ; 驱动器号, 0 int 0x13 ; 0x13中断
; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了
next: ; 接着读取其他的扇区
error: ; 错误处理
示例2:- 在上一个的基础上,实现读取一个柱面的18个扇区```bashinit:mov ax, 0x0820mov es, ax ; 不能直接给es赋值立即数; 下面这几个参数对应的寄存器不用背,查文档手册mov ch, 0 ; 柱面号,0mov dh, 0 ; 磁头号,0mov cl, 2 ; 扇区号,2; 开始读入扇区readloop:mov si, 0 ; 错误计数,超过5次读取错误则直接退出; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了retry:mov ah, 0x02 ; 功能号,0x02mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗mov dl, 0x00 ; 驱动器号,0int 0x13 ; 调用中断jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败add si, 1 ; 错误数加1cmp si, 5jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读mov ax, 0x00 ; 0x00功能号,驱动器复位mov dl, 0x00 ; 驱动器号, 0int 0x13 ; 0x13中断; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了next:; 接着读取其他的扇区; 读取一个柱面的18个扇区mox ax, esadd ax, 0x20mov es, ax ; 绝对地址后移512字节,实际上就是段地址; 解释; addr = es * 16 + bx; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bxadd cl, 1 ; 下一个扇区cmp cl, 18jbe readloop ; 没读完一个柱面的18个扇区则继续读error:; 错误处理
示例3(实际上就是我们最终需要的代码):实现读取10个柱面两个磁头的所有扇区
实际上,这里为什么不是先读完10个柱面,在换另一个磁头继续读。这两个地方不太清楚这些扇区有没有顺序要求。
init:mov ax, 0x0820mov es, ax ; 不能直接给es赋值立即数; 下面这几个参数对应的寄存器不用背,查文档手册mov ch, 0 ; 柱面号,0mov dh, 0 ; 磁头号,0mov cl, 2 ; 扇区号,2; 开始读入扇区readloop:mov si, 0 ; 错误计数,超过5次读取错误则直接退出; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了retry:mov ah, 0x02 ; 功能号,0x02mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗mov dl, 0x00 ; 驱动器号,0int 0x13 ; 调用中断jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败add si, 1 ; 错误数加1cmp si, 5jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读mov ax, 0x00 ; 0x00功能号,驱动器复位mov dl, 0x00 ; 驱动器号, 0int 0x13 ; 0x13中断; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了next:; 接着读取其他的扇区; 读取一个柱面的18个扇区mox ax, esadd ax, 0x20mov es, ax ; 绝对地址后移512字节,实际上就是段地址; 解释; addr = es * 16 + bx; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bxadd cl, 1 ; 下一个扇区cmp cl, 18jbe readloop ; 没读完一个柱面的18个扇区则继续读; 换到下一个磁头, 柱面号重置为0,扇区号重置为1mov cl, 1add dh, 1cmp dh, 2jb readloop;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0mov cl, 1mov dh, 0add ch, 1cmp ch, 10jb readlooperror:; 错误处理
最终的启动扇区程序:
; hxb-os; TAB=4; RESB指令会有个空间未初始化的告警,索性全换成times指令了; FAT32软盘格式化代码,不用记,找相关文档看格式就行,这里是直接复制随书文件的,注释也是直接照抄的,后面明白了再说ORG 0x7c00JMP entryDB 0x90DB "hxbos " ; 启动扇区名称,8字节DW 512 ; 单个扇区的大小DB 1 ; 簇的大小,必须为1个扇区DW 1 ; FAT文件的起始位置(一般从第一个扇区的第一个字节开始)DB 2 ; FAT的个数,2DW 224 ; 根目录的大小,一般设置为224项DW 2880 ; 磁盘的大小,必须为2880扇区DB 0xf0 ; 磁盘的种类DW 9 ; FAT的长度,必须是9扇区DW 18 ; 1个磁道几个扇区,必须为18DW 2 ; 磁头数必须为2DD 0 ; 不使用分区,必须为0DD 2880 ; 重写一次磁盘大小DB 0,0,0x29 ; 意义不明,固定DD 0xffffffff ; (可能是卷标号码)DB "HXB-OS " ; 磁盘的名称(11字节),不足加空格DB "FAT12 " ; 磁盘格式名称(8字节),不足加空格;RESB 18 ; reserve byte,预留18字节times 18 db 0x00; 程序主体,启动区的编写其实就是将之前的显示字符的核心程序换成加载扇区的核心程序entry:MOV AX,0 ; 初始化MOV SS,AXMOV SP,0x7c00MOV DS,AXmov ax, 0x0820mov es, ax ; 不能直接给es赋值立即数; 下面这几个参数对应的寄存器不用背,查文档手册mov ch, 0 ; 柱面号,0mov dh, 0 ; 磁头号,0mov cl, 2 ; 扇区号,2; 开始读入扇区readloop:mov si, 0 ; 错误计数,超过5次读取错误则直接退出; 读入一个扇区,就和平常编程时一样,指定函数,给定参数,然后调用就行了retry:mov ah, 0x02 ; 功能号,0x02mov al, 1 ; 读入的扇区数(实际编写直接连续读入多个扇区,好像是可能有点问题)mov bx, 0 ; 这个缓冲区的地址我也不太明白,既然是缓冲区,难道不用指定大小吗mov dl, 0x00 ; 驱动器号,0int 0x13 ; 调用中断jnc next ; 判断读取是否成功, 成功,读取下一个扇区。cf = 0,成功,否则失败add si, 1 ; 错误数加1cmp si, 5jae error ; 错误超过5次跳转到错误处理,否则重置驱动器重新读mov ax, 0x00 ; 0x00功能号,驱动器复位mov dl, 0x00 ; 驱动器号, 0int 0x13 ; 0x13中断; ================= 至此,读取一个启动扇区就完成了,对中断参数不太熟悉,写一遍就记住了next:; 接着读取其他的扇区; 读取一个柱面的18个扇区mov ax, esadd ax, 0x20mov es, ax ; 绝对地址后移512字节,实际上就是段地址; 解释; addr = es * 16 + bx; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bxadd cl, 1 ; 下一个扇区cmp cl, 18jbe readloop ; 没读完一个柱面的18个扇区则继续读; 换到下一个磁头, 扇区号重置为1mov cl, 1add dh, 1cmp dh, 2jb readloop;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0mov cl, 1mov dh, 0add ch, 1cmp ch, 10jb readloopfin:HLT ; CPU休眠JMP fin ; 无线循环error:; 错误处理,刚写的时候忘记写错误处理了,直接copy过来了,MOV SI,msgputloop:MOV AL,[SI]ADD SI,1 ;CMP AL,0JE finMOV AH,0x0e ; bios中断调用显示字符MOV BX,15 ;INT 0x10 ;JMP putloopmsg:DB 0x0a, 0x0a ;DB "load error"DB 0x0a ;DB 0; 随书资源的代码有一份是直接写到0x7dff的,但是那样引导扇区就没了,因为格式化软盘的代码在第一扇区。times 0x1fe-($-$$) db 0x00 ; 第一扇区末尾DB 0x55, 0xaa
Makefile文件编写(比较简单,不详细说了):
TOOLPATH = ..\..\tools\# TOOLPATH变量展开不知道为什么报个循环引用的错误,只能直接写在下面MAKE = ..\..\tools\make\make.exe -rMKIMAGE = ..\..\tools\mybximage\mkimage.exeDD = ..\..\tools\mydd\dd.exeNASM = ..\..\tools\nasm\nasm.exe# 虚拟机路径根据实际情况配置BOCHS = D:\Bochs-2.6.11\bochs.exe# windows自带命令COPY = copyDEL = del# 默认执行命令default:$(MAKE) wimg# 具体编译规则ipl.bin:ipl10.nas$(NASM) -o ipl.bin ipl10.nashxbos.img:$(MKIMAGE) -of hxbos.imgwrite_image:ipl.bin hxbos.img$(DD) -if ipl.bin -of hxbos.img -len 512run_bochs:bochsrc.bxrc$(BOCHS) -f bochsrc.bxrc -qclean:$(DEL) *.bin *.img *.lstboch_test:$(BOCHS)dd_test:$(DD)# 简化make命令用的ipl:$(MAKE) ipl.binimg:$(MAKE) hxbos.imgwimg:$(MAKE) write_imagerun:$(MAKE) ipl$(MAKE) img$(MAKE) wimg$(MAKE) run_bochs
boch配置文件:
# 内存分配32Mmegs:32# rom, 和操作系统中的rom是一个意思应该,存放指令代码的地方,CPU上电复位自检之后从这开始跑起来romimage:file=$BXSHARE/BIOS-bochs-latest# VGA rom, 应该是现存吧vgaromimage:file=$BXSHARE/VGABIOS-lgpl-latest# floppya,主软盘, floppya,floppyb...优先级依次降低floppya: image="hxbos.img", status=inserted# 从软盘启动boot:floppy# 日志输出文件位置log:bochsout.txt# 不启用鼠标mouse:enabled=0# 设置键盘keyboard: keymap=$BXSHARE/keymaps/x11-pc-de.map
目录结构:
运行:啥都没有,没事后边就有了。注意下,我们的程序是需要从引导扇区启动的,不能出现 no boot device..这类字样
任务2:操作系统程序的编写
fin:HLTjmp fin
任务3:启动扇区中执行操作系统程序
任务4:改进操作系统程序,是屏幕全黑,然后CPU进入休眠
任务5:引入C语言,编写一个休眠程序,需要通过汇编语言提供的接口使得CPU休眠
附录:
1. 1M内存的内存分布图:
2. 磁盘bios中断调用
bios中断参考:https://blog.csdn.net/weixin_37656939/article/details/79684611
3.windows下将文件保存到镜像文件中
这部分我也不是很清楚,大概说一下我的理解
书中说的是用作者自己写的edimg.exe工具,这类工具从大的方向说就是将一个文件的数据复制到另一个软盘镜像文件中,但是作者给的这个工具也没源码也没使用参数的说明,自己用的话基本只能照抄。
不过既然知道了这个工具做了什么,那就可以自己写了。不过自己写的话需要先去了解fat软盘文件的格式,不然你怎么知道你的文件名和文件内容该复制到哪个地方。有点难度,这就不自己写了。(我是不会说是我没把握能写出来的)。
windows环境下下个winImage,我自己的实现是在windows下实现的,所以从头到尾除非必须用到,否则我是不会去用linux下的工具的。(我是不会承认是自己懒得去装虚拟机的)。当然,tools文件夹中也有这个工具,解压直接用即可。
下面是将hxb.img保存到hxbos.img文件中的示例(看过一遍就会用了):
注:为了方便学习,day03中已编写好Makefile文件,通过下面两条命令即可生成
- 直接运行day03文件夹中的
make run生成即可生成hxbos.img软盘映像文件 - 运行
make hxb即可生成hxb.img文件。
- 打开软件

- 【File】->【open】打开
hxbos.img软盘文件。

- 插入
hxb.img文件
【Image】->【Inject】,然后选择要插入的文件hxb.img,弹出弹框确认你是否要插入,确认就行。
插入完毕后就会看到软盘映像文件中有个hxb.img文件了。并且通过十六进制编辑器Hex打开,也会发现0x2600地址处是有文件名的,在0x4200处就是文件的内容了。
十六进制编辑器中查看:

