OSASK学习第3天 进入32位模式并导入C语言
1 制作真正的IPL
BIOS 在启动时,会自动将引导扇区(512字节)读取到内存的 0x7c00 地址处,然后跳转到 0x7c00 开始执行。
将软盘上的第二个扇区(512字节)加载到内存的 0x8200 地址处,为下一步执行更大的程序做准备。
MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH,0 ; 磁头0 MOV CL,2 ; 扇区2 MOV AH,0x02 ; AH=0x02 : 读盘 MOV AL,1 ; 1个扇区 MOV BX,0 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JC errorJC: 是“jump if carry”的缩写,意思是如果进位标志(carry flag)是1的话,就跳转。
❏磁盘读、写,扇区校验(verify),以及寻道(seek)
AH=0x02;(读盘)
AH=0x03;(写盘)
AH=0x04;(校验)
AH=0x0c;(寻道)
AL=处理对象的扇区数;(只能同时处理连续的扇区)CH=柱面号 &0xff;
CL=扇区号(0-5位)|(柱面号&0x300)>>2;
DH=磁头号;
DL=驱动器号;
ES:BX=缓冲地址;(校验及寻道时不使用)返回值:
FLACS.CF == 0:没有错误,AH == 0
FLAGS.CF == 1:有错误,错误号码存入AH内(与重置(reset)功能一样)
我们这次用的是AH=0x02,是“读盘”的意思。
x86 实模式(1MB 空间)内存布局速查表
| 地址范围 | 空间大小 | 归属 / 主要用途 | 引导程序能否写入? |
|---|---|---|---|
0x00000-0x003FF | 1 KB | 中断向量表(IVT) CPU 处理键盘、时钟、磁盘中断时的跳转表。 | ❌绝对禁区 覆盖会导致中断机制崩溃,系统立即死机。 |
0x00400-0x004FF | 256 B | BIOS 数据区(BDA) 存放键盘状态、启动盘号、显存模式等。 | ❌绝对禁区 BIOS 依赖它维护硬件状态,乱写会导致外设异常。 |
0x00500-0x7BFFF | ~495 KB | 常规可用内存(用户区) 可自由加载内核、文件系统缓存等。 | ✅安全地带 常见加载地址如 0x500(DOS 常驻)、0x8000等。 |
0x7C00-0x7DFF | 512 B | 引导扇区运行区 你当前这 512 字节代码正驻留在此。 | ⚠️极度危险 这就是你自己!覆盖等于“开枪自杀”,程序立即跑飞。 |
0x7E00-0x9FBFF | ~150 KB | 常规可用内存(用户区) 引导扇区之后的预留加载区。 | ✅最推荐地带 你的 0x8200就落在这里,完美避开了栈(向下增长)和当前代码。 |
0x9FC00-0x9FFFF | 1 KB | EBDA(扩展 BIOS 数据区) 通常由 BIOS 用作高级电源管理(ACPI)等。 | ⚠️建议回避 虽然物理可写,但可能覆盖 BIOS 运行时变量,导致关机异常。 |
0xA0000-0xBFFFF | 128 KB | VGA 显存映射区 显卡帧缓冲(文本模式 0xB8000,图形模式0xA0000)。 | ❌硬件冲突 写入会直接改变屏幕显示(花屏或乱码),无法存储程序代码。 |
0xC0000-0xFFFFF | 256 KB | BIOS 固件 / 扩展 ROM 主板 BIOS、显卡 BIOS、硬盘控制器固件。 | ❌物理只读(或映射) 写入无效或直接触发硬件异常(#GP 保护错误)。 |
📌 记忆口诀
- 底部(1KB):BIOS/CPU 内部表,打死别碰。
- 中部(640KB - 1KB):你家菜地,随便种(只要你记得种子埋哪了)。
- 顶部(384KB):硬件厂房(显存、固件),只看不摸。
- 0x7C00(512B):你自己脚下,挪窝之前别踩空。
harib00a:
; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序装载地址 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code JMP entry DB 0x90 DB "HELLOIPL" ; 启动扇区名称(8字节) DW 512 ; 每个扇区(sector)大小(必须512字节) DB 1 ; 簇(cluster)大小(必须为1个扇区) DW 1 ; FAT起始位置(一般为第一个扇区) DB 2 ; FAT个数(必须为2) DW 224 ; 根目录大小(一般为224项) DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) DB 0xf0 ; 磁盘类型(必须为0xf0) DW 9 ; FAT的长度(必??9扇区) DW 18 ; 一个磁道(track)有几个扇区(必须为18) DW 2 ; 磁头数(必??2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明(固定) DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX ; 读磁盘 MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH,0 ; 磁头0 MOV CL,2 ; 扇区2 MOV AH,0x02 ; AH=0x02 : 读盘 MOV AL,1 ; 1个扇区 MOV BX,0 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JC error JMP fin error: MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; 给SI加1 CMP AL,0 JE fin MOV AH,0x0e ; 显示一个文字 MOV BX,15 ; 指定字符颜色 INT 0x10 ; 调用显卡BIOS JMP putloop fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 msg: DB 0x0a, 0x0a ; 换行两次 DB "load error" DB 0X07 DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 填写0x00直到0x001fe DB 0x55, 0xaa2 试错
这一节就是添加了判断是否读磁盘出错的代码,防止读出错误的数据。
harib00b
; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序装载地址 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code JMP entry DB 0x90 DB "HELLOIPL" ; 启动扇区名称(8字节) DW 512 ; 每个扇区(sector)大小(必须512字节) DB 1 ; 簇(cluster)大小(必须为1个扇区) DW 1 ; FAT起始位置(一般为第一个扇区) DB 2 ; FAT个数(必须为2) DW 224 ; 根目录大小(一般为224项) DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) DB 0xf0 ; 磁盘类型(必须为0xf0) DW 9 ; FAT的长度(必??9扇区) DW 18 ; 一个磁道(track)有几个扇区(必须为18) DW 2 ; 磁头数(必??2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明(固定) DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX ;读磁盘 MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH, 0 ; 磁头0 MOV CL, 2 ; 扇区2 MOV SI,0 ; 记录失败次数的寄存器 retry: MOV AH,0x02 ; AH=0x02 : 读入磁盘 MOV AL,1 ; 1个扇区 MOV BX,0 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JNC fin ; 没出错的话跳转到fin ADD SI,1 ; 往SI加1 CMP SI,5 ; 比较SI与5 JAE error ; SI >= 5时,跳转到error MOV AH,0x00 MOV DL,0x00 ; A驱动器 INT 0x13 ; 重置驱动器 JMP retry error: MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; 给SI加1 CMP AL,0 JE fin MOV AH,0x0e ; 显示一个文字 MOV BX,15 ; 指定字符颜色 INT 0x10 ; 调用显卡BIOS JMP putloop fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 msg: DB 0x0a, 0x0a ; 换行两次 DB "load error" DB 0X07 DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 填写0x00直到0x001fe DB 0x55, 0xaaJNC是另一个条件跳转指令,是“Jump if not carry”的缩写。也就是说进位标志是0的话就跳转。
JAE也是条件跳转,是“Jump if above or equal”的缩写,意思是大于或等于时跳转。
AH=0x00
DL=0x00
INT 0x13
// 这是“系统复位”。它的功能是复位软盘状态,再读一次。
3 读到18扇区
这一节添加的代码通过一个循环连续读出了18个扇区
; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序装载地址 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code JMP entry DB 0x90 DB "HELLOIPL" ; 启动扇区名称(8字节) DW 512 ; 每个扇区(sector)大小(必须512字节) DB 1 ; 簇(cluster)大小(必须为1个扇区) DW 1 ; FAT起始位置(一般为第一个扇区) DB 2 ; FAT个数(必须为2) DW 224 ; 根目录大小(一般为224项) DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) DB 0xf0 ; 磁盘类型(必须为0xf0) DW 9 ; FAT的长度(必??9扇区) DW 18 ; 一个磁道(track)有几个扇区(必须为18) DW 2 ; 磁头数(必??2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明(固定) DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX ;读磁盘 MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH, 0 ; 磁头0 MOV CL, 2 ; 扇区2 readloop: MOV SI,0 ; 记录失败次数的寄存器 retry: MOV AH,0x02 ; AH=0x02 : 读入磁盘 MOV AL,1 ; 1个扇区 MOV BX,0 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JNC next ; 没出错的话跳转到fin ADD SI,1 ; 往SI加1 CMP SI,5 ; 比较SI与5 JAE error ; SI >= 5时,跳转到error MOV AH,0x00 MOV DL,0x00 ; A驱动器 INT 0x13 ; 重置驱动器 JMP retry next: MOV AX,ES ;把内存地址后移0x0200 ADD AX,0X0020 ; MOV ES,AX ;因为没有ADD ES,0X0020指令,座椅这里稍微绕个弯 ADD CL,1 ;往CL里加1 CMP CL,18 ;比较CL和18 JBE readloop ;如果 CL <= 18 跳转至readloop读下一个扇区 fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 error: MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; 给SI加1 CMP AL,0 JE fin MOV AH,0x0e ; 显示一个文字 MOV BX,15 ; 指定字符颜色 INT 0x10 ; 调用显卡BIOS JMP putloop msg: DB 0x0a, 0x0a ; 换行两次 DB "load error" DB 0X07 DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 填写0x00直到0x001fe DB 0x55, 0xaaJBE:这也是个条件跳转指令,是“jump if below or equal”的缩写,意思是小于等于则跳转。
要读下一个扇区,只需给CL加1,给ES加上0x20就行了。CL是扇区号,ES指定读入地址。0x20是十六进制下512除以16的结果。
就结果而言,写成AL=17结果也是完全一样的。但这样的方式在下一次的程序中,就会成为问题,因此为了能够循序渐进,这里特意用循环来一个扇区一个扇区地读盘。
此时已经把磁盘上C0-H0-S2到C0-H0-S18的512×17=8704字节的内容,装载到了内存的0x8200~0xa3ff处。
4 读入10个柱面
C0-H0-S18扇区的下一扇区,是磁盘反面的C0-H1-S1。这次从0xa400读入,按顺序读到C0-H1-S18后,接着读下一个柱面C1-H0-S1。保持这个势头,一直读到C9-H1-S18。
; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序装载地址 CYLS EQU 10 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code JMP entry DB 0x90 DB "HELLOIPL" ; 启动扇区名称(8字节) DW 512 ; 每个扇区(sector)大小(必须512字节) DB 1 ; 簇(cluster)大小(必须为1个扇区) DW 1 ; FAT起始位置(一般为第一个扇区) DB 2 ; FAT个数(必须为2) DW 224 ; 根目录大小(一般为224项) DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) DB 0xf0 ; 磁盘类型(必须为0xf0) DW 9 ; FAT的长度(必??9扇区) DW 18 ; 一个磁道(track)有几个扇区(必须为18) DW 2 ; 磁头数(必??2) DD 0 ; 不使用分区,必须是0 DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明(固定) DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) RESB 18 ; 先空出18字节 ; 程序主体 entry: MOV AX,0 ; 初始化寄存器 MOV SS,AX MOV SP,0x7c00 MOV DS,AX MOV ES,AX ;读磁盘 MOV AX,0x0820 MOV ES, AX MOV CH,0 ; 柱面0 MOV DH,0 ; 磁头0 MOV CL,2 ; 扇区2 readloop: MOV SI,0 ; 记录失败次数的寄存器 retry: MOV AH,0x02 ; AH=0x02 : 读入磁盘 MOV AL,1 ; 1个扇区 MOV BX,0 MOV DL,0x00 ; A驱动器 INT 0x13 ; 调用磁盘BIOS JNC next ; 没出错时跳转到next ADD SI,1 ; SI加1 CMP SI,5 ; 比较SI与5 JAE error ; SI >= 5时,跳转到error MOV AH,0x00 MOV DL,0x00 ; A驱动器 INT 0x13 ; 重置驱动器 JMP retry next: MOV AX, ES ; 把内存地址后移0x200 ADD AX,0x0020 MOV ES, AX ; 因为没有ADD ES,0x020指令,所以这里稍微绕个弯 ADD CL,1 ; CL加1 CMP CL,18 ; 比较CL与18 JBE readloop ; 如果CL <= 18,则跳转至readloop MOV CL,1 ADD DH,1 CMP DH,2 JB readloop ; 如果DH < 2,则跳转到readloop MOV DH,0 ADD CH,1 CMP CH, CYLS JB readloop ; 如果CH < CYLS,则跳转到readloop fin: HLT ; 让CPU停止,等待指令 JMP fin ; 无限循环 error: MOV SI,msg putloop: MOV AL,[SI] ADD SI,1 ; 给SI加1 CMP AL,0 JE fin MOV AH,0x0e ; 显示一个文字 MOV BX,15 ; 指定字符颜色 INT 0x10 ; 调用显卡BIOS JMP putloop msg: DB 0x0a, 0x0a ; 换行两次 DB "load error" DB 0X07 DB 0x0a ; 换行 DB 0 RESB 0x7dfe-$ ; 填写0x00直到0x001fe DB 0x55, 0xaaJB。这也是条件跳转指令,是“jump if below”的缩写。翻译过来就是:“如果小于的话,就跳转。”
EQU。这相当于C语言的#define命令,用来声明常数。“CYLS EQU 10”意思是“CYLS = 10”。EQU是“equal”的缩写。只将它定义成常数是因为以后我们可能修改这个数字。现在我们先随意定义成10个柱面,以后再对它进行调整(CYLS代表cylinders)。
5 着手开发操作系统
一般向一个空软盘保存文件时 :
(1) 文件名会写在0x002600以后的地方;
(2) 文件的内容会写在0x004200以后的地方。
6 从启动区执行操作系统
那么,要怎样才能执行磁盘映像上位于0x004200号地址的程序呢?
现在的程序是从启动区开始,把磁盘上的内容装载到内存0x8000号地址,所以磁盘0x4200处的内容就应该位于内存0x8000+0x4200=0xc200号地址。
这样的话,我们就往haribote.nas里加上ORG 0xc200,然后在ipl.nas处理的最后加上JMP 0xc200这个指令。
7 确认操作系统的执行情况
这节就是切换一下画面模式。
设置显卡模式(video mode)
❏ AH=0x00;
❏ AL=模式:(省略了一些不重要的画面模式)
- 0x03:16色字符模式,80× 25
- 0x12:VGA图形模式,640× 480× 4位彩色模式,独特的4面存储模式
- 0x13:VGA图形模式,320× 200× 8位彩色模式,调色板模式
- 0x6a:扩展VGA图形模式,800× 600× 4位彩色模式,独特的4面存储模式(有的显卡不支持这个模式)
❏ 返回值:无
8 32位模式前期准备
; haribote-os ; TAB=4 ; 有关BOOT_INFO CYLS EQU 0x0ff0 ; 设定启动区 LEDS EQU 0x0ff1 VMODE EQU 0x0ff2 ; 关于颜色数目的信息。颜色的位数。 SCRNX EQU 0x0ff4 ; 分辨率的X(screen x) SCRNY EQU 0x0ff6 ; 分辨率的Y(screen y) VRAM EQU 0x0ff8 ; 图像缓冲区的开始地址 ORG 0xc200 ; 这个程序将要被装载到内存的什么地方呢? MOV AL,0x13 ; VGA显卡,320x200x8位彩色 MOV AH,0x00 INT 0x10 MOV BYTE [VMODE],8 ; 记录画面模式 MOV WORD [SCRNX],320 MOV WORD [SCRNY],200 MOV DWORD [VRAM],0x000a0000 ;用BIOS取得键盘上各种LED指示灯的状态 MOV AH,0x02 INT 0x16 ; keyboard BIOS MOV [LEDS], AL fin: HLT JMP fin9 - 10 开始导入C语言
这部分突然添加了很多莫名其妙的代码,有的没看懂,还有makefile里面的一些流程,也没搞清楚,几个obj怎么链接的小白看不太明白,总之跟着代码跑就完了
