UNIX 文件系统提供了层次结构的目录和文件,负责系统内文件信息的管理,是 UNNIX 系统中极为重要的一个部分。本次实验的目的是通过模拟 UNIX V6++ 编写一个 UNIX 文件系统,实现基本文件操作,从而掌握 UNIX 文件系统结构。
使用一个普通的大文件(如 c: myDisk.img,称之为一级文件)来模拟 UNIX V6++ 一个文件卷(把一个大文件当一张磁盘用)。
一个文件卷实际上就是一张逻辑磁盘,磁盘中存储的信息以块为单位,每块 512 字节。
磁盘文件结构
文件目录结构
文件打开结构
磁盘高速缓存:选作
文件操作接口
主程序:
图 3: 文件目录结构
把你的课设报告,关于课程设计报告的 ReadMe.txt 和一张图片存进这个文件系统,分别放在/home/texts ,/home/reports 和 /home/photos 文件夹;
图形界面或者命令行方式,等待用户输入;
根据用户不同的输入,返回结果。
通过命令行方式测试:
实验报告内容
运行结果分析(30%):
评分标准
操作系统:Windows 10 家庭版开发语言:C++ 编译器:Microsoft Visual Studio Community 2019
根据对题目要求分析和用户使用分析,文件系统采用控制台命令作为输入,按照系统给定的命令即可完成相应的功能。本文件模拟系统使用尽可能详细的控制台引导用户输入指令。
用户可以在控制台输入的指令如下:
HELP———–帮助文档。
ATTRIB———显示或更改文件属性。
CD————-显示当前目录的名称或将其更改。
DEL————删除至少一个文件。
DIR————显示一个目录中的文件和子目录。
EXIT———–退出文件系统。
MKDIR———-创建一个目录。
RMDIR———-删除目录。
PRINT———-打印文件内容。
WRITE———-向文件中写入内容
OPEN———–打开一个文件
CLOSE———-关闭一个文件
CREATE———创建一个文件
OPENLIST——-当前打开文件列表
FSEEK———-更改一个文件的指针
LOGOUT———用户退出登录
WHOAMI———显示当前用户信息
FORMAT———格式化文件卷
REGISTER——-用户注册
DELETEACCOUNT–删除用户(root 用户下)
SU————-改变用户
CHGRP———-改变用户所属组(root 用户下)
USERLIST——-显示所有用户信息(root 用户下)使用 help 命令查看各个指令的详细内容如下:
ATTRIB 显示或更改文件属性。
图 4: ATTRIB 指令
CD 显示当前目录的名称或将其更改。
图 5: CD 指令
DEL 删除至少一个文件。
图 6: DEL 指令
DIR 显示一个目录中的文件和子目录。
图 7: DIR 指令
EXIT 退出文件系统。
图 8: EXIT 指令
MKDIR 创建一个目录。
图 9: MKDIR 指令
RMDIR 删除目录。
图 10: RMDIR 指令
PRINT 打印文件内容。
图 11: PRINT 指令
WRITE 向文件中写入内容
图 12: WRITE 指令
OPEN 打开一个文件
图 13: OPEN 指令
CLOSE 关闭一个文件
图 14: CLOSE 指令
CREATE 创建一个文件
图 15: CREATE 指令
OPENLIST 当前打开文件列表
图 16: OPENLIST 指令
FSEEK 更改一个文件的指针
图 17: FSEEK 指令
LOGOUT 用户退出登录
图 18: LOGOUT 指令
WHOAMI 显示当前用户信息
图 19: WHOAMI 指令
FORMAT 格式化文件卷
图 20: FORMAT 指令
图 21: REGISTER 指令
DELETEACCOUNT 删除用户(root 用户下)
图 22: DELETEACCOUNT 指令
SU 改变用户
图 23: SU 指令
CHGRP 改变用户所属组(root 用户下)
图 24: CHGRP 指令
USERLIST 显示所有用户信息(root 用户下)
图 25: USERLIST 指令
程序通过控制台命令行交互的方式进行用户输入引导和显示反馈输出,具体输出将在后续进行介绍。
图 26: 程序输出
实验模拟 UNIX 文件操作系统实现了多用户下文件的一系列操作。具体可以实现的功能如下所示:
整个多用户文件系统可以划分为以下几个模块:
内存管理模块:主要负责 SuperBlock、Inode、Block 的初始化、申请和释放。提供读入读出 SuperBlock、Inode、Block 块以及申请释放 Block 块的接口,同时提供每个 Inode 中逻辑 Block 块号和物理 Block 块号映射关系转换接口。
文件管理模块:主要负责文件相关的一系列操作。提供创建文件、删除文件、显示文件列表、打开文件关闭文件、写文件、读文件、更改文件指针、更改文件权限等操作的相关接口。
用户管理模块:主要负责用户相关的一系列操作。提供用户登录、用户登出、创建用户、删除用户、获取当前登录用户信息、更改用户所属组、输出用户列表信息等操作的相关接口。
目录管理模块:主要负责文件目录相关的一系列操作。提供创建目录、删除目录、打开目录、获取当前目录等操作的相关接口
顶层管理模块:主要负责用户输入信息处理、将用户输入命令转化为内核操作、反馈信息输出、错误反馈等。
整个文件系统包含以下几个数据结构的定义:
Inode 结构体中存储一个 Inode,大小为 64 字节,4 个 Inode 占满一个 BLock 块。其内部有两个枚举类型 INodeMode 和 INodePermission,分别用于枚举 Inode 指向的为文件还是目录、该目录/文件的读写属性,其中读写属性分为文件主、文件主同组和其他用户三组,OWNER_R 为文件主可读,OWNER_W 为文件主可写,OWNER_E 为文件主可执行,GROUP_R 为文件主同组可读,GROUP_W 为文件主同组可写,
GROUP_E 为文件主同组可执行,ELSE_R 为其他用户可读,ELSE_W 为其他用户可写,ELSE_E 为其他用户可执行。
Inode 结构体主要包含的成员如下:i_addr 用于文件逻辑块和物理块的映射,其映射关系如图 27 所示,最多可以容纳 1281282+128*2+6 个 Block 块。i_size 存储文件大
小,以自己为单位,目录大小统一设置为 0,i_count 为当前文件被打开的引用计数, i_number 为当前 Inode 的编号,i_permission 为文件的读写权限,其值为 OWNER_R、 OWNER_W、OWNER_E、GROUP_R、GROUP_W、GROUP_E、ELSE_R、ELSE_W、 ELSE_E 的组合,当 i_permission 与这几个值与运算后不为 0,则代表当前 Inode 拥有这一权限,i_uid 为文件/目录创建者用户 id,i_gid 为文件/目录创建者用户组 id, i_time 为文件最后访问时间。
图 27: i_addr 索引图
//Inode 结构体 struct Inode {
enum INodeMode {
IFILE = 0x1, //是文件
IDIRECTORY = 0x2//是目录
};
enum INodePermission {//分为文件主、文件主同组和其他用户
OWNER_R = 0400,
OWNER_W = 0200,
OWNER_E = 0100,
GROUP_R = 040,
GROUP_W = 020,
GROUP_E = 010,
ELSE_R = 04,
ELSE_W = 02 ,
ELSE_E = 01 ,
};
unsigned int i_addr [NINODE] ; //逻辑块号和物理块号转换的索引表
unsigned int i_size ; //文件大小,字节为单位
unsigned short i_count ; //引用计数
unsigned short i_number ; //Inode 的编号
unsigned short i_mode ; //文件工作方式信息
unsigned short i_permission ; //文件权限
unsigned short i_uid ; //文件所有者的用户标识
unsigned short i_gid ; //文件所有者的组标识
};
time_t i_time ; //最后访问时间
SuperBlock 结构体
SuperBlock 结构体中存储整个文件的 SuperBlock(文件系统超级块)。其中包含以下成员:s_inodenum 存储 Inode 总数,s_finodenum 存储空闲 Inode 数,s_blocknum 存储 Block 总数,s_fblocknum 存储空闲 Block 数,s_nfree 和 s_free 通过成组链接法管理所有空闲 Block,每一组的最大空闲 Block 数为 50(其中 0 号位用于存储下一个空闲表位置)。
// SuperBlock 结构体
struct SuperBlock {
unsigned short s_inodenum ; //Inode 总数
unsigned short s_finodenum ; //空闲 Inode 数
unsigned short s_blocknum ; // Block 总数 unsigned
short s_fblocknum ; //空闲 Block 数
unsigned int s_nfree ; //直接管理的空闲块数
unsigned int s_free [FREE_BLOCK_GROUP_NUM] ; //空闲块索引表
};
Directory 结构体用于存储目录列表,对于一个 i_mode 为 IDIRECTORY 的 Inode,其 i_addr[0] 中所指向的 Block 存储一个 Directory,该 Directory 结构体中的信息即为该目录的信息。
Directory 结构体有两个成员 d_inodenumber 和 d_filename,分别用于存储该目录下的文件/目录的 Inode 号和名称。
// Directory 结构体
struct Directory {
unsigned int d_inodenumber [SUBDIRECTORY_NUM] ; //子目录Inode 号
char d_filename [SUBDIRECTORY_NUM] [FILE_NAME_MAX] ; //子目录文件名
};
User 结构体中存储所有的用户信息。User 结构体放置在 1 号 Block 中。
User 结构体包含四个成员:u_id 存储用户 id、u_gid 存储用户所在组 id、u_name 存储用户名、u_password 存储用户密码。用户 i 信息不存在时则 u_name[i] 为空。
//User 结构体
struct User {
unsigned short u_id [USER_NUM] ; //用户 id unsigned
short u_gid [USER_NUM] ; //用户所在组 id
char u_name[USER_NUM] [USER_NAME_MAX] ; //用户名
char u_password [USER_NUM] [USER_PASSWORD_MAX] ; //用户密码
};
结构体
File 结构体存储打开文件信息。其包含的成员如下:f_inodeid 为文件的 inode 编号、
f_offset 为文件的读写指针位置、f_uid 为文件打开用户。File 结构体伴随打开文件和关闭文件存在消失。
// File 结构体
struct File {
unsigned int f_inodeid ; //文件的 inode 编号
unsigned int f_offset ; //文件的读写指针位置
unsigned int f_uid ; //文件打开用户
};
文件系统 myDisk.img 的空间分配如图 28 所示
图 28: myDisk.img 的空间分配
各个模块之间的调用关系如图 29 所示:
图 29: 模块间调用关系图
整个算法的整体流程如图所示:
图 30: 整体流程图其中,根据命令执行操作的流程具体如下:
根据 help 后有无参数以及参数名,选择不同的提示输出。(具体可参考 2 需求分析模块)
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Edit_File_Permission 函数更改文件权限。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则:如果 cd 后有参数,则根据参数调用 Open_Directory 函数更改文件目录,如果 cd 后无参数,则输出当前所在目录。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数逐一调用 Delete_File 函数删除文件。
DIR
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用Show_File_List 函数显示文件信息。
EXIT
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则退出程序。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Create_Directory 函数创建文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Remove_Directory 函数删除目录。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Read_File 函数将文件内容读出到文件或屏幕。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Write_File 函数将文件或屏幕内容写入文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Open_File 函数打开文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Close_File 函数打开文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Create_File 函数打开文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则遍历输出所有打开文件信息。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Seek_File 函数更改文件指针。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则调用 User_Logout 函数使用户登出。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则调用 Get_User 函数输出当前用户信息。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则调用 Init 函数初始化文件卷。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
User_Register 函数进行用户注册。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
User_Delete 函数删除文件。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
User_Logout 函数和 User_Login 函数更改用户。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Change_User_Group 函数更改用户组。
首先检查命令输入的正确性,若不正确则输出错误信息,若输入正确则根据参数调用
Show_User_List 函数展示用户表。
顶层管理模块所涉及的部分已在前几部分进行了一定说明,下面对内存管理模块、目录管理模块、文件管理模块和用户管理模块的详细设计进行说明。
其流程图如图 31 所示:
空闲盘块采用成组链接法进行管理(如图 32),其流程图如图 33 所示。
图 33: 分配一个 Block 块
回收 Block 块为分配 Block 块的逆过程,两者流程相似。
//如果该空闲块已满,需要使 superblock 指向一个新的空块
i f ( superblock . s_nfree == FREE_BLOCK_GROUP_NUM 1) {
fd . open(DISK_NAME,ios : : out | ios : : in | ios : :binary ) ;
fd . seekg ((BLOCK_POSITION + block_num) BLOCK_SIZE, ios : : beg ) ;
fd . write (( char )&superblock . s_free, sizeof ( superblock . s_free ) ) ;
fd . close () ;
superblock . s_free [ 0 ] = block_num ;
superblock . s_nfree = 0;
superblock . s_fblocknum++;
}
else {
superblock . s_nfree++;
superblock . s_free [ superblock . s_nfree ] = block_num ;
}
小型文件的索引结构
小型文件使用文件素引表中 i_addr[0]-i_addr[5] 这 6 项作为直接索引表,也就相当于限制了小型文件长度范围是 0-6 个数据块,每个盘块大小等于 512 字节。其中,文件逻辑块号 n 对应的物理块号记录在 i_addr[n] 中。譬如,i_addr[2] 的值为 2058 表示该文件的第 3 块数据 (偏移量 1024 1535 字节) 占据的物理盘块编号为 2058。
大型文件的索引结构
当文件的长度超出小型文件限制时,就需要采用大型文件的素引结构。大型文件除了使用 i_addr[0]-i_addr[5] 记录该文件前 6 个物理块号之外,还用到 i_addr[6]、i_addr[7] 两项,每一项指向一个含有物理块号明细的一次间接索引表块,每个一次间接索引表块可以容纳 512/4-128 个物理块号, 可为 128 个文件的逻辑块和物理块建立对应关系。
因此大型文件的长度范围是 7 (6+ 128 *2) 个数据块。如果内核需要通过一次间接索引表块访问数据, 那么先要将一次间接索引表块读入内存,从中找到逻辑块号对应的物理块号,然后再读取该物理块上的文件数据。
巨型文件的索引结构
如果文件长度比大型文件还要大,则除了用到基本素引表中的 i_addr[0]-i_addr[5] 以及
i_addr[6]、i_addr[7] 两项记录该文件前 128 2+6 个物理块号之外, 还要使用 i_addr[8]、 i_addr[9] 来记录二次间接索引表块的物理共号。二次间接索引表块的作用类似于一次间接索引表,表中的每一项都记录者它所指向的一个一次间接索引表块的物理块号,然后再由该一次间接索引表块中的各项记录文件数据的物理块号。每张二次间接索引表块最多可记录 128 个一次间接索引表的物理块号,因此巨型文件的长度范围为(1282+7) (1281282+1282+6)。
创建目录的流程图 34 如图所示,创建目录成功的前提是目录名合法、有空闲的 block 和 inode、当前目录不存在同名目录、当前目录的文件数量未达到限制
删除目录的流程图如图 35 所示,删除目录的前提是目录名合法、目录为空。
打开一个目录需要根据目录路径一级级进入相应的位置,目录路径使用’ 或 ’/’ 进行分割,’.’ 代表当前目录,’…’ 代表父目录,根据目录名寻找到 Inode 号进而更改对应的 directory 即可完成目录的切换
从当前目录向上级目录一层层进行寻找,并使用’ 进 行连接,即可完成当前目录的获取
创建文件的流程图如图所示,创建文件成功的前提是文件名合法、有空闲的 block 和 inode、当前目录不存在同名文件、当前目录的文件数量未达到限制
图 36: 创建文件流程图
在保证当前用户为文件打开用户、用户具有写权限的前提下更改 File 结构体中的 file-
>f_offset 即可
在保证当前用户为文件打开用户、用户具有写权限的前提下,从 file->f_offset 指定位置开始依次取出 Block 块,直至读出文件长度等于 length 或读到文件末尾,在字符串末尾补尾零并返回读到字符串的长度。
每个文件的权限共有三组,分别为主用户、同组用户、和其他用户,每一组用户权限又分为读权限、写权限以及执行权限,在相应的 Inode 中修改 i_permission 属性即可对该权限进行修改。
删除文件的流程图如图所示,删除目录的前提是文件名合法、文件未被打开。
首先检查文件名是否合法,如果合法则在当前目录下寻找这一文件,找到后新建 File 结构体,在其中赋值 f_inodeid 为找到的 inode_num,f_offset 为 0,f_uid 为打开文件的用户 id,并返回 file 结构体。
写文件的流程如图所示,写文件需要保证当前用户为文件打开用户,用户具有写权限。
图 38: 写文件流程图
检查用户名和密码,若均正确,即可进行登录,若输入用户名错误,则提示此用户不存在,若输入密码错误,则输出密码错误
将全局的 user_id 改为-1,即可完成用户登录的退出
只有 root 用户可以进行用户的注册,注册前需要先检查该用户名是否已经存在,若存在,则给出报错,否则进行用户的新建。
只有 root 用户可以进行用户的删除,删除前需要先检查该用户是否存在,若存在则进行用户的删除,否则给出报错。
只有 root 用户可以进行用户所属组的更改,更改前需要先检查该用户是否存在,若存在则进行用户的所属组更改,否则给出报错。
随后提示需要进行登录,并输入用户名和密码,默认情况下存在两个用户:root 用户
拥有最高权限)、juju 用户(普通用户),此时使用 root 用户登录
对一些基本命令进行测试(详细测试将在下一部分进行展示):
图 41: 用户相关命令
图 42: help 命令
图 43: 文件及目录相关测试
用 mkdir 命令创建子目录,建立如图所示的目录结构;
图 44: 目录结构-1
图 45: 目录结构-2
把你的课设报告,关于课程设计报告的 ReadMe.txt 和一张图片存进这个文件系统,分别放在/home/texts ,/home/reports 和 /home/photos 文件夹;使用如下的命令将 ReadMe.txt 放入/home/texts
cd home/ texts
create readme . txt open readme . txt
1 2 3
图 48: 放入 report.pdf
write readme . txt f ReadMe. txt close readme . txt
4 5
图 46: 将 ReadMe.txt 放入/home/texts
使用如下的命令将 readme.txt 中的内容再次写出:
cd home/ texts open readme . txt
print readme . txt p tmp. txt fseek readme . txt 0 close readme . txt
1 2 3 4 5
比对两个文件,完全一致,证明了程序的正确性。
图 50: 定位读写指针并写回文件
可以看到与预期相同,写入了 300 个字节
图 51: jerryfile.txt
再将其写回到文件
图 52: 写回文件
可以看到与预期相同,jerry 目前共由 1100 个字节
对命令进行逐一测试:
HELP———–帮助文档。
图 53: help 命令测试
ATTRIB———显示或更改文件属性。
图 54: 更改文件读写属性
CD————-显示当前目录的名称或将其更改。
图 55: 更改目录
DEL————删除至少一个文件。
图 56: 删除文件
DIR————显示一个目录中的文件和子目录。
图 57: 显示目录中内容
MKDIR———-创建一个目录。
图 58: 创建目录
图 59: 删除目录
PRINT———-打印文件内容。
图 60: 打印文件内容
WRITE———-向文件中写入内容
图 61: 向文件写入
OPEN———–打开一个文件
图 62: 打开文件
CLOSE———-关闭一个文件
图 63: 关闭文件
CREATE———创建一个文件
图 64: 创建一个文件
OPENLIST——-当前打开文件列表
图 65: 打开文件列表
FSEEK———-更改一个文件的指针
图 66: 更改文件定位指针
LOGOUT———用户退出登录
图 67: 用户登出
WHOAMI———显示当前用户信息
图 68: 显示当前用户信息
REGISTER——-用户注册(root 用户下)
图 69: 用户注册
DELETEACCOUNT–删除用户(root 用户下)
图 70: 删除用户
SU————-改变用户
图 71: 改变用户
CHGRP———-改变用户所属组(root 用户下)
图 72: 改变用户所属组
USERLIST——-显示所有用户信息(root 用户下)
图 73: 显示用户信息
多用户读写测试
– 首先,用户 juju 在文件 test.txt 中写入 juju
– 随后用户 root 打开同一个文件 test.txt,在其中写入 root
可以看到,root 用户在刚进入文件系统时可以看到 juju 写入的 4 个字节,随后 root 用户写入 root 将 juju 覆盖
– 此时 juju 再次进行文件的读,读到的是 root 用户写入的 root
– juju 继续向文件中写入 juju
– 此时 root 用户再次读
图 80: 大文件测试-1
将其读出到屏幕
图 81: 大文件测试-2
将输出内容与文件进行比对,两者完全相同
图 82: 大文件测试-3 因此可以得出结论,该系统可以进行大文件的读入
读写权限测试
root 用户创建文件,并更改其权限为除自己与同组用户外都不可读写
– 切换到 juju 用户(与 root 非同组)
图 85: 读写权限测试-3 可以看到,系统提示 juju 无权进行写操作
由 5.1 及 5.2 可知,程序具有正确的运行结果,并在用户命令输入不当时可以进行正确的报错,同时可以对用户输入命令给予良好的引导,整个程序完整清晰,具有友好性和正确性
项目结构:
图 86: 项目结构
运行方法:
在 Windows 下直接点击生成的 exe 文件执行,Linux 下可以将源程序放在集成环境下编译,也可利 GNU 编译工具,通过写好的 Makefile 进行编译。运行界面为控制台的命令行方式,命令较为简单,通俗易懂,初始界面如下:
图 87: 初始界面
具体命令的使用方法可以通过输入 [ help 命令名] 进行查看
系统初始化后的文件结构如下:root –bin –etc –home —-texts —-reports —-photos –dev 最初进入系统时包含两个用户:root 用户和 juju 用户,其中 root 用户为最高权限用户,可以进行注册用户、删除用户、改变用户所属组、显示用户信息等操作,juju 为普通用户, root 用户密码为 root,juju 用户密码为 juju
该文件系统在节点数量等上具有一定限制,用户的最大数量为 6,文件及目录个数最多为 256,一个目录下的子目录与文件数之和最多为 12,文件名称的最大长度为 32,用户名称的最大长度为 16,用户密码的最大长度为 32 程序正常退出使用 exit 命令,格式化使用 format 命令。
大小: 5.59MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87415717