《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

相关文章

Python实现终端清屏的几种方式详解

《Python实现终端清屏的几种方式详解》在使用Python进行终端交互式编程时,我们经常需要清空当前终端屏幕的内容,本文为大家整理了几种常见的实现方法,有需要的小伙伴可以参考下... 目录方法一:使用 `os` 模块调用系统命令方法二:使用 `subprocess` 模块执行命令方法三:打印多个换行符模拟

SpringBoot+EasyPOI轻松实现Excel和Word导出PDF

《SpringBoot+EasyPOI轻松实现Excel和Word导出PDF》在企业级开发中,将Excel和Word文档导出为PDF是常见需求,本文将结合​​EasyPOI和​​Aspose系列工具实... 目录一、环境准备与依赖配置1.1 方案选型1.2 依赖配置(商业库方案)二、Excel 导出 PDF

Python实现MQTT通信的示例代码

《Python实现MQTT通信的示例代码》本文主要介绍了Python实现MQTT通信的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 安装paho-mqtt库‌2. 搭建MQTT代理服务器(Broker)‌‌3. pytho

使用zip4j实现Java中的ZIP文件加密压缩的操作方法

《使用zip4j实现Java中的ZIP文件加密压缩的操作方法》本文介绍如何通过Maven集成zip4j1.3.2库创建带密码保护的ZIP文件,涵盖依赖配置、代码示例及加密原理,确保数据安全性,感兴趣的... 目录1. zip4j库介绍和版本1.1 zip4j库概述1.2 zip4j的版本演变1.3 zip4

Python自动化批量重命名与整理文件系统

《Python自动化批量重命名与整理文件系统》这篇文章主要为大家详细介绍了如何使用Python实现一个强大的文件批量重命名与整理工具,帮助开发者自动化这一繁琐过程,有需要的小伙伴可以了解下... 目录简介环境准备项目功能概述代码详细解析1. 导入必要的库2. 配置参数设置3. 创建日志系统4. 安全文件名处

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

Spring StateMachine实现状态机使用示例详解

《SpringStateMachine实现状态机使用示例详解》本文介绍SpringStateMachine实现状态机的步骤,包括依赖导入、枚举定义、状态转移规则配置、上下文管理及服务调用示例,重点解... 目录什么是状态机使用示例什么是状态机状态机是计算机科学中的​​核心建模工具​​,用于描述对象在其生命

Spring Boot 结合 WxJava 实现文章上传微信公众号草稿箱与群发

《SpringBoot结合WxJava实现文章上传微信公众号草稿箱与群发》本文将详细介绍如何使用SpringBoot框架结合WxJava开发工具包,实现文章上传到微信公众号草稿箱以及群发功能,... 目录一、项目环境准备1.1 开发环境1.2 微信公众号准备二、Spring Boot 项目搭建2.1 创建

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录