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

文件系统概念

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

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

参考资料

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值