《操作系统-真象还原》14. 文件系统

news2024/12/23 3:46:23

文章目录

    • 文件系统概念
      • inode
      • 目录项与目录
      • 通过文件名查找文件的流程
      • 超级块与文件系统布局
      • 文件控制块 —— FCB
    • 创建文件系统
      • 创建相关结构
      • 创建文件系统
      • 挂载分区
    • 文件描述符
      • 文件描述符和文件表
        • 文件描述符
        • 文件表
        • 文件描述符与 inode 关联关系
      • 文件描述符的实现
    • 文件操作相关的基础函数
      • inode 操作有关的函数
      • 文件相关的函数
      • 目录相关的函数
      • 路径解析的函数
      • 实现文件检索功能
    • 操作思路 —— 思维导图
    • 创建文件
      • fs/file.c —— file_create
      • fs/fs.c —— sys_open
      • fs/fs.c —— 初始化
    • 文件的打开与关闭
      • 文件的打开
      • 文件的关闭
    • 文件的写入
      • fs/file.c —— file_write
      • 改进 sys_write 和 write 系统调用
    • 读取文件
    • 实现文件读写指针定位
    • 文件删除
      • 回收 inode
      • 删除目录项
      • 实现 sys_unlink
    • 创建目录
    • 遍历目录
      • 打开目录和关闭目录
      • 读取一个目录项
      • 实现 sys_readdir 及 sys_rewinddir
    • 删除目录
      • 删除目录与判断空目录
      • 实现 sys_rmdir
    • 任务的工作目录
      • 基础函数
      • sys_getcwd
      • sys_chdir
    • 文件属性
    • 参考资料

文件系统概念

文件系统是对文件的管理方案。

硬盘是低速设备,其读写单位是扇区,为了避免频繁访问硬盘,操作系统不会有了一扇区数据就去读写一次磁盘,往往等数据积攒到足够大时,在一次性访问硬盘,那么到底是多大呢?我们以“块”为单位,一个块可能 4K 或 32K 等。

硬盘读写单位是扇区,一次一个块是由多个扇区组成,块大小是扇区的整数倍。

块是文件系统的读写单位,因此一个文件可能被分成多个块进行存储。

FAT 文件系统的文件链式组织结构

image-20221221171033500

缺点:当需要访问文件中的某一块时,需要从头开始遍历。

另一种文件组织结构:UINX 操作系统中的索引结构 —— inode。

image-20221221171816687

inode

inode 提供了:文件的 inode 编号、权限、属组、时间、文件大小、基本的直接快(可以直接获取到块地址)、三个级别的间接块(若基本的直接块不够用,则启用这三级)。

关于三个间接块:

  • 一级间接块,存储的是新建块的索引表的地址,该索引表可容纳 256 个块的地址,因此加上一级块总共可容纳 12+256=168 块。
  • 二级间接块,存储的是新建的二级块索引表的地址,而二级块索引表中每项都是一级块索引表的地址,因此二级本身可容纳 256 × 256 256\times256 256×256 块,所以加上二级可总共可容纳 12 + 256 + 256 × 256 12+256+256\times256 12+256+256×256 块。
  • 三级间接块,存储的是新建的三级块索引表的地址,而三级块索引表中每项都是二级块索引表的地址,二级块索引表中每项都是一级块索引表的地址,因此三级块本身可容纳 256 × 256 × 256 256\times256\times256 256×256×256 块,所以总共可容纳 12 + 256 + 256 × 256 + 256 × 256 × 256 12+256+256\times256+256\times256\times256 12+256+256×256+256×256×256 块。

image-20221221171901777

inode 是文件在文件系统上的元信息(文件本身的元信息是文件本身的文件头),要想通过文件系统访问到文件,必须先找到文件的 inode,从这个意义上看,inode 也可以是文件。

每个文件都必须有一个 inode,有多少文件就有多少个 inode。

文件系统是针对各个分区来管理磁盘空间的,各个分区有所有文件的 inode 结构、文件的数据块、超级块、各种位图、目录项等,需要先确定下来一方,先划分 inode 结构和文件数据块,其它的暂时忽略,由于 inode 结构大小是确定的,因此可以先固定下来 inode 所占用的空间。

注:既然每个文件都必须有一个 inode,那么若 inode 所占空间固定下来,那么就说明该分区所允许创建的最大文件数量也是有限的。
当然,实际创建的文件数必然 <= 允许的最大文件数,因为还得考虑文件的大小,若空间为 10M,允许最多创建 10 个文件,即可创建 2 个 5M,也可创建 10 个 1M。

目录项与目录

我们是通过文件名来访问文件的,但文件系统是通过 inode 来访问文件的,因此对文件系统来说文件名并不重要。

我们需要把文件名和 inode 关联到一起。

我们找文件时,文件肯定存在于某个目录中,当然你可能不知道具体在哪个目录下,但可以肯定的是必然存在于根目录 / 之中。

在 Linux 中,目录和文件都用 inode 表示,因此目录也是文件,只是目录是包含文件的文件。我们通常称目录为目录文件,其它的称为普通文件。

既然都是文件,那么如何区分目录文件还是普通文件:根据数据块中描述的内容来确定,若数据块中的内容是数据项,那么该文件就是目录文件,反之为普通文件。

image-20221221182101274

inode 编号文件名文件类型
6165597.目录
6161422目录
6165599image.img文件
6165600index.html文件

通过文件名查找文件的流程

image-20221221185203675

  1. 根据 / 根目录的 inode 编号,找到对应的数据块。
  2. 因为 / 是目录文件,因此数据块中存储的都是目录项,根据文件名,在该数据块中找到 etc,判断其文件类型,发现 etc 是个目录文件,最后拿到 etc 的 inode 编号。
  3. 因为 etc 是目录文件,因此数据块中存储的都是目录项,根据文件名,在该数据块中找到 fstab,判断其文件类型是个普通文件,拿到其 inode 编号。
  4. 拿到 fstab 的 inode 编号后,去找到数据块,由于 fstab 是普通文件,因此该数据块存储的是实际的内容。

图片来着:https://www.bilibili.com/video/BV1yV411r7Qb/?vd_source=9d985a804a1c8cf904ddafbc9c973491

超级块与文件系统布局

超级块(Super Block):存储文件系统的元信息的元信息。

保存 inode 数组的位置、block 数组的位置、inode 和 block 位图位置、以及容量大小等信息。

在 Linux 中根目录就只有一个,即 /,但 Windows 中每个分区都各有不同的根目录。但我们需要考虑的问题是一样的,就是根目录要保存到哪儿,况且 Windows 中还需要保存多个根目录。
这个位置显然必须是固定的。

实际上超级块就是保存诸如上面的信息。

超级块可以看成是文件系统的配置文件。
超级块所存储的信息取决于文件系统的复杂程度。

超级块的逻辑结构:

image-20221221213207095

超级块是在为分区创建文件系统时创建的,所有有关文件系统元信息的配置都在超级块中,因此超级块的位置和大小不能再被配置了,必须是固定的,它被固定存储在各分区的第二个扇区中,通常占用一个扇区,具体与实际文件系统类型为准。

文件系统布局:

image-20221221214747048

注意:操作系统引导可能占用多个扇区,因此上图中写的是“块”,而不是“扇区”。

文件控制块 —— FCB

为了能对一个文件进行正确的存取,操作系统必须为文件设置用于描述和控制文件的数据结构,称之为“文件控制块(FCB)”。

创建文件系统

创建相关结构

fs/super_block.h

/* 超级块 */
struct super_block {
   uint32_t magic;		    // 用来标识文件系统类型,支持多文件系统的操作系统通过此标志来识别文件系统类型
   uint32_t sec_cnt;		    // 本分区总共的扇区数
   uint32_t inode_cnt;		    // 本分区中inode数量
   uint32_t part_lba_base;	    // 本分区的起始lba地址

   uint32_t block_bitmap_lba;	    // 块位图本身起始扇区地址
   uint32_t block_bitmap_sects;     // 扇区位图本身占用的扇区数量

   uint32_t inode_bitmap_lba;	    // i结点位图起始扇区lba地址
   uint32_t inode_bitmap_sects;	    // i结点位图占用的扇区数量

   uint32_t inode_table_lba;	    // i结点表起始扇区lba地址
   uint32_t inode_table_sects;	    // i结点表占用的扇区数量

   uint32_t data_start_lba;	    // 数据区开始的第一个扇区号
   uint32_t root_inode_no;	    // 根目录所在的I结点号
   uint32_t dir_entry_size;	    // 目录项大小

   uint8_t  pad[460];		    // 加上460字节,凑够512字节1扇区大小
} __attribute__ ((packed));

fs/inode.h

// inode 结构
struct inode {
    uint32_t i_no; // inode 编号
    uint32_t i_size; // 若 inode 为目录,则 i_size 便是该目录下所有目录项大小之和
                     // 若 inode 为文件,则 i_size 便是文件大小
    uint32_t i_open_cnt; // 记录该文件被打开的次数之和
    bool write_deny;     // 写文件不能多个进程并行修改,进程写文件前会先检查此标识符
    uint32_t i_sectors[13]; // [0~11] 是直接块,[12] 用来存储一级间接块指针
    struct list_elem inode_tag;
};

fs/dir.h

#define MAX_FILE_NAME_LEN 16 // 最大文件名长度

// 目录结构
struct dir {
    struct inode* inode;
    uint32_t dir_pos;       // 记录在目录内的偏移
    uint8_t dir_buf[512];   // 目录的数据缓存
};

// 目录项结构
struct dir_entry {
    char filename[MAX_FILE_NAME_LEN]; // 普通文件或目录名称
    uint32_t i_no; // inode 编号
    enum file_types f_type; // 文件类型
};

fs/fs.h

#define MAX_FILES_PER_PART 4096 // 每个分区所支持的最大文件数
#define BITS_PER_SECTOR 4096    // 每个扇区的位数 512(byte) * 8(bit)
#define SECTOR_SIZE 512         // 一个扇区的字节大小
#define BLOCK_SIZE SECTOR_SIZE  // 块字节大小

// 文件类型
enum file_types {
    FT_UNKNOWN,     // 其它类型文件
    FT_REGULAR,     // 普通文件
    FT_DIRECTORY    // 目录文件
};

创建文件系统

fs/fs.c —— 格式化分区

// 格式化分区,也就是初始化分区中的元信息,从而创建文件系统
static void partition_format(struct partition* part) {
    // 为了方便,一个块大小 = 一个扇区
    uint32_t boot_sector_sects = 1;
    uint32_t super_block_sects = 1;
    // inode 位图所占用的扇区数,最多支持 4096 个文件
    // 目前我理解为:512byte = 4096bit, 即每个 bit 对应一个“块”, 每个块可以是 inode
    uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR);
    // inode_table 所需要的扇区数
    uint32_t inode_table_sects = DIV_ROUND_UP(((sizeof(struct inode) * MAX_FILES_PER_PART)), SECTOR_SIZE);
    // 已使用的扇区数
    uint32_t used_sects = boot_sector_sects + super_block_sects + inode_bitmap_sects + inode_table_sects;
    // 空闲空间的扇区数
    uint32_t free_sects = part -> sec_cnt - used_sects;

    // 块的位图 所占用的扇区数
    // 一个块 = 一个扇区, 4096 位 = 4096 个块, 直接除于 4096 可得到扇区数
    uint32_t block_bitmap_sects;
    block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
    // 位图中位的个数
    uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects;
    block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);

    /*------------------------------------*/
    // super_block 超级块的初始化
    /*------------------------------------*/
    struct super_block sb;
    sb.magic = 0x19590318;
    sb.sec_cnt = part -> sec_cnt;
    sb.inode_cnt = MAX_FILES_PER_PART;
    sb.part_lba_base = part -> start_lba;

    sb.block_bitmap_lba = sb.part_lba_base + 2; // 第 0 块是引导块,第 1 块是超级块
    sb.block_bitmap_sects = block_bitmap_sects;

    sb.inode_bitmap_lba = sb.block_bitmap_lba + sb.block_bitmap_sects;
    sb.inode_bitmap_sects = inode_bitmap_sects;

    sb.inode_table_lba = sb.inode_bitmap_lba + sb.inode_bitmap_sects;
    sb.inode_table_sects = inode_table_sects;

    sb.data_start_lba = sb.inode_table_lba + sb.inode_table_sects;
    sb.root_inode_no = 0;
    sb.dir_entry_size = sizeof(struct dir_entry);

    printk("%s info:\n", part->name);
    printk("   magic:0x%x\n   part_lba_base:0x%x\n   all_sectors:0x%x\n   inode_cnt:0x%x\n   block_bitmap_lba:0x%x\n   block_bitmap_sectors:0x%x\n   inode_bitmap_lba:0x%x\n   inode_bitmap_sectors:0x%x\n   inode_table_lba:0x%x\n   inode_table_sectors:0x%x\n   data_start_lba:0x%x\n", sb.magic, sb.part_lba_base, sb.sec_cnt, sb.inode_cnt, sb.block_bitmap_lba, sb.block_bitmap_sects, sb.inode_bitmap_lba, sb.inode_bitmap_sects, sb.inode_table_lba, sb.inode_table_sects, sb.data_start_lba);

    /*-----------------------------------------*/
    // 将超级块写入本分区的 1 扇区(0 扇区是引导扇区)
    /*-----------------------------------------*/
    struct disk* hd = part -> my_disk;
    ide_write(hd, part -> start_lba + 1, &sb, 1);

    printk("   super_block_lba:0x%x\n", part->start_lba + 1);

    // 找出数据量最大的元信息,将其作为存储缓冲区
    uint32_t buf_size = (sb.block_bitmap_sects >= sb.inode_bitmap_sects ? sb.block_bitmap_sects : sb.inode_bitmap_sects);
    buf_size = (buf_size >= sb.inode_table_sects ? buf_size : sb.inode_table_sects) * SECTOR_SIZE;
    uint8_t* buf = (uint8_t*) sys_malloc(buf_size);

    /*-----------------------------------------*/
    // 将块位图初始化并写入 sb.block_bitmap_lba
    /*-----------------------------------------*/
    buf[0] |= 0x01; // 第 0 块先预留给根目录,位图先占位
    uint32_t block_bitmap_last_byte = block_bitmap_bit_len / 8; // 位图的长度,以字节为单位
    uint8_t block_bitmap_last_bit = block_bitmap_bit_len % 8; // 最后不足一个字节的位的个数
    // last_size 是位图所在扇区最后不足一扇区的部分
    uint32_t last_size = SECTOR_SIZE - (block_bitmap_last_byte % SECTOR_SIZE);

    // 将位图最后一个字节所在扇区的有效位全部置为 1, 也就是说超出的那 last_size 全部设置为已占用状态
    // 因为我们创建块位图大小是通过宏 DIV_ROUND_UP 把 free_sects 、 BITS_PER_SECTOR 向上取整得到的,因此位图中最后一个扇区的末尾在大多数情况下都会有多余的位,这些多余的位并不代表某些资源,也就是说这些资源即便存在也对我们毫无意义,并不是我们需要的,因此需要进行处理
    memset(&buf[block_bitmap_last_byte], 0xFF, last_size);

    // 将最后一个字节中其有效的部分将其置为 0,也就是没有超出的那部分
    uint8_t bit_idx = 0;
    while(bit_idx <= block_bitmap_last_bit)
        buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
    ide_write(hd, sb.block_bitmap_lba, buf, sb.block_bitmap_sects);

    /*--------------------------------------------*/
    // 将 inode 位图初始化并写入 sb.inode_bitmap_lba
    /*--------------------------------------------*/
    // 先清空缓冲区
    memset(buf, 0, buf_size);
    buf[0] |= 0x01; // 第 0 个 inode 分给了根目录
    /**
     * 因为 inode_table 共 4096 个 inode,inode_bitmap 位图正好占用一个扇区
     * 即 inode_bitmap_sects = 1
     * 所以位图中的所有扇区均是有效位,无需再像 block_bimap 那样处理残余的扇区
     */
    ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);

    /*--------------------------------------------*/
    // 将 inode 数组初始化并写入 sb.inode_table_lba
    /*--------------------------------------------*/
    memset(buf, 0, buf_size);
    // 初始化第一个 inode 项,它表示根目录
    struct inode* i = (struct inode*) buf; 
    i -> i_size = sb.dir_entry_size * 2; // . 和 ..
    i -> i_no = 0; // inode 编号
    i -> i_sectors[0] = sb.data_start_lba; // 第 0 个块指向 sb.data_start_lba
                                           // 也就是将根目录安排在最开始的空闲块中
    ide_write(hd, sb.inode_table_lba, buf, sb.inode_table_sects);

    /*--------------------------------------------*/
    // 将根目录初始化并写入 sb.data_start_lba
    /*--------------------------------------------*/
    // 写入目录项 . 和 ..
    memset(buf, 0, buf_size);
    struct dir_entry* p_de = (struct dir_entry*) buf;
    // 初始化当前目录 .
    memcpy(p_de -> filename, ".", 1);
    p_de -> i_no = 0;
    p_de -> f_type = FT_DIRECTORY;
    p_de++;

    // 初始化当前目录的父目录 ..
    memcpy(p_de -> filename, "..", 2);
    p_de -> i_no = 0; // 根父目录还是自己
    p_de -> f_type = FT_DIRECTORY;

    ide_write(hd, sb.data_start_lba, buf, 1);

    printk("   root_dir_lba:0x%x\n", sb.data_start_lba);
    printk("%s format done\n", part->name);
    sys_free(buf);
}

fs/fs.c —— 初始化文件系统

struct partition* cur_part; // 默认情况下操作的是哪个分区

// 初始化文件系统
void filesys_init() {
    uint8_t channel_no = 0, dev_no = 0, part_idx = 0;

    // sb_buf 用来存储从硬盘上读入的超级块
    struct super_block* sb_buf = (struct super_block*) sys_malloc(SECTOR_SIZE);

    if(sb_buf == NULL) 
        PANIC("alloc memory failed!");
    printk("searching filesystem...\n");

    while(channel_no < channel_cnt) { // 遍历通道
        dev_no = 0;
        while(dev_no < 2) { // 
            if(dev_no == 0) {
                dev_no++;
                continue;
            }
            struct disk* hd = &channels[channel_no].devices[dev_no];
            struct partition* part = hd -> prim_parts;
            while(part_idx < 12) { // 4个主分区 + 8个逻辑分区
                if(part_idx == 4) // 主分区遍历完了,开始切换到逻辑分区
                    part = hd -> logic_parts;
                if(part -> sec_cnt != 0) { // 判断该分区是否存在
                    memset(sb_buf, 0, SECTOR_SIZE);
                    // 读取分区的超级块,根据魔数是否正确来判断是否存在文件系统
                    ide_read(hd, part -> start_lba + 1, sb_buf, 1);
                    // 若该分区已存在文件系统,则不再进行格式化
                    if(sb_buf -> magic == 0x19590318) {
                        printk("%s had filesystem.\n", part -> name);
                    } else {
                        printk("formatting %s's partition %s...\n", hd -> name, part -> name);
                        partition_format(part);
                    }
                }
                part_idx++;
                part++; // 下一个分区
            }
            dev_no++; // 下一个硬盘
        }
        channel_no++; // 下一个通道
    }
    sys_free(sb_buf);

}

挂载分区

文件系统的主要工作是资源管理,跟踪资源的状态是通过位图来实现的,因此创建文件系统就是创建各种资源的位图,而位图肯定是在内存中先创建好,然后将其持久化到硬盘上,这些位图将来挂载分区时会用到。

当我们需要使用一个分区时,就需要使用到资源位图,而对资源位图的操作必然是在内存中的,但挂载分区前可没有位图,因此我们需要提取将位图写入到硬盘上,将来若需要使用到某个分区的数据时,就可以通过挂载分区再把硬盘上的位图重新加载到内存中。

fs/fs.c —— 文件挂载

// 在分区链表中找到名为 part_name 的分区,并将其指针赋值给 cur_part
static bool mount_partition(struct list_elem* pelem, int arg) {
    char* part_name = (char*) arg;
    struct partition* part = elem2entry(struct partition, part_tag, pelem);

    if(!strcmp(part -> name, part_name)) {
        cur_part = part;
        struct disk* hd = cur_part -> my_disk;

        // 准备 super_block 结构体准备用来存储从硬盘读入的超级块数据
        struct super_block* sb_buf = (struct super_block*) sys_malloc(SECTOR_SIZE);

        // 在内存中创建分区 cur_part 的超级块
        cur_part -> sb = (struct super_block*) sys_malloc(sizeof(struct super_block));

        if(cur_part -> sb == NULL)
            PANIC("alloc memory failed!");
        
        // 读入超级块
        memset(sb_buf, 0, SECTOR_SIZE);
        ide_read(hd, cur_part -> start_lba + 1, sb_buf, 1);

        // 把 sb_buf 中的超级块信息复制到分区的超级块 sb 中
        memcpy(cur_part -> sb, sb_buf, sizeof(struct super_block));

        /*---------------------------------------*/
        // 将硬盘上的块位图读入到内存
        /*---------------------------------------*/
        cur_part -> block_bitmap.bits = (uint8_t*) sys_malloc(sb_buf -> block_bitmap_sects * SECTOR_SIZE);
        if(cur_part -> block_bitmap.bits == NULL)
            PANIC("alloc memory failed!");
        cur_part -> block_bitmap.btmp_bytes_len = sb_buf -> block_bitmap_sects * SECTOR_SIZE;
        // 从硬盘上读入块位图到分区的 block_bitmap.bits
        ide_read(hd, sb_buf -> block_bitmap_lba, cur_part -> block_bitmap.bits, sb_buf -> block_bitmap_sects);

        /*---------------------------------------*/
        // 将硬盘上的 inode 位图读入到内存
        /*---------------------------------------*/
        cur_part -> inode_bitmap.bits = (uint8_t*) sys_malloc(sb_buf -> inode_bitmap_sects * SECTOR_SIZE);
        if(cur_part -> inode_bitmap.bits == NULL)
            PANIC("alloc memroy failed!");
        cur_part -> inode_bitmap.btmp_bytes_len = sb_buf -> inode_bitmap_sects * SECTOR_SIZE;
        // 从硬盘上读入 inode 位图到分区的 inode_bitmap.bits
        ide_read(hd, sb_buf -> inode_bitmap_lba, cur_part -> inode_bitmap.bits, sb_buf -> inode_bitmap_sects);

        // ------------
        list_init(&cur_part -> open_inodes);
        printk("mount %s done!\n", part -> name);

     /* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
        只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
        return true;
    }
    return false; // 使 list_traversal 继续遍历
}

fs/fs.c —— 初始化文件系统

// 初始化文件系统
void filesys_init() {
    ...
    // 确定默认操作的分区
    char default_part[8] = "sdb1";
    // 挂载分区
    list_traversal(&partition_list, mount_partition, (int) default_part);
}

文件描述符

文件描述符和文件表

文件描述符

​ 内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。

​ 文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

​ 习惯上,标准输入(standard input)的文件描述符是 0,标准输出(standard output)是 1,标准错误(standard error)是 2。尽管这种习惯并非Unix内核的特性,但是因为一些 shell 和很多应用程序都使用这种习惯,因此,如果内核不遵循这种习惯的话,很多应用程序将不能使用。

文件表

Linux 提供了称为“文件结构”的数据结构,专门用于记录与文件操作相关的信息,每次打开一个文件就会产生一个文件结构,多次打开就会产生多个文件结构。

Linux 把所有的“文件结构”组织到一起形成数组统一管理,该数组称为文件表。

文件描述符与 inode 关联关系

image-20230102120652615

在 Linux 中文件通过先通过 open 打开文件以获得该文件的文件描述符,然后将文件描述符传给 read 或 write 等函数对该文件进行相关操作。

当用户进程打开文件时,文件系统给用户进程返回的是该 PCB 中文件描述符数组的下标值,也就是文件描述符。

上图是 Linux 通过文件描述符查找文件数据块(也就是查找文件)的过程,涉及的三个数据结构,它们都位于内存中,如下:

  1. PCB 中的文件描述符数组。
  2. 存储所有文件结构的文件表,也就是说这个数组是全局的。
  3. inode 队列,也就是 inode 缓存。

注:若要打开的文件所对应的 inode 不存在于 inode 缓存中,此时需要多做一个处理,就是文件系统会从硬盘上将该 inode 加载到 inode 缓存中,并使文件结构中的 fd_inode 指向它。

文件结构和文件表可查看:文件操作相关的基础函数 -> 文件相关的函数 -> file.h

文件描述符的实现

thread/thread.h —— 修改 PCB

// 进程或线程的 PCB,即程序控制块
struct task_struct {
    ...
    int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 文件描述符数组,存储的都是文件结构的地址
	...
};

thread/thread.c —— 修改初始化线程

// 初始化线程基本信息
void init_thread(struct task_struct* pthread, char* name, int prio) {
	...
    // 预留标准输入输出
    pthread -> fd_table[0] = 0;
    pthread -> fd_table[1] = 1;
    pthread -> fd_table[2] = 2;
    // 其余全部设置为 -1,表示空位
    uint8_t fd_idx = 3;
    while(fd_idx < MAX_FILES_OPEN_PER_PROC) {
        pthread -> fd_table[fd_idx] = -1;
        fd_idx++;
    }
	...
}

文件操作相关的基础函数

inode 操作有关的函数

fs/inode.c

// 用来存储 inode 的位置信息
struct inode_position {
    bool two_sec;       // inode 是否跨扇区
    uint32_t sec_lba;   // inode 所在的扇区号
    uint32_t off_size;  // inode 所在扇区内的字节偏移量
};

// 根据 inode 编号获取 inode 所在扇区和扇区内的偏移量
static void inode_locate(struct partition* part, uint32_t inode_no, struct inode_position* inode_pos) {
    // inode_table 在硬盘上是连续的
    ASSERT(inode_no < 4096); 

    uint32_t inode_table_lba = part -> sb -> inode_table_lba;
    uint32_t inode_size = sizeof(struct inode);
    uint32_t off_size = inode_no * inode_size; // 第 inode_no 号 inode 相对于 inode_table_lba 偏移多少字节
    uint32_t off_sec = off_size / 512; // 第 inode_no 号 inode 相对于 inode_table_lba 偏移多少扇区
    uint32_t off_size_in_sec = off_size % 512; // 第 inode_no 号 inode 在扇区内的字节偏移

    // 判断此 inode 是否跨越两个扇区
    uint32_t left_in_sec = 512 - off_sec;
    if(left_in_sec < inode_size) // 若扇区内剩余的空间不足以容纳一个 inode,则 inode 必然跨越了两个扇区
        inode_pos -> two_sec = true;
    else
        inode_pos -> two_sec = false;
    inode_pos -> sec_lba = inode_table_lba + off_sec;
    inode_pos -> off_size = off_size_in_sec;
}

// 将 inode 写入到分区 part 中 —— 同步
void inode_sync(struct partition* part, struct inode* inode, void* io_buf) {
    uint8_t inode_no = inode -> i_no;

    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos); // 将 inode 的位置信息保存到 inode_pos

    ASSERT(inode_pos.sec_lba <= (part -> start_lba + part -> sec_cnt));

    /**
     * 硬盘中 inode 的成员 inode_tag 和 i_open_cnts 
     * 它们只在内存中记录链表位置和被多少进程所共享
     * 因此在写入硬盘前,需要对其清理
     */
    // 不对现有的 inode 进行清理,而是复制一个 inode 进行清理,最后同步这个复制的 inode
    struct inode pure_inode;
    memcpy(&pure_inode, inode, sizeof(struct inode));

    // 进行清理
    pure_inode.i_open_cnt = 0;
    pure_inode.write_deny = false;
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;

    char* inode_buf = (char*) io_buf;
    if(inode_pos.two_sec) { // 处理两个扇区
        // 先读取两个扇区
        ide_read(part -> my_disk, inode_pos.sec_lba, inode_buf, 2);
        // 将 pure_inode 复制到 (inode_buf + inode_pos.off_size)
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        // 写入两个扇区
        ide_write(part -> my_disk, inode_pos.sec_lba, inode_buf, 2);
    } else { // 处理一个扇区
        ide_read(part -> my_disk, inode_pos.sec_lba, inode_buf, 1);
        memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
        ide_write(part -> my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

// 根据 inode 编号返回相应的 inode 实体
struct inode* inode_open(struct partition* part, uint32_t inode_no) {
    // 先在已打开的 inode 中查找,也就是 inode 缓存队列
    struct list_elem* elem = part -> open_inodes.head.next;
    struct inode* inode_found;
    while(elem != &part -> open_inodes.tail) {
        inode_found = elem2entry(struct inode, inode_tag, elem);
        if(inode_found -> i_no == inode_no) {
            inode_found -> i_open_cnt++;
            return inode_found;
        }
        elem = elem -> next;
    }

    // --------------------------------------------------------
    // 在已有的找不到,则从硬盘查找,并且加入到 part -> open_inodes 中
    // --------------------------------------------------------
    // 得到 inode 信息
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);

    /**
     * 为了使 sys_malloc 创建的新 inode 被所有任务所共享
     * 需要将 inode 置于内核空间
     * 则需要临时将 cur_pbc -> pgdir = NULL
     */
    struct task_struct* cur = running_thread();
    uint32_t* cur_pagedir_bak = cur -> pgdir;
    cur -> pgdir = NULL;
    // cur -> pgdir = NULL 后,此时申请的就是内核空间
    inode_found = (struct inode*) sys_malloc(sizeof(struct inode));
    // 恢复 pgdir
    cur -> pgdir = cur_pagedir_bak;

    char* inode_buf;
    if(inode_pos.two_sec) { // 跨两个扇区
        inode_buf = (char*) sys_malloc(1024);
        ide_read(part -> my_disk, inode_pos.sec_lba, inode_buf, 2);
    } else { // 没跨扇区
        inode_buf = (char*) sys_malloc(512);
        ide_read(part -> my_disk, inode_pos.sec_lba, inode_buf, 1);
    }

    memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode));

    list_push(&part -> open_inodes, &inode_found -> inode_tag);
    inode_found -> i_open_cnt = 1; // 打开了一次

    sys_free(inode_buf);
    return inode_found;
}

// 关闭 inode 或减少 inode 的打开次数
void inode_close(struct inode* inode) {
    enum intr_status old_status = intr_disable();
    if(--inode -> i_open_cnt == 0) {
        list_remove(&inode -> inode_tag);
        /**
         * inode_open 时为实现 inode 被所有进程所共享
         * 已经为 inode 分配了内核空间的资源
         * 所以此时释放也需要释放内核空间的资源
        */
        struct task_struct* cur = running_thread();
        uint32_t* cur_pagedir_bak = cur -> pgdir;
        cur -> pgdir = NULL;
        sys_free(inode);
        cur -> pgdir = cur_pagedir_bak;
    }
    intr_set_status(old_status);
}

// 初始化 inode
void inode_init(uint32_t inode_no, struct inode* new_inode) {
    new_inode -> i_no = inode_no;
    new_inode -> i_size = 0;
    new_inode -> i_open_cnt = 0;
    new_inode -> write_deny = false;

    uint8_t sec_idx = 0;
    while(sec_idx < 13) { // 初始化直接块(0~11),索引 >= 12 就是间接块
        new_inode -> i_sectors[sec_idx] = 0;
        sec_idx++;
    }
}

文件相关的函数

fs/file.h

// 文件结构
struct file {
    uint32_t fd_pos; // 记录当前文件操作的偏移量,文件中的偏移量,以0起始,最大为文件大小-1
    uint32_t fd_flag;
    struct inode* fd_inode;
};

// 标志输入输出描述符
enum std_fd {
    stdin_no,   // 0 标准输入
    stdout_no,  // 1 标准输出
    stderr_no   // 2 标准错误
};

// 位图类型
enum bitmap_type {
    INODE_BITMAP, // inode 位图
    BLOCK_BITMAP  // block 位图
};

#define MAX_FILE_OPEN 32 // 系统可打开的最大文件数量

extern struct file file_table[MAX_FILE_OPEN]; // 全局的文件表

fs/file.c

#define DEFAULT_SECS 1

// 文件表
struct file file_table[MAX_FILE_OPEN];

// 从文件表 file_table 中获取一个空闲位,返回下标
int32_t get_free_slot_in_global(void) {
    uint32_t fd_idx = 3; // 前面 0 1 2 是标准输入输出
    while(fd_idx < MAX_FILE_OPEN) {
        if(file_table[fd_idx].fd_inode == NULL) break;
        fd_idx++;
    }
    if(fd_idx == MAX_FILE_OPEN) {
        printk("exceed max open files.\n");
        return -1;
    }
    return fd_idx;
}

// 将全局描述符下标安装到进程或线程自己的文件描述符数组 fd_table 中,返回其下标
int32_t pcb_fd_install(int32_t global_fd_idx) {
    struct task_struct* cur = running_thread();
    uint8_t local_fd_idx = 3; // 跨过标准输入输出
    while(local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
        if(cur -> fd_table[local_fd_idx] == -1) { // 查找空闲位
            cur -> fd_table[local_fd_idx] = global_fd_idx;
            break;
        }
        local_fd_idx++;
    }
    if(local_fd_idx == MAX_FILES_OPEN_PER_PROC) {
        printk("exceed max open files_per_proc\n");
        return -1;
    }
    return local_fd_idx;
}

// 分配一个 inode,返回 inode 编号(位图索引)
int32_t inode_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> inode_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> inode_bitmap, bit_idx, 1);
    return bit_idx;
}

// 分配一个扇区,返回其扇区地址
int32_t block_bitmap_alloc(struct partition* part) {
    int32_t bit_idx = bitmap_scan(&part -> block_bitmap, 1);
    if(bit_idx == -1) return -1;
    bitmap_set(&part -> block_bitmap, bit_idx, 1);
    return (part -> sb -> data_start_lba + bit_idx);
}

// 将内存中 bitmap 第 bit_idx 位所在的扇区同步到硬盘
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type) {
    /**
     * Tips
     * 4096 = 512字节 = 1扇区
     * 但在位图(数组)中是以 0 为起始
     * 因此第 4096 位占用两个扇区
     * 0~4095 为一个扇区
    */
    uint32_t off_sec = bit_idx / 4096; // 求得 bit_idx 所在扇区
    uint32_t off_size = off_sec * BLOCK_SIZE; // 字节偏移量
    uint32_t sec_lba;     // 起始地址
    uint8_t* bitmap_off;  // 内容的起始地址

    // 需要被同步到硬盘的位图只有 inode_bitmap 和 block_bitmap
    switch(btmp_type) {
        case INODE_BITMAP:
            sec_lba = part -> sb -> inode_bitmap_lba + off_sec;
            bitmap_off = part -> inode_bitmap.bits + off_size;
            break;
        case BLOCK_BITMAP:
            sec_lba = part -> sb -> block_bitmap_lba + off_sec;
            bitmap_off = part -> block_bitmap.bits + off_size;
            break;
    }

    ide_write(part -> my_disk, sec_lba, bitmap_off, 1);
}

目录相关的函数

fs/dir.c

struct dir root_dir; // 根目录

// 打开根目录
void open_root_dir(struct partition* part) {
    root_dir.inode = inode_open(part, part -> sb -> root_inode_no);
    root_dir.dir_pos = 0;
}

// 在分区 Part 上打开 inode 编号为 inode_no 的目录,并返回目录指针
struct dir* dir_open(struct partition* part, uint32_t inode_no) {
    struct dir* pdir = (struct dir*) sys_malloc(sizeof(struct dir));
    pdir -> inode = inode_open(part, inode_no);
    pdir -> dir_pos = 0;
    return pdir;
}

/*在 part 分区内的 pdir 目录内寻找名为 name 的文件或目录
  找到返回 true,并且将其目录项存入 dir_e,否则返回 false*/ 
bool search_dir_entry(struct partition* part, struct dir* pdir, const char* name, struct dir_entry* dir_e) {
    uint32_t block_cnt = 140; // 12个直接块+128个一级间接块

    // 12个直接块+128个一级间接块,总共 560 字节
    // 存储的都是该文件或目录的所有扇区地址
    uint32_t* all_blocks = (uint32_t*) sys_malloc(48 + 512);

    if(all_blocks == NULL) {
        printk("search_dir_entry: sys_malloc for all_blocks failed");
        return false;
    }

    uint32_t block_idx = 0;
    while(block_idx < 12) { // 处理直接块
        all_blocks[block_idx] = pdir -> inode -> i_sectors[block_idx];
        block_idx++;
    }
    block_idx = 0;

    if(pdir -> inode -> i_sectors[12] != 0) { // 处理一级间接块
        ide_read(part -> my_disk, pdir -> inode -> i_sectors[12], all_blocks + 12, 1);
    }

    /**
     * 由于写目录项的时候已保证不跨越扇区
     * 所以只需要申请一个扇区的内存
     */
    uint8_t* buf = (uint8_t*) sys_malloc(SECTOR_SIZE);
    struct dir_entry* p_de = (struct dir_entry*) buf;
    uint32_t dir_entry_size = part -> sb -> dir_entry_size; // 目录项大小
    uint32_t dir_entry_cnt = SECTOR_SIZE / dir_entry_size;  // 一个扇区可容纳的目录项个数

    // 开始在所有块中查找目录项
    while(block_idx < block_cnt) {
        if(all_blocks[block_idx] == 0) {
            block_idx++;
            continue;
        }
        ide_read(part -> my_disk, all_blocks[block_idx], buf, 1);

        uint32_t dir_entry_idx = 0;
        // 遍历扇区中所有目录项
        while(dir_entry_idx < dir_entry_cnt) {
            if(!strcmp(p_de -> filename, name)) {
                memcpy(dir_e, p_de, dir_entry_size);
                sys_free(buf);
                sys_free(all_blocks);
                return true;
            }
            dir_entry_idx++;
            p_de++;
        }
        block_idx++;

        // 还原
        p_de = (struct dir_entry*) buf;
        memset(buf, 0, SECTOR_SIZE);
    }

    sys_free(buf);
    sys_free(all_blocks);
    return false;
}

// 关闭目录
void dir_close(struct dir* dir) {
    // ---------------------------------
    // 根目录不能关闭
    // :root_dir 所在内存是低端 1M 内,并不再堆中,free 会出问题
    // ---------------------------------
    if(dir == &root_dir) return; // 不对根目录做处理
    inode_close(dir -> inode);
    sys_free(dir);
}

// 在内存中初始化目录项 p_de
void create_dir_entry(char* filename, uint32_t inode_no, uint8_t file_type, struct dir_entry* p_de) {
    ASSERT(strlen(filename) <= MAX_FILE_NAME_LEN);

    // 初始化目录项
    memcpy(p_de -> filename, filename, strlen(filename));
    p_de -> i_no = inode_no;
    p_de -> f_type = file_type;
}

// 将目录项 p_de 写入父目录中
bool sync_dir_entry(struct dir* parent_dir, struct dir_entry* p_de, void* io_buf) {
    struct inode* dir_inode = parent_dir -> inode;
    uint32_t dir_size = dir_inode -> i_size;
    uint32_t dir_entry_size = cur_part -> sb -> dir_entry_size;

    ASSERT(dir_size % dir_entry_size == 0); // dir_size 应该是 dir_entry_size 的整数倍

    uint32_t dir_entry_per_sec = (512 / dir_entry_size); // 每扇区最大的目录项数目
    int32_t block_lba = -1;

    // 将该目录的所有扇区地址存入(12个直接块+128个一级间接块) all_blocks
    uint8_t block_idx = 0;
    uint32_t all_blocks[140] = {0};

    // 将 12 个直接块存入 all_blocks
    while(block_idx < 12) {
        all_blocks[block_idx] = dir_inode -> i_sectors[block_idx];
        block_idx++;
    }

    struct dir_entry* dir_e = (struct dir_entry*) io_buf;
    int32_t block_bitmap_idx = -1;

    /**
     * 开始遍历所有块,寻找目录项空闲位,若已有扇区中没有空闲位
     * 在不超过文件大小的情况下申请新的扇区来存储新目录项
     */
    block_idx = 0;
    while(block_idx < 140) {
        block_bitmap_idx = -1;
        if(all_blocks[block_idx] == 0) { // 该块未被分配
            // 申请一个新的块
            block_lba = block_bitmap_alloc(cur_part);
            if(block_lba == -1) {
                printk("alloc block bitmap for sync_dir_entry failed\n");
                return false;
            }
            // 同步进硬盘
            block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
            ASSERT(block_bitmap_idx != -1);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

            block_bitmap_idx = -1;
            if(block_idx < 12) { // 直接块
                dir_inode -> i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
            } else if(block_idx == 12) { // 一级间接块
                dir_inode -> i_sectors[12] = block_lba; // 将上面申请的这个 block_lba 块作为一级间接表
                block_lba = -1;
                block_lba = block_bitmap_alloc(cur_part); // 再分配一个块作为一级间接块的第 0 个间接块
                if(block_lba == -1) {
                    block_bitmap_idx = dir_inode -> i_sectors[12] - cur_part -> sb -> data_start_lba;
                    bitmap_set(&cur_part -> block_bitmap, block_bitmap_idx, 0);
                    dir_inode -> i_sectors[12] = 0;
                    printk("alloc block bitmap for sync_dir_entry failed\n");
                    return false;
                }

                // 同步
                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                ASSERT(block_bitmap_idx != -1);
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                // 将新分配的第0个间接块地址写入一级间接表
                all_blocks[12] = block_lba;
                ide_write(cur_part -> my_disk, dir_inode -> i_sectors[12], all_blocks + 12, 1);
            } else { // 间接块未分配
                all_blocks[block_idx] = block_lba;
                /* 把新分配的第(block_idx-12)个间接块地址写入一级间接块表 */
                ide_write(cur_part -> my_disk, dir_inode -> i_sectors[12], all_blocks + 12, 1);
            }

            // 将新目录项 p_de 写入新分配的间接块
            memset(io_buf, 0, 512);
            memcpy(io_buf, p_de, dir_entry_size);
            ide_write(cur_part -> my_disk, all_blocks[block_idx], io_buf, 1);
            dir_inode -> i_size += dir_entry_size;
            return true;
        }

        // 该 block_idx 已经存在,将其读入内存,查询其空闲目录项
        ide_read(cur_part -> my_disk, all_blocks[block_idx], io_buf, 1);
        uint8_t dir_entry_idx = 0;
        while(dir_entry_idx < dir_entry_per_sec) {
            if((dir_e + dir_entry_idx) -> f_type == FT_UNKNOWN) { // FT_UNKNOWN为0,无论是初始化或是删除文件后,都会将f_type置为FT_UNKNOWN.
                memcpy(dir_e + dir_entry_idx, p_de, dir_entry_size);
                ide_write(cur_part -> my_disk, all_blocks[block_idx], io_buf, 1);
                dir_inode -> i_size += dir_entry_size;
                return true;
            }
            dir_entry_idx++;
        }
        block_idx++;
    }
    printk("directory is full!\n");
    return false;
}

路径解析的函数

fs/fs.c

// 将最顶层的路径名称解析出来
static char* path_parse(char* pathname, char* name_store) {
    if(pathname[0] == '/') { // 根目录不单独解析
        /* 路径中出现1个或多个连续的字符'/',将这些'/'跳过,如"///a/b" */
        while(*(++pathname) == '/');
    }

    // 一般路径解析
    while(*pathname != '/' && *pathname != 0)
        *name_store++ = *pathname++;
    
    if(pathname[0] == 0)
        return NULL;
    
    return pathname;
}

// 返回路径深度
int32_t path_depth_cnt(char* pathname) {
    ASSERT(pathname != NULL);
    char* p = pathname;
    char name[MAX_FILE_NAME_LEN];
    uint32_t depth = 0;

    // 解析路径,从中拆分出各级名称
    p = path_parse(p, name);
    while(name[0]) {
        depth++;
        memset(name, 0, MAX_FILE_NAME_LEN);
        if(p) // 若 p != NULL,则继续解析
            p = path_parse(p, name);
    }

    return depth;
}

实现文件检索功能

fs/fs.h

// 文件类型
enum file_types {
    FT_UNKNOWN,     // 其它类型文件
    FT_REGULAR,     // 普通文件
    FT_DIRECTORY    // 目录文件
};

/* 打开文件的选项 */
enum oflags {
   O_RDONLY,	  // 只读
   O_WRONLY,	  // 只写
   O_RDWR,	  // 读写
   O_CREAT = 4	  // 创建
};

// 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中走过的地方
struct path_search_record {
    char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
    struct dir* parent_dir; // 文件或目录的直接父目录
    enum file_types file_type; // 找到的文件所属类型
};

/* /a/b/c 假设 c 不存在,那么 
	searched_path=/a/b/c
	parent_dir=b
   /a/b/c 假设 b 不存在,那么
   	searched_path=/a/b
   	parent_dir=a
*/

enum oflag 结构中使用“位”作为值,例如 O_RDONLY=000b, O_WRONLY=001b, O_RDWR=010b, O_CREAT=100b, 这样的好处是当这几个标识通过位运算“|”叠加一起可作为复核参数,通过位运算“&”可单独解析出各位以反推标识位。

fs/fs.c

// 搜索文件
static int search_file(const char* pathname, struct path_search_record* searched_record) {
    // 若待查找的是根目录,直接返回
    if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/..")) {
        searched_record->parent_dir = &root_dir;
        searched_record->file_type = FT_DIRECTORY;
        searched_record->searched_path[0] = 0;	   // 搜索路径置空
        return 0;
    }

    uint32_t path_len = strlen(pathname);
    /* 保证pathname至少是这样的路径/x且小于最大长度 */
    ASSERT(pathname[0] == '/' && path_len > 1 && path_len < MAX_PATH_LEN);
    char* sub_path = (char*) pathname;
    struct dir* parent_dir = &root_dir;
    struct dir_entry dir_e;

    char name[MAX_FILE_NAME_LEN] = {0};

    searched_record -> parent_dir = parent_dir;
    searched_record -> file_type = FT_UNKNOWN;
    uint32_t parent_inode_no = 0; // 父目录的 inode 编号

    sub_path = path_parse(sub_path, name);
    while(name[0]) { // 路径不许为空
        /* 记录查找过的路径,但不能超过searched_path的长度512字节 */
        ASSERT(strlen(searched_record->searched_path) < 512);

        // 记录已存在的父目录
        strcat(searched_record -> searched_path, "/");
        strcat(searched_record -> searched_path, name);

        if(search_dir_entry(cur_part, parent_dir, name, &dir_e)) { // 在所给的目录中查找文件
            memset(name, 0, MAX_FILE_NAME_LEN);
            if(sub_path) // 继续解析路径
                sub_path = path_parse(sub_path, name);
            if(FT_DIRECTORY == dir_e.f_type) { // 被打开的是目录
                parent_inode_no = parent_dir -> inode -> i_no;
                dir_close(parent_dir);
                parent_dir = dir_open(cur_part, dir_e.i_no); // 更新父目录
                searched_record -> parent_dir = parent_dir;
                continue;
            } else if(FT_REGULAR == dir_e.f_type) { // 被打开的是普通文件
                searched_record -> file_type = FT_REGULAR;
                return dir_e.i_no;
            }
        } else { // 找不到
            /**
             * 找不到目录项时,要留着 parent_dir 不要关闭
             * 因为若需要创建文件的话,需要在 parent_dir 中创建
             */
            return -1;
        }
    }

    // ------------------------------------------
    // 执行到这里,可说明:
    // 1. 路径 pathname 已经被完整的解析过了,各级都存在
    // 2. pathname 的最后一层路径不是普通文件,而是目录
    // ------------------------------------------
    /*
    	/a/b/c,它们都是目录,则此时:
    		parent_dir=c, 而不是 b
    		searched_path=/a/b/c
    */
    dir_close(searched_record -> parent_dir); // 先关闭 c
	
    searched_record -> parent_dir = dir_open(cur_part, parent_inode_no); // 重新打开 b
    searched_record -> file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

注意:parent_dir 不可关闭,因为调用者或许需要使用父目录做操作,例如创建文件等,因此 parent_dir 最后需要由调用者关闭。

操作思路 —— 思维导图

image-20230102202631796

创建文件

fs/file.c —— file_create

// 创建文件,成功则返回文件描述符
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag) {
    void* io_buf = sys_malloc(1024);
    if(io_buf == NULL) {
        printk("in file_creat: sys_malloc for io_buf failed\n");
        return -1;
    }

    uint8_t rollback_step = 0; // 回滚点

    // 为新文件分配 inode
    int32_t inode_no = inode_bitmap_alloc(cur_part);
    if(inode_no == -1) {
        printk("in file_creat: allocate inode failed\n");
        return -1;
    }

    /**
     * 此 inode 要从堆中申请内存,不可申请局部变量,因为函数生命周期结束后会释放
     * 因为 file_table 数组中的文件描述符的 inode 指针要指向它
     */
    struct inode* new_file_inode = (struct inode*) sys_malloc(sizeof(struct inode));
    if(new_file_inode == NULL) {
        printk("file_create: sys_malloc for inode failded\n");
        rollback_step = 1;
        goto rollback;
    }
    inode_init(inode_no, new_file_inode); // 初始化 inode

    // 得到全局的文件表空闲位
    int fd_idx = get_free_slot_in_global();
    if(fd_idx == -1) {
        printk("exceed max open files\n");
        rollback_step = 2;
        goto rollback;
    }

    // 初始化文件表信息
    file_table[fd_idx].fd_inode = new_file_inode;
    file_table[fd_idx].fd_pos = 0;
    file_table[fd_idx].fd_flag = flag;
    file_table[fd_idx].fd_inode->write_deny = false;

    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));

    create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry); // create_dir_entry 只是内存操作不出意外,不会返回失败,因此不需要回滚

    // 同步目录项
    if(!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
        printk("sync dir_entry to disk failed\n");
        rollback_step = 3;
        goto rollback;
    }

    memset(io_buf, 0, 1024);
    // --------------------------------
    // 因为 sync_dir_entry 会改变父目录 inode 中的信息,即 i_sectors 中的信息,因此需要同步父目录的 inode
    inode_sync(cur_part, parent_dir -> inode, io_buf); // 同步父目录的 inode
    // --------------------------------
    memset(io_buf, 0, 1024);
    inode_sync(cur_part, new_file_inode, io_buf); // 同步新文件的 inode
    bitmap_sync(cur_part, inode_no, INODE_BITMAP); // 同步 inode_bitmap 位图

    // 将新 inode 添加入已打开的 inode 链表中
    list_push(&cur_part -> open_inodes, &new_file_inode -> inode_tag);
    new_file_inode -> i_open_cnt = 1; // 打开次数设置为 1

    sys_free(io_buf);
    return pcb_fd_install(fd_idx); // 安装到当前进程的文件描述符数组中,并且返回下标

    rollback:
        switch(rollback_step) {
            case 3:
                memset(&file_table[fd_idx], 0, sizeof(struct file));
            case 2:
                sys_free(new_file_inode);
            case 1:
                bitmap_set(&cur_part -> inode_bitmap, inode_no, 0);
                break;
        }
        sys_free(io_buf);
        return -1;
}

fs/fs.c —— sys_open

// 打开或创建文件成功后,返回文件描述符
int32_t sys_open(const char* pathname, uint8_t flags) {
    if(pathname[strlen(pathname)] == '/') {
        printk("can`t open a directory %s\n",pathname);
        return -1;
    }

    ASSERT(flags <= 7);

    int32_t fd = -1; // 默认找不到

    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));

    // 记录目录深度,便于判断中间某个目录不存在的情况
    uint32_t pathname_depth = path_depth_cnt((char*) pathname);

    // 先检查文件是否存在
    int inode_no = search_file(pathname, &searched_record);
    bool found = inode_no != -1 ? true : false;

    if(searched_record.file_type == FT_DIRECTORY) {
        printk("can`t open a direcotry with open(), use opendir() to instead\n");
        dir_close(searched_record.parent_dir);
        return -1;
    }

    uint32_t path_search_depth = path_depth_cnt(searched_record.searched_path);

    // 判断是否把 pathname 各层目录都访问到了,即是否出现目录/文件找不到的情况
    if(pathname_depth != path_search_depth) {
        printk("cannot access %s: Not a directory, subpath %s is`t exist\n", \
	     pathname, searched_record.searched_path);
        dir_close(searched_record.parent_dir);
        return -1;
    }

    if(!found && !(flags & O_CREAT)) { // 找不到目标文件,且并不是要创建文件
        printk("in path %s, file %s is`t exist\n", \
	     searched_record.searched_path, \
	     (strrchr(searched_record.searched_path, '/') + 1));
        dir_close(searched_record.parent_dir);
        return -1;
    } else if(found && (flags & O_CREAT)) { // 找到目标文件,且是创建文件操作,表示:创建的文件已存在
        printk("%s has already exist!\n", pathname);
        dir_close(searched_record.parent_dir);
        return -1;
    }

    switch(flags & O_CREAT) {
        case O_CREAT:
            printk("creating file.\n");
            fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
            dir_close(searched_record.parent_dir);
            break;
        // 其余为打开文件
        default: // O_RDONLY, O_WRONLY, O_RDWR
            fd = file_open(inode_no, flags);
    }

    return fd; // 此 fd 并非全局文件表 file_table 的下标,而是进程 PCB -> fd_table 的下标
}

fs/fs.c —— 初始化

// 初始化文件系统
void filesys_init() {
    ...
    // 将当前分区的根目录打开
    open_root_dir(cur_part);

    // 初始化文件表
    uint32_t fd_idx = 0;
    while(fd_idx < MAX_FILE_OPEN) {
        file_table[fd_idx++].fd_inode = NULL;
    }
}

文件的打开与关闭

文件的打开

fs/file.c

// 打开编号为 inode_no 所对应的文件,成功返回文件描述符
int32_t file_open(uint32_t inode_no, uint8_t flag) {
    // 先得到一个全局的文件表空闲位
    int fd_idx = get_free_slot_in_global();
    if(fd_idx == -1) {
        printk("exceed max open files.\n");
        return -1;
    }
	
    // 初始化文件表结构信息
    file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
    file_table[fd_idx].fd_pos = 0; // 每次打开文件都要将偏移量指针重置为 0
    file_table[fd_idx].fd_flag = flag;

    // 文件是否可写,该变量指向 inode 是否可写的状态
    bool* write_deny = &file_table[fd_idx].fd_inode -> write_deny;

    if(flag & O_WRONLY || flag & O_RDWR) { // 只写 / 读写
        enum intr_status old_status = intr_disable(); // 进入临界区前先关闭中断
        if(!(*write_deny)) { // 写入
            *write_deny = true; // 设置为 true,表示不可写,避免多个进程同时对此文件进行写入
            intr_set_status(old_status);
        } else { // 写入失败
            intr_set_status(old_status);
            printk("file can`t be write now, try again later\n");
            return -1;
        }
    }
    // 若是读文件或创建文件,则不用理会 write_deny,保持默认即可

    return pcb_fd_install(fd_idx);
}

fs/fs.c

// 要修改的我已经在 创建文件 时就补全了

文件的关闭

fs/file.c

// 关闭文件
int32_t file_close(struct file* file) {
    if(file == NULL) return -1;
    file -> fd_inode -> write_deny = false;
    inode_close(file -> fd_inode);
    file -> fd_inode = NULL; // 重新使其文件结构可用
    return 0;
}

fs/fs.c

// 将文件描述符转换为文件表下标
static uint32_t fd_local2global(uint32_t local_fd) {
    struct task_struct* cur = running_thread();
    int32_t global_fd = cur -> fd_table[local_fd];
    ASSERT(global_fd >= 0 && global_fd < MAX_FILE_OPEN);
    return (uint32_t) global_fd;
}

// 关闭文件描述符 fd 所指向的文件,成功返回0,失败返回-1
int32_t sys_close(int32_t fd) {
    int32_t ret = -1;
    if(fd > 2) { // 跨越标准输入输出
        uint32_t _fd = fd_local2global(fd);
        ret = file_close(&file_table[_fd]);
        running_thread() -> fd_table[fd] = -1; // 将文件描述符重置为可用状态
    }
    return ret;
}

文件的写入

fs/file.c —— file_write

// 将 buf 中的 count 个字节写入到 file,成功则返回字节数,失败返回-1
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
    if((file -> fd_inode -> i_size + count) > (BLOCK_SIZE * 140)) { // 文件目前最大只支持 512*140 字节
        printk("exceed max file_size 71680 bytes, write file failed\n");
        return -1;
    }
    uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
    if(io_buf == NULL) {
        printk("file_write: sys_malloc for io_buf failed\n");
        return -1;
    }
    uint32_t* all_blocks = (uint32_t*) sys_malloc(BLOCK_SIZE + 48);
    if(all_blocks == NULL) {
        printk("file_write: sys_malloc for all_blocks failed\n");
        return -1;
    }

    const uint8_t* src = buf;	    // 用src指向buf中待写入的数据 
    uint32_t bytes_written = 0;	    // 用来记录已写入数据大小
    uint32_t size_left = count;	    // 用来记录未写入数据大小
    int32_t block_lba = -1;	        // 块地址
    uint32_t block_bitmap_idx = 0;  // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_sync
    uint32_t sec_idx;	            // 用来索引扇区
    uint32_t sec_lba;	            // 扇区地址
    uint32_t sec_off_bytes;         // 扇区内字节偏移量
    uint32_t sec_left_bytes;        // 扇区内剩余字节量
    uint32_t chunk_size;	        // 每次写入硬盘的数据块大小
    int32_t indirect_block_table;   // 用来获取一级间接表地址
    uint32_t block_idx;		        // 块索引

    // 判断文件是否是第一次写,若是,则先分配一个块
    if(file -> fd_inode -> i_sectors[0] == 0) {
        block_lba = block_bitmap_alloc(cur_part);
        if(block_lba == -1) {
            printk("file_write: block_bitmap_alloc failed\n");
            return -1;
        }
        file -> fd_inode -> i_sectors[0] = block_lba;

        // 同步
        block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
        ASSERT(block_bitmap_idx != 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }

    // 写入 count 个字节前,该文件已占的块数
    uint32_t file_has_used_blocks = file -> fd_inode -> i_size / BLOCK_SIZE + 1;
    // 存储 count 字节后,该文件将占用的块数
    uint32_t file_will_use_blocks = (file -> fd_inode -> i_size + count) / BLOCK_SIZE + 1;
    ASSERT(file_will_use_blocks <= 140);
    // 通过此增量判断是否需要分配新的扇区,为0则不需要分配
    uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;
    // 我得说句实话,感觉这样判断完全没必要,它们都以块为单位,得到的差不就是需要分配的新的块数吗?不需要分配的话,那不就表示两者块数相同吗?

    // -------------------------------------
    // 将所有的数据块地址收集到 all_blocks 中
    // -------------------------------------
    if(add_blocks == 0) { // 不需要分配新的块
        if(file_has_used_blocks <= 12) { // 都属于12个直接块
            block_idx = file_has_used_blocks - 1; // 指向最后一个数据扇区(即数据块)
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
        } else {
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12]; // 得到一级间接块索引表的地址
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    } else { // 需要分配新的块
        if(file_will_use_blocks <= 12) { // 所需要的块数直接块够用
            // 先将有剩余空间的扇区,也就是最后那个块,先写入 all_blocks
            block_idx = file_has_used_blocks - 1;
            ASSERT(file -> fd_inode -> i_sectors[block_idx] != 0);
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];

            // 再将未来需要用到的块写入 all_blocks
            block_idx = file_has_used_blocks;
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 1 failed\n");
                    return -1;
                }

                // 写文件时,不应该出现块未使用,但已经分配扇区的情况,当文件删除时,就会把块地址请0
                ASSERT(file -> fd_inode -> i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
                file -> fd_inode -> i_sectors[block_idx] = all_blocks[block_idx] = block_lba;

                // 同步
                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                block_idx++; // 下一个分配的新块
            }
        } else if(file_has_used_blocks <= 12 && file_will_use_blocks > 12) { // 已经使用的块数在12块之内,新增若干后,超过了12块,需要分配一级间接块
            // 道理同上,记录最后一个块,因为这个块数据未满
            block_idx = file_has_used_blocks - 1;
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];

            // 创建一级间接块索引表
            block_lba = block_bitmap_alloc(cur_part);
            if(block_lba == -1) {
                printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                return -1;
            }

            ASSERT(file -> fd_inode -> i_sectors[12] == 0); // 确保一级间接块表未分配
            indirect_block_table = file -> fd_inode -> i_sectors[12] = block_lba;

            block_idx = file_has_used_blocks; // 第一个未使用的块,即本文件最后一个已使用的直接块的下一块
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 2 failed\n");
                    return -1;
                }

                if(block_idx < 12) {
                    ASSERT(file -> fd_inode -> i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
                    file -> fd_inode -> i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
                } else {
                    all_blocks[block_idx] = block_lba;
                }

                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                block_idx++; // 下一个新扇区
            }

            // 同步一级间接表的所有间接块
            ide_write(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        } else if(file_has_used_blocks > 12) { // 已经使用完了12个直接块,要创建一级间接块表中的间接块
            ASSERT(file -> fd_inode -> i_sectors[12] != 0); // 确保一级间接块表存在
            indirect_block_table = file -> fd_inode -> i_sectors[12]; // 获取一级间接块表

            // 读取所有间接块
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);

            block_idx = file_has_used_blocks; // 已使用的间接块的下一块
            while(block_idx < file_will_use_blocks) {
                block_lba = block_bitmap_alloc(cur_part);
                if(block_lba == -1) {
                    printk("file_write: block_bitmap_alloc for situation 3 failed\n");
                    return -1;
                }
                all_blocks[block_idx++] = block_lba;

                // 同步
                block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
                bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
            }

            // 同步所有间接块
            ide_write(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    }

    bool first_write_block = true; // 含有剩余空间的扇区标识
    file -> fd_pos = file -> fd_inode -> i_size - 1;
    while(bytes_written < count) { // 直到写完所有数据
        memset(io_buf, 0, BLOCK_SIZE);
        sec_idx = file -> fd_inode -> i_size / BLOCK_SIZE;
        sec_lba = all_blocks[sec_idx];
        sec_off_bytes = file -> fd_inode -> i_size % BLOCK_SIZE;
        sec_left_bytes = BLOCK_SIZE - sec_off_bytes;

        // 判断此次写入硬盘的数据大小
        // 若剩余数据 < 扇区内剩余的内容 => size_left
        // 否则 => sec_left_bytes
        chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;

        if(first_write_block) { // 只需要执行一次,只为处理最开始记录的 all_blocks 中的那个块
            // 因为唯独这个扇区的数据可能本就不满一个扇区,也正是因为它有数据,所以要先读
            ide_read(cur_part -> my_disk, sec_lba, io_buf, 1);
            first_write_block = false; // 往后不再执行
        }

        memcpy(io_buf + sec_off_bytes, src, chunk_size);
        ide_write(cur_part -> my_disk, sec_lba, io_buf, 1);
        printk("file write at lba 0x%x\n", sec_lba);    //调试,完成后去掉

        src += chunk_size; // 对数据源偏移
        file -> fd_inode -> i_size += chunk_size; // 更新文件大小
        file -> fd_pos += chunk_size; // 文件位置指针更新
        bytes_written += chunk_size;
        size_left -= chunk_size;
    }

    inode_sync(cur_part, file -> fd_inode, io_buf);
    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_written;
}

改进 sys_write 和 write 系统调用

fs/fs.c

// 将 buf 中连续 count 个字节写入文件描述符 fd,成功返回字节数,失败返回-1
int32_t sys_write(int32_t fd, const void* buf, uint32_t count) {
    if(fd < 0) {
        printk("sys_write: fd error.\n");
        return -1;
    }
    if(fd == stdout_no) {
        char tmp_buf[1024] = {0};
        memcpy(tmp_buf, buf, count);
        console_put_str(tmp_buf);
        return count;
    }
    uint32_t _fd = fd_local2global(fd);
    struct file* wr_file = &file_table[_fd];
    if(wr_file -> fd_flag & O_WRONLY || wr_file -> fd_flag & O_RDWR) {
        uint32_t bytes_written = file_write(wr_file, buf, count);
        return bytes_written;
    } else {
        console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY.\n");
        return -1;
    }
}

lib/user/syscall.c

// 打印字符串
uint32_t write(int32_t fd, const void* buf, uint32_t count) {
    return _syscall3(SYS_WRITE, fd, buf, count);
}

lib/stdio.c

// 格式化输出字符串 format
uint32_t printf(const char* format, ...) {
    va_list args;
    va_start(args, format);     // 将 args 指向 format
    char buf[1024] = {0};       // 用于存储拼接后的字符串
    vsprintf(buf, format, args);
    va_end(args);
    return write(1, buf, strlen(buf));
}

读取文件

fs/file.c —— file_read

// 从文件 file 中读取 count 个字节到 buf,返回读取的字节数,若到末尾则返回-1
int32_t file_read(struct file* file, void* buf, uint32_t count) {
    uint8_t* buf_dst = (uint8_t*) buf;
    uint32_t size = count, size_left = size;

    // 若要读取的字节数超过了文件本身的大小,则全部读取
    if((file -> fd_pos + count) > file -> fd_inode -> i_size) {
        size = file -> fd_inode -> i_size - file -> fd_pos;
        size_left = size;
        if(size == 0) return -1; // 到文件末尾了
    }

    uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
    if(io_buf == NULL) {
        printk("file_read: sys_malloc for io_buf failed\n");
    }
    uint32_t* all_blocks = (uint32_t*) sys_malloc(BLOCK_SIZE + 48);
    if(all_blocks == NULL) {
        printk("file_read: sys_malloc for all_blocks failed\n");
        return -1;
    }
    uint32_t block_read_start_idx = file -> fd_pos / BLOCK_SIZE;        // 从哪个数据块开始读
    uint32_t block_read_end_idx = (file -> fd_pos + size) / BLOCK_SIZE; // 最终要读取到哪儿,落在哪个块
    uint32_t read_blocks = block_read_start_idx - block_read_end_idx; // 差为0,则表示要读取的起始和终止都在同一个块中,只读取一个扇区
    ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);

    int32_t indirect_block_table; // 一级间接块表的地址
    uint32_t block_idx;           // 待读取的块

    // 开始构建 all_blocks
    if(read_blocks == 0) { // 读取同一个扇区
        ASSERT(block_read_start_idx == block_read_end_idx);
        if(block_read_end_idx < 12) { // 直接块
            block_idx = block_read_end_idx;
            all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
        } else { // 间接块
            indirect_block_table = file -> fd_inode -> i_sectors[block_idx];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    } else { // 读取多个扇区
        if(block_read_end_idx < 12) { // 起始和终止块都在12个直接块内
            block_idx = block_read_start_idx;
            while(block_idx <= block_read_end_idx) {
                all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
                block_idx++;
            }
        } else if(block_read_start_idx < 12 && block_read_end_idx >= 12) { // 起始块在直接块中,最终要落到间接块中
            block_idx = block_read_start_idx;
            while(block_idx < 12) {
                all_blocks[block_idx] = file -> fd_inode -> i_sectors[block_idx];
                block_idx++;
            }
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        } else { // 起始和终止都是间接块中
            ASSERT(file -> fd_inode -> i_sectors[12] != 0);
            indirect_block_table = file -> fd_inode -> i_sectors[12];
            ide_read(cur_part -> my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    }

    // 读取数据
    uint32_t sec_idx, sec_lba, sec_off_bytes, sec_left_bytes, chunk_size;
    uint32_t bytes_read = 0;
    while(bytes_read < size) {
        sec_idx = file -> fd_pos / BLOCK_SIZE;
        sec_lba = all_blocks[sec_idx];
        sec_off_bytes = file -> fd_pos % BLOCK_SIZE;
        sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
        chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;

        memset(io_buf, 0, BLOCK_SIZE);
        ide_read(cur_part -> my_disk, sec_lba, io_buf, 1);
        memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);

        buf_dst += chunk_size;
        file -> fd_pos += chunk_size;
        bytes_read += chunk_size;
        size_left -= chunk_size;
    }
    sys_free(all_blocks);
    sys_free(io_buf);
    return bytes_read;
}

fs/fs.c

// 从文件描述符 fd 指向的文件中读取 count 个字节到 buf,成功返回读取的字节数,到文末返回-1
int32_t sys_read(int32_t fd, void* buf, uint32_t count) {
    if(fd < 0) {
        printk("sys_read: fd error\n");
        return -1;
    }
    ASSERT(buf != NULL);
    uint32_t _fd = fd_local2global(fd);
    return file_read(&file_table[_fd], buf, count);
}

实现文件读写指针定位

fs/fs.h

/* 文件读写位置偏移量 */
enum whence {
   SEEK_SET = 1,
   SEEK_CUR,
   SEEK_END
};

fs.fs.c

// 重置文件偏移指针,返回新的偏移量
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence) {
    if(fd < 0) {
        printk("sys_lseek: fd error\n");
        return -1;
    }
    ASSERT(whence > 0 && whence < 4);
    uint32_t _fd = fd_local2global(fd);
    struct file* pf = &file_table[_fd];
    int32_t new_pos = 0;
    int32_t file_size = (int32_t) pf -> fd_inode -> i_size;
    switch(whence) {
        case SEEK_SET: // 相对于文件开头偏移 offset
            new_pos = offset;
            break;
        case SEEK_CUR: // 相对于当前位置 fd_pos 偏移 offset
            new_pos = (int32_t) pf -> fd_pos + offset;
            break;
        case SEEK_END: // 相对于文件结尾偏移 offset
            new_pos = file_size + offset;
            break;
    }
    if(new_pos < 0 || new_pos > (file_size - 1)) return -1;
    pf -> fd_pos = new_pos;
    return pf -> fd_pos;
}

文件删除

回收 inode

fs/inode.c

// 将硬盘分区 part 上的 inode 清空(该函数并没有存在的必要,因为这些内容后面会被新内容覆盖)
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf) {
    ASSERT(inode_no < 4096);
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);
    ASSERT(inode_pos.sec_lba <= (part -> start_lba + part -> sec_cnt));

    char* inode_buf = (char*) io_buf;
    if(inode_pos.two_sec) {
        ide_read(part -> my_disk, inode_pos.sec_lba, inode_buf, 2);
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    } else { // 未跨扇区,只读入1个扇区就好
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

// 回收 inode 的数据块和 inode 本身
void inode_release(struct partition* part, uint32_t inode_no) {
    struct inode* inode_to_del = inode_open(part, inode_no);
    ASSERT(inode_to_del -> i_no == inode_no);

    uint8_t block_idx = 0, block_cnt = 12;
    uint32_t block_bitmap_idx;
    uint32_t all_blocks[140] = {0}; // 12个直接块+128个间接块

    // 先收集12个直接块
    while(block_idx < 12) {
        all_blocks[block_idx] = inode_to_del -> i_sectors[block_idx];
        block_idx++;
    }

    // 再收集间接块
    if(inode_to_del -> i_sectors[12] != 0) {
        // 读取一级间接块表中的所有间接块
        ide_read(part -> my_disk, inode_to_del -> i_sectors[12], all_blocks + 12, 1);
        block_cnt = 140;

        // 回收一级间接块表
        block_bitmap_idx = inode_to_del -> i_sectors[12] - part -> sb -> data_start_lba;
        ASSERT(block_bitmap_idx > 0);
        bitmap_set(&part -> block_bitmap, block_bitmap_idx, 0);
        bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
    }

    // 开始逐渐回收

    block_idx = 0;
    while(block_idx < block_cnt) {
        if(all_blocks[block_idx] != 0) {
            block_bitmap_idx = 0;
            block_bitmap_idx = all_blocks[block_idx] - part -> sb -> data_start_lba;
            ASSERT(block_bitmap_idx > 0);
            bitmap_set(&part -> block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
        }
        block_idx++;
    }

    // 回收 inode
    // 注:inode_no 编号便是 inode 在位图中的位置
    bitmap_set(&part -> inode_bitmap, inode_no, 0);
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);

    /******     以下inode_delete是调试用的    ******
     * 此函数会在inode_table中将此inode清0,
     * 但实际上是不需要的,inode分配是由inode位图控制的,
     * 硬盘上的数据不需要清0,可以直接覆盖*/
    void* io_buf = sys_malloc(1024);
    inode_delete(part, inode_no, io_buf);
    sys_free(io_buf);
    /***********************************************/

    inode_close(inode_to_del);
}

删除目录项

fs/dir.c

// 将分区 part 中目录 pdir 中编号为 inode_no 的目录项删除
bool delete_dir_entry(struct partition* part, struct dir* pdir, uint32_t inode_no, void* io_buf) {
    struct inode* dir_inode = pdir -> inode;
    uint32_t block_idx = 0, all_blocks[140] = {0};

    while(block_idx < 12) {
        all_blocks[block_idx] = dir_inode -> i_sectors[block_idx];
        block_idx++;
    }
    if(dir_inode -> i_sectors[12]) {
        ide_read(part -> my_disk, dir_inode -> i_sectors[12], all_blocks + 12, 1);
    }

    // 目录项在存储时保证不会跨扇区
    uint32_t dir_entry_size = part->sb->dir_entry_size;           // 目录项大小
    uint32_t dir_entrys_per_sec = (SECTOR_SIZE / dir_entry_size); // 每扇区最大的目录项数目
    struct dir_entry* dir_e = (struct dir_entry*)io_buf;          // 将 dir_e 指向 io_buf
    struct dir_entry* dir_entry_found = NULL;                     // 是否找到目录项
    uint8_t dir_entry_idx, dir_entry_cnt;                         // 当前是哪个目录项,总目录项
    bool is_dir_first_block = false;                              // 目录的第1个块 

    // 遍历所有块,寻找目录项
    block_idx = 0;
    while(block_idx < 140) {
        is_dir_first_block = false;
        if(all_blocks[block_idx] == 0) {
            block_idx++;
            continue;
        }
        dir_entry_idx = dir_entry_cnt = 0;
        memset(io_buf, 0, SECTOR_SIZE);
        // 获取目录项
        ide_read(part -> my_disk, all_blocks[block_idx], io_buf, 1);
        
        // 遍历所有目录项,统计该扇区的目录项数量以及是否有待删除的目录项
        while(dir_entry_idx < dir_entrys_per_sec) {
            if((dir_e + dir_entry_idx) -> f_type == FT_UNKNOWN) continue;
            if(!strcmp((dir_e + dir_entry_idx) -> filename, ".")) {
                is_dir_first_block = true;
            } else if(strcmp((dir_e + dir_entry_idx) -> filename, ".") && strcmp((dir_e + dir_entry_idx) -> filename, "..")) {
                dir_entry_cnt++;
                if((dir_e + dir_entry_idx) -> i_no == inode_no) { // 找到了要删除的目录项
                    ASSERT(dir_entry_found == NULL); // 确保 inode_no 是唯一的
                    dir_entry_found = dir_e + dir_entry_idx; // 记录下来找到的这个目录项
                } // 即使找到了要删除的目标,但此时还不能停止,而是需要继续统计总共的目录项数量
            }
            dir_entry_idx++;
        }

        if(dir_entry_found == NULL) { // 若未找到目标项,则继续去下一个扇区查找
            block_idx++;
            continue;
        }

        // 找到目标项后,清除该目标项并判断是否回收该扇区
        ASSERT(dir_entry_cnt >= 1);
        
        if(dir_entry_cnt == 1 && !is_dir_first_block) { // 除目录第一个扇区外,若该扇区上只有该目录项自己,则将整个扇区回收
            // 回收该块
            uint32_t block_bitmap_idx = all_blocks[block_idx] - part -> sb -> data_start_lba;
            bitmap_set(&part -> block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

            // 将块地址从 i_sectors 或索引表(间接块表)中去掉
            if(block_idx < 12) {
                dir_inode -> i_sectors[block_idx] = 0;
            } else {
                uint32_t indirect_blocks = 0;
                uint32_t indirect_blocks_idx = 12;

                while(indirect_blocks_idx < 12) {
                    if(all_blocks[indirect_blocks_idx] != 0) {
                        indirect_blocks++;
                    }
                }

                ASSERT(indirect_blocks >= 1);

                if(indirect_blocks > 1) {
                    all_blocks[block_idx] = 0;
                    ide_write(part -> my_disk, dir_inode -> i_sectors[12], all_blocks + 12, 1);
                } else { // 间接块索引表中就当前这一个间接块,直接把间接索引表所在的块一并回收了,然后擦除表的地址
                    block_bitmap_idx = dir_inode -> i_sectors[12] - part -> sb -> data_start_lba;
                    bitmap_set(&part -> block_bitmap, block_bitmap_idx, 0);
                    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

                    // 擦除间接块索引表的地址
                    dir_inode -> i_sectors[12] = 0;
                }
            }
        } else { // 仅将该目录项清空
            memset(dir_entry_found, 0, dir_entry_size);
            ide_write(part -> my_disk, all_blocks[block_idx], io_buf, 1);
        }

        // 更新 inode 结点信息并同步到硬盘
        ASSERT(dir_inode -> i_size >= dir_entry_size);
        dir_inode -> i_size -= dir_entry_size;
        memset(io_buf, 0, SECTOR_SIZE * 2);
        inode_sync(part, dir_inode, io_buf);

        return true;
    }

    return false;
}

实现 sys_unlink

fs/fs.c

// 删除文件(非目录),成功返回0,失败返回-1
int32_t sys_unlink(const char* pathname) {
    ASSERT(strlen(pathname) < MAX_PATH_LEN);

    // 检查待删除的文件是否存在
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));
    int inode_no = search_file(pathname, &searched_record);

    ASSERT(inode_no != 0);

    if(inode_no == -1) {
        printk("file %s not found!\n", pathname);
        dir_close(searched_record.parent_dir);
        return -1;
    }

    // 判断待删除的目标是否为目录
    if(searched_record.file_type == FT_DIRECTORY) {
        printk("can`t delete a direcotry with unlink(), use rmdir() to instead\n");
        dir_close(searched_record.parent_dir);
        return -1;
    }

    // 检查是否被打开
    // Tips:正在使用的文件不可删除
    uint32_t file_idx = 0;
    while(file_idx < MAX_FILE_OPEN) {
        if(file_table[file_idx].fd_inode != NULL && (uint32_t) inode_no == file_table[file_idx].fd_inode->i_no) {
            break;
        }
        file_idx++;
    }

    if(file_idx < MAX_FILE_OPEN) {
        dir_close(searched_record.parent_dir);
        printk("file %s is in use, not allow to delete!\n", pathname);
        return -1;
    }

    ASSERT(file_idx == MAX_FILE_OPEN);

    void* io_buf = sys_malloc(SECTOR_SIZE + SECTOR_SIZE);
    if(io_buf == NULL) {
        dir_close(searched_record.parent_dir);
        printk("sys_unlink: malloc for io_buf failed\n");
        return -1;
    }

    struct dir* parent_dir = searched_record.parent_dir;
    delete_dir_entry(cur_part, parent_dir, inode_no, io_buf);
    inode_release(cur_part, inode_no);
    sys_free(io_buf);
    dir_close(searched_record.parent_dir);

    return 0;
}

创建目录

fs/fs.c

// 创建目录 pathname,成功返回0,失败返回-1
int32_t sys_mkdir(const char* pathname) {
    uint8_t rollback_step = 0;
    void* io_buf = sys_malloc(SECTOR_SIZE * 2);
    if(io_buf == NULL) {
        printk("sys_mkdir: sys_malloc for io_buf failed\n");
        return -1;
    }

    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));

    int inode_no = -1;
    inode_no = search_file(pathname,  &searched_record);
    if(inode_no != -1) { // 文件或目录已存在
        printk("sys_mkdir: file or directory %s exist!\n", pathname);
        rollback_step = 1;
        goto rollback;
    } else { // 中间目录是否缺失
        uint32_t pathname_depth = path_depth_cnt((char*) pathname);
        uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);
        // 判断中间目录是否缺失
        if(pathname_depth != path_searched_depth) {
            printk("sys_mkdir: can`t access %s, subpath %s is`t exist\n", pathname, searched_record.searched_path);
            rollback_step = 1;
            goto rollback;
        }
    }

    struct dir* parent_dir = searched_record.parent_dir;
    char* dirname = strrchr(searched_record.searched_path, '/') + 1;

    inode_no = inode_bitmap_alloc(cur_part);
    if(inode_no == -1) {
        printk("sys_mkdir: allocate inode failed\n");
        rollback_step = 1;
        goto rollback;
    }

    struct inode new_dir_inode;
    inode_init(inode_no, &new_dir_inode);

    uint32_t block_bitmap_idx = 0;
    int32_t block_lba = -1;

    // 为目录分配一个块,用来写入目录项 . 和 ..
    block_lba = block_bitmap_alloc(cur_part);
    if(block_lba == -1) {
        printk("sys_mkdir: block_bitmap_alloc for create directory failed\n");
        rollback_step = 2;
        goto rollback;
    }

    new_dir_inode.i_sectors[0] = block_lba;
    block_bitmap_idx = block_lba - cur_part -> sb -> data_start_lba;
    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

    // 写入目录项 . 和 ..
    memset(io_buf, 0, SECTOR_SIZE * 2);
    struct dir_entry* p_de = (struct dir_entry*) io_buf;

    // 初始化当前目录 .
    memcpy(p_de -> filename, ".", 1);
    p_de -> i_no = inode_no;
    p_de -> f_type = FT_DIRECTORY;
    p_de++;

    // 初始化目录项 ..
    memcpy(p_de -> filename, "..", 2);
    p_de -> i_no = parent_dir -> inode -> i_no;
    p_de -> f_type = FT_DIRECTORY;

    // 写入目录项
    ide_write(cur_part -> my_disk, new_dir_inode.i_sectors[0], io_buf, 1);

    new_dir_inode.i_size = 2 * cur_part -> sb -> dir_entry_size;

    // 在父目录中添加新的目录项(也就是新创建的那个目录的目录项)
    struct dir_entry new_dir_entry;
    memset(&new_dir_entry, 0, sizeof(struct dir_entry));
    create_dir_entry(dirname, inode_no, FT_DIRECTORY, &new_dir_entry);
    memset(io_buf, 0, SECTOR_SIZE * 2);

    // 父目录将新目录项写入
    if(!sync_dir_entry(parent_dir, &new_dir_entry,  io_buf)) {
        printk("sys_mkdir: sync_dir_entry to disk failed!\n");
        rollback_step = 2;
        goto rollback;
    }

    // 同步父目录 inode
    memset(io_buf, 0, SECTOR_SIZE * 2);
    inode_sync(cur_part, parent_dir -> inode, io_buf);

    // 同步新创建的目录 inode
    memset(io_buf, 0, SECTOR_SIZE * 2);
    inode_sync(cur_part, &new_dir_inode, io_buf);

    // 将位图同步
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);

    sys_free(io_buf);

    dir_close(searched_record.parent_dir);
    return 0;

    rollback:
        switch(rollback_step) {
            case 2:
                bitmap_set(&cur_part -> inode_bitmap, inode_no, 0);
            case 1:
                dir_close(searched_record.parent_dir);
        }
        sys_free(io_buf);
        return -1;
}

遍历目录

打开目录和关闭目录

fs/fs.c

// 打开目录,返回目录指针,失败返回NULL
struct dir* sys_opendir(const char* name) {
    ASSERT(strlen(name) < MAX_PATH_LEN);

    // 若是根目录,直接返回
    if(name[0] == '/' && (name[1] == 0 || name[0] == ".")) return &root_dir;

    // 先检查待打开的目录是否存在
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));
    int inode_no = search_file(name, &searched_record);
    struct dir* ret = NULL;
    if(inode_no == -1) { // 找不到,提示路径不存在
        printk("In %s, sub path %s not exist\n", name, searched_record.searched_path); 
    } else {
        if(searched_record.file_type == FT_REGULAR) { // 目标是文件类型为普通文件,这不合法
            printk("%s is regular file!\n", name);
        } else if(searched_record.file_type == FT_DIRECTORY) { // 找到了目标目录
            ret = dir_open(cur_part, inode_no);
        }
    }

    dir_close(searched_record.parent_dir);

    return ret;
}

// 关闭目录,返回0,失败-1
int32_t sys_closedir(struct dir* dir) {
    int32_t ret = -1;
    if(dir != NULL) {
        dir_close(dir);
        ret = 0;
    }
    return ret;
}

读取一个目录项

fs/dir.c

// 读取目录,返回一个目录项
struct dir_entry* dir_read(struct dir* dir) {
    struct dir_entry* dir_e = (struct dir_entry*) dir -> dir_buf;
    struct inode* dir_inode = dir -> inode;
    uint32_t all_blocks[140] = {0}, block_cnt = 12;
    uint32_t block_idx = 0, dir_entry_idx = 0;
    while(block_idx < 12) {
        all_blocks[block_idx] = dir_inode -> i_sectors[block_idx];
        block_idx++;
    }
    if(dir_inode -> i_sectors[12] != 0) {
        ide_read(cur_part -> my_disk, dir_inode -> i_sectors[12], all_blocks + 12, 1);
        block_cnt = 140;
    }
    block_idx = 0;

    uint32_t cur_dir_entry_pos = 0; // 当前目录项的偏移,此项的作用是与 dir_pos 进行比较,判断是否之前已经返回了某些目录项
    uint32_t dir_entry_size = cur_part -> sb -> dir_entry_size;
    uint32_t dir_entrys_per_sec = SECTOR_SIZE / dir_entry_size;

    while(block_idx < block_cnt) {
        if(dir -> dir_pos >= dir -> inode -> i_size) // 若指针位置超过了文件大小,则说明已经遍历完了该 inode 的所有目录项
            return NULL;
        if(all_blocks[block_idx] == 0) {
            block_idx++;
            continue;
        }
        memset(dir_e, 0, SECTOR_SIZE);
        ide_read(cur_part -> my_disk, all_blocks[block_idx], dir_e, 1);
        dir_entry_idx = 0;
        // 遍历该扇区的所有目录项
        while(dir_entry_idx < dir_entrys_per_sec) {
            if((dir_e + dir_entry_idx) -> f_type == FT_UNKNOWN) { // 没意义的不要
                dir_entry_idx++;
                continue;
            } 
            if(cur_dir_entry_pos < dir -> dir_pos) { // 遍历过的不要
                cur_dir_entry_pos += dir_entry_size;
                dir_entry_idx++;
                continue;
            }
            ASSERT(cur_dir_entry_pos == dir -> dir_pos);
            dir -> dir_pos += dir_entry_size; // 更新位置,即下一个返回的目录项地址
            return dir_e + dir_entry_idx;
        }
        block_idx++;
    }

    return NULL;
}

实现 sys_readdir 及 sys_rewinddir

fs/fs.c

// 读取目录 dir 中的 1 个目录项,返回目录项地址,否则NULL
struct dir_entry* sys_readdir(struct dir* dir) {
    ASSERT(dir != NULL);
    return dir_read(dir);
}

// 把目录 dir 的指针 dir_pos 置为 0
void sys_rewinddir(struct dir* dir) {
    dir -> dir_pos = 0;
}

删除目录

删除目录与判断空目录

fs/dir.c

// 判断目录是否为空
bool dir_is_empty(struct dir* dir) {
    struct inode* dir_inode = dir -> inode;
    return (dir_inode -> i_size == cur_part -> sb -> dir_entry_size * 2);
}

// 在父目录中删除子目录 child_dir
int32_t dir_remove(struct dir* parent_dir, struct dir* child_dir) {
    struct inode* child_dir_inode = child_dir -> inode;
    // .和..只存在 i_sectors[0] 中,因此其它删除应该都要为空
    int32_t block_idx = 1; // 跨越 0
    while(block_idx < 13) {
        ASSERT(child_dir -> inode -> i_sectors[block_idx] == 0);
        block_idx++;
    }
    void* io_buf = sys_malloc(SECTOR_SIZE * 2);
    if(io_buf == NULL) {
        printk("dir_remove: malloc for io_buf failed\n");
        return -1;
    }

    // 在父目录中删除子目录所对应的目录项
    delete_dir_entry(cur_part, parent_dir, child_dir_inode -> i_no, io_buf);

    // 回收 inode 中 i_sectors 所占用的扇区,并同步
    inode_release(cur_part, child_dir_inode -> i_no);

    sys_free(io_buf);
    return 0;
}

实现 sys_rmdir

fs/fs.c

// 删除空目录,成功0,失败-1
int32_t sys_rmdir(const char* pathname) {
    // 先检查文件是否存在
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));
    int inode_no = search_file(pathname, &searched_record);
    ASSERT(inode_no != 0);
    int retval = -1;

    if(inode_no == -1) { // 已存在
        printk("In %s, sub path %s not exist\n", pathname, searched_record.searched_path); 
    } else {
        if(searched_record.file_type == FT_REGULAR) { // 是普通文件类型
            printk("%s is regular file!\n", pathname);
        } else {
            struct dir* dir = dir_open(cur_part, inode_no);
            if(!dir_is_empty(dir)) { // 判断目录是否为空
                printk("dir %s is not empty, it is not allowed to delete a nonempty directory!\n", pathname);
            } else { // 空目录,进行删除
                if(!dir_remove(searched_record.parent_dir, dir)) retval = 0;
            }
            dir_close(dir);
        }
    }

    dir_close(searched_record.parent_dir);
    return retval;
}

任务的工作目录

基础函数

fs/fs.c

// 获得父目录的 inode 编号
static uint32_t get_parent_dir_inode_nr(uint32_t child_inode_nr, void* io_buf) {
    struct inode* child_dir_inode = inode_open(cur_part, child_inode_nr);
    uint32_t block_lba = child_dir_inode -> i_sectors[0];
    ASSERT(block_lba >= cur_part -> sb -> data_start_lba);
    inode_close(child_dir_inode);
    ide_read(cur_part -> my_disk, block_lba, io_buf, 1);
    struct dir_entry* dir_e = (struct dir_entry*) io_buf;
    // 第0个目录项是., 第1个目录项是 ..
    ASSERT(dir_e[1].i_no < 4096 && dir_e[1].f_type == FT_DIRECTORY);
    return dir_e[1].i_no;
}

// 在 inode 编号为 p_inode_nr 的目录中查到 inode 编号为 c_inode_nr 的子目录名称,将名字存入 path
static int get_child_dir_name(uint32_t p_inode_nr, uint32_t c_inode_nr, char* path, void* io_buf) {
    struct inode* parent_dir_inode = inode_open(cur_part, p_inode_nr);
    uint8_t block_idx = 0;
    uint32_t all_blocks[140] = {0}, block_cnt = 12;
    while(block_idx < 12) {
        all_blocks[block_idx] = parent_dir_inode -> i_sectors[block_idx];
        block_idx++;
    }
    if(parent_dir_inode -> i_sectors[12]) {
        ide_read(cur_part -> my_disk, parent_dir_inode -> i_sectors[12], all_blocks + 12, 1);
        block_cnt = 140;
    }
    inode_close(parent_dir_inode);

    struct dir_entry* dir_e = (struct dir_entry*) io_buf;
    uint32_t dir_entry_size = cur_part -> sb -> dir_entry_size;
    uint32_t dir_entrys_per_sec = 512 / dir_entry_size;
    block_idx = 0;

    // 遍历所有块
    while(block_idx < block_cnt) {
        if(all_blocks[block_idx] != 0) {
            ide_read(cur_part -> my_disk, all_blocks[block_idx], io_buf, 1);
            uint8_t dir_e_idx = 0;
            // 遍历该扇区的所有目录项
            while(dir_e_idx < dir_entrys_per_sec) {
                if((dir_e + dir_e_idx) -> i_no == c_inode_nr) { // 找到目标
                    strcat(path, "/");
                    strcat(path, (dir_e + dir_e_idx) -> filename);
                    return 0;
                }
                dir_e_idx++;
            }
            block_idx++;
        }
    }

    return -1;
}

sys_getcwd

fs/fs.c

thread/thread.h

// 进程或线程的 PCB,即程序控制块
struct task_struct {
	...
    uint32_t cwd_inode_nr; // 进程所在的工作目录的 inode 编号
	...
};

thread/thread.c

// 初始化线程基本信息
void init_thread(struct task_struct* pthread, char* name, int prio) {
    pthread -> cwd_inode_nr = 0; // 以根目录作为默认的工作路径
    pthread -> stack_magic = 0x19870916; // 自定义的魔数,用于栈边界判断
}

fs/fs.c

// 将当前工作目录绝对路径写入 buf
char* sys_getcwd(char* buf, uint32_t size) {
    ASSERT(buf != NULL);
    void* io_buf = sys_malloc(SECTOR_SIZE);
    if(io_buf == NULL) {
        return NULL;
    }

    struct task_struct* cur_thread = running_thread();
    int32_t parent_inode_nr = 0;
    int32_t child_inode_nr = cur_thread -> cwd_inode_nr;
    ASSERT(child_inode_nr >= 0 && child_inode_nr < 4096);
    if(child_inode_nr == 0) {
        buf[0] = '/';
        buf[1] = 0;
        return buf;
    }

    memset(buf, 0, size);
    char full_path_reverse[MAX_PATH_LEN] = {0};

    // 从下往上查找父目录,直到找到根目录为止
    while(child_inode_nr) {
        parent_inode_nr = get_parent_dir_inode_nr(child_inode_nr, io_buf);
        if(get_child_dir_name(parent_inode_nr, child_inode_nr, full_path_reverse, io_buf) == -1) {
            // 未找到对应的名字
            sys_free(io_buf);;
            return NULL;
        }
        child_inode_nr = parent_inode_nr;
    }

    ASSERT(strlen(full_path_reverse) <= size);
    /**
     * 至此 full_path_reverse 中的路径是反着的
     * 即子目录在左边,父目录在右边
     * 现在将其逆转
    */
   char* last_slash; // 
   while((last_slash = strrchr(full_path_reverse, '/'))) {
        uint16_t len = strlen(buf);
        strcpy(buf + len, last_slash);
        *last_slash = 0;
   }

   sys_free(io_buf);
   return buf;
}

sys_chdir

fs/fs.c

// 更改当前工作目录为绝对路径 path,成功0,失败-1
int32_t sys_chdir(const char* path) {
    int32_t ret = -1;
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));
    int inode_no = search_file(path, &searched_record);
    if(inode_no != -1) {
        if(searched_record.file_type == FT_DIRECTORY) {
            running_thread() -> cwd_inode_nr = inode_no;
            ret = 0;
        } else {
            printk("sys_chdir: %s is regular file or other!\n", path);
        }
    }
    dir_close(searched_record.parent_dir);
    return ret;
}

文件属性

fs/fs.h

// 文件属性结构
struct stat {
    uint32_t st_ino;                // inode 编号
    uint32_t st_size;               // 尺寸
    enum file_types st_filetype;    // 文件类型
};

fs/fs.c

// 获取文件属性信息,存入到 buf 中,成功0,失败-1
int32_t sys_stat(const char* path, struct stat* buf) {
    // 找的是根目录
    if(!strcmp(path, "/") || !strcmp(path, "/.") || !strcmp(path, "/..")) {
        buf -> st_filetype = FT_DIRECTORY;
        buf -> st_ino = 0;
        buf -> st_size = root_dir.inode -> i_size;
        return 0;
    }

    int ret = -1;
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record));
    int inode_no = search_file(path, &searched_record);
    if(inode_no != -1) { // 找到了
        struct inode* obj_inode = inode_open(cur_part, inode_no);
        buf -> st_filetype = searched_record.file_type;
        buf -> st_ino = inode_no;
        buf -> st_size = obj_inode -> i_size;
        ret = 0;
        inode_close(obj_inode);
    } else { // 未找到
        printk("sys_stat: %s not found.\n", path);
    }
    dir_close(searched_record.parent_dir);
    return ret;
}

参考资料

  • 文件描述符
  • 文件系统inode详解

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

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

相关文章

单例模式

单例模式 1. 单例模式介绍 单例模式可以说是整个设计中最简单的模式之一&#xff0c;而且这种方式即使在没有看设计模式相关资料也会常用在编码开发中。 因为在编程开发中经常会遇到这样一种场景&#xff0c;那就是需要保证一个类只有一个实例哪怕多线程同时访问&#xff0c;…

C#语言实例源码系列-实现IC卡的读写

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

机器学习:通俗理解马尔科夫随机场(MRF)及其应用(附例题)

目录0 写在前面1 无向概率图2 马尔科夫随机场3 马尔科夫独立性4 例题分析0 写在前面 机器学习强基计划聚焦深度和广度&#xff0c;加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理&#xff1b;“广”在分析多个机器学习模型&#xff1a;决策树、支持…

Git使用,在github中创建仓库

一.本地生成密钥&#xff1a; ssh-keygen //生成密钥 cat id_rsa.pub # 查看公钥 查看公钥&#xff0c;并将公钥添加到github的服务器上 二.创建文件&#xff0c;并且将文件上传到GitHub 设置全局用户信息&#xff1a; git config --global user.name dwerrwgit config…

LabVIEW NI数字万用表与开关握手扫描速率

LabVIEW NI数字万用表与开关握手扫描速率 在决定需要哪些设备来满足系统要求时&#xff0c;对扫描速率数据进行基准测试非常有用。数字万用表&#xff08;DMM&#xff09;和开关系统也是如此&#xff0c;因为扫描速率取决于数字万用表、开关和它们之间的触发器的速度。本文包含…

高并发系统设计 -- 抢红包设计

抢红包的业务分析 可以明显的看到打开了红包不一定可以抢到。这样做的好处是&#xff1a; 符合现实生活逻辑&#xff0c;有仪式感防止误领&#xff0c;发现不对劲可以马上退出流程拆的长一些&#xff0c;平摊高并发下的压力 预拆包&#xff1a;我在发红包的时候&#xff0c;就…

ansible的静态清单配置文件

清单文件 定义主机清单文件 清单中定义ansible将要管理的一批主机&#xff0c;这些主机也可以分配到组中&#xff0c;以进行集中管理。组中也可以包含子组&#xff0c;一台主机也可以是多个组中的成员。清单还可以设置应用到它所定义的主机和组的变量。 编写主机清单文件 主机…

归置归置,我的 2022

J3code杂文&#xff08;程序人生 # 年终总结&#xff09; 记得 2021 年我没有进行年终总结&#xff0c;也就没有发出过相关的内容出来。总结原因就是一个&#xff0c;躺平了&#xff0c;自毕业换工作之后&#xff0c;就一直在适应工作环境与生活环境中默默的度过了 2021 年。 …

你真的懂树吗?二叉树、AVL平衡二叉树、伸展树、B-树和B+树原理和实现代码详解...

树&#xff08;Tree&#xff09;是一种相当灵活的数据结构&#xff08;上一节已经详细讲解了基本的数据结构&#xff1a;线性表、栈和队列&#xff09;&#xff0c;你可能接触过二叉树&#xff0c;但是树的使用并不限于此&#xff0c;从简单的使用二叉树进行数据排序&#xff0…

(深度学习快速入门)第三章第一节:多层感知器简介

文章目录一&#xff1a;引入二&#xff1a;定义三&#xff1a;反向传播算法四&#xff1a;构建多层感知器完成波士顿房价预测一&#xff1a;引入 前文所讲到的波士顿房价预测案例中&#xff0c;涉及到的仅仅是一个非常简单的神经网络&#xff0c;它只含有输入层和输出层 但观…

vue3 antd项目实战——Form表单的重置与清空【resetFields重置表单未生效(手写重置函数)】

vue3 antd项目实战——resetFields重置表单无效【手写重置函数重置表单数据】关于form表单的文章合集场景复现原因分析解决方案(手写清空函数)关于form表单的文章合集 文章内容文章链接Form表单提交和校验https://blog.csdn.net/XSL_HR/article/details/128495087?spm1001.20…

面向对象定义一个hero类

问题定义一个hero类&#xff0c;属性有power&#xff0c;name&#xff0c;分别代表体力值和英雄的名字&#xff0c;体力值默认为100&#xff1b;方法有&#xff1a;1.行走的方法如果体力值为0&#xff0c;则输出不能行走&#xff0c;此英雌已死亡的信息&#xff1b;2.吃的方法&…

双非二本、已获HCIA认证的大二学生与C站相遇的2022

目录 前言 2022年1月、2月——迷茫 2022年3月~6月——调整规划 ​2022年7月——在CSDN发布第一篇博客 2022年8月——步入正轨&#xff0c;获得2022谷歌开发者大会入场名额 2022年9月~10月——开学季&#xff0c;收获季 2022年11月——第一次接触项目并去公司学习实践&…

01通信/协议一些简要概念

通信的目的 将一个设备的数据传送到另一个设备&#xff0c;扩展硬件系统。 通信协议 制定通信的规则&#xff0c;通信双方按照协议规则进行数据收发。 每一种通讯协议都有硬件与软件上的要求。 常见的协议 USART TX、RX 全双工 异步 单端 点对点 I2C SCL、SDA 半双…

百度大规模知识图谱构建及技术应用实践

省时查报告-专业、及时、全面的行研报告库省时查方案-专业、及时、全面的营销策划方案库【免费下载】2022年11月份热门报告盘点罗振宇2023年跨年演讲PPT原稿2023年&#xff0c;如何科学制定年度规划&#xff1f;《底层逻辑》高清配图‍基于深度学习的个性化推荐系统实时化改造与…

【Node.js实战】一文带你开发博客项目之登录(对接完毕,cookie、session、redis各司其职)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

【蓝桥杯选拔赛真题54】Scratch小猫钓鱼 少儿编程scratch图形化编程 蓝桥杯选拔赛真题讲解

目录 scratch小猫钓鱼 一、题目要求 编程实现 二、案例分析 1、角色分析

河道船只识别系统 yolov5

河道船只识别系统通过Python基于YOLOv5深度学习框架模型技术对画面中船只进行监测&#xff0c;如识别到有船只违规行为&#xff0c;立即抓拍告警同步回传给平台。YOLOv5是一种单阶段目标检测算法&#xff0c;该算法在YOLOv4的基础上添加了一些新的改进思路&#xff0c;使其速度…

写了个自动巡检多个接口地址的脚本!

作者&#xff1a;JackTian 来源&#xff1a;公众号「杰哥的IT之旅」 ID&#xff1a;Jake_Internet 转载请联系授权&#xff08;微信ID&#xff1a;Hc220088&#xff09; 原文链接&#xff1a;写了个自动巡检多个接口地址的脚本&#xff01; 没错&#xff0c;这次我结合工作运用…

【C语言】你对动态内存分配有多少了解呢

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;《初识C语言》 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、什么是动态内存分配二、为什…