admin 管理员组文章数量: 1184232
前言:通过阅读并理解操作系统源码中的代码作用和应用场景,可以快速学会64位操作系统开发的相关知识点。在这里感谢田宇先生的开源贡献精神,他的想法,与我不谋而合,作为白帽黑客,对于底层原理的技术探索与研究,是必要的一个发展过程!
操作系统开发的技术探索:基于Linux内核的64位操作系统(MINE)
本文章仅提供学习,切勿将其用于不法手段!
引言
在第一篇文章中,我们梳理了引导扇区的 基本流程 与 核心组件 。本文将聚焦引导程序与 FAT12文件系统 的深度交互,解析其如何精准定位内核文件,并探讨引导流程的 优化方向 与 扩展能力 。通过对代码细节的拆解,我们将揭示x86引导过程中“文件系统适配”的底层逻辑,以及如何通过设计改进提升引导程序的健壮性与效率。
一、FAT12文件系统:引导程序的“寻宝地图”
引导程序的核心任务是 从软盘中找到并加载内核文件 ,而FAT12文件系统就是这张“寻宝地图”。要理解引导程序的文件搜索逻辑,必须先拆解FAT12的核心结构。
1.1 FAT12文件系统的核心组件
FAT12是DOS时代经典的文件系统,专为软盘设计,其核心结构包括:
- 引导扇区(Boot Sector) :存储文件系统元数据(如每扇区字节数、簇大小、FAT表位置);
- FAT表(File Allocation Table) :记录每个簇的使用状态与下一个簇的编号(“簇链”);
- 根目录区(Root Directory) :存储所有文件的元数据(文件名、起始簇号、文件大小);
- 数据区(Data Area) :存储文件的实际内容(按簇划分)。
1.2 根目录项:文件的“身份证”
引导程序遍历根目录区时,本质是在查找 匹配的文件名 。每个根目录项是 32字节的固定结构 ,其格式如下(以“LOADER BIN”为例):
| 偏移 | 长度 | 描述 | 示例值(“LOADER BIN”) |
|---|---|---|---|
| 0x00 | 8 | 文件名(ASCII,补空格) |
LOADER BIN
→ 实际存储为
LOADER BIN
(11字节,后补空格)
|
| 0x08 | 3 | 扩展名(ASCII) |
空(因为是.COM文件?不,这里是
BIN
,扩展名占3字节,所以完整文件名是
LOADER.BIN
?不对,代码中是
LOADER BIN
,11字节,所以文件名是
LOADER
,扩展名是
BIN
?哦,代码中的
LoaderFileName
是
"LOADER BIN",0
,11字节,对应根目录项的0x00-0x0A偏移,其中0x00-0x07是文件名
LOADER
(8字节),0x08-0x0A是扩展名
BIN
(3字节),剩下的0x0B是属性字节。
|
| 0x0B | 1 | 文件属性(隐藏、只读等) | 0x20(普通文件) |
| 0x0C | 10 | 保留字段 | 全0 |
| 0x16 | 2 | 创建时间 | 0 |
| 0x18 | 2 | 创建日期 | 0 |
| 0x1A | 2 | 起始簇号 | 文件第一个数据簇的编号 |
| 0x1C | 4 | 文件大小 | 内核文件的大小(字节) |
1.3 簇链追踪:从起始簇到文件末尾
引导程序找到文件名后,需通过 FAT表 追踪文件的“簇链”:
-
从根目录项获取
起始簇号
(如
0x1A偏移的值); - 查询FAT表,获取该簇的 下一个簇号 (FAT12中每个条目占12位);
-
重复步骤2,直至遇到
0xFFF(簇链结束标志); - 将每个簇对应的扇区数据读取到内存。
二、引导程序的“性能瓶颈”与优化策略
尽管引导程序的逻辑清晰,但在实际运行中仍存在 效率问题 (如多次磁盘I/O、冗余计算)。本节将针对这些瓶颈提出优化方案。
2.1 问题1:根目录遍历的“全扫描”
原程序遍历根目录区的
所有扇区
(
RootDirSectors=14
),即使文件早已找到。这种“全扫描”方式在软盘容量较大时(如1.44MB)会浪费大量时间。
优化方案:提前终止遍历
在找到文件名后,立即跳出循环,避免不必要的扇区读取。修改
Lable_Search_In_Root_Dir_Begin
的逻辑:
Lable_Search_In_Root_Dir_Begin:
cmp word [RootDirSizeForLoop], 0
jz Label_No_LoaderBin
dec word [RootDirSizeForLoop]
mov ax, 00h
mov es, ax
mov bx, 8000h
mov ax, [SectorNo]
mov cl, 1
call Func_ReadOneSector
mov si, LoaderFileName
mov di, 8000h
cld
mov dx, 10h
Label_Search_For_LoaderBin:
cmp dx, 0
jz Label_Goto_Next_Sector_In_Root_Dir
dec dx
mov cx, 11
Label_Cmp_FileName:
cmp cx, 0
jz Label_FileName_Found ; 找到文件名,直接跳出循环!
dec cx
lodsb
cmp al, byte [es:di]
jz Label_Go_On
jmp Label_Different
; ... 其他逻辑不变 ... 优化效果 :找到文件后立即终止根目录遍历,减少磁盘I/O次数。
2.2 问题2:FAT表读取的“重复加载”
原程序每次查询FAT条目时,都会重新读取FAT表的扇区(
Func_GetFATEntry
中调用
Func_ReadOneSector
)。实际上,FAT表的前几个扇区已包含大部分簇的信息,无需重复读取。
优化方案:缓存FAT表数据
在引导程序初始化时,将FAT1表的前
2个扇区
(覆盖前
2×512×12=12KB
的簇信息)读入内存缓存,后续查询直接从缓存中获取:
; 在Label_Start后添加:缓存FAT1表前2扇区
mov ax, 00h
mov es, ax
mov bx, 8000h ; 缓存地址:ES:8000h
mov ax, SectorNumOfFAT1Start ; FAT1起始扇区=1
mov cx, 2 ; 读取2扇区
call Func_ReadOneSector ; 读取FAT1前2扇区到ES:8000h
; 修改Func_GetFATEntry:从缓存中读取
Func_GetFATEntry:
push es
push bx
push ax
mov ax, 00
mov es, ax
pop ax
mov byte [Odd], 0
mov bx, 3
mul bx
mov bx, 2
div bx
cmp dx, 0
jz Label_Even
mov byte [Odd], 1
Label_Even:
xor dx, dx
mov bx, [BPB_BytesPerSec]
div bx
push dx
; 从缓存中读取:ES=0x0000,BX=8000h + 偏移量
mov si, ax ; SI=偏移量(扇区内偏移)
mov ax, [es:bx+si] ; 直接从缓存读取FAT条目
cmp byte [Odd], 1
jnz Label_Even_2
shr ax, 4
Label_Even_2:
and ax, 0fffh
pop bx
pop es
ret 优化效果 :避免重复读取FAT表,减少磁盘I/O次数约50%。
三、引导程序的“健壮性”增强:错误处理与恢复
原程序在找不到loader.bin时,仅显示错误信息并死循环(
jmp $
)。实际应用中,引导程序需具备
错误恢复能力
,比如提示用户重新插入软盘,或尝试加载备用内核。
3.1 改进错误处理:提示用户操作
修改
Label_No_LoaderBin
的逻辑,显示更友好的提示,并等待用户按键:
Label_No_LoaderBin:
mov ax, 1301h
mov bx, 008ch
mov dx, 0100h
mov cx, 21
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, NoLoaderMessage
int 10h
; 等待用户按键
mov ah, 00h ; BIOS中断0x16:键盘输入
int 16h ; 等待按键,返回按键码在AL
; 重新初始化,尝试重新搜索
mov word [RootDirSizeForLoop], RootDirSectors
mov word [SectorNo], SectorNumOfRootDirStart
jmp Lable_Search_In_Root_Dir_Begin3.2 支持备用内核:多文件加载
引导程序可扩展为
支持多个内核文件
(如
LOADER.BIN
、
KERNEL.BIN
),若首选文件找不到,自动尝试加载备用文件:
; 定义备用文件名
BackupLoaderFileName: db "KERNEL BIN",0
; 在Label_No_LoaderBin后添加:尝试加载备用文件
Label_No_LoaderBin:
; ... 显示错误信息 ...
; 尝试加载备用文件
mov si, BackupLoaderFileName
mov di, 8000h
cld
mov dx, 10h
jmp Label_Search_For_LoaderBin ; 重新搜索备用文件四、引导程序与内核的“交接仪式”:参数传递
引导程序加载内核后,需向内核传递 硬件环境信息 (如内存布局、设备参数),确保内核能正确初始化。
4.1 传递参数的方式
x86引导程序通常通过
栈
或
内存区域
向内核传递参数。本引导程序可将硬件信息存储在
BaseOfLoader
之前的内存区域:
; 在Label_File_Loaded前添加:准备内核参数
mov ax, 0x0000 ; 参数区域地址:0x0000:0x0000
mov ds, ax
mov es, ax
mov di, 0x0000 ; 参数偏移地址
; 传递栈顶地址
mov ax, BaseOfStack
mov [es:di], ax
inc di
inc di
; 传递软盘驱动器号
mov al, [BS_DrvNum]
mov [es:di], al
inc di
; 传递内核加载地址
mov ax, BaseOfLoader
mov [es:di], ax
inc di
mov ax, OffsetOfLoader
mov [es:di], ax
; 跳转到内核,传递参数指针
push es ; 参数区域段地址
push di ; 参数区域偏移地址
jmp BaseOfLoader:OffsetOfLoader4.2 内核接收参数
内核的入口函数需从栈中获取引导程序传递的参数:
; 假设内核入口函数为_start
void _start() {
// 从栈中获取参数指针
unsigned int *param_ptr = (unsigned int *)__builtin_return_address(0);
unsigned int stack_top = param_ptr[0];
unsigned int drv_num = param_ptr[1];
unsigned int loader_base = param_ptr[2];
unsigned int loader_offset = param_ptr[3];
// 初始化栈
__asm__ volatile("mov %0, %%esp" : : "r"(stack_top));
// 初始化硬件
init_hardware(drv_num);
// 加载文件系统
init_filesystem(loader_base, loader_offset);
// 启动用户程序
start_user_program();
}五、结语
引导扇区虽小,却是连接硬件与操作系统的“关键纽带”。通过对FAT12文件系统的深度解析、引导流程的优化,以及健壮性的增强,我们可以打造出更高效、可靠的引导程序。
对于操作系统开发者而言,理解引导程序的工作原理,不仅能解决“系统无法启动”的问题,更能为设计 定制化引导加载程序 (如UEFI引导)奠定基础。未来,随着硬件的发展(如NVMe固态硬盘、UEFI标准),引导程序也将面临新的挑战——但核心逻辑(初始化、文件搜索、内核移交)始终不变。
延伸阅读 :
- 《操作系统真相还原》——引导部分;
- FAT12文件系统规范(微软官方文档);
- Bochs调试指南——引导扇区调试技巧。
注:本文仅用于教育目的,实际渗透测试必须获得合法授权。未经授权的黑客行为是违法的。
版权声明:本文标题:操作系统的灵魂:MINE视角下的64位源码解析 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1773473732a3562338.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论