任务概述:
- 启动扇区的编写,读入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个扇区
```bash
init:
mov ax, 0x0820
mov es, ax ; 不能直接给es赋值立即数
; 下面这几个参数对应的寄存器不用背,查文档手册
mov ch, 0 ; 柱面号,0
mov dh, 0 ; 磁头号,0
mov cl, 2 ; 扇区号,2
; 开始读入扇区
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:
; 接着读取其他的扇区
; 读取一个柱面的18个扇区
mox ax, es
add ax, 0x20
mov es, ax ; 绝对地址后移512字节,实际上就是段地址
; 解释
; addr = es * 16 + bx
; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
add cl, 1 ; 下一个扇区
cmp cl, 18
jbe readloop ; 没读完一个柱面的18个扇区则继续读
error:
; 错误处理
示例3(实际上就是我们最终需要的代码):实现读取10个柱面两个磁头的所有扇区
实际上,这里为什么不是先读完10个柱面,在换另一个磁头继续读。这两个地方不太清楚这些扇区有没有顺序要求。
init:
mov ax, 0x0820
mov es, ax ; 不能直接给es赋值立即数
; 下面这几个参数对应的寄存器不用背,查文档手册
mov ch, 0 ; 柱面号,0
mov dh, 0 ; 磁头号,0
mov cl, 2 ; 扇区号,2
; 开始读入扇区
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:
; 接着读取其他的扇区
; 读取一个柱面的18个扇区
mox ax, es
add ax, 0x20
mov es, ax ; 绝对地址后移512字节,实际上就是段地址
; 解释
; addr = es * 16 + bx
; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
add cl, 1 ; 下一个扇区
cmp cl, 18
jbe readloop ; 没读完一个柱面的18个扇区则继续读
; 换到下一个磁头, 柱面号重置为0,扇区号重置为1
mov cl, 1
add dh, 1
cmp dh, 2
jb readloop
;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0
mov cl, 1
mov dh, 0
add ch, 1
cmp ch, 10
jb readloop
error:
; 错误处理
最终的启动扇区程序:
; hxb-os
; TAB=4
; RESB指令会有个空间未初始化的告警,索性全换成times指令了
; FAT32软盘格式化代码,不用记,找相关文档看格式就行,这里是直接复制随书文件的,注释也是直接照抄的,后面明白了再说
ORG 0x7c00
JMP entry
DB 0x90
DB "hxbos " ; 启动扇区名称,8字节
DW 512 ; 单个扇区的大小
DB 1 ; 簇的大小,必须为1个扇区
DW 1 ; FAT文件的起始位置(一般从第一个扇区的第一个字节开始)
DB 2 ; FAT的个数,2
DW 224 ; 根目录的大小,一般设置为224项
DW 2880 ; 磁盘的大小,必须为2880扇区
DB 0xf0 ; 磁盘的种类
DW 9 ; FAT的长度,必须是9扇区
DW 18 ; 1个磁道几个扇区,必须为18
DW 2 ; 磁头数必须为2
DD 0 ; 不使用分区,必须为0
DD 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,AX
MOV SP,0x7c00
MOV DS,AX
mov ax, 0x0820
mov es, ax ; 不能直接给es赋值立即数
; 下面这几个参数对应的寄存器不用背,查文档手册
mov ch, 0 ; 柱面号,0
mov dh, 0 ; 磁头号,0
mov cl, 2 ; 扇区号,2
; 开始读入扇区
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:
; 接着读取其他的扇区
; 读取一个柱面的18个扇区
mov ax, es
add ax, 0x20
mov es, ax ; 绝对地址后移512字节,实际上就是段地址
; 解释
; addr = es * 16 + bx
; addr + 512 = es * 16 + bx + 512 = (es + 32) * 16 + bx = (es + 0x20) * 16 + bx
add cl, 1 ; 下一个扇区
cmp cl, 18
jbe readloop ; 没读完一个柱面的18个扇区则继续读
; 换到下一个磁头, 扇区号重置为1
mov cl, 1
add dh, 1
cmp dh, 2
jb readloop
;换到下一个柱面,柱面号加1,扇区号重置为每个柱面的开始扇区1,磁头号重置为0
mov cl, 1
mov dh, 0
add ch, 1
cmp ch, 10
jb readloop
fin:
HLT ; CPU休眠
JMP fin ; 无线循环
error:
; 错误处理,刚写的时候忘记写错误处理了,直接copy过来了,
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ;
CMP AL,0
JE fin
MOV AH,0x0e ; bios中断调用显示字符
MOV BX,15 ;
INT 0x10 ;
JMP putloop
msg:
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 -r
MKIMAGE = ..\..\tools\mybximage\mkimage.exe
DD = ..\..\tools\mydd\dd.exe
NASM = ..\..\tools\nasm\nasm.exe
# 虚拟机路径根据实际情况配置
BOCHS = D:\Bochs-2.6.11\bochs.exe
# windows自带命令
COPY = copy
DEL = del
# 默认执行命令
default:
$(MAKE) wimg
# 具体编译规则
ipl.bin:ipl10.nas
$(NASM) -o ipl.bin ipl10.nas
hxbos.img:
$(MKIMAGE) -of hxbos.img
write_image:ipl.bin hxbos.img
$(DD) -if ipl.bin -of hxbos.img -len 512
run_bochs:bochsrc.bxrc
$(BOCHS) -f bochsrc.bxrc -q
clean:
$(DEL) *.bin *.img *.lst
boch_test:
$(BOCHS)
dd_test:
$(DD)
# 简化make命令用的
ipl:
$(MAKE) ipl.bin
img:
$(MAKE) hxbos.img
wimg:
$(MAKE) write_image
run:
$(MAKE) ipl
$(MAKE) img
$(MAKE) wimg
$(MAKE) run_bochs
boch配置文件:
# 内存分配32M
megs: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:
HLT
jmp 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处就是文件的内容了。
十六进制编辑器中查看: