《30天自制操作系统》笔记3 --- (Day2 上节)完全解析文件系统
Day2 汇编语言学习与Makefile入门
本文仅带着思路,研究源码里关于文件系统的参数
当年文笔稚嫩,工程能力弱,所以写出来的文章难免有所不足,敬请批评指正。在此感谢 xwwwb 的指正。
关于day2主程序部分及更多内容,请看《30天自制操作系统》笔记
导航
发现学习中的变化
day1代码(只简单地使用DB DW DD RESB指令)
1 ; hello-os 2 ; TAB=4 3 4 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code 5 6 DB 0xeb, 0x4e, 0x90 7 DB "HELLOIPL" ; 启动扇区名称(8字节) 8 DW 512 ; 每个扇区(sector)大小(必须512字节) 9 DB 1 ; 簇(cluster)大小(必须为1个扇区) 10 DW 1 ; FAT起始位置(一般为第一个扇区) 11 DB 2 ; FAT个数(必须为2) 12 DW 224 ; 根目录大小(一般为224项) 13 DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) 14 DB 0xf0 ; 磁盘类型(必须为0xf0) 15 DW 9 ; FAT的长度(必??9扇区) 16 DW 18 ; 一个磁道(track)有几个扇区(必须为18) 17 DW 2 ; 磁头数(必??2) 18 DD 0 ; 不使用分区,必须是0 19 DD 2880 ; 重写一次磁盘大小 20 DB 0,0,0x29 ; 意义不明(固定) 21 DD 0xffffffff ; (可能是)卷标号码 22 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) 23 DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) 24 RESB 18 ; 先空出18字节 25 26 ; 程序主体 27 28 DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c 29 DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a 30 DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09 31 DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb 32 DB 0xee, 0xf4, 0xeb, 0xfd 33 34 ; 信息显示部分 35 36 DB 0x0a, 0x0a ; 换行两次 37 DB "hello, world" 38 DB 0x0a ; 换行 39 DB 0 40 41 RESB 0x1fe-$ ; 填写0x00直到0x001fe 42 43 DB 0x55, 0xaa 44 45 ; 启动扇区以外部分输出 46 47 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 48 RESB 4600 49 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 50 RESB 1469432
以下是day2代码(使用了真正的汇编指令,编译出来的文件和day1的完全一致,我用二进制文件对比工具对比过了)
1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序装载地址 5 6 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code 7 8 JMP entry 9 DB 0x90 10 DB "HELLOIPL" ; 启动扇区名称(8字节) 11 DW 512 ; 每个扇区(sector)大小(必须512字节) 12 DB 1 ; 簇(cluster)大小(必须为1个扇区) 13 DW 1 ; FAT起始位置(一般为第一个扇区) 14 DB 2 ; FAT个数(必须为2) 15 DW 224 ; 根目录大小(一般为224项) 16 DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) 17 DB 0xf0 ; 磁盘类型(必须为0xf0) 18 DW 9 ; FAT的长度(必??9扇区) 19 DW 18 ; 一个磁道(track)有几个扇区(必须为18) 20 DW 2 ; 磁头数(必??2) 21 DD 0 ; 不使用分区,必须是0 22 DD 2880 ; 重写一次磁盘大小 23 DB 0,0,0x29 ; 意义不明(固定) 24 DD 0xffffffff ; (可能是)卷标号码 25 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) 26 DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) 27 RESB 18 ; 先空出18字节 28 29 ; 程序主体 30 31 entry: 32 MOV AX,0 ; 初始化寄存器 33 MOV SS,AX 34 MOV SP,0x7c00 35 MOV DS,AX 36 MOV ES,AX 37 38 MOV SI,msg 39 putloop: 40 MOV AL,[SI] 41 ADD SI,1 ; 给SI加1 42 CMP AL,0 43 JE fin 44 MOV AH,0x0e ; 显示一个文字 45 MOV BX,15 ; 指定字符颜色 46 INT 0x10 ; 调用显卡BIOS 47 JMP putloop 48 fin: 49 HLT ; 让CPU停止,等待指令 50 JMP fin ; 无限循环 51 52 msg: 53 DB 0x0a, 0x0a ; 换行两次 54 DB "hello, world" 55 DB 0x0a ; 换行 56 DB 0 57 58 RESB 0x7dfe-$ ; 填写0x00直到0x001fe 59 60 DB 0x55, 0xaa 61 62 ; 启动扇区以外部分输出 63 64 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 65 RESB 4600 66 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 67 RESB 1469432
注意:RESB 0x7dfe-$ ; 填写0x00直到0x001fe这一行没有错,编译出来的二进制的确能和day一致
所以我们要研究出:day2与day1汇编代码对应关系,如MOV AX,0是怎么表达成二进制的
首先是这个ORG 0x7c00,对比后面的
0x7dfe-$竟然和0x1fe-$一样,而且7dfe与1fe刚好差了7c00,结合注释,推断这个ORG规定了在内存中被开始加载的地址
实际上:ORG 0x7c00 ,用于指定编译后的二进制程序起点。这样设计的好处是: 后续所有的代码直接以相对地址来编写的。然后ORG指定了起始地址。这样编译出来的地址=起始地址+相对地址。具体可以看下文参考资料的NASM相关链接。
这里或许还不能体现ORG的好处,是因为当前的代码基本都是顺序填充的,等到使用各种复杂地址的时候就能体现好处了。(不然手算地址算死你哦,算错了还能写出了一堆操作系统引导级的BUG,太危险了)
这里要感谢 xwwwb 的提醒:关于您在博客园的博客 存在一些疑问想讨论下 · Issue #4 · sky5454/30daysMakeOS-Origin-ISOfiles (github.com)
这样为了让主板从内存0x7c00处开始加载我们写的helloos.img。
可以猜得出,day1版本是用了相对地址,在源码中 把helloos.img的开头用0x0000表示,所以实际在哪个内存地址被加载 应该是由汇编编译器或主板BIOS决定的,
而day2用了绝对地址,指定helloos.img要在内存0x7c00处加载。
先找不同
↑↑↑上半部分源码, 下半部分源码↓↓↓
这个0xeb 0x4e 0x90是什么意思我们还不知道,他就用汇编改了,所以有重点,搜索一下发现了一篇:
file allocation table - 16bit: http://www.beginningtoseethelight.org/fat16/index.htm 以及
FAT32中文版分析+补充(1) 和 FAT32中文版分析+补充(2)
这里引用这篇文章里的 BS_jmpBoot (跳转指令),如图
查了一下0xEB是x86汇编指令JMP SHORT(详见 JMP地址公式推导)
而NASK汇编把JMP SHORT 简写为 JMP,0x90是x86汇编里的 NOP(空指令),0xE9则是JMP NEAR指令,Near JMP需要的地址位多,所以地址参数用两个字节表示。
也就是说 0xEB 0x58 0x90 和汇编代码 JUMP 0x58; NOP; 是等价的
也就是
day1版本是 JMP 0x58; NOP; (表格里的第一种形式)。
day2版本是 JMP 指向entry的实际地址; (表格里的第二种形式)。
entry和c语言里对应的label类似,就是便于人类记忆的内存地址标号。这里实际地址 = ORG指定的起点地址0x7c00+标号在程序里的偏移地址。(实际地址刚好是16-bit值,符合第二种上面表格里的形式)
至于偏移你可以算一下,xwwwb网友说刚好是78字节偏移,78的十六进制值是0x4e。如果是的话,那么实际地址也就是 0x7c00+0x4e = 0x7c4e,这部分我没算过,请自己验证。
从表格里我们也得知,这是x86架构的专用启动指令,所以我们可以猜测helloos不能在非Intel x86架构的主板上工作。
好了,看完资料,我们发现书中的启动区源码是为FAT12格式软盘服务的,所以如果以后我们要做fat32(甚至需要主板驱动支持的exfat、NTFS),就需要自己去找对应分区格式的启动区参数。
开始全面分析
从day1的启动区开始吧,再贴一次图片,方便对比
Day1的helloOS.nas前半部分
后半部分(程序主体、信息显示部分、启动扇区以外部分输出)
开头的 eb 4e 90,结合fat启动区分区分析图,可以知道在Intel x86里这就是JMP 0x4e,而3e到4f是都是零,4e刚好位于程序主体前面的两个0x00。那这里4e能不能写成3e到50里任意hex呢?用改img文件的十六进制就可以测试。
day2里面把程序主体写作entry,所以源码里的JMP entry就和0xeb 0x4e 0x90作用相同了:
Ps.作者在书里解释说是JMP 0x7c50
而JMP entry下面的东西是fat12的分区扇区参数。(分区扇区参数的都是计算机行业规定,一般不能自己随便改的),如图(引用自FAT32中文版分析+补充(1) )
刚好就对应着我们的
0x00到0x10(0x0F是15,0x10就是16,偏移量16就是0x10)
|
零扇区BPB[BIOS Parameter Block]从下面内容可以看出它的作用,即储存了文件系统的参数信息
偏移量 | 零扇区BPB | helloos里的值 | 大小 | helloos值的说明 |
0xB | BPB_BytesPersec(每扇字节数) | 512 | 2bits |
可填512,1024,2048,4096, 由于书里用的软盘,所以是512 |
0xC | BPB_SecPerClus(每簇扇区数) | 1 | 1bit | 2^n, (n>=0) |
0xD | BPB_RsvdSecCnt(保留扇区数) | 1 | 2bits | FAT12/16硬性规定为1,FAT32为32 |
0x10 | BPB_NumFATs(FAT副本数) | 2 | 1bit | FAT 建议写2 |
从0x11到20
偏移量 | helloos里的值 | 大小 | helloos值的说明 | |
0x11 | BPB_RootEntCnt(根目录项数) | 224 | 2bits | FAT12/16为32的偶数倍,FAT32必须为0 |
0x13 | BPB_TotSec16(总扇区数) | 2880 | 2bits | FAT12/16填总扇区数 |
0x15 | BPB_Media(媒体描述符) | 0xf0 | 1bit | 可移动存储介质经常使用0xF0 |
0x16 | BPB_FATSz16(每FAT扇区数) | 9 | 2bits | FAT12/16一个FAT表所占的扇区数 |
0x18 | BPB_SecPerTrk(每磁道扇区数) | 18 | 2bits | 每磁道扇区数,这个数用于BIOS中断0x13 |
0x1A | BPB_NumHeads(磁头数) | 2 | 2bits | 磁头数,也用于0x13中断 |
0x1C | BPB_HiddSec(隐藏扇区数) | 0 | 4bits |
FAT分区之前所隐藏的扇区数, 调用0x13中断可得到, 对于没有分区的储存介质,此域必须为0, 具体使用什么由操作系统决定 |
0x20 | BPB_TotSec32(总扇区数) | 2880 | 4bits |
FAT12/16中,若是总扇区>=10000(64KB) ,那么此域就是总扇区 |
注解:这里的磁头数等东西指的是逻辑上的,如果没有实体,那么指的就是逻辑磁头数。其他参数同理
对比一下源码,这下子就明明白白了
1 ; hello-os 2 ; TAB=4 3 4 ORG 0x7c00 ; 指明程序装载地址 5 6 ; 标准FAT12格式软盘专用的代码 Stand FAT12 format floppy code 7 8 JMP entry 9 DB 0x90 10 DB "HELLOIPL" ; 启动扇区名称(8字节) 11 DW 512 ; 每个扇区(sector)大小(必须512字节) 12 DB 1 ; 簇(cluster)大小(必须为1个扇区) 13 DW 1 ; FAT起始位置(一般为第一个扇区) 14 DB 2 ; FAT个数(必须为2) 15 DW 224 ; 根目录大小(一般为224项) 16 DW 2880 ; 该磁盘大小(必须为2880扇区1440*1024/512) 17 DB 0xf0 ; 磁盘类型(必须为0xf0) 18 DW 9 ; FAT的长度(必??9扇区) 19 DW 18 ; 一个磁道(track)有几个扇区(必须为18) 20 DW 2 ; 磁头数(必??2) 21 DD 0 ; 不使用分区,必须是0 22 DD 2880 ; 重写一次磁盘大小 23 24 ; 以上部分就是表格里描述的 25 ; 下面这部分还没介绍,不急 26 DB 0,0,0x29 ; 意义不明(固定) 27 DD 0xffffffff ; (可能是)卷标号码 28 DB "HELLO-OS " ; 磁盘的名称(必须为11字?,不足填空格) 29 DB "FAT12 " ; 磁盘格式名称(必??8字?,不足填空格) 30 RESB 18 ; 先空出18字节 31 32 ; 程序主体
剩余部分的详细说明在FAT32中文版分析+补充(2),当然也可以看下图的简单说明:
此图资料来自:【文件系统】FAT12文件系统简介
Ps.其实还可以用c语言来做一个文件系统写入工具 (用结构体储存分区信息)
最后搜了很久,发现百度百科也有一点记录:https://baike.baidu.com/item/FAt12#ref_[1]_273750 ,维基百科应该有更详细,可惜英文版也被墙了....
通过搜集资料,我们现在不再仅仅只能做FAT12,还能做FAT32的启动甚至是exfat ext ntfs的
到这里,我们明白了整体
第一部分就是启动区描述和文件系统(书中例子为fat12)
附图(DG加载虚拟磁盘文件,并勾选显示元文件)
当然更推荐用 WinHex 来看 (查看-模板管理器-Fat32)
第二部分才是系统和程序(HelloWorld)
好,让我们把精力放在第二部分吧:《30天自制操作系统》笔记4 --- (Day2 下节)了解如何写汇编HelloWorld程序
待续更新...
关于NASM指令的资料
ORG指令,全称二进制文件程序原点,它唯一的功能是:指定一个偏移量,该偏移量被添加到节中的所有内部地址引用中;
(笔者注:即全局地址偏移,如 ORG 0x3A,那么节内(section)的所有地址在编译成二进制之后都会往后偏移 0x3A)
NASM - The Netwide Assembler/8.1.1-ORG
zhangjunlei26/NASM-Tutorial-CN: Nasm指南中文 (NASM Tutorial) (github.com)
FAT 系权威资料大全
FAT(File Allocation Table) 相关资料:
图源:http://elm-chan.org/docs/fat_e.html
常见文件系统 资料
https://www.ntfs.com/index.html
微软官方资料(是真的难找):
https://social.technet.microsoft.com/wiki/contents/articles/6771.the-fat-file-system.aspx
How FAT Works: Local File Systems | Microsoft Docs(WinServer2003)
FAT File System | Microsoft Docs(WinServer2000)
exFAT file system specification - Win32 apps | Microsoft Docs
(最推荐的方案)另外也可以用必应或谷歌搜 FAT32 white paper 或 FAT32 白皮书 https://files.cnblogs.com/files/yucloud/FATPDF.7z
附上中译版:
FAT32中文版分析+补充(1)
FAT32中文版分析+补充(2)
FAT32中文版分析+补充(3)
FAT32中文版分析+补充(4)
FAT32中文版分析+补充(5)
FAT32中文版分析+补充(6)
exFAT 容易丢数据的原因
值得一提的是,exFAT 分区设计里是有两个分配表的(见exFAT file system specification - Win32 apps | Microsoft Docs)
用 Windows 格式化 exFAT 分区,只会给一个文件分配表(也就是说没有留作备份的分配表),而格式化 FAT32 分区则会给两个分配表。
(但 exFAT 文档表示 exFAT 是可以有两个分配表的。而 微软认为闪存类型的储存介质不需要两个分配表,但事实上 闪存因 exFAT 单分配表丢失的情况比比皆是,
网上所说的 exFAT 容易丢数据应该就是这个原因)
exFAT FAT16 FAT32 对比图
(Windows 自带的格式化工具,磁盘精灵,Linux上的 mkexfat ,均只给 exFAT 一个分配表)
可见微软的规范倡导里,只有 exFAT 是给一个分配表,其他 FAT 系(FAT12 FAT16 FAT32) 都是两个分配表
没有两个分配表(一个主表,一个备份表 )的分区非常容易丢数据,网上搜一下 exFAT 丢数据,你就知道
可是 exFAT 是原生支持两个分配表的啊,微软有病吧!