1. 实验介绍
本次实验主要学习Linux 文件系统的底层实现,加深学生对文件系统底层存储数据结构的理解。为便于
学生实现,本次实验只要求实现一个与ramfs 类似的内存文件系统,无需关注与硬盘等设备的交互。
在实验开始之前,需要注意以下两点:
① 本次实验服务器已完成内核编译(openEuler 4.19.08),可直接开始实验;
② 本次实验可能用到的内核函数和系统调用均已在正文中给出,若要查看内核函数的详细信息,可前
往https://manpages.org/查询。
2. 实验目的
l 学习掌握Linux 系统中普通文件和目录文件的区别与联系
l 学习掌握Linux 管理文件的底层数据结构
l 学习掌握Linux 文件存储的常见形式
l 加深学生对读写者问题的理解和信号量的使用
3. 实验内容
3.1 任务概述
- 内存文件系统myRAMFS 的功能要求
本次实验要求学生在Linux 下实现一个类似于ramfs 的内存文件系统myRAMFS,该文件系统至少 支持下表中
描述的10 条命令,其中实验手册已提供了部分命令的实现,其他命令需要大家自行实现。
注意:以上命令只需要在当前目录下正常执行即可,无需提供对非当前路径下文件或目录操作的支持。例
如:touch 命令无需支持在其它路径下创建文件、mkdir 命令无需支持在其它路径下创建目录、read/write 命
令无需支持读写非当前路径下的文件。
- 文件系统功能完善与可用性测试
内存文件系统由Disk 模块和File 模块组成,其中Disk 模块用于与内存交互,提供存储接口,完成内存的分配与回收操作;File 模块负责实现基于内存的虚拟文件系统。请你根据任务引导完成myRAMFS 文件系统中 File 功能模块的编写。
实验手册已给出Makefile、myRAMFS.cpp、Disk.h、Disk.cpp 和File.h 的完整代码以及待填充的File.cpp,请你 根据任务引导完善 File.cpp,要求编译并运行 myRAMFS.cpp 后能够类似于 shell 命令窗口,实时从命令行中 读取命令,解析并执行。
代码 1. Makefile
代码2. myRAMFS.cpp
代码3. Disk.h
代码4. File.h
代码5. Disk.cpp
代码6. File.cpp
3.2任务引导
- 内存文件系统——Linux 文件读取和写入的本质
用户和操作系统对文件的读写操作是有差异的,用户进程习惯以字节的方式读写文件;而操作系统内核则
是以数据块的形式读写。文件系统的作用就是屏蔽掉这种差异。本次实验要求学生设计一个基于内存的文
件系统,即文件存储在内存而非硬盘上,相对于 硬盘文件系统,内存文件系统的实现更为简单,也能让学生
专注于文件系统本身。
- 内存文件系统——文件的存储
内存文件系统的文件数据需要存储在内存上,与程序在内存中的存放类似,文件在内存中的存放方式主要
有连续空间存放和非连续空间存放两种,其中非连续空间存放又可分为链表方式和索引方式。为了降低实
验难度,本次实验设计的内存文件系统myRAMFS 要求学生使用连续空间存放的存储方式。
连续空间存放
连续空间存放方式顾名思义,文件存放在内存连续的物理空间中(注意,本次设计的myRAMFS 是虚拟内存
文件系统,虚拟内存连续,物理内存不一定连续),在这种存储形式下,文件数据紧密相连,读写效率较高。
另外,使用连续的存储方式需要在文件头中指定起始块的位置和文件占用的块大小。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTgJytYs-1653902854281)(https://secure2.wostatic.cn/static/5vZNy5MeCpzBnSfNdqozHV/image.png)]
连续存储的方式虽然读写效率较高,但同时也会带来内存空间碎片和文件长度不易于扩展等缺陷。
- 内存文件系统——空闲空间管理
myRAMFS 虚拟内存文件系统使用位示图法管理空闲空间
3.3具体任务
请你完成File.cpp 中未完成的函数的编写,实现完整的myRAMFS 虚拟内存文件系统的功能。
本次实验难度不算大,现在来依次来讲解一下每个函数的实现:
- addDirUnit(dirTable myDirTable, char fileName[], int
type, int FCBBlockNum)* 可以看到,函数内已经有一些注释来指引我们code了,
- 测目录表示是否已满。进行一下判断就行(好像代码已经给了)
- 是否存在同名文件。 我将char[]转化为string再用c++的string.compare()进行比较
- 构建新的目录项:需要对文件以及目录结构体有所了解 File.h,可见新建目录项,需要新建一个dirUnit对象,并且添加到当前myDirTable中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O49Pa48K-1653902854284)(https://secure2.wostatic.cn/static/vAHVfR1ff8Bci9yaoP5Vqb/image.png)]
int addDirUnit(dirTable* myDirTable, char fileName[], int type, int FCBBlockNum)
{
//检测目录表示是否已满
if(myDirTable->dirUnitAmount == 15) {
perror("the directory is full!\n");
return -1;
}
//是否存在同名文件
string fileNameStr(fileName);
for (int i = 0; i < myDirTable->dirUnitAmount; ++i) {
string name(myDirTable->dirs[i].fileName); // 将字符数组转成string比较
if(fileNameStr.compare(name) == 0) {
perror("already has the same name file!\n");
return -1;
}
}
//构建新目录项
myDirTable->dirUnitAmount += 1;
dirUnit newd;
myDirTable->dirs[myDirTable->dirUnitAmount - 1] = newd; // 初始化并且赋值
strcpy(myDirTable->dirs[myDirTable->dirUnitAmount - 1].fileName, fileName);
myDirTable->dirs[myDirTable->dirUnitAmount - 1].type = type;
myDirTable->dirs[myDirTable->dirUnitAmount - 1].startBlock = FCBBlockNum;
return 0; //return -1 is error
}
**2. int changeDir(char dirName[]) **
- 先找到目录项的位置
- 之后修改目录
- 最后修改全局路径
//DONE 切换目录 cd
int changeDir(char dirName[])
{
//目录项在目录位置
string fileNameStr(dirName);
int dirSize = currentDirTable->dirUnitAmount;
int k = -1;
for (int i = 0; i < dirSize; ++i) {
string name(currentDirTable->dirs[i].fileName);
if(fileNameStr.compare(name) == 0) {
k = i;
break;
}
}
//文件名不存在
if(k == -1) {
perror("cannot find this file!\n");
return -1;
}
//不是目录类型
if (currentDirTable->dirs[k].type != 0) {
perror("the file type is not directory!\n");
return -1;
}
//修改当前目录(currentDirTable)
dirTable* findDir = (dirTable *)getBlockAddr(currentDirTable->dirs[k].startBlock);
currentDirTable = findDir;
//修改全局绝对路径(path)
int i = 0;
for(; path[i] != '\0'; i++) {
;
}
for (int j = 0; j < fileNameStr.size(); ++j) {
path[i++] = fileNameStr[j];
}
path[i++] = '/';
path[i] = '\0';
return 0;
}
3. int changeName(char oldName[], char newName[])
- 实现比较简单,找到对应的目录或者文件,改名即可
//DONE 修改文件名或者目录名 rn
int changeName(char oldName[], char newName[])
{
// 不能改名".."
string fileNameStr(oldName);
if(fileNameStr.compare("..") == 0) {
perror("cannot rm the parent dir!\n");
return -1;
}
// 找到对应目录或者文件
int dirSize = currentDirTable->dirUnitAmount;
int k = -1;
for (int i = 0; i < dirSize; ++i) {
string name(currentDirTable->dirs[i].fileName);
if(fileNameStr.compare(name) == 0) {
k = i;
break;
}
}
if(k == -1) {
perror("cannot find this file!\n");
return -1;
}
//
strcpy(currentDirTable->dirs[k].fileName, newName);
return 0;
}
4. int creatDir(char dirName[])
- 先检测文件长度
- 分配空间用到了getBlock(int size) (模仿touch函数实现)
- 最后将目录添加到当前目录
- 并给新建目录一个父目录
//TODO 创建目录 mkdir
int creatDir(char dirName[])
{
//检测文件名字长度
if (strlen(dirName) >= FILENAME_MAX_LENGTH) {
perror("the name length is out of bound!\n");
return -1;
}
//为目录表分配空间
int FCBBlock = getBlock(1);
if (FCBBlock == -1)
return -1;
if (creatFCB(FCBBlock, 0, 0) == -1)
return -1;
dirTable* newDir = (dirTable *)getBlockAddr(FCBBlock);
//将目录作为目录项添加到当前目录
if(addDirUnit(currentDirTable, dirName, 0, FCBBlock) == -1)
return -1;
//为新建的目录添加一个到父目录的目录项
if(addDirUnit(newDir, "..", 0, currentDirTable->dirs->startBlock) == -1)
return -1;
return 0;
}
5. deleteFile(char fileName[])
- 找到文件,将其删除
- 实现比较流水线化,写好每一步都代码就行(见注释)
// 删除文件 rm
int deleteFile(char fileName[])
{
//忽略系统的自动创建的父目录
string fileNameStr(fileName);
if(fileNameStr.compare("..") == 0) {
perror("cannot rm the parent dir!\n");
return -1;
}
//查找文件的目录项位置
int dirSize = currentDirTable->dirUnitAmount;
int k = -1;
for (int i = 0; i < dirSize; ++i) {
string name(currentDirTable->dirs[i].fileName);
if(fileNameStr.compare(name) == 0) {
k = i;
break;
}
}
if(k == -1) {
perror("cannot find this file!\n");
return -1;
}
//若文件类型为目录,则返回错误
dirUnit findFile = currentDirTable->dirs[k];
if(findFile.type == 0) {
perror("cannot rm a directory!\n");
return -1;
}
//释放文件内存
int FCBBlock = findFile.startBlock;
releaseFile(FCBBlock);
//从目录表中剔除
deleteDirUnit(currentDirTable, k);
return 0;
}
6. write_file(char fileName[], char content[])
- 模仿read_file写法
- 获得锁,写数据
- 退出
//DONE 写文件,从末尾写入 write
int write_file(char fileName[], char content[])
{
//获得控制块
FCB *myFCB = open(fileName);
/* 获得写者锁 */
myFCB->write_sem = sem_open("write_sem", 0);
if (sem_wait(myFCB->write_sem) == -1)
perror("sem_wait error");
//在不超出文件的大小的范围内写入
int fileSize = myFCB->fileSize;
char* data = (char*)getBlockAddr(myFCB->dataStartBlock);
for (int i = 0; i < strlen(content) && myFCB->dataSize < fileSize; i++, myFCB->dataSize++) {
*(myFCB->dataSize + data) = content[i];
}
/* 模拟编辑器,控制写者不立即退出 */
printf("> Write finished, press any key to continue....");
getchar();
/* 释放写者锁 */
sem_post(myFCB->write_sem);
return 0;
}
4. 实验成果
我的运行环境是wsl2, 图片导不进来进不放了
5. 实验总结
- 本次实验不需要那么复杂的配置环境,直接上手写代码,做实验的体验非常的好
- 操作系统实验在这之后也就结束了,我给出对于操作系统实验的评价:
我认为nachos太老了,内容质量也不算太好,没有难度的阶梯性;华为openeuler实验还是不够成熟,虽然是中国开源操作系统的未来之光,但还是有用于教学的更好系统。
我推荐xv6(一个基于riscv指令集的类unix系统),代码量小,却五脏俱全。 能够引进几个实验,带同学读读源码,肯定能给大家带来更多收获。