《30天自制操作系统》 01-03 从汇编到C语言
01-03-Day-Note
第一天到第三天的笔记
1. 运行环境准备
中文源码(包含每章节最终的翻译代码):https://github.com/yourtion/30dayMakeOS
可以直接在作者提供的开发包中执行作者提供的文件,执行过程如下:
- 将对应
project
中包含源代码的目录复制到tolset
中 - 执行对应的
Makefile
指令
需要在Windows的Shell环境下执行,在Git Bash执行会存在命令的不一致问题
作者在nasm
的基础上开发了名为nask
的汇编编译器,然后通过imgtol
制作img镜像文件,最后通过qemu
虚拟机运行此镜像文件,作者已经写好所有样例代码以及运行的脚本文件(批处理文件和Makefile)
上面所有的作者工具都在书本配套文件的tolset
中
当然以上基础编译工具都可以使用已有工具代替(作者的类似很古老了)
其他工具替代
使用
nasm
替代nask
1
nasm infile.asm -o outfile.img
使用最新的
qemu
替代(此处为qemu4)qemu
基本命令行参数参考 https://www.datarelab.com/blog/Technical_literature/562.html1
qemu-system-i386 -fda youros.img
代替作者的
imgtol
可以使用Linux的
dd
命令替换 Windows 下可在此处下载:http://www.chrysocome.net/downloaddd
是类似cp
的一个工具,不过dd
针对的是块而cp针对的是文件可参考:http://blackblog.tech/2018/07/19/CreateOSDay3/#comments
2. 汇编
i. 寄存器
16 位寄存器
名字 | 功能 |
---|---|
AX | accumulator, 累加寄存器 |
CX | counter, 计数寄存器 |
DX | data, 数据寄存器 |
BX | base, 基址寄存器 |
SP | stack pointer, 栈指针寄存器 |
BP | base pointer, 基址指针寄存器 |
SI | source index, 源变址寄存器 |
DI | destination index, 目的变址寄存器 |
8 位寄存器
名字 | 功能 |
---|---|
AL | 累加寄存器低位(accumulator low) |
CL | 计数寄存器低位(counter low) |
DL | 数据寄存器低位(data low) |
BL | 基址寄存器低位(base low) |
AH | 累加寄存器高位(accumulator high) |
CH | 计数寄存器高位(counter high) |
DH | 数据寄存器高位(data high) |
BH | 基址寄存器高位(base high) |
段寄存器
名字 | 功能 |
---|---|
ES | 附加段寄存器(extra segment) |
CS | 代码段寄存器(code segment) |
SS | 栈段寄存器(stack segment) |
DS | 数据段寄存器(data segment) |
FS | 没有名称(segment part 2) |
GS | 没有名称(segment part 3) |
32 位拓展寄存器
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI (加了个extend拓展的标签)
ii. 基础指令
DB:按字节定义类似的还有DW(定义字),DD(定义双字)
MOV:移动指令
mov A, B
意为A = B
,mov
后的寄存器或者字面量加上[]
则代表引用此地址的值如:
MOV AL, BYTE [BX]
,会将指定段寄存器乘16倍将上BX的值形成目标地址BX、 BP、 SI、 DI这几个。 剩下的AX、 CX、 DX、 SP不能用来指定内存地址
JMP:跳转指令 跳转到指定内存地址
INT:软件中断指令 后接中断号,调用BIOS预设的函数(功能)
JE:相等则跳转(工具FLAGS寄存器的标志寄存器的值跳转)
CMP:比较两个寄存器(书面量)的值,修改对应的标志寄存器
HLT:让CPU进入待机状态只要外部发生变化, 比如按下键盘, 或是移动鼠标, CPU就会醒过来, 继续执行程序
RESB:填充指定数量字节的0x00
ORG:将指令加载到指定位置,详情可见:https://blog.csdn.net/yuduoluogongwu/article/details/7359242
iii. NASM 和 NASK 的区别
nask 和 nasm 部分语法不同,差别如下:
NASK代码 | NASM代码 |
---|---|
JMP entry | JMP SHORT entry |
RESB <填充字节数> | TIMES <填充字节数> DB <填充数据> |
RESB 0x7dfe-$ | TIMES 0x1fe-($-$$) DB 0 |
ALIGNB 16 | ALIGN 16, DB 0 |
在文中出现了美元符代表的意思如下:
1 | $ 是当前位置 |
3. Makefile
Makefile就像是一个非常聪明的批处理文件
具体操作说明可参考:http://www.ruanyifeng.com/blog/2015/02/make.html
4. IPL
软盘 FAT12
作者使用的是格式为FAT12
格式的软盘
用Windows或MS-DOS格式化出来的软盘就是这种格式。 作者的helloos也采用了这种格式, 其中容纳了作者开发的操作系统。 这个格式兼容性好, 在Windows上也能用, 而且剩余的磁盘空间还可以用来保存自己喜欢的文件。
1张软盘有80个柱面, 2个磁头, 18个扇区, 且一个扇区有512字节。 所以, 一张软盘的容量是: 80×2×18×512 = 1474560 Byte = 1440KB
启动区
(boot sector) 软盘第一个的扇区称为启动区。 那么什么是扇区呢? 计算机读写软盘的时候, 并不是一个字节一个字节地读写的, 而是以512字节为一个单位进行读写。 因此,软盘的512字节就称为一个扇区。 一张软盘的空间共有1440KB, 也就是1474560字节, 除以512得2880, 这也就是说一张软盘共有2880个扇区。 那为什么第一个扇区称为启动区呢? 那是因为计算机首先从最初一个扇区开始读软盘, 然后去检查这个扇区最后2个字节的内容。如果这最后2个字节不是0x55 AA, 计算机会认为这张盘上没有所需的启动程序, 就会报一个不能启动的错误。 (也许有人会问为什么一定是0x55AA呢? 那是当初的设计者随便定的, 笔者也没法解释) 。 如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA, 那它就认为这个扇区的开头是启动程序, 并开始执行这个程序。
IPL 启动程序装载器
initial program loader的缩写。 启动程序加载器。 启动区只有区区512字节, 实际的操作系统不像hello-os这么小, 根本装不进去。 所以几乎所有的操作系统, 都是把加载操作系统本身的程序放在启动区里的。 有鉴于此, 有时也将启动区称为IPL。 但hello-os没有加载程序的功能, 所以HELLOIPL这个名字不太顺理成章。 如果有人正义感特别强, 觉得“这是撒谎造假, 万万不能容忍! ”, 那也可以改成其他的名字。 但是必须起一个8字节的名字, 如果名字长度不到8字节的话, 需要在最后补上空格
制作 IPL
计算机加载操作系统的流程如下:
- 从特定位置读取操作系统数据(USB或者软盘,软盘已经淘汰了),但这里使用的是软盘
- 软盘的第一个512字节的扇区作为启动区,执行此启动区指令
- 该启动区将软盘内容加载到内存指定位置(0x7c00)运行,根据最后两字节判断是否是启动区
文中的IPL加载了软盘的10个柱面
文中的IPL如下:
1 | ; haribote-ipl |
最后以0x55aa结尾说明是启动区
该启动区代码包含了试错,循环读取扇区和柱面
主要注意:
第41行:
MOV AX,0x0820
这段是把第一个柱面的第二个扇区(第一个为启动扇区),加载到内存
0x8200
的位置,0x13通过段寄存器ES和BX设置,这里ES为0x0820
需要扩大16倍即为0x8200
这里BIOS将系统启动代码(第一个扇区)加载到
0x8000
处,然后我们的IPL加载之后的扇区,所以将AX赋值为0x0820
然后在赋值给ES第82行:
JMP 0xc200
这里是启动区代码执行成功后,跳转到
0xc200
处执行代码我们的真正的OS代码保留在软盘的
0x4200
的位置,软盘的第一个扇区的位置是0x8000
所以有0x8000+0x4200 = 0xc200
,所以跳转到此位置0x4200
是因为向软盘写文件时一帮保存到此位置第107行:
RESB 0x7dfe-$ ; 填写0x00直到0x001fe
只是将启动区后续部分填充为0
0x7dfe = 0x7c00 + 511
得到,表示512字节的启动区
5. 导入C语言
文章中将C语言代码bootpack.c
编译为32位汇编,要使用C语言,在操作系统中必然是C语言和汇编是混合复用的,所以需要专门的代码进行链接,文章中给出的是asmhead.nas
,这里进行了对显卡显示模式的设置,以及对C语言的导入操作,可以到此文件中看一看,作者给出了很清楚的注释。(中文代码:https://github.com/yourtion/30dayMakeOS/blob/master/03_day/)
对C语言的处理作者分为以下几步:
- 使用cc1.exe从bootpack.c生成bootpack.gas
- 使用gas2nask.exe从bootpack.gas生成bootpack.nas
- 使用nask.exe从bootpack.nas生成bootpack.obj
- 使用obj2bim.exe从bootpack.obj生成bootpack.bim
- 使用bim2hrb.exe从bootpack.bim生成bootpack.hrb
- 这样就做成了机器语言, 再使用copy指令将asmhead.bin与bootpack.hrb单纯结合到起来, 就成了haribote.sys
cc1
是C编译器, 将C语言代码编译为32位的GAS的汇编代码
gas2nask
是将gas汇编编译为nasm识别的汇编格式了,通过nask(nasm)编译位OBJ目标文件
obj2bim
将目标文件编译为二进制镜像文件,方便不同的目标文件进行合并
bim2hrb
将最后的合并目标文件编译为hrb
文件(这个是适合作者的这个编译环境的最终二进制文件)
C语言调用汇编
1 | ; naskfunc |
将此文件的obj
文件和C语言的obj
文件一起编译为bim
即可(使用作者自带的工具)
有几个需要注意的地方:
- 需要和C语言链接的函数都需要标识为
GLOBAL
,反义为LOCAL
- 导出的函数需要前加
_
,这样才能和C语言链接,C语言编译后的函数会加_
可以看看作者的Makefile
可以更好的明白整个编译的过程