代码仓库地址:https://github.com/freedom-xiao007/operating-system
简介
上篇中我们成功将软盘数据读取到内存并显示到屏幕上,接下来我们将加载其他的文件并执行文件代码
新增软件说明
本篇中需要用到相应的软件,Windows相关的下载链接如下:
- git for windows:在本篇中需要拼接其他文件放到镜像文件后面,本文使用的linux下的dd命令,使用git的bash窗口可以使用dd命令,比较方便
程序整体思路
本次我们将写一个新的nasm文件,其功能是清屏并输出文字:Start Loader
在上篇的程序读入内容后,调用我们新的nasm文件,然后执行对应的代码
经过这些操作后,我们需要运行的命令也相对比较多了,我们将其整合到一个脚本里面,方便一键运行
大致的步骤如下:
-
- 新nasm文件编写
-
- 原启动nasm程序读取软盘数据,执行新的nasm文件代码
-
- 将相关的命令整合成脚本,方便一键运行
1. 新nasm文件编写
想着比较简单,但实际操作还是出现了点问题
首先是ORG的地址问题,原来是跟着《30天写操作系统》书中使用程序加载到内存中的地址,但导致在显示字符串时有问题,经过不断尝试,感觉是地址没有对应上,导致显示了空白字符(知识掌握程度还是不够深,目前还是凭感觉猜测)
后面使用了《一个64位操作系统的设计和实现》书的地址,设置比较大的地址,成功显示了,所以猜测是地址的问题
其他的就是从《汇编语言程序设计》书中抄了一个清屏的代码
loader.asm文件完整代码如下:
;加载到一块无人用的地址
ORG 10000h
JMP print_load_info
print_load_info:
;清屏
MOV AH,6
MOV AL,0
MOV CH,0
MOV CL,0
MOV DH,26
MOV DL,79
MOV BH,7
INT 10H
MOV AX, CS
MOV DS, AX
MOV ES, AX
MOV AX, 0x00
MOV SS, AX
MOV SP, 0x7c00
;======= display on screen : Start Loader......
MOV AX, 1301h
MOV BX, 000fh
MOV DX, 0200h ;row 2
MOV CX, 12
PUSH AX
MOV AX, DS
MOV ES, AX
POP AX
MOV BP, StartLoaderMessage
INT 10h
JMP fin
fin:
HLT ;CPU停止,等待指令
JMP fin ;无限循环
;======= display messages
StartLoaderMessage: db "Start Loader"
2. 原启动nasm程序读取软盘数据,执行新的nasm文件代码
需要对我们原来的主文件进行调整
首先是对加载扇区的调整,原来我们是从扇区1开始读取的,现在改为2。因为扇区1是启动引导扇区,存放的就是我们的主文件,但不需要这个,所以我们直接从扇区2开始读取我们的新的文件到内存中
对应的就是把read_file_ready中的CL改为2
read_file_ready: ;读软盘准备
MOV AH, 02 ;指明读扇区功能调用
MOV AL, 1 ;指明要读的扇区数为1
MOV DL, 0x00 ;指明要读的驱动为A
MOV DH, 0 ;指定读取的磁头0
MOV CH, 0 ;柱面0
MOV CL, 2 ;原来我们是从扇区1开始读取的,现在改为2。因为扇区1是启动引导扇区,存放的就是我们的主文件,但不需要这个,所以我们直接从扇区2开始读取我们的新的文件到内存中
;下面三句指定读取到内存地址0x0820处
MOV AX, OffSetOfLoader
MOV ES, AX
MOV BX, 0 ;数据读取后放到的内存地址
最后是读取完成后进行调用,汇编里面好像是直接跳转到相关的内存地址后,指令IP就会自动处理
跳转这里的地址问题是需要注意的,需要给出完成的地址
我们直接修改fin即可:
fin:
;下面三句指定读取到内存地址0x0820处
JMP OffSetOfLoader:0
3. 将相关的命令整合成脚本,方便一键运行
我们新建一个run.sh的脚本,然后将我们需要运行的命令都写到里面去,如下:
# !bash
D:\\software\\NASM\\nasm.exe .\\myOS.asm -o .\\myOS.img
D:\\software\\NASM\\nasm.exe .\\loader.asm -o .\\loader.bin
cp myOS.img os.img
dd if=./loader.bin of=./os.img oflag=append conv=notrunc
D:\\software\\qemu\\qemu-system-x86_64.exe -L . -m 64 -fda .\\os.img
上面的脚本功能是:
-
- 将主文件myOS.asm进行编译
-
- 将文件loader.asm进行编译
-
- 将编译后的myOS.img复制一份
-
- 使用dd命令,将loader.bin文件拼接到os.img文件后面
-
- 最后运行即可
在安装完git后,可以在根目录中代码git bash,然后运行run.sh脚本即可
如下图:运行git bash、运行脚本、成功显示
总结
终于快要进行到使用C或者其他语句进行操作系统编写的进程了,泪目
写一个引导从这几天看来确实是挺麻烦的,也难怪用rust写操作系统中直接使用grub之类的引导
虽然过程艰辛,但结果是令人振奋的,根据书中的描述内存,后面会相对好点,虽然预感还是艰辛,但加油
本次完整代码
myOs.asm文件
; cherry-os
ORG 0x7c00 ;指定程序装载的位置
;下面用于描述FAT12格式的软盘
JMP entry
DB 0x90
DB "CHRRYIPL" ;启动区的名称可以是任意的字符串,但长度必须是8字节
DW 512; 每一个扇区的大小,必须是512字节
DB 1 ;簇的大小(必须为1个扇区)
DW 1 ;FAT的起始位置(一般从第一个扇区开始)
DB 2 ;FAT的个数 必须是2
DW 224;根目录的大小 一般是224项
DW 2880; 该磁盘的大小 必须是2880扇区
DB 0xf0;磁盘的种类 必须是0xf0
DW 9;FAT的长度 必须是9扇区
DW 18;1个磁道(track) 有几个扇区 必须是18
DW 2; 磁头个数 必须是2
DD 0; 不使用分区,必须是0
DD 2880; 重写一次磁盘大小
DB 0,0,0x29 ;扩展引导标记 固定0x29
DD 0xffffffff ;卷列序号
DB "CHERRY-OS " ;磁盘的名称(11个字节)
DB "FAT12 " ;磁盘的格式名称(8字节)
TIMES 18 DB 0; 先空出18字节 这里与原文写法不同
OffSetOfLoader equ 0x0820
;程序核心
entry:
MOV AX,0 ;初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
read_file_ready: ;读软盘准备
MOV AH, 02 ;指明读扇区功能调用
MOV AL, 1 ;指明要读的扇区数为1
MOV DL, 0x00 ;指明要读的驱动为A
MOV DH, 0 ;指定读取的磁头0
MOV CH, 0 ;柱面0
MOV CL, 2 ;原来我们是从扇区1开始读取的,现在改为2。因为扇区1是启动引导扇区,存放的就是我们的主文件,但不需要这个,所以我们直接从扇区2开始读取我们的新的文件到内存中
;下面三句指定读取到内存地址0x0820处
MOV AX, OffSetOfLoader
MOV ES, AX
MOV BX, 0 ;数据读取后放到的内存地址
read_file:
;调试用,用于查看读取磁盘是否达到了循环次数,call相当于函数调用
MOV SI, read_file_msg
CALL func_show_msg
;前面调用函数时,改变了有关的寄存器,这里重置回来(也可以使用栈,但这个方便点)
MOV AH, 02 ;指明读扇区功能调用
MOV AL, 1 ;指明要读的扇区数为1
MOV BX, 0 ;数据读取后放到的内存地址
INT 13H ;调用BIOS文件读取
JNC read_file_loop
MOV SI, read_file_error_msg
JMP show_msg_info
read_file_loop: ;循环读取内容,循环的顺序是扇区->磁头->柱面,不知道这里面是否有什么说法?可以调换吗?
;把内存地址后移0x200
MOV AX, ES
ADD AX, 0x0020
MOV ES, AX
ADD CL, 1 ;加1,读取下一个扇区
CMP CL, 18 ;如果CL扇区大于软盘总扇区数18,则说明读取完成,不再读取
JBE read_file
; 上面是扇区的循环,完毕后到磁头的循环,重置扇区,增加磁头到反面1
MOV CL, 1
ADD DH, 1
CMP DH, 2
JB read_file
;上面都完成后,到柱面的读取循环,重置扇区(前面已重置)和磁头,增加柱面(不知道为啥书中不是80而是10)
MOV DH, 0
ADD CH, 1
CMP CH, 19
JB read_file
show_mem_file: ;打印显示刚才加载的文件内容
MOV AX, OffSetOfLoader
; MOV AX, 0x0a20
MOV ES, AX
MOV DI, 0
show_mem_byte: ;整个数据也就512,所以循环512次即可
MOV AL, BYTE [ES:DI]
CMP DI, 512
JE loader_end
ADD DI, 1
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
JMP show_mem_byte
loader_end: ;启动程序加载完成
MOV AL,0x0a
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
MOV SI,msg
show_msg_info: ;加载完成,成功显示
MOV AL,[SI]
ADD SI,1
CMP AL,0
JE fin
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
JMP show_msg_info
fin:
; HLT ;CPU停止,等待指令
; JMP fin ;无限循环
MOV SI, jmp_msg
CALL func_show_msg
;下面三句指定读取到内存地址0x0820处
JMP OffSetOfLoader:0
func_show_msg:
MOV AL,[SI]
ADD SI,1
CMP AL,0
JE func_ret
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
JMP func_show_msg
func_ret:
RET
read_file_msg:
DB "r"
DB 0
read_file_error_msg:
DB 0x0a , 0x0a ;换行两次
DB "read file error!!!"
DB 0x0a
DB 0
show_mem_info_msg:
DB 0x0a , 0x0a ;换行两次
DB "start show mem file!!!"
DB 0x0a
DB 0
msg:
DB 0x0a , 0x0a ;换行两次
DB "hello, my OS, boot loader end"
DB 0x0a
DB 0
jmp_msg:
DB 0x0a ;换行两次
DB "start jmp loader bin"
DB 0
boot_flag: ;启动区标识
TIMES 0x1fe-($-$$) DB 0 ;填写0x00,直到0x001fe
DB 0x55, 0xaa
loader.asm 文件
;加载到一块无人用的地址
ORG 10000h
JMP print_load_info
print_load_info:
;清屏
MOV AH,6
MOV AL,0
MOV CH,0
MOV CL,0
MOV DH,26
MOV DL,79
MOV BH,7
INT 10H
MOV AX, CS
MOV DS, AX
MOV ES, AX
MOV AX, 0x00
MOV SS, AX
MOV SP, 0x7c00
;======= display on screen : Start Loader......
MOV AX, 1301h
MOV BX, 000fh
MOV DX, 0200h ;row 2
MOV CX, 12
PUSH AX
MOV AX, DS
MOV ES, AX
POP AX
MOV BP, StartLoaderMessage
INT 10h
JMP fin
fin:
HLT ;CPU停止,等待指令
JMP fin ;无限循环
;======= display messages
StartLoaderMessage: db "Start Loader"