资讯动态

二级文件系统设计:Java树形目录与C语言FAT虚拟磁盘实现

发布时间:2026/9/18 16:03:18 来源:尧图企业网站定制
简介这份操作系统课程设计文档围绕 Linux 环境下简易二级文件系统的设计与实现展开面向需要提交课程设计报告与可运行源程序的高校学生。内容覆盖 Login、Dir、Create、Delete、Open、Close、Read、Write 等命令的实现思路列目录时需给出文件名、物理地址、保护码与文件长度并支持源文件的读写保护同时附有实现原理、关键算法流程图、运行环境与限制、结果分析及心得体会等规范章节预览中还可见基于 Java Swing 树形控件与右键菜单实现新建目录、新建文件的代码片段并涉及 Linux、Windows 两套程序。包内为 1 个 doc 文档压缩包约 651KB。目前已有 2911 人学习适合作为课设选题、报告框架与代码实现的对照样例也可用于梳理目录管理、权限检查与文件 I/O 等知识点。1. 从一份课程设计文档说起二级文件系统到底在模拟什么很多人第一次看到「二级文件系统」这个词会以为它比一级更复杂。实际上恰恰相反它是把真实操作系统的文件管理机制剥到只剩骨架一个主目录、若干子目录、若干普通文件目录本身也当文件存盘。这份课程设计文档就干了两件事Java Swing 那套跑在 Windows 上用 JTree 加右键菜单做交互外壳C 那套跑在 Linux 上直接手搓 FAT 表和目录项用内存里的一块 buffer 当虚拟磁盘。两套代码加起来覆盖了课程设计的核心考点目录结构怎么组织、物理块怎么分配、打开文件表怎么维护、权限码怎么判断。适合正在啃《计算机操作系统》文件管理这一章、需要交一份能答辩的课程设计的同学也适合想回头把 FAT、FCB、位示图这些概念用代码串一遍的从业者。2. Java 端树形目录的构建与文件操作实现Windows 那套方案的思路是「借力」——不自己实现磁盘分配而是把 JTree 的节点树当作逻辑目录把真实的文件系统当作物理层。节点名字对应文件名节点路径拼出来的字符串对应物理地址。这样课程设计要求的「列目录时列出文件名、物理地址」就有了落点物理地址就是fullPath那个字符串。2.1 JTree 节点与物理路径的双向映射JTree 的每个MutableTreeNode只是存了个显示文本本身不携带父路径信息。所以任何一次文件操作之前都得先从选中的TreePath反推出完整路径。文档里这段循环出现了五次以上是个必须吃透的公共逻辑// 从被选中的树节点反推完整物理路径 TreePath path tree.getSelectionPath(); String fullPath ; for (Object obj : path.getPath()) { String str obj.toString(); // 盘符根节点形如 C:\末尾的反斜杠要先削掉否则拼接会出双斜杠 if (str.endsWith(\\)) { str str.substring(0, str.length() - 1); } if (fullPath.equals()) { fullPath str; } else { fullPath \\ str; } } File currentFile new File(fullPath);逻辑是按根到叶的顺序逐段拼接第一段不加分隔符后续每段前面补一个\。参数上要盯住的是str.endsWith(\\)这个判断Java 里反斜杠是转义字符\\才表示一个真实的反斜杠写成一个\直接编译不过——初学者在这里卡半小时是常事。另一个坑是path.getPath()返回的是根到当前节点的完整数组不是父节点别拿getLastPathComponent()去拼路径那样只能拿到自己的名字。2.2 新建目录、新建文件与递归删除有了路径之后就简单了。新建目录调mkdir()新建文件调createNewFile()删除目录必须递归因为File.delete()对非空目录直接返回 false。// 递归删除目录先清空子项再删自己 public static boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] subDir dir.list(); // list() 返回 null 表示无权限或IO异常必须判空否则NPE if (subDir ! null) { for (String s : subDir) { deleteDir(new File(dir, s)); } } } return dir.delete(); }对比表里几个文件操作 API 的行为差异值得单独记一下API作用对象失败表现注意点mkdir()单级目录父目录不存在返回 false多级要用mkdirs()createNewFile()普通文件文件已存在返回 false不覆盖已有文件delete()文件或空目录非空目录返回 false需递归配合list()目录非目录或异常返回 null必须判空2.3 右键菜单事件的分发机制文档把每个菜单项都做成一个内部类实现ActionListenerMenuAction 管删除、MenuAction2 管新建目录、MenuAction5 管新建文件挂到JPopupMenu上。这种写法能跑但类会越堆越多。常见做法是改成给每个JMenuItem设置setActionCommand(createDir)然后只写一个监听器在actionPerformed里switch (e.getActionCommand())分发代码量能砍掉三分之二答辩时也更容易讲清楚调用链。3. Linux 端 FAT 表、目录项与虚拟磁盘的落地C 这套才是文件管理的本体。它不依赖任何现成文件系统调用做管理而是 malloc 一块 1MB 内存当磁盘自己维护 FAT 表和目录项退出时把整块内存 dump 成disk.dat。这个设计非常聪明一次运行结束后数据还能留下来下次enter()再从文件读回内存。3.1 磁盘布局与核心数据结构定义整个 1MB 空间被切成固定几段这是理解后续所有代码的前提。引导区占 1 个盘块FAT 表紧跟着再往后是根目录区最后是数据区。宏定义里已经把地址算好了ROOT_DISK_NO FATSIZE/DISKSIZE 1即根目录起始块号等于 FAT 表占用的块数加一去加引导块。#define MEM_D_SIZE 1024*1024 // 虚拟磁盘总大小 1M #define DISKSIZE 1024 // 每个盘块 1K #define DISK_NUM 1024 // 共 1024 个盘块 #define MSD 5 // 每个目录最多 5 个子目录项 #define MOFN 5 // 打开文件表最多 5 项 // FAT 表项记录下一块的块号 空闲标志 struct fatitem { int item; // 下一磁盘块指针-1 表示链尾 char em_disk; // 0 空闲1 占用 }; // 文件控制块 FCB二级文件系统的核心 struct FCB { char name[9]; // 文件名 8 字节 \0 char property; // 1 目录0 普通文件 int size; // 字节数 int firstdisk; // 起始盘块号同时充当分配标志 int next; // 子目录起始块号 int sign; // 1 根目录0 非根 };参数上有几个点答辩常被问name[9]是 8 字节文件名加终止符短文件名设计是为了对齐firstdisk -1被复用为「该目录项空闲」的判断条件这是显式链式分配里常见的省字段做法MSD限死 5 个子目录是因为根目录大小固定为sizeof(struct direct)必须能塞进一个盘块。3.2 格式化 format 与磁盘装载 enter/haltformat()干的是初始化虚拟磁盘。FAT 表的前若干个表项标记为被 FAT 自己占用根目录块标记占用其余全部置空闲。目录项的第 0、1 项固定填.和..这就是二级文件系统「当前目录可回退」的实现基础。void format() { fat (struct fatitem *)(fdisk DISKSIZE); // 跳过引导块 fat[0].item -1; fat[0].em_disk 1; // 引导块占用 for (i 1; i ROOT_DISK_NO - 1; i) { // FAT 自身占用块 fat[i].item i 1; fat[i].em_disk 1; } fat[ROOT_DISK_NO].item -1; // 根目录块链尾 fat[ROOT_DISK_NO].em_disk 1; for (i ROOT_DISK_NO 1; i DISK_NUM; i) { // 数据区全部空闲 fat[i].item -1; fat[i].em_disk 0; } root (struct direct *)(fdisk DISKSIZE FATSIZE); // 根目录地址 root-directitem[0].sign 1; strcpy(root-directitem[0].name, .); // ... 第1项填 .. 第2~MSD1 项全部置空 }注意fwrite(fdisk, MEM_D_SIZE, 1, fp)这行第三个参数是 1 不是MEM_D_SIZE。fwrite的签名是fwrite(ptr, size, nmemb, stream)一次写 1M 字节返回 1 表示成功。如果写成MEM_D_SIZE, 1行为其实一样但语义不同返回的是字节数——判断! 1就会一直触发「写错误」这是个隐蔽的坑。enter()做反向操作读回内存halt()再写盘并 free三者配对使用。3.3 create/delete 与目录项空间回收创建文件要做三件事查重、找空闲目录项、挂入。文档给出的 create 前两步已经能看到int create(char *name) { int i, j; if (strlen(name) 8) return (-1); // 文件名超长 for (j 2; j MSD 2; j) { // 从第2项开始查重跳过 . 和 .. if (!strcmp(cur_dir-directitem[j].name, name)) break; } if (j MSD 2) return (-4); // 重名 for (i 2; i MSD 2; i) { // 找第一个空闲项 if (cur_dir-directitem[i].firstdisk -1) break; } // 后续分配盘块、填 FCB、挂到空闲项 }返回值用负数编码错误类型-1 是名字太长、-4 是重名这是 C 里很典型的错误码约定比返回布尔值信息量大。循环从索引 2 开始是因为 0、1 被.和..占了。删除时要注意删一个已打开的文件应该拒绝遍历u_opentable.openitem[i].name比对即可删目录则要先递归清空里面所有文件把占用的盘块在 FAT 里重新标回0否则就是内存泄漏——在课程设计里叫「磁盘块泄漏」答辩时被追问 FAT 回收逻辑答不上来会很尴尬。4. 登录、权限码与打开文件表的权限闭环课程设计要求里的「源文件可以进行读写保护」和「Login 登录」靠的是权限码加打开文件表两层控制。文档里 admin/admin 是最简实现但真实设计里权限位要能表达读、写、执行三种组合。4.1 登录状态机与保护码位运算常见做法是把property字段从一个字符扩成整数位掩码第 0 位读、第 1 位写、第 2 位执行1表示有权限。这样目录和文件的属性判断能统一处理。#define PERM_READ 0x1 #define PERM_WRITE 0x2 #define PERM_EXEC 0x4 // 判断当前用户对目标 FCB 是否有读权限 int check_perm(struct FCB *fcb, int want) { if (want PERM_READ !(fcb-property PERM_READ)) return -3; // 无读权限 if (want PERM_WRITE !(fcb-property PERM_WRITE)) return -3; // 无写权限 return 0; }位运算的好处是组合判断只写一次。参数want用按位或传参比如要同时要读和写就传PERM_READ | PERM_WRITE。open命令里先查文件是否存在、再查权限位任一不满足就返回负错误码上层打印提示。这套和 Linux 的rwx权限在思路上完全一致只是位数简化了。4.2 打开文件表与 fd 分配策略u_opentable是用户打开文件表最多 5 项每项记录文件名、起始盘块号、文件大小。open成功时返回表项下标作为 fdclose时把该项的firstdisk置回 -1 表示释放。字段含义重置时机name[9]打开的文件名close 时清空firstdisk起始盘块号close 时置 -1size文件大小close 时置 0cur_size当前打开文件数open 加一、close 减一cur_size达到MOFN时 open 必须失败否则越界写表。这里有个易错点read和write的 fd 参数要先校验是否在[0, MOFN)且对应项firstdisk ! -1不校验的话一个野 fd 能直接读出一片垃圾数据甚至崩溃。4.3 dir 命令输出与读写回落验证dir要列出文件名、物理地址、保护码、长度四项。物理地址就是firstdisk保护码读property位长度读size。输出后用read读回来、write写进去做闭环验证// 写入后立即读回验证盘块链未断 int fd open(test.txt); write(fd, hello filesystem, 16); close(fd); fd open(test.txt); char buf[128] {0}; int n read(fd, buf); printf(读回 %d 字节: %s\n, n, buf); // 应输出 16 字节: hello filesystem close(fd);如果读回长度对不上八成是 FAT 链在写的时候没接上或者size字段没更新。另一种典型故障是第二次format()把disk.dat覆盖了之前的数据全丢——开发阶段建议把format和enter做成两条独立命令别每次启动都格式化。5. 在真实 Linux 上验证二级文件系统的一致性代码能跑不代表逻辑对。真正要确认这套二级文件系统没写歪得用几个手段交叉验证避免答辩现场被一问就穿帮。第一招是给disk.dat加个 dump 工具把 FAT 表前 32 项和根目录前 8 项打出来。文件分配对不对看 FAT 里的链和 FCB 的firstdisk是否一致一眼能看出来。下面这段可以直接编译进去加个隐藏命令触发void show() { int i; printf(--- FAT 前32项 ---\n); for (i 0; i 32; i) printf(block %d: next%d, used%c\n, i, fat[i].item, fat[i].em_disk); printf(--- 根目录项 ---\n); for (i 0; i MSD 2; i) printf([%d] name%-8s prop%d first%d size%d\n, i, root-directitem[i].name, root-directitem[i].property, root-directitem[i].firstdisk, root-directitem[i].size); }第二招是做一致性约束测试创建 5 个文件把目录项占满再创建第 6 个应该失败每个文件写入后 close 再 open 读回内容必须一致删除一个文件后它占过的盘块在 FAT 里应全部回到0连续 delete/create 循环 100 次磁盘可用块数不能持续下降。这几条走通说明分配和回收闭环没漏。第三招关乎运行环境。文档写的是 JDK 1.6 JBuilder2007那是当年的配置。现在拿这套 Java 代码跑JTree.fireTreeExpanded(path)在较新 JDK 上依然可用但 Swing 的JOptionPane在高分屏下会有缩放问题。更实际的做法是保留业务逻辑把界面层换成 JavaFX 的TreeView加ContextMenu或者干脆降级成命令行交互把重点放回文件系统本身而不是 UI。C 那套则基本无依赖gcc -o fs fs.c即可编译注意fwrite/fread的返回值判断、malloc后的空指针判断这两处必须补上源码文档里malloc没判空内存紧张时会直接崩。答辩时最容易被追问的三个点提前想清楚一是 FAT 和目录项为什么要分离设计二是删除目录为什么必须递归回收盘块三是打开文件表和 FCB 的关系。把这三条能用代码指出来比背心得体会有用得多。最后提醒一句disk.dat是二进制文件用文本编辑器打开是乱码别以为是文件损坏了验证内容请用xxd disk.dat | head看十六进制。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价