《30天自制操作系统》笔记-day2
本文最后更新于 2026年7月4日 凌晨
Day2
Day2,这一章主要讲述了寄存器的基础知识,汇编进阶指令,makefile的使用;并完成了一个512字节的启动程序
寄存器
寄存器是CPU中的存储电路,可以理解为物理上的变量
16位寄存器
| 16bit寄存器 | 含义 |
|---|---|
| AX | 累加寄存器 |
| CX | 计数寄存器 |
| DX | 数据寄存器 |
| BX | 基址寄存器 |
| SP | 栈指针寄存器 |
| BP | 基址指针寄存器 |
| SI | 源变址寄存器 |
| DI | 目的变址寄存器 |
每个寄存器有各自的优点,编译成机器语言后程序的简洁程度不同,比如AX寄存器有一组专属的短编码
以ADD为例:
- AX寄存器:编译后的机器语言只需要一个操作码字节不需要指定目标寄存器字节,就是AX寄存器
- 其他寄存器:编译后的机器语言除了操作码字节还有一个目标寄存器字节
1 | |
ModR/M 字节
规则:
以0xC1为例将其拆分为二进制:1100000111:Mod(寻址方式),11两个操作都是寄存器,00/01/10都与内存有关000:reg,扩展操作码,配合0x81使用也就是ADD=000001:R/M,目标寄存器,001代表CX寄存器0xC1翻译过来就是寄存器模式+ADD运算+目标寄存器为CX
寄存器的扩展
上述寄存器是16位寄存器,除指针寄存器以外都是8位寄存器拼接而来
| 16bit | 8bit | 含义 |
|---|---|---|
| AX | AH | 累加寄存器高8位 |
| AL | 累加寄存器低8位 | |
| CX | CH | 计数寄存器高8位 |
| CL | 计数寄存器低8位 | |
| BX | BH | 基址寄存器高8位 |
| BL | 基址寄存器低8位 | |
| DX | DH | 数据寄存器高8位 |
| DL | 数据寄存器低8位 |
指针寄存器不可拆分:Intel在设计8086时就没有给SP/BP/SI/DI提供8位拆分访问,属于硬件设计选择
32位寄存器
32位寄存器与上述类似,由16位寄存器拼接而来,但是又稍有区别
- 低16位名称是原16位寄存器名称
- 高16位无名
| 32bit | 16bit | 含义 |
|---|---|---|
| EAX | 无名 | 累加寄存器高16位 |
| AX | 累加寄存器低16位 | |
| ECX | 无名 | 计数寄存器高16位 |
| CX | 计数寄存器低16位 | |
| EDX | 无名 | 数据寄存器高16位 |
| DX | 数据寄存器低16位 | |
| EBX | 无名 | 基址寄存器高16位 |
| BX | 基址寄存器低16位 | |
| ESP | 无名 | 栈指针寄存器高16位 |
| SP | 栈指针寄存器低16位 | |
| EBP | 无名 | 基址指针寄存器高16位 |
| BP | 基址指针寄存器低16位 | |
| ESI | 无名 | 源变址寄存器高16位 |
| SI | 源变址寄存器低16位 | |
| EDI | 无名 | 目的变址寄存器高16位 |
| DI | 目的变址寄存器低16位 |
汇编语言
ORG
开始执行的时候,把机器语言装载到内存的哪个地址
同时[[Day1]]中的$意思也随之改变,不是文件的第几个字节,代表将要读入的内存地址
规定:0x00007c00-0x00007dff :启动区内容的装载地址
对于启动区程序,ORG必须指定为0x7c00
JMP
跳转,类似于c语言中的goto语句
MOV
功能上来说类似于赋值
1 | |
标号
entry:,msg:这些都是标号,与JMP搭配使用,可以跳转到指定的目的地
- 每一个标号都是一个对应内存地址,这个地址是基于
ORG计算出来的 - 相当于是一个别名,用别名代替内存地址方便编码
在本程序中entry对应地址0x7c50
1 | |
[]符号
- 方括号代表内存地址
- 可以通过
BYTE,WORD,DWORD来控制字节长度
例:
1 | |
BYTE:操作一个字节- 如果
BYTE换成WORD,就会同时操作两个字节,123转换为16位二进制,低8位存储在[678]地址,高8位存储在[679]地址
- 如果
678:内存地址- 这里涉及到一个地址的解引用,类似于C语言的指针
*p类似于[678]p类似于678
- 内存地址可以是常数,可以是寄存器;比如SI寄存器的值是678(一个内存地址),
[SI]就是取地址678处存储的值(假设为 123)
- 这里涉及到一个地址的解引用,类似于C语言的指针
123:存储在内存地址里的值
只有BX/BP/SI/DI才能做指向内存地址,并且完成[]的解引用
其他寄存器只能用作存储地址数值,但是不能解引用
再来看程序中的代码,对SI进行解引用,也就是获取对应地址存储的值,将其一字节截取并赋值给AL
1 | |
ADD
加法指令
给SI+1,这里是对寄存器本身的值做加法;不要被上一小节影响,这里是单纯的寄存器操作,不涉及内存地址
1 | |
CMP
比较指令
相当于把AL寄存器中的数值与后面的常数做比较
1 | |
JE
跳转指令的一种
如果相等就做跳转,不等则不跳转
1 | |
CMP和JE相结合就相当于是一个C语言的if语句
1 | |
INT
软件中断指令
程序员主动写指令,手动触发中断,用来调用 BIOS 内置函数,相当于一套底层系统调用接口,和嵌入式硬件中断不是一回事,只是底层机制同源。
参考网站
1 | |
AH设置为0x0e,显示字符
BX指定了显示字符的颜色INT调用BIOS中的函数,实现字符显示
HLT
HLT是让CPU停止动作的指令,不过并不是彻底地停止(如果要彻底停止CPU的动作,只能切断电源),而是让CPU进入待机状态。
启动区程序
ipl.nas
1 | |
程序的流程
- 指定输出字符串的首地址
- 在循环里面循环输出每个字符
- 直到指针指向的内容为0
- HLT指令CPU停止
启动区
当前程序将作为一个512字节的启动区
改写asm.bat
1 | |
输出的ipl.bin只有512字节
编辑makeimg.bat
1 | |
输出镜像后run即可
Makefile
上述使用bat脚本过于麻烦,可以使用Makefile文件整合起来,把所有操作整合到一个文件,一气呵成完成镜像制作
1 | |
使用
作者准备了一个make脚本,tolset\z_new_w\make.bat
复制到makefile同级文件夹下
执行
1 | |
run启动与bat生成的img效果一样
但是现在还是需要加参数-r [文件名]
可以通过在Makefile里面添加命令来实现更简洁的编译
在之前的文件追加即可,因为我没有软盘所以就没有写install
1 | |
运行
1 | |
问题
- 为什么Day1中
RESB 0x1FE-$和RESB 0x7DFE-$不一样但是在十六进制编辑器里面启动标签0x55 0xAA的偏移量都一样
Day1 无 ORG 时$=文件偏移
Day2有ORG 0x7c00后$=内存地址,两者差了一个0x7c00的偏移,所以0x1FE - 文件偏移和0x7DFE - 内存地址结果相同。
小结
Day2的核心收获
- 寄存器的基础知识
- 汇编语言的进阶指令
MOV/JMP/ADD等等 - Makefile的基础使用方法
- Day1与Day2的
$符的区别