实验5:简单文件系统的实现
基础架构与持久化层
宏定义
|
|
-
FS_MAGIC用于识别文件系统格式,加载时校验合法性,防止用其他文件创建的文件误当作文件系统镜像。这里用一串十六进制数表示,可以解析为"MFS1"。
-
BLOCK_SIZE与DATA_BLOCKS定义的数据区大小(512KB)是纯数据区大小,不包括FAT表和目录项占用的元数据空间。
结构体
SuperBlock(超级块)
|
|
-
这是文件系统的 “身份证”,存储在镜像文件的最开头。
-
在加载时,会首先读取并校验
magic,如果不匹配则拒绝挂载,防止用普通文本文件或损坏数据误当文件系统加载。 具体见load_filesystem()的实现。
DirEntry(目录项)
|
|
-
这里定义了目录项,即我们说的文件控制块FCB
-
#pragma pack(push, 1):这是编译器指令,强制编译器不进行字节对齐填充。默认情况下,结构体会按照4或8字节对齐,这会导致内存中插入“空洞”。
这样设置的紧凑布局确保磁盘镜像大小精确可控,无浪费。
-
name固定28字节,注意这里与C字符串不同,不会以\0结尾,可以用strncpy写入,但需要小心越界。 -
parent是构建目录树的核心。根目录的parent会指向自身,形成递归终止条件。
FsImage(内存镜像)
|
|
-
这是一个内存映射文件系统的设计。
整个镜像一次性
fread进内存,操作完成后再fwrite回磁盘。 -
saved_current_dir会记录退出时的当前目录,下次启动自动恢复。
OpenFile (打开文件表项)
|
|
-
这是 OFT 表的表项,与 DirEntry 分离,专门跟踪文件打开状态。
-
offset跟踪读写位置,是不同写模式实现的关键。
FileSystem(运行时上下文)
|
|
- 与
FsImage不同,FileSystem属于会话层(运行时状态,包括未保存到磁盘的当前目录和打开文件);而FsImage是持久化层(存磁盘的样子)。
load_filesystem()加载文件系统
|
|
-
调用时机:在
main()函数中的第一件事就是加载镜像:1 2 3 4 5int main(void) { FileSystem fs; load_filesystem(&fs); …… } -
开始的防御性清零是系统编程的惯用手法。
FileSystem初始化时,内存中是随机值。若不清零,结构体中的标志位可能是非0的野值,导致后续逻辑混乱。 -
fopen(IMAGE_FILE, "rb")打开镜像文件-
"rb"是二进制只读模式 -
如果无镜像文件(
fp=0,首次运行或镜像被删除),提示用户运行my_format命令初始化。 -
此时
fs->mounted=0(经过防御性清零),表示文件系统未挂载,符合逻辑。
-
-
加载镜像后,需要进行多方面的校验:
-
整读校验:检查镜像文件读出的内容大小与设计的结构体大小相同,即镜像中所有内容都被正常读出。
其中读数据用到了
fread(),其函数原型为:1size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);-
void *ptr:内存缓冲区起始地址 -
size_t size:每个数据单元的字节数(这里取1表示按字节读取) -
size_t nmemb:要读取的单元数量 -
FILE *stream:文件指针 -
返回值:成功读取的单元数量,因为
size=1,因此返回值等于实际读取到字节数
-
-
魔数校验:确保打开的文件是本程序创建的镜像文件
假设其他文件被当成镜像文件打开,这里检查
magic值无法正常加载;相反,镜像文件加载后,
magic值可以与宏定义的值匹配(这是在初始化镜像文件时做的工作,详见函数cmd_format())。 -
如果未通过检验,提示镜像损坏,清零image,保持
fs->mounted=0。
-
-
在恢复工作目录时,有详细的检查,如果不符合打开条件,把根目录作为当前工作目录:
1 2 3 4 5 6 7fs->current_dir = fs->image.saved_current_dir; if (fs->current_dir < 0 || // 当前目录序列号<0 fs->current_dir >= MAX_FILES || // 当前目录序列号>最大序列号 !fs->image.entries[fs->current_dir].used || // 当前目录为空 fs->image.entries[fs->current_dir].type != ENTRY_DIR) { // 序列号对应目录项不是目录 fs->current_dir = fs->image.super.root_dir_index; }
save_filesystem()持久化镜像
|
|
-
调用时机:用户使用
my_exitsys时:1 2 3 4 5 6 7 8 9static void cmd_exitsys(FileSystem *fs, int *running) { if (!check_mounted(fs)) { return; } if (save_filesystem(fs) == 0) { printf("File system saved. Bye!\n"); *running = 0; } } -
之所以要检查挂载状态
fs->mounted,是因为我们要防止生成空的或者损坏的镜像文件 -
写镜像时,用
fopen(IMAGE_FILE, "wb"),"wb"表示二进制写(覆盖模式):-
若
fs.img文件已存在,直接清空重写; -
若不存在,则创建
fs.img文件。
写入时,整个内存内容一次性写入,与
fread一次性读对称。 -
reset_open_files()重置打开文件表(OFT)
|
|
-
该函数会初始化进程级的文件描述符表(OFT,Open File Table),将OFT所有表项标记为“未使用”状态。
-
used:设置为0,即空闲,后续可以供cmd_open()分配。 -
entry_index:设置为 -1,表示未关联任何目录项。 -
offset:设置为0,将读写位置重置到文件开头位置。
-
-
调用时机
-
系统加载时(
load_filesystem())1 2 3 4 5 6static void load_filesystem(FileSystem *fs) { …… // 初始化OFT reset_open_files(fs); printf("Loaded file system image from %s.\n", IMAGE_FILE); } -
格式化时(
cmd_format())1 2 3 4 5static void cmd_format(FileSystem *fs) { …… reset_open_files(fs); printf("File system formatted.\n"); }
-
check_mounted()检查挂载状态
|
|
-
封装的检查
fs->mounted方法,注意检查的是内存,而不是磁盘。 -
调用时机:所有的
cmd_XXX()函数,用户的所有操作都需要先检查文件系统的挂载状态。
相关问题
-
请问文件系统如何处理
FsImage的序列化与反序列化?当前程序使用的是C结构体内存直接映射。
序列化,即数据从内存到磁盘的过程,我们把整个
FsImage结构体作为连续的字节流,原封不动地写入fs.img文件:1fwrite(&fs->image, 1, sizeof(fs->image), fp);相应的,反序列化,即数据从磁盘到内存的过程,也是将磁盘文件的二进制字节流,直接覆盖到
FsImage结构体的内存地址。为了保证直接映射正常工作,我们做了以下工作:
-
紧凑布局:我们使用了
#pragma pack(push, 1),强制结构体1字节对齐,避免空隙影响结构体数据读取。 -
校验:采用
magic,保证加载的文件一定是我们程序创建的镜像文件 -
定长数组:结构体中的所有成员均为定长数组,保证
sizeof(fs->image)在编译期就确定。
-