openEuler实验二 文件系统
openEuler实验二 文件系统实验目的• 学习掌握Linux系统中普通文件和目录文件的区别与联系• 学习掌握Linux管理文件的底层数据结构• 学习掌握Linux文件存储的常见形式• 加深学生对读写者问题的理解和信号量的使用实验任务完成File.cpp中未完成函数的编写:addDirUnit(dirTable* myDirTable, char fileName[], int type, in
openEuler实验二 文件系统
实验目的
• 学习掌握Linux系统中普通文件和目录文件的区别与联系
• 学习掌握Linux管理文件的底层数据结构
• 学习掌握Linux文件存储的常见形式
• 加深学生对读写者问题的理解和信号量的使用
实验任务
完成File.cpp中未完成函数的编写:
addDirUnit(dirTable* myDirTable, char fileName[], int type, int FCBBlockNum)
这是一个工具函数,用于根据参数给定信息,创建目录项挂到给定参数表下。
int addDirUnit(dirTable* myDirTable, char fileName[], int type, int FCBBlockNum)
{
//检测目录表示是否已满
if(myDirTable->dirUnitAmount == DIR_TABLE_MAX_SIZE)
{
printf("the directory is full! \n");
return -1;
}
//是否存在同名文件
if(findUnitInTable(myDirTable, fileName) != -1)
{
printf("%s existed!\n", fileName);
return -1;
}
//构建新目录项
dirUnit *op = &myDirTable->dirs[myDirTable->dirUnitAmount ++];
strcpy(op->fileName, fileName);
op->startBlock = FCBBlockNum;
op->type = type;
return 0;
}
int changeDir(char dirName[])
该函数用于实现文件系统切换工作目录的功能cd,为了实现任意跳转,我在设计时额外设计了一个函数int changeToRealDir(char *dirName),用于将传入的参数dirName转换为以/结尾的绝对路径。然后从根节点开始搜索,最后修改全局的path和当前目录表currentDirTable。
int changeToRealDir(char *dirName)
{
if(dirName[0] == '/'){
if(dirName[strlen(dirName) - 1] != '/')
strcat(dirName, "/");
return 0;
}
if(dirName[0] == '.')
{
if(dirName[1] == '/')
{
char address[200];
strcpy(address, path);
strcat(address, dirName + 2);
strcpy(dirName, address);
if(dirName[strlen(dirName) - 1] != '/')
strcat(dirName, "/");
return 0;
}
if(dirName[1] == 0)
{
strcpy(dirName, path);
if(dirName[strlen(dirName) - 1] != '/')
strcat(dirName, "/");
return 0;
}
if(dirName[1] == '.')
{
char address[200];
if(dirName[2] == 0)
{
strcpy(address, path);
int i = strlen(address) - 2;
while(i >= 0 && address[i] != '/') address[i --] = 0;
strcpy(dirName, address);
}
else if(dirName[2] == '/')
{
strcpy(address, path);
int i = strlen(address) - 1;
while(address[i] != '/') address[i --] = 0;
strcat(address, dirName + 3);
strcpy(dirName, address);
}
else{
return -1;
}
if(dirName[strlen(dirName) - 1] != '/')
strcat(dirName, "/");
return 0;
}
}
char address[200];
strcpy(address, path);
strcat(address, dirName);
strcpy(dirName, address);
if(dirName[strlen(dirName) - 1] != '/')
strcat(dirName, "/");
return 0;
}
//TODO 切换目录 cd
int changeDir(char DirName[])
{
char dirName[200];
strcpy(dirName, DirName);
changeToRealDir(dirName);
//目录项在目录位置
char *switch_to;
int i = 1;
dirTable *tmp = rootDirTable;
char address[200];
strcpy(address, dirName);
while(~i){
switch_to = dirName + i;
for(; dirName[i]; i ++)
if(dirName[i] == '/')
{
dirName[i] = 0;
i ++;
break;
}
if(dirName[i] == 0)
{
if(!strlen(switch_to)) break;
i = -1;
}
int dirno = findUnitInTable(tmp, switch_to);
//文件名不存在
if(dirno == -1)
{
printf("the directory is not exist! \n");
return -1;
}
//不是目录类型
if(tmp->dirs[dirno].type)
{
printf("%s is not a directory! \n", address);
return -1;
}
//修改当前目录(currentDirTable)
tmp = (dirTable* ) getBlockAddr(tmp->dirs[dirno].startBlock);
}
//修改全局绝对路径(path)
strcpy(path, address);
currentDirTable = tmp;
return 0;
}
int changeName(char oldName[], char newName[])
修改名字的操作比较简单,只要能定位到相应的目录或文件,修改对应的目录单元即可。
int changeName(char oldName[], char newName[])
{
int target = findUnitInTable(currentDirTable, oldName);
if(target == -1 || target == 0)
{
printf("cannot find %s\n", oldName);
return -1;
}
strcpy(currentDirTable->dirs[target].fileName, newName);
return 0;
}
int creatDir(char dirName[])
在前述功能已经准备好的情况下,创建目录可以先使用addDirUnit挂上一个目录单元,然后使用changDir切换到挂好的目录单元中,再次调用addDirUnit挂上..目录,然后重新使用changeDir(".."),切换回原来的目录。
关于上述功能实现上,可能存在strcpy后段错误的坑,这是由于将长字符串复制给短字符串造成内存溢出所导致的,所以在必要的时候,需要额外重新开一个数组空间存储地址。
int creatDir(char DirName[])
{
char dirName[200];
strcpy(dirName, DirName);
//检测文件名字长度
if(strlen(dirName) > DIR_TABLE_MAX_SIZE)
{
printf("dirname is too long !\n");
return -1;
}
//为目录表分配空间
int block = getBlock(1);
//将目录作为目录项添加到当前目录
addDirUnit(currentDirTable, dirName, 0, block);
//为新建的目录添加一个到父目录的目录项i
dirTable* new_one = (dirTable*) getBlockAddr(block);
new_one -> dirUnitAmount = 0;
block = getAddrBlock((char *) currentDirTable);
changeDir(dirName);
addDirUnit(currentDirTable, "..", 0, block);
strcpy(dirName, "..");
changeDir("..");
return 0;
}
int deleteFile(char fileName[])
删除文件首先要定位文件,定位文件之后判断是否为文件,如果是文件,则调用releaseFile释放文件内存,然后再调用deleteDirUnit从目录表中删除该文件的目录项。
int deleteFile(char fileName[])
{
//查找文件的目录项位置
//忽略系统的自动创建的父目录
int op = findUnitInTable(currentDirTable, fileName);
if(op == -1)
{
printf("file not exist! \n");
return -1;
}
if(!op)
{
printf("you cannot remove .. \n");
return -1;
}
//若文件类型为目录,则返回错误
if(!currentDirTable->dirs[op].type)
{
printf("not a file\n");
return -1;
}
//释放文件内存
releaseFile(currentDirTable->dirs[op].startBlock);
//从目录表中剔除
deleteDirUnit(currentDirTable, op);
return 0;
}
int write_file(char fileName[], char content[])
关于读者写者问题,写者准备书写时,首先按顺序获取read_sem2锁(阻止新的读者、写者进入)和write_sem锁(确保所有读者退出),然后书写,书写完成后释放所获取的锁。
int write_file(char fileName[], char content[])
{
//获得控制块
FCB *myFCB = open(fileName);
if(!myFCB) return -1;
/* 获得写者锁 */
char *data = (char *) getBlockAddr(myFCB->dataStartBlock);
myFCB->write_sem = sem_open("write_sem", 0);
myFCB->read_sem2 = sem_open("read_sem2", 0);
if (sem_wait(myFCB->read_sem2) == -1)
perror("sem_wait error");
if (sem_wait(myFCB->write_sem) == -1)
perror("sem_wait error");
//在不超出文件的大小的范围内写入
int dataSize = myFCB->dataSize;
for (int i = myFCB->dataSize, j = 0; i < myFCB -> fileSize * 1024 && j < strlen(content); i++, j++) {
data[i] = content[j];
myFCB->dataSize ++;
}
/* 模拟编辑器,控制写者不立即退出 */
printf("> Write finished, press any key to continue....");
getchar();
/* 释放写者锁 */
sem_post(myFCB->write_sem);
sem_post(myFCB->read_sem2);
return 0;
}
实验结果测试
使用实验手册中要求的测试序列,获得了与实验手册中完全一致的结果。


实验代码改进建议
在实验手册中,关于信号量的命名上,可以从creatFCB函数中看出,它们使用的信号量均采用了相同的命名方式:
//创建FCB
int creatFCB(int fcbdataStartBlock, int filedataStartBlock, int fileSize) {
//找到fcb的存储位置
FCB *currentFCB = (FCB *) getBlockAddr(fcbdataStartBlock);
currentFCB->dataStartBlock = filedataStartBlock;//文件数据起始位置
currentFCB->fileSize = fileSize;//文件大小
currentFCB->link = 1;//文件链接数
currentFCB->dataSize = 0;//文件已写入数据长度
currentFCB->readPtr = 0;//文件读指针
currentFCB->read_sem = sem_open("read_sem", O_CREAT, 0644, READER_MAX_NUM);
if (currentFCB->read_sem == SEM_FAILED) {
perror("sem_open error");
exit(1);
}
currentFCB->write_sem = sem_open("write_sem", O_CREAT, 0644, 1);
if (currentFCB->write_sem == SEM_FAILED) {
perror("sem_open error");
exit(1);
}
currentFCB->read_sem2 = sem_open("read_sem2", O_CREAT, 0644, 1);
if (currentFCB->read_sem2 == SEM_FAILED) {
perror("sem_open error");
exit(1);
}
return 0;
}
在每一个信号量创建时,信号量的名称都采用了"read_sem"、"write_sem"、"read_sem2",对于有名信号量而言,访问不同文件时,会尝试获取相同的信号量,这违背了防止互斥的初衷,更加糟糕的是,在删除文件相关的函数int releaseFile(int FCBBlock)里面,对应于下附代码的14、15、16行进行了unlink操作,本意是释放关于这个文件而言的读写锁,但实际上是在系统内删除了该有名信号量。在当前这样的设计下,如果其他文件正在进行读写操作,而另一窗口进行删除文件的操作,这会直接导致异常。
//释放文件内存
int releaseFile(int FCBBlock) {
FCB *myFCB = (FCB *) getBlockAddr(FCBBlock);
myFCB->link--; //链接数减一
//无链接,删除文件
if (myFCB->link == 0) {
//释放文件的数据空间
releaseBlock(myFCB->dataStartBlock, myFCB->fileSize);
}
//释放FCB的空间
sem_close(myFCB->read_sem);
sem_close(myFCB->write_sem);
sem_close(myFCB->read_sem2);
sem_unlink("read_sem");
sem_unlink("write_sem");
sem_unlink("read_sem2");
//释放FCB空间
releaseBlock(FCBBlock, 1);
return 0;
}
使用两个进程去访问相同信号量,然后在一个进程中删除文件:

修改了showDir函数,使得它会同时输出信号量的值,可以发现,右边获取读者锁后,左边对应信号量减1,右边仍在读写未释放锁,左边进行删除后,信号量已经被unlink,此时,右边同样是无法正常工作的。
由于在实验中,不需要刻意去实现相关错误,因此该现象并不明显,但是显然违背了互斥的初衷,因此,应该在初始化信号量时,按照文件的名称和其他的标识,或者直接使用文件的绝对路径(保证唯一性)作为信号量的名称,从而来避免以上的问题。

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐


所有评论(0)