• 基于C语言实现的Linux系统中的EXT2文件系统


    简单文件系统的设计与实现

    一、实验目的

    以 Linux 系统中的 EXT2 文件系统为例,熟悉该文件系统内部数据结构的组织方式和基本处理流程,在此基础上设计并实现一个简单的文件系统。

    二、实验环境

    Linux
    Microsoft Visual Code

    三、实验内容

    3.1 实验任务

    仅列出必做部分

    实现青春版 Ext2 文件系统功能包括:

    创建文件/文件夹(数据块可预分配);

    读取文件夹内容;

    复制文件;

    关闭系统;

    系统关闭后,再次进入该系统还能还原出上次关闭时系统内的文件部署。

    为实现的文件系统实现简单的 shell 以及 shell 命令以展示实现的功能。

    ls - 展示读取文件夹内容

    mkdir - 创建文件夹

    touch - 创建文件

    cp - 复制文件

    shutdown - 关闭系统

    3.2 实验过程

    首先对 disk.c 进行了解

    disk.c 是本次实验提供的虚拟磁盘接口,可以看出,这个文件模拟了一个大小为

    4MB 的磁盘。

    inline int get_disk_size() 
    {         return 4*1024*1024;  } 
    
    • 1
    • 2

    提供的一些接口:

    static int create_disk() 
    
    • 1

    用于创建磁盘,将一个文件名为 disk 的 4MB 文件用 0 填满,进行初始化;

    int open_disk() 
    
    • 1

    用于打开磁盘,即读取 disk 文件中的内容;

    int disk_read_block(unsigned int block_num, char* buf) 
    
    • 1

    读磁盘块,注意这里要与后面会用到的数据块、超级块区分开;

    DEVICE_BLOCK_SIZE,即每一个磁盘块的大小为 512B,而后面的数据块大小为KB(1MB),即每个数据块占用 2 个磁盘块;

    int disk_write_block(unsigned int block_num, char* buf) 
    
    • 1

    与上面相反,写入磁盘块是将 buf 中的内容写到磁盘块中,同样 buf 的大小为 512B;

    int close_disk() 
    
    • 1

    关闭磁盘。

    整体设计结构

    shell.c —— 实现简单的 shell block.c —— 数据块相关操作 command.c —— 对于输入 cmd type 的处理 dir_item.c —— 目录项相关操作 disk.c —— 磁盘接口 init_fs.c —— 文件系统初始化 inode.c —— inode 相关操作 path.c —— 对于输入 cmd path 部分的处理 utils.c ——常用接口封装

    实验原理的再理解

    Ext2 文件系统

    在这里插入图片描述

    指导书中把文件系统的基本布局简化为:超级块、inode 数组、数据块数组。

    其中:

    超级块:记录系统的整体信息。

    typedef struct super_block 
    {    
         int32_t magic_num;        // 幻数     
         int32_t free_block_count; // 空闲数据块数     
         int32_t free_inode_count; // 空闲inode数     
         int32_t dir_inode_count;  // 目录inode数     
         uint32_t block_map[128];  // 数据块占用位图     
         uint32_t inode_map[32];   // inode占用位图 
    } 
    sp_block; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    超级块记录文件系统的一些信息,分析一下占用大小可以发现,超级块占用字节为(32+32+32+32+32128+3232)/8 = 656B,超出了一个磁盘块的大小,为了我们处理起来比较方便,选择了让一个超级块占用 2 个磁盘块。 block_map 的每一个元素大小为 int32,而每一个比特代表一个数据块的使用情况,所以一共可以记录 128*32 个数据块的使用情况。

    inode_map 同理,可以记录 32*32 个 inode 索引节点的占用情况。

    inode 数组:对于每一个文件,对应一个 inode 结构体,记录文件大小、文件类型、连接数、数据块指针。

    typedef struct inode 
    {    
         uint32_t size;           // 文件大小     
         uint16_t file_type;      // 文件类型(文件/文件夹)     
         uint16_t link;           // 连接数     
         uint32_t block_point[6]; // 数据块指针 
    } inode; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    看得出来一个 inode 结构体的大小为 32B,而前面讲过,一个数据块的大小为,因此我们在这里定义两个量:

    inode_map_index = inode_number / 32; bit_index = inode_number % 32; 
    
    • 1

    这两个变量用于记录位图信息,inode_map_index 是 inode_map 的索引,而 bit_index 是用于修改每一比特的 0/1 大小。

    block_point 大小为 6 个 int,那么这说明在文件系统中如果使用直接索引方式,文件大小不能超过 6KB。

    文件和文件夹 dir_item:

    这里为了简化整个系统的表示,实际上目录和文件的区别仅仅在于 dir_item 和 inode 中的 type 不同,也不涉及文件内容的修改,因此创建文件和文件夹的逻辑是类似的。

    typedef struct dir_item 
    {                      // 目录项一个更常见的叫法是 
        dirent(directory entry)     
        uint32_t inode_id; // 当前目录项表示的文件/目录的对应inode     
        uint16_t valid;    // 当前目录项是否有效    
        uint8_t type;      // 当前目录项类型(文件/目录) 
        char name[121];    // 目录项表示的文件/目录的文件名/目录名 
    } dir_item; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以计算出来一个 dir_item 的大小为 128B。

    对于 shell 的理解

    shell 的实现实际上之前在写 xv6 实验的时候已经试过,第一个是要实现对于输入 cmd 的拆分,将其拆分为 type 和 path。

    type 是指 shell 命令,这里包括 ls、mkdir、touch、cp、shutdown,对于其他命令应该给与报错提示。

    对于 path 的拆分处理放在了后面(使用堆栈实现)。

    具体函数设计实现(节省篇幅,仅写出了每个命令的执行该过程,其他的注释里都有写,这里就不再重复了)。

    void shutdown_cmd() 
    {     
        close_disk();     
        exit(0); 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    shutdown 命令直接就是调用 close_disk()函数接口来实现了。

    ls_cmd() //ls 命令 
    
    • 1

    ls 命令的输入是路径,而要根据路径进行处理,则要将路径进行一下拆分。这里用到了一个自己定义的函数 search_dir_item_by_path(),从函数名理解,即根据路径找到 dir_item,然后根据 dir_item,也有了 inode_id(在 dir_item 中有记录),调用 read_inode()函数,可以找到对应的 inode,再一级一级查找到 dir_item(此过程每次有一个上一目录和当前目录,然后结构类似于栈,每次到下一级目录以后,上一级目录出栈)。

    touch_cmd() //touch 命令 
    
    • 1

    根据输入的路径,路径的最后一级就是所要创建的文件,而之前的是文件夹。首先要对路径进行查找,如果路径不存在,则不能创建。创建文件与创建文件夹是类似的,创建新的 inode 和 dir_item 标记类型为 FILE 并更新之即可。

    mkdir_cmd() //mkdir 命令 
    
    • 1

    检测创建的目录是否为根目录,是根目录提示不能创建(第一次可以创建),不是根目录的话,找到上一级文件夹的 dir_item 和 inode,然后为需要创建的文件夹创建对应的 dir_item。

    cp_cmd() //cp 命令 
    
    • 1

    cp 命令是复制命令。这里首先要提到,之前在 shell 中 cmd 定义为二维数组,这样就可以方便对复制文件的原文件和复制后文件的路径进行管理,当然写报告的时候也想到了通过对空格进行检测的方式,也还是可以实现路径的分离的。

  • 相关阅读:
    Vue3 - 异步组件(写法及详细示例)
    Apache Hudi vs Delta Lake:透明TPC-DS Lakehouse性能基准
    Redis源码漂流记(二)-搭建Redis调试环境
    如何防止重复提交订单
    二进制概述-0day漏洞利用原理(1)
    数字孪生与元宇宙在关键技术方面的异同
    Java 中经常被提到的 SPI 到底是什么?
    基于51单片机的温度控制系统数码管显示蜂鸣器报警proteus仿真原理图PCB
    ARM Day2
    OSCP系列靶场-Esay-Vegeta1保姆级
  • 原文地址:https://blog.csdn.net/newlw/article/details/126764037