《ORANGE’S:一个操作系统的实现》读书笔记(三十一)文件系统(六)

本文主要是介绍《ORANGE’S:一个操作系统的实现》读书笔记(三十一)文件系统(六),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇文章记录了对文件的读写操作,那么文件操作到目前为止,已经完成了创建和读写,还剩下的常用操作就是删除文件了。这篇文章就来记录删除文件的实现以及总结一下为文件系统添加系统调用的步骤。

删除文件

删除是添加的反过程,所以要删除文件,我们需要做以下工作:

  • 释放 inode-map 中的相应位。
  • 释放 sector-map 中的相应位。
  • 删除根目录中的目录项。

注意我们不需要在inode_array中释放相应的i-node,因为释放inode-map中的相应位已经将inode_array中的位置标记为“未使用”了,不过在接下来的代码中我们仍然将i-node清空。

另外从最简单的删除功能出发,我们并不需要释放为文件分配的扇区,因为sector-map中的1和0已经清楚地表明了扇区是否可以使用。

现在我们来看一下删除文件的代码。

代码 fs/link.c,do_unlink,这是新建的文件。

/*** Remove a file.* * @note We clear the i-node in inode_array[] although it is not really needed.*       We don't clear the data bytes so the file is recoverable.* * @return On success, zero is returned.    On error, -1 is returned.*/
PUBLIC int do_unlink()
{char pathname[MAX_PATH];/* get parameters from the message */int name_len = fs_msg.NAME_LEN; /* length of filename */int src = fs_msg.source; /* caller proc nr. */assert(name_len < MAX_PATH);phys_copy((void*)va2la(TASK_FS, pathname),(void*)va2la(src, fs_msg.PATHNAME),name_len);pathname[name_len] = 0;if (strcmp(pathname, "/") == 0) {printl("FS:do_unlink():: cannot unlink the root\n");return -1;}int inode_nr = search_file(pathname);if (inode_nr == INVALID_INODE) { /* file not found */printl("FS::do_unlink()::search_file() returns invalid inode: %s\n", pathname);return -1;}char filename[MAX_PATH];struct inode * dir_inode;if (strip_path(filename, pathname, &dir_inode) != 0) {return -1;}struct inode * pin = get_inode(dir_inode->i_dev, inode_nr);if (pin->i_mode != I_REGULAR) { /* can only remove regular files */printl("cannot remove file %s, because it is not a regular file.\n", pathname);return -1;}if (pin->i_cnt > 1) { /* the file was opened */printl("cannot remove file %s, because pin->i_cnt is %d.\n", pathname, pin->i_cnt);return -1;}struct super_block * sb = get_super_block(pin->i_dev);/* free the bit in i-map */int byte_idx = inode_nr / 8;int bit_idx = inode_nr % 8;assert(byte_idx < SECTOR_SIZE); /* we have only one i-map sector *//* read sector 2 (skip bootsect and superblk): */RD_SECT(pin->i_dev, 2);assert(fsbuf[byte_idx % SECTOR_SIZE] & (1 << bit_idx));fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << bit_idx);WR_SECT(pin->i_dev, 2);/* free the bits in s-map *//**           bit_idx: bit idx in the entire i-map*     ... ____|____*                  \        .-- byte_cnt: how many bytes between*                   \      |              the first and last byte*        +-+-+-+-+-+-+-+-+ V +-+-+-+-+-+-+-+-+*    ... | | | | | |*|*|*|...|*|*|*|*| | | | |*        +-+-+-+-+-+-+-+-+   +-+-+-+-+-+-+-+-+*         0 1 2 3 4 5 6 7     0 1 2 3 4 5 6 7*  ...__/*      byte_idx: byte idx in the entire i-map*/bit_idx = pin->i_start_sect - sb->n_1st_sect + 1;byte_idx = bit_idx / 8;int bits_left = pin->i_nr_sects;int byte_cnt = (bits_left - (8 - (bit_idx % 8))) / 8;/* current sector nr. */int s = 2 /* 2: bootsect + superblk */+ sb->nr_imap_sects + byte_idx / SECTOR_SIZE;RD_SECT(pin->i_dev, s);int i;/* clear the first byte */for (i = bit_idx % 8; (i < 8) && bits_left; i++, bits_left--) {assert((fsbuf[byte_idx % SECTOR_SIZE] >> i & 1) == 1);fsbuf[byte_idx % SECTOR_SIZE] &= ~(1 << i);}/* clear bytes from the second bytes to the second to last */int k;i = (byte_idx % SECTOR_SIZE) + 1; /* the second byte */for (k = 0; k < byte_cnt; k++, i++, bits_left-=8) {if (i == SECTOR_SIZE) {i = 0;WR_SECT(pin->i_dev, s);RD_SECT(pin->i_dev, ++s);}assert(fsbuf[i] == 0xFF);fsbuf[i] = 0;}/* clear the last byte */if (i == SECTOR_SIZE) {i = 0;WR_SECT(pin->i_dev, s);RD_SECT(pin->i_dev, ++s);}unsigned char mask = ~((unsigned char)(~0) << bits_left);assert((fsbuf[i] & mask) == mask);fsbuf[i] &= (~0) << bits_left;WR_SECT(pin->i_dev, s);/* clear the i-node itself */pin->i_mode = 0;pin->i_size = 0;pin->i_start_sect = 0;pin->i_nr_sects = 0;sync_inode(pin);/* release slot in inode_table[] */put_inode(pin);/* set the inode-nr to 0 in the directory entry */int dir_blk0_nr = dir_inode->i_start_sect;int nr_dir_blks = (dir_inode->i_size + SECTOR_SIZE) / SECTOR_SIZE;int nr_dir_entries = dir_inode->i_size / DIR_ENTRY_SIZE; /* including unused slots (the file has been deleted but the slot is still there) */int m = 0;struct dir_entry * pde = 0;int flg = 0;int dir_size = 0;for (i = 0; i < nr_dir_blks; i++) {RD_SECT(dir_inode->i_dev, dir_blk0_nr + i);pde = (struct dir_entry *)fsbuf;int j;for (j = 0; j < SECTOR_SIZE / DIR_ENTRY_SIZE; j++, pde++) {if (++m > nr_dir_entries) {break;}if (pde->inode_nr == inode_nr) {memset(pde, 0, DIR_ENTRY_SIZE);WR_SECT(dir_inode->i_dev, dir_blk0_nr + i);flg = 1;break;}if (pde->inode_nr != INVALID_INODE) {dir_size += DIR_ENTRY_SIZE;}}if (m > nr_dir_entries || /* all entries have been iterated OR */flg) { /* file is found */break;}}assert(flg);if (m == nr_dir_entries) { /* the file is the last one in the dir */dir_inode->i_size = dir_size;sync_inode(dir_inode);}return 0;
}

代码分四部分:

  1. 释放 inode-map 中的相应位;
  2. 释放 sector-map 中的相应位;
  3. 将 inode_array 中的 i-node 清零;
  4. 删除根目录中的目录项。

第46行我们对pin->i_cnt进行了判断,只有当i_cnt等于1时我们才能继续下去。也就是说,此时的i-node应该只被引用了一次。只要i_cnt大于1,函数就直接返回代表不成功的-1,因为此时一定还有别的文件描述符(file descriptor)引用了i-node。这时文件还在使用中,我们当然不能就这样删除它。

由于代码第39行调用了get_inode(),所以一定不要忘记在使用完pin之后调用put_inode()来释放i_cnt,不然inode_table[]中的相应项就永远不会被覆盖,这就造成内存泄漏了。

在删除根目录的目录项时,我们面临一个选择:是让删除后的目录项空洞留在那里还是重新布置根目录。我们选择了前者,因为这样比较省事。这样做的坏处在于,一旦系统中有过文件删除操作,那么根目录文件的i_size就可能无法准备反应根目录中文件的多少。比如下图所示的情况,虽然根目录中连表示自身的“.”都算上也只有7个文件,但根目录文件的i_size却是(7+N)×DIR_ENTRY_SIZE,因为中间有N个之前存在的文件被删掉,此刻留下了大小为N×DIR_ENTRY_SIZE的空洞。

不过这个坏处其实无关紧要,只是要注意两个地方。一是当我们创建文件时,有空洞就先利用空洞,没有任何空洞时再扩展根目录文件的大小;二是空洞之后的文件都已被删除之后,我们最好改变根目录文件的i_size。

好了,删除文件的核心代码有了,我们在task_fs中将删除文件的消息处理添加上,代码如下所示。

代码 fs/main.c,task_fs。

/*** <Ring 1> The main loop of TASK FS.*/
PUBLIC void task_fs()
{
...switch (fs_msg.type) {case OPEN:fs_msg.FD = do_open();break;case CLOSE:fs_msg.RETVAL = do_close();break;case READ:case WRITE:fs_msg.CNT = do_rdwt();break;case UNLINK:fs_msg.RETVAL = do_unlink();break;default:dump_msg("FS::unknown message:", &fs_msg);assert(0);break;}
...
}

接下来,我们来写一个对用户的接口函数,代码如下所示。

代码 lib/unlink.c,unlink(),这是新建的文件。

/*** Delete a file.* * @param pathname  The full path of the file to delete.* * @return Zero if successful, otherwise -1.*/
PUBLIC int unlink(const char * pathname)
{MESSAGE msg;msg.type = UNLINK;msg.PATHNAME = (void*)pathname;msg.NAME_LEN = strlen(pathname);send_recv(BOTH, TASK_FS, &msg);return msg.RETVAL;
}

然后就可以在用户进程中调用它了,代码如下所示。

代码 kernel/main.c,删除文件。

void TestA()
{
...char * filenames[] = {"/foo", "/bar", "/baz"};int i;/* create files */for (i = 0; i < sizeof(filenames) / sizeof(filenames[0]); i++) {fd = open(filenames[i], O_CREAT | O_RDWR);assert(fd != -1);printf("File create: %s (fd %d)\n", filenames[i], fd);close(fd);}char * rfilenames[] = {"/foo", "/bar", "/baz", "/dev_tty0"};/* remove files */for (i = 0; i < sizeof(rfilenames) / sizeof(rfilenames[0]); i++) {if (unlink(rfilenames[i]) == 0) {printf("File removed: %s\n", rfilenames[i]);} else {printf("Failed to remove file: %s\n", rfilenames[i]);}}spin("TestA");
}

现在我们可以make并运行一下,看一下运行结果了,由于我们增加了C文件,所以不要忘记更改Makefile。运行结果如下图所示。

我们创建了“foo”、“bar”和“baz”三个文件,然后又以不同的顺序删除它们。另外我们还试图删除特殊文件“/dev_tty0”,这也被及时地“制止”了。

为文件系统添加系统调用的步骤

如今我们已经有了open()、close()、read()、write()以及unlink()等系统调用,如果对比Linux的系统调用,这显然还不够,不过更多的系统调用也不会有太多的新意了,有兴趣的话,可以自行添加需要的系统调用。为文件系统添加一个系统调用xxx()的步骤大致上有这些:

  1. 定义一种消息,比如MM(可参照 include/const.h 中 UNLINK 的定义方法)。
  2. 写一个函数来处理MM消息(可参照 fs/link.c 中 do_unlink() 的代码)。
  3. 修改task_fs(),增加对消息MM的处理。
  4. 写一个用户接口函数xxx()(可参照 lib/unlink.c 中 unlink() 的代码)。

另外诸如增加函数声明的琐碎事宜,在此不再赘述。

欢迎关注我的公众号


 

公众号中对应文章附有当前文章代码下载说明。

这篇关于《ORANGE’S:一个操作系统的实现》读书笔记(三十一)文件系统(六)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/609986

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

Python如何实现PDF隐私信息检测

《Python如何实现PDF隐私信息检测》随着越来越多的个人信息以电子形式存储和传输,确保这些信息的安全至关重要,本文将介绍如何使用Python检测PDF文件中的隐私信息,需要的可以参考下... 目录项目背景技术栈代码解析功能说明运行结php果在当今,数据隐私保护变得尤为重要。随着越来越多的个人信息以电子形

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

使用Python快速实现链接转word文档

《使用Python快速实现链接转word文档》这篇文章主要为大家详细介绍了如何使用Python快速实现链接转word文档功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 演示代码展示from newspaper import Articlefrom docx import