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

news2024/11/16 22:55:36

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

删除文件

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

  • 释放 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() 的代码)。

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

欢迎关注我的公众号


 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1386745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EndNote登录一直显示The username/password specified is not valid

EndNote登录一直显示The username/password specified is not valid EndNote20今天想打开之前的share library的时候一直显示 ‘The Username/password specified is not valid’&#xff0c;查了很多解决方案&#xff0c;现在献上解决方案&#xff1a; 该密码然后重新登陆…

如何在 SwiftUI 中使用 AccessibilityCustomContentKey 修饰符

文章目录 前言创建 User 结构体添加辅助修饰符使用新的修饰符使用修饰符来替换和覆盖数据可运行代码总结 前言 SwiftUI 3 发布了许多新的辅助功能 API&#xff0c;我们可以利用这些 API 以轻松的方式显著提高用户体验。本篇文章来聊聊另一个新的 API&#xff0c;我们可以使用 …

数仓面试之手写拉链表SQL,并分析有多少个job

数仓面试之手写拉链表SQL&#xff0c;并分析有多少个job 拉链表定义 维护历史状态&#xff0c;以及最新状态数据的一种表&#xff0c;拉链表根据拉链粒度的不同&#xff0c;实际上相当于快照&#xff0c;只不过做了优化&#xff0c;去除了一部分不变的记录而已,通过拉链表可以…

phpinfo和php -m 加载的php.ini不一致

目的&#xff1a; 将phpinfo在web中展示的php.ini和在命令行中展示的php.ini加载路径设置一致。 原本的php.ini加载路劲是&#xff1a; /usr/local/lib/php.ini 解决思路&#xff1a; &#xff08;1&#xff09;which php 查看服务器加载的php的位置&#xff0c;这里原来是&a…

Sketch不会安装?教你在Windows中打开Sketch!

使用 Windows 系统的 UI 设计师可能遇到过这样一个问题&#xff1a;他们收到了其他人发送的 Sketch 文件&#xff0c;但 Windows 系统无法打开 Sketch 文件&#xff0c;也不知道如何在 Windows 上打开 Sketch 文件。这是一个真实工作场景的问题。对于这个问题&#xff0c;即时设…

RT-Thread: 控制台调试串口波特率更改

说明&#xff1a;rt_kprintf 函数是RT 的一个调试接口使用的函数&#xff0c;波特率默认是 115200 &#xff0c;本文介绍更改这个波特率。 1.根据截图路径找到文件 serial.h 修改如下代码中关于波特率定义部分。 /* Default config for serial_configure structure */ #defin…

测试工程师必会能力之缺陷分析入门

缺陷分析也是测试工程师需要掌握的一个能力&#xff0c;但是很多时候大家只记得要提交缺陷、统计缺陷情况&#xff0c;而忽视了缺陷分析。 其实每个项目的缺陷记录都是有很大价值的&#xff1a; 在测试阶段分析当前缺陷情况&#xff0c;及时发现存在的问题并调整测试策略&…

微软Power Platform使用Canvas app画布应用添加自定义连接器调用外部API展示数据

微软Power Platform使用Power Apps的Canvas app画布应用添加自定义连接器&#xff0c;调用外部API展示数据 目录 微软Power Platform使用Power Apps的Canvas app画布应用添加自定义连接器&#xff0c;调用外部API展示数据1、在Power Apps中找到自定义连接器2、创建一个空白的自…

数码秒表设计

#include<reg51.h> // 包含51单片机寄存器定义的头文件 unsigned char code Tab[10]{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //数码管显示0&#xff5e;9的段码表 unsigned char int_time; //记录中断次数 unsigned char second; //储存…

java基础 - 03 List之AbstractSequentialList、LinkedList

上一篇我们围绕了ArrayList以及List进行简单介绍&#xff0c;本篇我们将围绕AbstractSequentialList、LinkedList进行。 AbstractSequentialList AbstractSequentialList是Java集合框架中的一个抽象类&#xff0c;它实现了List接口&#xff0c;并且是针对顺序访问的列表数据结…

ME6211C33M5G-N 输出3.3V 500mA 线性稳压器LDO 参数

描述 ME6211系列是高精度&#xff0c;低噪声&#xff0c;CMOS LDO电压调压器。ME6211系列提供低输出噪声&#xff0c;高纹波抑制率&#xff0c;低辍学率和非常快速的开启时间&#xff0c;ME6211系列是当今最前沿的手机的理想选择。ME6211内部包括参考电压源、误差放大器、驱动…

运放负反馈

学习记录所使用书籍为西安交通大学杨建国教授著《新概念模拟电路》&#xff0c;可在ADI官网下载PDF版学习。 运算放大器&#xff0c;英文为 Operational Amplifier&#xff0c;简写 OA 或 OPA&#xff0c;中文简称为运放。 理想运算放大器如图所示&#xff0c;它具有两个差分的…

uni-table改表头的样式,uniapp项目,颜色,字体颜色

:first-child,:nth-child选择器的使用和隔行变色_firstchild怎么用-CSDN博客

C++_虚函数表

虚函数表 介绍源码运行结果笔记扩充函数名联编静态联编动态联编 介绍 1.编译器通过指针或引用调用虚函数&#xff0c;不会立即生成函数调用指令&#xff0c;而是用 二级函数指针 代替 1.1确定真实类型 1.2找到虚函数表从而找到入口地址 1.3根据入口地址调用函数(PS:俗称 函数指…

[ACM学习] 动态规划基础之一二三维dp

课内学习的动态规划 有记忆的迭代 优化解的结构&#xff1a;原始问题的一部分解是子问题的解 三要素&#xff1a;1.子问题 2.状态的定义 3.状态转移方程 定义 线性dp的一道例题 dp[i]表示以位置 i 结尾的方案总数&#xff0c;dp[4]2&#xff0c;因为&#xff1a;首先只放一…

卓越协同,数字化运维:智能工单系统助力企业解决派单难题-亿发

不少企业的I运维部门在管理制度上存在架构混乱、分工不明、流程不透明等问题&#xff0c;导致部门内部和合作服务商之间的协作常常呈现出“踢皮球”的状态。因此&#xff0c;有效的企业运维协同管理显得尤为关键。然而&#xff0c;如果内部的协同流程设计不合理&#xff0c;过多…

【数据结构】归并排序的非递归写法和计数排序

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

Sqoop作业调度:自动化数据传输任务

自动化数据传输任务是大数据处理中的一个重要方面&#xff0c;可以定期执行Sqoop作业&#xff0c;确保数据在不同系统之间的同步。本文将深入探讨如何使用Sqoop作业调度来自动化数据传输任务&#xff0c;并提供详细的示例代码和全面的内容&#xff0c;以帮助大家更好地理解和应…

网络安全B模块(笔记详解)- 利用python脚本进行web渗透测试

利用python脚本进行web渗透测试 1.使用渗透机场景kali中工具扫描确定Web服务器场景地址,浏览网站Flag.html页面,并将Flag.html中的Flag提交; 扫描发现是8081端口 访问页面查看 Flag:WXL0601 2.进入渗透机场景win7操作系统,完善桌面上的tupian.py文件,填写该文件当中空缺…

rime中州韵小狼毫 日期/农历 时间 事件 节气 滤镜

教程目录&#xff1a;rime中州韵小狼毫须鼠管安装配置教程 保姆级教程 100增强功能配置教程 网络上但凡提到 rime中州韵小狼毫须鼠管输入法&#xff0c;总少不了智能时间&#xff0c;日期等炫技&#xff0c;可见这个便捷时间/日期输入功能是多么的受欢迎。作者也不落窠臼&…