《操作系统真象还原》 第十四章 文件系统

配合视频学习体验更佳!
a小节一部分:https://www.bilibili.com/video/BV1Uh4y1U7G6/?vd_source=701807c4f8684b13e922d0a8b116af31
a小节二部分:https://www.bilibili.com/video/BV1Lj41127Bc/?vd_source=701807c4f8684b13e922d0a8b116af31
b小节:https://www.bilibili.com/video/BV1Pp4y1J7pN/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节一部分:https://www.bilibili.com/video/BV1gp4y1N7GG/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节二部分:https://www.bilibili.com/video/BV11N411v7s5/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节三部分:https://www.bilibili.com/video/BV1uu411P7yV/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节四部分:https://www.bilibili.com/video/BV1YH4y1Q769/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节五部分:https://www.bilibili.com/video/BV1UH4y1Q7kT/?vd_source=701807c4f8684b13e922d0a8b116af31
c小节六部分:https://www.bilibili.com/video/BV1594y1x7NH/?vd_source=701807c4f8684b13e922d0a8b116af31
d小节:https://www.bilibili.com/video/BV1Fz4y1j71L/?vd_source=701807c4f8684b13e922d0a8b116af31
e小节:https://www.bilibili.com/video/BV1Rh4y1P7HG/?vd_source=701807c4f8684b13e922d0a8b116af31
f小节:https://www.bilibili.com/video/BV1MV411P7kL/?vd_source=701807c4f8684b13e922d0a8b116af31
g小节:https://www.bilibili.com/video/BV148411i7se/?vd_source=701807c4f8684b13e922d0a8b116af31
h小节:https://www.bilibili.com/video/BV18u4y1k7oR/?vd_source=701807c4f8684b13e922d0a8b116af31
i小节:https://www.bilibili.com/video/BV1ph4y1Y7j9/?vd_source=701807c4f8684b13e922d0a8b116af31
j小节:https://www.bilibili.com/video/BV1Ah4y1A7Hf/?vd_source=701807c4f8684b13e922d0a8b116af31
k小节:https://www.bilibili.com/video/BV1Gk4y1w7Yb/?vd_source=701807c4f8684b13e922d0a8b116af31
l小节:https://www.bilibili.com/video/BV1a14y1r7y6/?vd_source=701807c4f8684b13e922d0a8b116af31
m小节:https://www.bilibili.com/video/BV1aF411D7kZ/?vd_source=701807c4f8684b13e922d0a8b116af31
n小节:https://www.bilibili.com/video/BV1Kw411i79c/?vd_source=701807c4f8684b13e922d0a8b116af31

代码仓库:https://github.com/xukanshan/the_truth_of_operationg_system

文件系统中,最核心的就是三个概念:inode、目录(目录也是文件)和超级块。文件系统是以分区为单位,也就是每个分区都有自己的文件系统。

  1. inode
    • inode记录了文件与磁盘位置的映射。
    • 每个文件对应一个inode。当我们找到了这个文件对应的inode,就能知道这个文件在磁盘中的存储位置。
    • 每个磁盘分区的所有inode都会形成一个数组。使用文件的inode数组下标,我们可以在该数组中查找对应的inode信息。
  2. 目录
    • 一个目录其实也是一个特殊的文件。
    • 目录由众多目录项构成,目录项记录了文件名到inode的映射。如果一个目录管理/Desktop文件夹,那么这个目录下的众多目录项就是管理/Desktop文件夹下的各个文件和子目录。
  3. 超级块
    • 超级块包含了关于inode、目录及其他文件系统元数据的信息。
    • 通常,它位于每个磁盘分区的第二个扇区,这是一个固定位置。
    • 对于inode,它记录了inode数组在磁盘中的起始位置;对于根目录,超级块还会记录其inode标号。

查找文件的流程示例:

如果我们要查找一个/home/test.c,那么文件系统是如何工作的呢?

  • 首先,由于超级块位置固定,我们可以去磁盘中直接访问它。
  • 从超级块中,我们能知道根目录的inode标号和inode数组的位置。
  • 使用根目录的inode标号与inode数组位置,我们可以找到根目录文件对应的inode,然后在磁盘中找到根目录文件。
  • 在根目录文件中,我们查找一个名叫home的目录项,从中取出home的inode数组标号。
  • 使用home的inode标号,我们再次访问inode数组,找到home目录文件在磁盘上的实际位置。
  • 最后,在home目录文件中,我们查找一个名叫test.c的目录项,从中取出test.c的inode数组标号,进而找到test.c在磁盘上的位置。

一个典型的文件系统元数据结构在磁盘中的位置如图所示
在这里插入图片描述
小节a:

我们就是写一个函数partition_format用于创建文件系统,也就是创建文件系统元数据(超级块,空闲块位图,inode位图,inode数组,根目录);

然后写一个函数filesys_init遍历所有分区,如果该分区没有文件系统就调用partition_format来创建文件系统

首先我们来准备数据结构

超级块 (myos/fs/super_block.h

#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_H
#include "stdint.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));
#endif

inode (myos/fs/inode.h

#ifndef __FS_INODE_H
#define __FS_INODE_H
#include "stdint.h"
#include "list.h"

/* inode结构 */
struct inode {
    uint32_t i_no;    // inode编号

    /* 当此inode是文件时,i_size是指文件大小,
    若此inode是目录,i_size是指该目录下所有目录项大小之和*/
    uint32_t i_size;

    uint32_t i_open_cnts;   // 记录此文件被打开的次数
    bool write_deny;	   // 写文件不能并行,进程写文件前检查此标识

    /* i_sectors[0-11]是直接块, i_sectors[12]用来存储一级间接块指针 */
    uint32_t i_sectors[13];
    struct list_elem inode_tag;
};
#endif

目录与目录项 (myos/fs/dir.h) 需要注意的是:目录的数据结构struct dir只会存在于内存之中,因为它管理的是对一个目录文件的操作(比如打开一个目录文件,就会在内存中创建这样一个结构体)

#ifndef __FS_DIR_H
#define __FS_DIR_H
#include "stdint.h"
#include "inode.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;	      // 文件类型
};

#endif

文件类型定义: (myos/fs/fs.h

#ifndef __FS_FS_H
#define __FS_FS_H
#include "stdint.h"

#define MAX_FILES_PER_PART 4096	    // 每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096	    // 每扇区的位数
#define SECTOR_SIZE 512		    // 扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE	    // 块字节大小

/* 文件类型 */
enum file_types {
    FT_UNKNOWN,	  // 不支持的文件类型
    FT_REGULAR,	  // 普通文件
    FT_DIRECTORY	  // 目录
};

#endif

然后编写函数partition_format用于创建文件系统,也就是创建文件系统元数据(超级块,空闲块位图,inode位图,inode数组,根目录)

myos/fs/fs.c

#include "stdint.h"
#include "fs.h"
#include "inode.h"
#include "ide.h"
#include "memory.h"
#include "super_block.h"
#include "dir.h"
#include "stdio-kernel.h"
#include "string.h"



/* 格式化分区,也就是初始化分区的元信息,创建文件系统 */
static void partition_format(struct partition* part) {
    /* 为方便实现,一个块大小是一扇区 */
    uint32_t boot_sector_sects = 1;	  
    uint32_t super_block_sects = 1;
    uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR);	   // I结点位图占用的扇区数.最多支持4096个文件
    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;  

    /************** 简单处理块位图占据的扇区数 ***************/
    uint32_t block_bitmap_sects;
    block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
    /* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
    uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects; 
    block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR); 
    /*********************************************************/
    
    /* 超级块初始化 */
    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;  //数据区的起始就是inode数组的结束
    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);

    struct disk* hd = part->my_disk;
    /*******************************
     * 1 将超级块写入本分区的1扇区 *
     ******************************/
    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);	// 申请的内存由内存管理系统清0后返回
    
    /**************************************
     * 2 将块位图初始化并写入sb.block_bitmap_lba *
     *************************************/
    /* 初始化块位图block_bitmap */
    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; //计算出块位图最后一位的偏移
    uint32_t last_size = SECTOR_SIZE - (block_bitmap_last_byte % SECTOR_SIZE);	     // last_size是位图所在最后一个扇区中,不足一扇区的其余部分

    /* 1 先将位图最后一字节到其所在的扇区的结束全置为1,即超出实际块数的部分直接置为已占用*/
    memset(&buf[block_bitmap_last_byte], 0xff, last_size);
    
    /* 2 再将上一步中覆盖的最后一字节内的有效位重新置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);

    /***************************************
     * 3 将inode位图初始化并写入sb.inode_bitmap_lba *
     ***************************************/
    /* 先清空缓冲区*/
    memset(buf, 0, buf_size);
    buf[0] |= 0x1;      // 第0个inode分给了根目录
    /* 由于inode_table中共4096个inode,位图inode_bitmap正好占用1扇区,
        * 即inode_bitmap_sects等于1, 所以位图中的位全都代表inode_table中的inode,
        * 无须再像block_bitmap那样单独处理最后一扇区的剩余部分,
        * inode_bitmap所在的扇区中没有多余的无效位 */
    ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);

    /***************************************
     * 4 将inode数组初始化并写入sb.inode_table_lba *
     ***************************************/
    /* 准备写inode_table中的第0项,即根目录所在的inode */
    memset(buf, 0, buf_size);  // 先清空缓冲区buf
    struct inode* i = (struct inode*)buf; 
    i->i_size = sb.dir_entry_size * 2;	 // .和..
    i->i_no = 0;   // 根目录占inode数组中第0个inode
    i->i_sectors[0] = sb.data_start_lba;	     // 由于上面的memset,i_sectors数组的其它元素都初始化为0 
    ide_write(hd, sb.inode_table_lba, buf, sb.inode_table_sects);
    
    /***************************************
     * 5 将根目录初始化并写入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;

    /* sb.data_start_lba已经分配给了根目录,里面是根目录的目录项 */
    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);
}

作者的这两句代码只迭代了两次,并不算正确,应该采用无限迭代,直到值稳定下来的方法。这个代码实际解决的问题就是:在总量不变的情况下,有多少是可用资源,有多少是管理资源。

例子1:
考虑这样的一个例子:一个公司有12个人,假设一个人可以管理5个人。那么迭代过程如下:

  • 第一次迭代:3个人管理,9个人干活。
  • 第二次迭代:2个人管理,10个人干活。

很明显,我们得到了正确的答案。

例子2:
考虑另一个例子:共100个人,1个人可以管理3个人。那么迭代过程如下:

  • 第一次迭代:34个人管理,66个人干活。
  • 第二次迭代:22个人管理,78个人干活。
  • 第三次迭代:26个人管理,74个人干活。
  • 第四次迭代:25个人管理,75个人干活。
  • 第五次迭代:25个人管理,75个人干活。

最终,我们达到一个稳定的数值。

我们可以得出结论,当一个人管理人数比较少时,需要迭代非常多的次数。但当一个人管理人数比较多时,只需迭代很少的次数。由于我们的一个扇区可以管理4096个扇区,所以,作者的代码在大多数情况下都能够正常工作。

修改(myos/fs/fs.c/partition_format

    /************** 简单处理块位图占据的扇区数 ***************/
    uint32_t block_bitmap_sects;
    block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);
    /* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
    uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects; 
    block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR); 
    /*********************************************************/

    /************** 简单处理块位图占据的扇区数 ***************/
    uint32_t now_total_free_sects = free_sects; // 定义一个现在总的可用扇区数
    uint32_t prev_block_bitmap_sects = 0; // 之前的块位图扇区数
    uint32_t block_bitmap_sects = DIV_ROUND_UP(now_total_free_sects, BITS_PER_SECTOR); // 初始估算
    uint32_t block_bitmap_bit_len;

    while (block_bitmap_sects != prev_block_bitmap_sects) {
        prev_block_bitmap_sects = block_bitmap_sects;
        /* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */
        block_bitmap_bit_len = now_total_free_sects - block_bitmap_sects;
        block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
    }
    /*********************************************************/

由于block_bitmap_last_bit实际含义是块位图最后一字节有效位的数量,且bit_idx从0开始取,所以以下while循环会多循环1次,应该修改:(myos/fs/fs.c/partition_format

    while (bit_idx <= block_bitmap_last_bit) {
        buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
    }

    while (bit_idx < block_bitmap_last_bit) {
        buf[block_bitmap_last_byte] &= ~(1 << bit_idx++);
    }

然后写一个函数filesys_init遍历所有分区,如果该分区没有文件系统就调用partition_format来创建文件系统

修改(myos/fs/fs.c

#include "debug.h"

/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
    uint8_t channel_no = 0, dev_no, 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) { //遍历通道下1主1从两个硬盘
            if (dev_no == 0) {   // 跨过裸盘hd60M.img
                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;
                }
            
            /* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
            * partition又为disk的嵌套结构,因此partition中的成员默认也为0.
            * 若partition未初始化,则partition中的成员仍为0. 
            * 下面处理存在的分区. */
                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 has 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);
}

函数声明 修改(myos/fs/fs.h

void filesys_init(void);

修改 (myos/kernel/init.c)以初始化文件系统

#include "fs.h"

/*负责初始化所有模块 */
void init_all() {
	put_str("init_all\n");
	idt_init();	     // 初始化中断
	mem_init();	     // 初始化内存管理系统
	thread_init();    // 初始化线程相关结构
	timer_init();     // 初始化PIT
	console_init();   // 控制台初始化最好放在开中断之前
	keyboard_init();  // 键盘初始化
	tss_init();       // tss初始化
	syscall_init();   // 初始化系统调用
	intr_enable();    // 后面的ide_init需要打开中断
	ide_init();	     // 初始化硬盘
	filesys_init();   // 初始化文件系统
}

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"

void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);

int main(void) {
   put_str("I am kernel\n");
   init_all();
   while(1);
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");
   thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
   thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
   while(1);
   return 0;
}

/* 在线程中运行的函数 */
void k_thread_a(void* arg) {     
   void* addr1 = sys_malloc(256);
   void* addr2 = sys_malloc(255);
   void* addr3 = sys_malloc(254);
   console_put_str(" thread_a malloc addr:0x");
   console_put_int((int)addr1);
   console_put_char(',');
   console_put_int((int)addr2);
   console_put_char(',');
   console_put_int((int)addr3);
   console_put_char('\n');

   int cpu_delay = 100000;
   while(cpu_delay-- > 0);
   sys_free(addr1);
   sys_free(addr2);
   sys_free(addr3);
   while(1);
}

/* 在线程中运行的函数 */
void k_thread_b(void* arg) {     
   void* addr1 = sys_malloc(256);
   void* addr2 = sys_malloc(255);
   void* addr3 = sys_malloc(254);
   console_put_str(" thread_b malloc addr:0x");
   console_put_int((int)addr1);
   console_put_char(',');
   console_put_int((int)addr2);
   console_put_char(',');
   console_put_int((int)addr3);
   console_put_char('\n');

   int cpu_delay = 100000;
   while(cpu_delay-- > 0);
   sys_free(addr1);
   sys_free(addr2);
   sys_free(addr3);
   while(1);
}

/* 测试用户进程 */
void u_prog_a(void) {
   void* addr1 = malloc(256);
   void* addr2 = malloc(255);
   void* addr3 = malloc(254);
   printf(" prog_a malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);

   int cpu_delay = 100000;
   while(cpu_delay-- > 0);
   free(addr1);
   free(addr2);
   free(addr3);
   while(1);
}

/* 测试用户进程 */
void u_prog_b(void) {
   void* addr1 = malloc(256);
   void* addr2 = malloc(255);
   void* addr3 = malloc(254);
   printf(" prog_b malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);

   int cpu_delay = 100000;
   while(cpu_delay-- > 0);
   free(addr1);
   free(addr2);
   free(addr3);
   while(1);
}

Makefile 除了增加编译fs.c的规则外,还要新增-I fs/

LIB= -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/ -I thread/ -I userprog/	-I fs/

建议在运行前,先备份hd80M.img,不然出错了,很可能这里面会写入无意义数据。命令:cp hd80M.img hd80M.img-beifen

小节b:

之前我们写好了函数用于创建文件系统元数据,并且将其写入了磁盘。当我们实际要用到某个分区的数据,就需要挂载该分区,也就是读取磁盘中文件系统元数据(超级块,块位图,inode位图,inode数组由于过大,不读入到内存中,)到内存中,然后对这些元数据的改动,要及时同步到磁盘中。现在我们就来写这个挂载分区的函数mount_partition。这个函数会被list_traversal调用。

修改(myos/fs/fs.c

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

/* 在分区链表中找到名为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;

        /* sb_buf用来存储从硬盘上读入的超级块 */
        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 memory 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继续遍历
}

修改(myos/fs/fs.cfilesys_init增加使用list_traversal调用mount_partition挂载指定分区文件系统的代码

/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
void filesys_init() {
    uint8_t channel_no = 0, dev_no, 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) { //遍历通道下1主1从两个硬盘
            if (dev_no == 0) {   // 跨过裸盘hd60M.img
                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;
                }
            
            /* channels数组是全局变量,默认值为0,disk属于其嵌套结构,
            * partition又为disk的嵌套结构,因此partition中的成员默认也为0.
            * 若partition未初始化,则partition中的成员仍为0. 
            * 下面处理存在的分区. */
                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 has 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);
    /* 确定默认操作的分区 */
   char default_part[8] = "sdb1";
   /* 挂载分区 */
   list_traversal(&partition_list, mount_partition, (int)default_part);
}

支持代码

修改(myos/device/ide.h

(这个分区链表是我们挂载磁盘ide_init函数中调用partition_scan就创建了的,所以上一小节创建分区文件系统,其实我们可以去遍历这个链表)

extern struct list partition_list;

mount_partition内的sb_buf没有释放!会造成内存泄漏!

修改(myos/fs/fs.c/mountpartition

        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继续遍历

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

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

小节c:

文件系统的核心功能是管理文件(inode)与其在磁盘上存储位置之间的映射关系。但当我们谈到实际的文件操作(如打开、读取、写入),这些操作总是基于特定的进程或线程来进行。所以我们需要一个机制来建立进程或线程到文件(inode)的映射,这正是文件描述符的作用。而文件描述符则是基于“文件结构”来实现的。

文件结构详细描述了应用进程对于一个文件的当前操作状态:

  • 当应用进程操作(比如打开)一个文件时,操作系统会创建一个对应的全局文件结构,放置于全局文件结构数组(也叫文件表)中,用于跟踪该进程在该文件上的操作状态。
  • 文件结构中包含一个fd_pos字段,它表示当前进程操作的位置。例如,当我们打开一个文件test.txtfd_pos的初始值通常是文件的开头。
  • 当我们对文件执行实际的读写操作,fd_pos会被更新到操作的位置。例如,如果在文件中间开始写入数据,fd_pos会更新为那个位置并随着写入操作继续前移。当进行编辑操作时,输入的内容首先被存放在一个内存输入缓冲区中。按下ctrl + s后,操作系统会使用文件结构中的fd_pos作为起始点,将缓冲区的内容写入磁盘文件中,同时更新fd_pos到写入结束后的位置。
  • 文件结构还包含一个字段fd_inode,它是对应文件的inode的指针。通过这个fd_inode字段,操作系统可以轻松地找到文件在磁盘上的位置,并将内存输入缓冲区的内容写入磁盘。

在这里插入图片描述
由于文件结构与具体的应用程序高度相关,我们需要引入一个新的概念:文件描述符数组

  • 文件描述符数组是存储在进程的进程控制块(PCB)中的一个数组。每个元素都是一个文件描述符(就是一个数字),它指向全局文件结构数组中的一个特定条目。

  • 当进程打开一个文件,操作系统会为这个文件创建一个文件结构并将其放入全局文件结构数组中。接着,操作系统会将这个文件结构在全局数组中的位置(或索引)赋给进程的文件描述符数组中的一个空闲元素。

    例如一个进程打开了两个文件,它们在全局文件结构数组中的位置分别是32和58。那么,进程的PCB中的文件描述符数组的第0个元素就会被赋值为32,而第1个元素则会被赋值为58。

  • PCB中的文件描述符数组的大小限制了一个进程能够同时打开文件的最大数量。如果进程打开一个新的文件,但文件描述符数组已满,则操作将失败。

  • 需要注意的是,打开不同的文件当然会消耗一个文件描述符。但由于我们在文件结构中引入了文件操作位置的概念(即fd_pos),如果一个进程多次打开同一个文件(即每次打开都有自己的读写位置),那么每次打开都会消耗一个新的文件描述符。
    在这里插入图片描述
    文件系统这一章函数,代码非常多,其实核心就是处理超级块,inode,目录,文件结构这四个概念之间的交互

为了支持文件描述符,我们要取修改task_struct结构体的定义。(还新增了parent_idcwd_inode_nr

修改(myos/thread/thread.h

#define MAX_FILES_OPEN_PER_PROC 8

/* 进程或线程的pcb,程序控制块, 此结构体用于存储线程的管理信息*/
struct task_struct
{
    uint32_t *self_kstack; // 用于存储线程的栈顶位置,栈顶放着线程要用到的运行信息
    pid_t pid;
    enum task_status status;
    uint8_t priority; // 线程优先级
    char name[16];    // 用于存储自己的线程的名字

    uint8_t ticks;                                // 线程允许上处理器运行还剩下的滴答值,因为priority不能改变,所以要在其之外另行定义一个值来倒计时
    uint32_t elapsed_ticks;                       // 此任务自上cpu运行后至今占用了多少cpu嘀嗒数, 也就是此任务执行了多久*/
    struct list_elem general_tag;                 // general_tag的作用是用于线程在一般的队列(如就绪队列或者等待队列)中的结点
    struct list_elem all_list_tag;                // all_list_tag的作用是用于线程队列thread_all_list(这个队列用于管理所有线程)中的结点
    uint32_t *pgdir;                              // 进程自己页表的虚拟地址
    struct virtual_addr userprog_vaddr;           // 用户进程的虚拟地址
    int32_t fd_table[MAX_FILES_OPEN_PER_PROC];    // 已打开文件数组
    uint32_t cwd_inode_nr;                        // 进程所在的工作目录的inode编号
    int16_t parent_pid;                           // 父进程pid
    struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
    uint32_t stack_magic;                         // 如果线程的栈无限生长,总会覆盖地pcb的信息,那么需要定义个边界数来检测是否栈已经到了PCB的边界
};

在创建进程/线程的初始化pcb环节,增加对这个文件描述符数组以及parent_idcwd_inode_nr初始化代码

修改(myos/thread/thread.c

/* 初始化线程基本信息 , pcb中存储的是线程的管理信息,此函数用于根据传入的pcb的地址,线程的名字等来初始化线程的管理信息*/
void init_thread(struct task_struct *pthread, char *name, int prio)
{
    memset(pthread, 0, sizeof(*pthread)); // 把pcb初始化为0
    pthread->pid = allocate_pid();
    strcpy(pthread->name, name); // 将传入的线程的名字填入线程的pcb中

    if (pthread == main_thread)
    {
        pthread->status = TASK_RUNNING; // 由于把main函数也封装成一个线程,并且它一直是运行的,故将其直接设为TASK_RUNNING */
    }
    else
    {
        pthread->status = TASK_READY;
    }
    pthread->priority = prio;
    /* self_kstack是线程自己在内核态下使用的栈顶地址 */
    pthread->ticks = prio;
    pthread->elapsed_ticks = 0;
    pthread->pgdir = NULL;                                            // 线程没有自己的地址空间,进程的pcb这一项才有用,指向自己的页表虚拟地址
    pthread->self_kstack = (uint32_t *)((uint32_t)pthread + PG_SIZE); // 本操作系统比较简单,线程不会太大,就将线程栈顶定义为pcb地址
                                                                      //+4096的地方,这样就留了一页给线程的信息(包含管理信息与运行信息)空间
    /* 标准输入输出先空出来 */
    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++;
    }
    pthread->cwd_inode_nr = 0;         // 以根目录做为默认工作路径
    pthread->parent_pid = -1;          // -1表示没有父进程
    pthread->stack_magic = 0x19870916; // 定义的边界数字,随便选的数字来判断线程的栈是否已经生长到覆盖pcb信息了
}

任何有关文件及目录的操作都了不开对inode的操作,因为我们需要通过inode知道文件的存储位置,所以操作文件,总是意味着要找到该文件的inode。接下来我们实现一堆对于inode的处理函数。涉及:找到一个inode在磁盘中的位置,初始化一个inode,加载该inode到内存中,修改内存中的inode之后同步到磁盘中,从内存中删除一个inode。

inode_locate用于通过传入指定inode在inode数组中的索引,来获取inode所在扇区和扇区内的偏移。原理:由于传入了inode在inode数组中的索引,且超级块(挂载文件系统后,超级块在内存中)中已经记录了inode数组起始扇区。所以我们能通过索引 * 每个inode大小,计算出这个inode所在扇区和扇区内的偏移。

myos/fs/inode.c

#include "stdint.h"
#include "ide.h"
#include "inode.h"
#include "debug.h"
#include "super_block.h"

/* 用来存储inode位置 */
struct inode_position {
   bool	 two_sec;	// inode是否跨扇区
   uint32_t sec_lba;	// inode所在的扇区号
   uint32_t off_size;	// 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号I结点相对于inode_table_lba的字节偏移量
    uint32_t off_sec = off_size / 512;         // 第inode_no号I结点相对于inode_table_lba的扇区偏移量
    uint32_t off_size_in_sec = off_size % 512; // 待查找的inode所在扇区中的起始地址

    /* 判断此i结点是否跨越2个扇区 */
    uint32_t left_in_sec = 512 - off_size_in_sec;
    if (left_in_sec < inode_size)
    { // 若扇区内剩下的空间不足以容纳一个inode,必然是I结点跨越了2个扇区
        inode_pos->two_sec = true;
    }
    else
    { // 否则,所查找的inode未跨扇区
        inode_pos->two_sec = false;
    }
    inode_pos->sec_lba = inode_table_lba + off_sec;
    inode_pos->off_size = off_size_in_sec;
}

inode_sync用于将一个inode写入磁盘中inode数组对应的位置处。原理:调用inode_locate解析出这个inode在磁盘中的位置,然后将这个inode所在的块整个读出到内存缓冲中,再将这个inode写入缓冲中对应的位置处,再将整个块写回磁盘。

修改(myos/fs/inode.c

#include "string.h"

/* 将inode写入到分区part */
void inode_sync(struct partition *part, struct inode *inode, void *io_buf)
{ // io_buf是用于硬盘io的缓冲区
    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是不需要的,
     * 它们只在内存中记录链表位置和被多少进程共享 */
    struct inode pure_inode;
    memcpy(&pure_inode, inode, sizeof(struct inode));

    /* 以下inode的三个成员只存在于内存中,现在将inode同步到硬盘,清掉这三项即可 */
    pure_inode.i_open_cnts = 0;
    pure_inode.write_deny = false; // 置为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); // inode在format中写入硬盘时是连续写入的,所以读入2块扇区

        /* 开始将待写入的inode拼入到这2个扇区中的相应位置 */
        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_open用于打开一个inode,也就是根据传入的inode数组索引找到该inode并加载到内存中。由于我们在管理分区的结构体struct partition中维护了一个本分区打开文件的inode链表,所以我们优先去这个链表中查找(这个链表在内存中),找不到再去磁盘中找,也就是调用inode_locate解析他在磁盘中的位置,然后读到内存中。由于打开的Inode链表需要对所有进程共享,所有我们存放inode的内存需要去内核堆区申请(因为所有的进程均可访问内核)

修改(myos/fs/inode.c

/* 根据i结点号返回相应的i结点 */
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_cnts++;
            return inode_found;
        }
        elem = elem->next;
    }

    /*由于open_inodes链表中找不到,下面从硬盘上读入此inode并加入到此链表 */
    struct inode_position inode_pos;

    /* inode位置信息会存入inode_pos, 包括inode所在扇区地址和扇区内的字节偏移量 */
    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;
    /* 以上三行代码完成后下面分配的内存将位于内核区 */
    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);

        /* i结点表是被partition_format函数连续写入扇区的,
         * 所以下面可以连续读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
    }
    else
    { // 否则,所查找的inode未跨扇区,一个扇区大小的缓冲区足够
        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));

    /* 因为一会很可能要用到此inode,故将其插入到队首便于提前检索到 */
    list_push(&part->open_inodes, &inode_found->inode_tag);
    inode_found->i_open_cnts = 1;

    sys_free(inode_buf);
    return inode_found;
}

inode_close用于关闭一个inode,也就是从内存中移除一个inode。由于一个inode可以被多次打开,我们需要判断此时是否还有进程/线程打开这个inode,再决定是否真正移除。移除的inode所占用的内存空间是内核的堆空间。

修改(myos/fs/inode.c

#include "interrupt.h"

/* 关闭inode或减少inode的打开数 */
void inode_close(struct inode *inode)
{
    /* 若没有进程再打开此文件,将此inode去掉并释放空间 */
    enum intr_status old_status = intr_disable();
    if (--inode->i_open_cnts == 0)
    {
        list_remove(&inode->inode_tag); // 将I结点从part->open_inodes中去掉
                                        /* inode_open时为实现inode被所有进程共享,
                                         * 已经在sys_malloc为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_init通过传入inode编号初始化一个inode

修改(myos/fs/inode.c

/* 初始化new_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_cnts = 0;
    new_inode->write_deny = false;

    /* 初始化块索引数组i_sector */
    uint8_t sec_idx = 0;
    while (sec_idx < 13)
    {
        /* i_sectors[12]为一级间接块地址 */
        new_inode->i_sectors[sec_idx] = 0;
        sec_idx++;
    }
}

函数声明:修改(myos/fs/fs.h

void inode_sync(struct partition* part, struct inode* inode, void* io_buf);
struct inode *inode_open(struct partition *part, uint32_t inode_no);
void inode_close(struct inode *inode);
void inode_init(uint32_t inode_no, struct inode *new_inode);

接下来实现一堆与目录相关的函数,涉及目录打开、关闭,在一个目录文件中寻找指定目录项,初始化一个目录项,将目录项写入父目录中

open_root_dir用于根据传入的分区,调用inode_open将根目录文件的inode调入内存,并将全局变量struct root_dir中的inode指针,指向根目录文件的inode

(myos/fs/dir.c)

#include "dir.h"
#include "inode.h"
#include "super_block.h"


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;
}

dir_open用于根据传入的分区与inode偏移,申请一块内存区域存放目录,调用inode_open找到这个目录对应的inode并载入内存,然后让目录中的inode指针指向这个inode。功能和open_root_dir类似,只不过一个是直接修改全局变量,一个要自行申请内存。

修改(myos/fs/dir.c)

/* 在分区part上打开i结点为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;
}

所以我们可以发现,打开目录,就是建立目录(struct dir结构体)与inode之间的关系。为什么?因为目录是一个内存中的概念,但它本身是磁盘中的一个文件!为了建立从内存到磁盘的关系,所以我们需要建立struct dir与inode之间的关系。所以我们打开目录,自然意味着这个struct dir结构体要在内存中。再进一步思考,我们打开目录,肯定意味着要查找这个目录下什么东西(就像是你打开文件夹,是为了找到文件夹下某个文件),而只有inode记录着这个目录文件在磁盘中的位置,而目录文件就是一大堆目录项的集合,这些目录项记录着这个目录有啥。所以为了找到目录下有啥,我们肯定要找到它的inode,调入内存,并建立二者之间的关系。

search_dir_entry用于在某个目录内找到指定名称的目录项。核心原理就是目录结构体struct dir 有一个指向自己inode的成员,该成员记录了该目录在磁盘中存储的位置(inode的i_sectors[ ])我们可以根据这个找到目录文件,然后遍历其中的目录项即可

修改(myos/fs/dir.c)

#include "stdio-kernel.h"
#include "string.h"

/* 在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个一级间接块=140块

    /* 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);
    }
    /* 至此,all_blocks存储的是该文件或目录的所有扇区地址 */

    /* 写目录项的时候已保证目录项不跨扇区,
     * 这样读目录项时容易处理, 只申请容纳1个扇区的内存 */
    uint8_t *buf = (uint8_t *)sys_malloc(SECTOR_SIZE);
    struct dir_entry *p_de = (struct dir_entry *)buf; // p_de为指向目录项的指针,值为buf起始地址
    uint32_t dir_entry_size = part->sb->dir_entry_size;
    uint32_t dir_entry_cnt = SECTOR_SIZE / dir_entry_size; // 1扇区内可容纳的目录项个数

    /* 开始在所有块中查找目录项 */
    while (block_idx < block_cnt)
    {
        /* 块地址为0时表示该块中无数据,继续在其它块中找 */
        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; // 此时p_de已经指向扇区内最后一个完整目录项了,需要恢复p_de指向为buf
        memset(buf, 0, SECTOR_SIZE);    // 将buf清0,下次再用
    }
    sys_free(buf);
    sys_free(all_blocks);
    return false;
}

dir_close用于关闭目录,实质就是调用inode_close从内存中释放该目录的inode占用的内存,并且释放目录占用的内存,也就是解绑struct dir与inode之间的关系。需要注意的是:根目录不能被释放,因为一定会反复被用到

修改(myos/fs/dir.c)

/* 关闭目录 */
void dir_close(struct dir *dir)
{
    /*************      根目录不能关闭     ***************
     *1 根目录自打开后就不应该关闭,否则还需要再次open_root_dir();
     *2 root_dir所在的内存是低端1M之内,并非在堆中,free会出问题 */
    if (dir == &root_dir)
    {
        /* 不做任何处理直接返回*/
        return;
    }
    inode_close(dir->inode);
    sys_free(dir);
}

create_dir_entry用于初始化一个目录项,也就是给目录项指向的文件一个名字与inode索引

修改(myos/fs/dir.c)

#include "debug.h"

/* 在内存中初始化目录项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;
}

sync_dir_entry用于将目录项写入父目录中。核心原理就是:父目录结构体struct dir有一个指向自己inode的成员,该成员记录了该父目录文件在磁盘中存储的位置(inode的i_sectors[ ])我们可以根据这个找到目录文件,然后遍历目录文件找到空位,然后将目录项插入到这个空位即可。有些麻烦的是,需要判断inode的i_sectors[ ]这些元素是否为空,如果为空,那么我们还需要申请块用于承载目录文件。

修改(myos/fs/dir.c)

#include "file.h"

/* 将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供 */
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_entrys_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}; // all_blocks保存目录所有的块

    /* 将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; // dir_e用来在io_buf中遍历目录项
    int32_t block_bitmap_idx = -1;

    /* 开始遍历所有块以寻找目录项空位,若已有扇区中没有空闲位,
     * 在不超过文件大小的情况下申请新扇区来存储新目录项 */
    block_idx = 0;
    while (block_idx < 140)
    { // 文件(包括目录)最大支持12个直接块+128个间接块=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 */
            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)
            {                                         // 若是尚未分配一级间接块表(block_idx等于12表示第0个间接块地址为0)
                dir_inode->i_sectors[12] = 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 */
                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);

                all_blocks[12] = block_lba;
                /* 把新分配的第0个间接块地址写入一级间接块表 */
                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_entrys_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;
}

支持代码:

修改(myos/fs/fs.h

extern struct partition* cur_part;

由于作者在这两句代码中,

(myos/fs/fs.c/sync_dir_entry)

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

仅仅从目录文件中读出了前12个直接块,导致all_blocks数组仅前12项是有非0值的。假设整个目录文件,前12个直接块指向的块已经存满了目录项,那么新的目录项只能存储至一级间接块(i_sectors[12])指向的块其中某个地址指向的目录文件中。但是代码逻辑,会必然判断出all_blocks[12] == 0,然后进入block_idx == 12,就会重新申请一个块充当一级间接块,就把原有的一级间接块覆盖了。这会导致对之前存储在间接块中的所有目录项的引用丢失,这是一个严重的数据完整性问题。

修改如下:

   /* 将12个直接块存入all_blocks */
    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);
   	}

函数声明:

修改(myos/fs/dir.h)

void open_root_dir(struct partition *part);
struct dir *dir_open(struct partition *part, uint32_t inode_no);
bool search_dir_entry(struct partition *part, struct dir *pdir, const char *name, struct dir_entry *dir_e);
void dir_close(struct dir *dir);
void create_dir_entry(char *filename, uint32_t inode_no, uint8_t file_type, struct dir_entry *p_de);
bool sync_dir_entry(struct dir *parent_dir, struct dir_entry *p_de, void *io_buf);

接下来我们要实现路径解析功能。比如我们要查找某个文件,我们提供路径/home/kanshan/Desktop/test.c,然后系统为我们找到它。解析路径的核心原理我们在本章一开始就提到了,就是那个查找/home/test.c文件系统是如何工作的例子。

path_parse每次调用就会解析最上层路径名,并且在缓冲区name_store中存储解析的路径名,由于根目录是所有路径的最上层,所以不用参与解析(如/home/test.c目录层次是/hometest.c,但是/是所有目录最上层,我们可以忽略,所以/home/test.c目录层次就可以认为是hometest.c)。比如此函数解析/home/kanshan/Desktop/test.c,第一次调用返回/kanshan/Desktop/test.cname_store中存储home。再次调用返回/Desktop/test.cname_store中存储kanshan

修改(myos/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)
    { // 若路径字符串为空则返回NULL
        return NULL;
    }
    return pathname;
}

path_depth_cnt用于返回路径深度,比如/home/kanshan/Desktop/test.c路径深度就是4。原理就是循环调用path_parse,看看返回值是不是空,决定是否继续调用path_parse

修改(myos/fs/fs.c)

/* 返回路径深度,比如/a/b/c,深度为3 */
int32_t path_depth_cnt(char *pathname)
{
    ASSERT(pathname != NULL);
    char *p = pathname;
    char name[MAX_FILE_NAME_LEN]; // 用于path_parse的参数做路径解析
    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;
}

函数声明

修改(myos/fs/fs.h)

int32_t path_depth_cnt(char *pathname);

接下来实现文件检索功能

search_file用于提供文件路径,然后返回inode索引。核心原理就是不断循环:1,调用path_parse进行地址解析得到除根目录外最上层目录的名字;2,然后调用search_dir_entry从搜索目录中(第一次调用这个函数,其搜索目录自然是根目录)查找 1 得到的名字;3,然后更新下一次的搜索目录(就是 2 查找出来的目录项);继续解析,然后继续查找,继续更新搜索目录…

修改(myos/fs/fs.h)增加用于记录搜索路径的结构体

#define MAX_PATH_LEN 512 // 路径最大长度

/* 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方" */
struct path_search_record
{
    char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
    struct dir *parent_dir;           // 文件或目录所在的直接父目录
    enum file_types file_type;        // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};

修改(myos/fs/fs.c)

/* 搜索文件pathname,若找到则返回其inode号,否则返回-1 */
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;

    /* 记录路径解析出来的各级名称,如路径"/a/b/c",
     * 数组name每次的值分别是"a","b","c" */
    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);
            /* 若sub_path不等于NULL,也就是未结束时继续拆分路径 */
            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
        { // 若找不到,则返回-1
            /* 找不到目录项时,要留着parent_dir不要关闭,
             * 若是创建新文件的话需要在parent_dir中创建 */
            return -1;
        }
    }

    /* 执行到此,必然是遍历了完整路径并且查找的文件或目录只有同名目录存在 */
    dir_close(searched_record->parent_dir);

    /* 保存被查找目录的直接父目录 */
    searched_record->parent_dir = dir_open(cur_part, parent_inode_no);
    searched_record->file_type = FT_DIRECTORY;
    return dir_e.i_no;
}

支持代码:

修改(myos/fs/dir.h)

extern struct dir root_dir;

接下来实现文件创建功能

file_create用于创建文件,若成功就会返回文件描述符。核心步骤:1,创建新文件的inode,其中涉及inode位图修改,inode初始化;2,文件结构创建,因为文件结构是描述进程或线程对于文件的操作状态,创建自然也属于这种操作状态。其中涉及全局打开文件结构数组更改;3、创建对应目录项;4、同步新文件的目录项到父目录中去;同步父目录inode,父节点inode其中会涉及inode->i_size,inode->i_sectors[ ](因为文件的目录项会存在父目录文件中,可能会涉及新的块申请)。同步inode也会同步磁盘中的inode数组;创建文件的inode;inode位图;5、将新文件的inode加入打开链表;6、在创建文件进程或线程的pcb中安装文件描述符;

我们可以发现文件创建,核心就是要处理inode,目录,文件结构之间的关系。

修改(myos/fs/file.c

#include "string.h"

/* 创建文件,若成功则返回文件描述符,否则返回-1 */
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); // 初始化i结点

    /* 返回的是file_table数组的下标 */
    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只是内存操作不出意外,不会返回失败

    /* 同步内存数据到硬盘 */
    /* a 在目录parent_dir下安装目录项new_dir_entry, 写入硬盘后返回true,否则false */
    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);
    /* b 将父目录i结点的内容同步到硬盘 */
    inode_sync(cur_part, parent_dir->inode, io_buf);

    memset(io_buf, 0, 1024);
    /* c 将新创建文件的i结点内容同步到硬盘 */
    inode_sync(cur_part, new_file_inode, io_buf);

    /* d 将inode_bitmap位图同步到硬盘 */
    bitmap_sync(cur_part, inode_no, INODE_BITMAP);

    /* e 将创建的文件i结点添加到open_inodes链表 */
    list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
    new_file_inode->i_open_cnts = 1;

    sys_free(io_buf);
    return pcb_fd_install(fd_idx);

/*创建文件需要创建相关的多个资源,若某步失败则会执行到下面的回滚步骤 */
rollback:
    switch (rollback_step)
    {
    case 3:
        /* 失败时,将file_table中的相应位清空 */
        memset(&file_table[fd_idx], 0, sizeof(struct file));
    case 2:
        sys_free(new_file_inode);
    case 1:
        /* 如果新文件的i结点创建失败,之前位图中分配的inode_no也要恢复 */
        bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
        break;
    }
    sys_free(io_buf);
    return -1;
}

代码块

   /* 为新文件分配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 */
   int32_t inode_no = inode_bitmap_alloc(cur_part); 
   if (inode_no == -1) {
      printk("in file_creat: allocate inode failed\n");
      goto rollback;
   }

函数声明:

修改(myos/fs/file.h

#include "dir.h"

int32_t file_create(struct dir *parent_dir, char *filename, uint8_t flag);

sys_open用于根据传入的路径打开或创建文件,并返回文件描述符,由于现在是写个初版,我们只实现创建功能。核心原理就是:先调用search_file来查找指定路径是否有这个文件,如果没有,直接调用file_create来创建文件。从查找是否存在,到最后创建之间,要排除以下情况:1、找到了,但是找到的是个目录;2、查找过程中某个中间路径就不存在,比如我要创建/home/kanshan/test.c,但是调用search_file之后,发现,这个/kanshan目录都不存在;3、在最后一个路径上没找到,但是没有传入表示创建文件的标志;4、要创建的文件已经存在;

修改(myos/fs/fs.h

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

修改(myos/fs/fs.c)

#include "file.h"

/* 打开或创建文件成功后,返回文件描述符,否则返回-1 */
int32_t sys_open(const char *pathname, uint8_t flags)
{
    /* 对目录要用dir_open,这里只有open文件 */
    if (pathname[strlen(pathname) - 1] == '/')
    {
        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_searched_depth = path_depth_cnt(searched_record.searched_path);

    /* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
    if (pathname_depth != path_searched_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;
    }

    /* 若是在最后一个路径上没找到,并且并不是要创建文件,直接返回-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);

        /* 此fd是指任务pcb->fd_table数组中的元素下标,
         * 并不是指全局file_table中的下标 */
    }
    return fd;
}

函数声明:

修改(myos/fs/fs.h)

int32_t sys_open(const char *pathname, uint8_t flags);

filesys_init,在list_traversal轮询分区时调用mount_partition以挂载分区。然后调用open_root_dir加入打开的当前分区的根目录,初始化全局打开文件结构数组

修改(myos/fs/fs.c/filesys_init

    sys_free(sb_buf);
    /* 确定默认操作的分区 */
    char default_part[8] = "sdb1";
    /* 挂载分区 */
    list_traversal(&partition_list, mount_partition, (int)default_part);
}

    sys_free(sb_buf);
    /* 确定默认操作的分区 */
    char default_part[8] = "sdb1";
    /* 挂载分区 */
    list_traversal(&partition_list, mount_partition, (int)default_part);
    /* 将当前分区的根目录打开 */
    open_root_dir(cur_part);

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

支持代码,修改(myos/fs/file.h

extern struct file file_table[MAX_FILE_OPEN];

测试代码,修改(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"


int main(void) {
   put_str("I am kernel\n");
   init_all();
   sys_open("/file1", O_CREAT);
   while(1);
   return 0;
}

为了方便调试,我们修改(myos/fs/fs.c/mount_partition),调试完毕需删除增加的代码

        printk("mount %s done!\n", part->name);

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

        printk("mount %s done!\n", part->name);
        printk("sb1 data_start_lba: %x\n", cur_part->sb->data_start_lba);

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

当启动虚拟机后,会显示sdb 1的数据区起始扇区(我是A6B,如果你一直跟着我做,那么也应该是这个),由于我们的根目录文件的第一个块就放在数据区第一个扇区中,所以我们拿到这个数据后将其 * 512,然后带入以下代命令,就可以查看hd80M.img 数据区开始 512字节的数据,我们就可以得到书上的效果

xxd -s 1365504 -l 512 hd80M.img

xxd是一个命令行工具,它主要用于在十六进制格式下显示文件的内容,-s参数是指定要显示的数据字节偏移,-l参数是显示字节数量。

小节d:

文件的打开与关闭

file_open:用于打开文件,实质就是让文件结构指向一个内存中的inode。原理:传入需要打开文件的inode数组索引,调用get_free_slot_in_global在全局打开文件结构数组中找个空位,调用inode_open将inode调入内存中,然后让该文件结构指向这个inode,最后调用pcb_fd_install安装文件描述符。为了同步性,以写的方式打开的文件不应该是正在写入的文件,需要判断该文件对应inode中的正在写入标志。

修改(myos/fs/file.c

#include "interrupt.h"

/* 打开编号为inode_no的inode对应的文件,若成功则返回文件描述符,否则返回-1 */
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; // 每次打开文件,要将fd_pos还原为0,即让文件内的指针指向开头
    file_table[fd_idx].fd_flag = flag;
    bool *write_deny = &file_table[fd_idx].fd_inode->write_deny;

    if (flag & O_WRONLY || flag & O_RDWR)
    { // 只要是关于写文件,判断是否有其它进程正写此文件
        // 若是读文件,不考虑write_deny
        /* 以下进入临界区前先关中断 */
        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);
}

以与写有关的方式打开文件,但是遇见其他线程或进程正在写这个文件,作者代码没有释放文件结构!所以,修改

        else
        { // 直接失败返回
            intr_set_status(old_status);
            printk("file can`t be write now, try again later\n");
            return -1;
        }

        else
        { // 直接失败返回
            intr_set_status(old_status);
            file_table[fd_idx].fd_inode = NULL;
            printk("file can`t be write now, try again later\n");
            return -1;
        }

之前我们实现的sys_open只有创建文件功能,现在我们将file_open封装进入,这样sys_open就有了打开文件的功能了

修改(myos/fs/fs.c/sys_open

    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);

        /* 此fd是指任务pcb->fd_table数组中的元素下标,
         * 并不是指全局file_table中的下标 */
    }
    return fd;

    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);
    }

    /* 此fd是指任务pcb->fd_table数组中的元素下标,
     * 并不是指全局file_table中的下标 */
    return fd;

file_close用传入的文件结构指针关闭文件。步骤:将文件正在写标志关闭,调用inode_close移除内存中的inode,并解除文件结构与inode的关系。

修改(myos/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;
}

到这里我们发现,打开关闭文件,就是在处理文件结构与inode的关系。

函数声明,修改(myos/fs/file.h

int32_t file_open(uint32_t inode_no, uint8_t flag);
int32_t file_close(struct file *file);

fd_local2global,用于将文件描述符转换为全局打开文件结构数组下标,原理就是去当前正在运行的进程或线程的pcb中的fd_table数组中找到文件描述符对应的元素,这里面存的就是全局打开文件结构数组下标。

修改(myos/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;
}

sys_close用传入的文件描述符关闭文件,原理:调用fd_close2global将文件描述符转换为全局打开文件结构数组下标,然后调用file_close关闭这个文件结构对应的文件,最后清空pcb中的文件描述符位

修改(myos/fs/fs.c

/* 关闭文件描述符fd指向的文件,成功返回0,否则返回-1 */
int32_t sys_close(int32_t fd)
{
    int32_t ret = -1; // 返回值默认为-1,即失败
    if (fd > 2)
    {
        uint32_t _fd = fd_local2global(fd);
        ret = file_close(&file_table[_fd]);
        running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
    }
    return ret;
}

函数声明,修改(myos/fs/fs.h

int32_t sys_close(int32_t fd);

测试代码,修改(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"


int main(void) {
   put_str("I am kernel\n");
   init_all();
   uint32_t fd = sys_open("/file1", O_RDONLY);
   printf("fd:%d\n", fd);
   sys_close(fd);
   printf("%d closed now\n", fd);
   while(1);
   return 0;
}

小节e:

file_write,用于把buf中的count个字节写入文件。核心原理:传进函数的文件结构struct file指针中有个指向操作文件inode的指针,通过这个inode中的i_size与i_sectors[ ],我们可以顺利知道文件大小与存储位置信息。先将文件已有数据的最后一块数据读出来并与将要写入的数据在缓冲区中共同拼凑成一个完整的块,然后写入磁盘。剩下的数据以块为单位继续写入磁盘即可。

修改(myos/fs/file.c

/* 把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=71680字节
        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,(系统中块大小等于扇区大小)
     * 后面都统一在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
    {
        /* 若有增量,便涉及到分配新扇区及是否分配一级间接块表,下面要分三种情况处理 */
        /* 第一种情况:12个直接块够用*/
        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个直接块内,新数据将使用间接块*/

            /* 先将有剩余空间的可继续用的扇区地址收集到all_blocks */
            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)
                {                                                      // 新创建的0~11块直接存入all_blocks数组
                    ASSERT(file->fd_inode->i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
                    file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
                }
                else
                { // 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
                    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)
        {
            /* 第三种情况:新数据占据间接块*/
            ASSERT(file->fd_inode->i_sectors[12] != 0);           // 已经具备了一级间接块表
            indirect_block_table = file->fd_inode->i_sectors[12]; // 获取一级间接表地址

            /* 已使用的间接块也将被读入all_blocks,无须单独收录 */
            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; // 含有剩余空间的扇区标识
    /* 块地址已经收集到all_blocks中,下面开始写数据 */
    file->fd_pos = file->fd_inode->i_size - 1; // 置fd_pos为文件大小-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;

        /* 判断此次写入硬盘的数据大小 */
        chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;
        if (first_write_block)
        {
            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;
}

函数声明,修改(myos/fs/file.h

int32_t file_write(struct file *file, const void *buf, uint32_t count);

我们之前在第十二章实现printf时,实现了sys_write,由于当时没有文件系统,所以它就是调用了console_put_str打印字符串,现在我们改进sys_write

sys_write用于将buf中连续count个字节写入文件描述符fd。如果传入的fd表示标准输出,直接调用console_put_str打印即可。如果不是,直接调用file_write即可

修改(myos/fs/fs.c

#include "console.h"

/* 将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;
    }
}

支持代码,修改(myos/fs/file.h

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

函数声明,修改(myos/fs/fs.h

int32_t sys_write(int32_t fd, const void *buf, uint32_t count)

修改(myos/userprog/syscall-init.c),删除原有sys_write定义

/* 打印字符串str(未实现文件系统前的版本) */
uint32_t sys_write(char* str) {
   	console_put_str(str);
   	return strlen(str);
}

然后(myos/userprog/syscall-init.c)添加fs.h头文件

#include "fs.h"

最后删除(myos/userprog/syscall-init.c)的sys_write声明

uint32_t sys_write(char* str);

然后修改用户态的入口write

修改(myos/fs/syscall.c

/* 打印字符串str */
uint32_t write(char* str) {
   return _syscall1(SYS_WRITE, str);
}

/* 把buf中count个字符写入文件描述符fd */
uint32_t write(int32_t fd, const void *buf, uint32_t count)
{
    return _syscall3(SYS_WRITE, fd, buf, count);
}

修改函数声明

修改(myos/lib/user/syscall.h

uint32_t write(char* str);

uint32_t write(int32_t fd, const void *buf, uint32_t count)

最后修改printf,改为向标准输出写入替换后的字符串

修改(myos/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(buf); 
}

/* 格式化输出字符串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));
}

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"


int main(void) {
   put_str("I am kernel\n");
   init_all();
   uint32_t fd = sys_open("/file1", O_RDWR);
   printf("fd:%d\n", fd);
   sys_write(fd, "hello,world\n", 12);
   sys_close(fd);
   printf("%d closed now\n", fd);
   while(1);
   return 0;
}

(注:作者将本部分代码运行了两次,也就是共写入了两次hello,world\n

xxd -s 屏幕显示写入地址 *512 -l 512 hd80M.img查看磁盘中的文件

小节f:

file_read从文件file中读取count个字节到buf。核心原理:文件结构struct file内有个fd_pos表示当前操作的文件内容位置,其实就是要读取的内容在文件中的起始位置,比如1个1KB大小的文本文件,fd_pos = 500,那么就是表示读取当前文件中偏移500字节的这个字符开始的内容。通过struct file中的fd_pos与传入函数的count,我们可以确定要读取的内容相对于整个文件的字节偏移量。然后struct file中有个指向操作文件inode的指 针,通过这个inode中的i_size与i_sectors[ ],我们可以顺利知道文件存储位置信息。所以,自然就能知道要读取内容在磁盘中的位置。由于我们操作单位是块,所以对于起始位置,我们要抛弃这个块前面的无用数据,对于结束位置,我们要抛弃这个块后面的无用数据。

修改(myos/fs/file.c

/* 从文件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)
        { // 若到文件尾则返回-1
            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_end_idx == block_read_start_idx);
        if (block_read_end_idx < 12)
        { // 待读的数据在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[12];
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
        }
    }
    else
    {   // 若要读多个块
        /* 第一种情况: 起始块和终止块属于直接块*/
        if (block_read_end_idx < 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)
        {
            /* 第二种情况: 待读入的数据跨越直接块和间接块两类*/
            /* 先将直接块地址写入all_blocks */
            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); // 确保已经分配了一级间接块表

            /* 再将间接块地址写入all_blocks */
            indirect_block_table = file->fd_inode->i_sectors[12];
            ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 将一级间接块表读进来写入到第13个块的位置之后
        }
        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); // 将一级间接块表读进来写入到第13个块的位置之后
        }
    }

    /* 用到的块地址已经收集到all_blocks中,下面开始读数据 */
    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;
}

函数声明,修改(myos/fs/file.h

int32_t file_read(struct file *file, void *buf, uint32_t count);

sys_read根据传入的文件描述符,调用fd_local2global将文件描述符转换成指定的全局打开文件结构数组索引,然后调用file_read读出count字节放入buf

修改(myos/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);
}

函数声明,修改(myos/fs/fs.h

int32_t sys_read(int32_t fd, void *buf, uint32_t count);

测试代码,(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"


int main(void) {
   put_str("I am kernel\n");
   init_all();
   uint32_t fd = sys_open("/file1", O_RDWR);
   printf("open /file1, fd:%d\n", fd);
   char buf[64] = {0};
   int read_bytes = sys_read(fd, buf, 18);
   printf("1_ read %d bytes:\n%s\n", read_bytes, buf);

   memset(buf, 0, 64);
   read_bytes = sys_read(fd, buf, 6);
   printf("2_ read %d bytes:\n%s", read_bytes, buf);

   memset(buf, 0, 64);
   read_bytes = sys_read(fd, buf, 6);
   printf("3_ read %d bytes:\n%s", read_bytes, buf);

   printf("________  close file1 and reopen  ________\n");
   sys_close(fd);
   fd = sys_open("/file1", O_RDWR);
   memset(buf, 0, 64);
   read_bytes = sys_read(fd, buf, 24);
   printf("4_ read %d bytes:\n%s", read_bytes, buf);

   sys_close(fd);
   while(1);
   return 0;
}

小节g:

sys_lseek用于根据传入的参照物与偏移,重置传入的文件描述符对应的全局打开文件结构中的fd_pos。核心原理:根据传入的文件描述符,调用fd_local2global将文件描述符转换成指定的全局打开文件结构数组索引,然后switch case对传入的参照物情况进行选择,以对fd_pos做不同处理。参照物为SEET_SET,那么新的fd_pos就等于传入的偏移;参照物为SEET_CUR,那么新的fd_pos = 原来fd_pos + 传入偏移;参照物为SEET_END,那么新的fd_pos为原来文件大小 + 偏移(此时偏移量不出意外的话是负数)

定义参照位置,修改(myos/fs/fs.h

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

函数sys_lseek,修改(myos/fs/fs.c

/* 重置用于文件读写操作的偏移指针,成功时返回新的偏移量,出错时返回-1 */
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)
    {
    /* SEEK_SET 新的读写位置是相对于文件开头再增加offset个位移量 */
    case SEEK_SET:
        new_pos = offset;
        break;

    /* SEEK_CUR 新的读写位置是相对于当前的位置增加offset个位移量 */
    case SEEK_CUR: // offse可正可负
        new_pos = (int32_t)pf->fd_pos + offset;
        break;

    /* SEEK_END 新的读写位置是相对于文件尺寸再增加offset个位移量 */
    case SEEK_END: // 此情况下,offset应该为负值
        new_pos = file_size + offset;
    }
    if (new_pos < 0 || new_pos > (file_size - 1))
    {
        return -1;
    }
    pf->fd_pos = new_pos;
    return pf->fd_pos;
}

函数声明(myos/fs/fs.h

int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence);

测试代码(myso/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"

int main(void)
{
    put_str("I am kernel\n");
    init_all();
    uint32_t fd = sys_open("/file1", O_RDWR);
    printf("open /file1, fd:%d\n", fd);
    char buf[64] = {0};
    int read_bytes = sys_read(fd, buf, 18);
    printf("1_ read %d bytes:\n%s\n", read_bytes, buf);

    memset(buf, 0, 64);
    read_bytes = sys_read(fd, buf, 6);
    printf("2_ read %d bytes:\n%s", read_bytes, buf);

    memset(buf, 0, 64);
    read_bytes = sys_read(fd, buf, 6);
    printf("3_ read %d bytes:\n%s", read_bytes, buf);

    printf("________  SEEK_SET 0  ________\n");
    sys_lseek(fd, 0, SEEK_SET);
    memset(buf, 0, 64);
    read_bytes = sys_read(fd, buf, 24);
    printf("4_ read %d bytes:\n%s", read_bytes, buf);

    sys_close(fd);
    while (1)
        ;
    return 0;
}

小节h:

我们接下来实现文件删除功能,其过程是创建文件的逆过程。涉及inode与目录项。

首先是实现删除inode:

inode_delete,传入要删除的inode在inode数组中的索引,然后删除磁盘中的这个inode。这个函数可有可无,因为inode分配是依靠inode位图来完成的,我们回收一个inode,只需要回收一个inode位图中的位即可。因为下次分配这个inode位之后,新的inode数据会覆盖旧有inode数据,这样还能避免不必要的磁盘读写。函数原理:调用inode_locate可以将inode数组索引转换为这个inode在磁盘中的起始扇区和字节偏移。我们将这个inode所在整体扇区读出内存缓冲区中,然后将这个内存缓冲区中的inode清除,再将内存缓冲区中的数据写回磁盘中即可。

修改(myos/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); // inode位置信息会存入inode_pos
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));

    char *inode_buf = (char *)io_buf;
    if (inode_pos.two_sec)
    { // inode跨扇区,读入2个扇区
        /* 将原硬盘上的内容先读出来 */
        ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
        /* 将inode_buf清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        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);
        /* 将inode_buf清0 */
        memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
        /* 用清0的内存数据覆盖磁盘 */
        ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    }
}

inode_release删除文件,包含:删除磁盘中的inode,并回收文件占用的块。这个过程包含:1、回收文件所占用的所有块,通过inode中的i_sectors[ ]即可知道占用了哪些块,然后去清除对应的块位图中的位即可,也就是并没有真正删除文件数据;2、回收inode,首先清除该inode在inode位图中对应的位,然后调用inode_delete删除在磁盘中的这个inode

修改(myos/fs/inode.c

#include "file.h"

/* 回收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);

    /* 1 回收inode占用的所有块 */
    uint8_t block_idx = 0, block_cnt = 12;
    uint32_t block_bitmap_idx;
    uint32_t all_blocks[140] = {0}; // 12个直接块+128个间接块

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

    /* b 如果一级间接块表存在,将其128个间接块读到all_blocks[12~], 并释放一级间接块表所占的扇区 */
    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);
    }

    /* c inode所有的块地址已经收集到all_blocks中,下面逐个回收 */
    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++;
    }

    /*2 回收该inode所占用的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);
}

函数声明,修改(myos/fs/inode.h

void inode_delete(struct partition *part, uint32_t inode_no, void *io_buf);
void inode_release(struct partition *part, uint32_t inode_no);

接下来是删除一个目录项:

delete_dir_entry删除指定文件对应在磁盘中的目录项。核心原理:传入的要删除文件的父目录结构体struct dir指针内有inode成员,这个inode内有i_sectors[ ]记录着这个目录文件的存储位置,我们自然可以以块为单位从磁盘中把父目录文件读取到缓冲区中,然后遍历找到要删除的目录项,删除缓冲区内对应的目录项,然后写回缓冲区数据。

在这个过程中:如果发现该目录项所在块(非.所在块)仅有要删除的这一个目录项,那么就采取回收这个块(删除块位图中的位,然后同步块位图)的方式清除目录项。回收这个块时,还要判断这个块是不是一级间接块当中的唯一一个的那个块,如果是,则还需要回收这个一级间接块。

修改(myos/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;
    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)
            {
                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)
                    {                                    // 如果找到此i结点,就将其记录在dir_entry_found
                        ASSERT(dir_entry_found == NULL); // 确保目录中只有一个编号为inode_no的inode,找到一次后dir_entry_found就不再是NULL
                        dir_entry_found = dir_e + dir_entry_idx;
                        /* 找到后也继续遍历,统计总共的目录项数 */
                    }
                }
            }
            dir_entry_idx++;
        }

        /* 若此扇区未找到该目录项,继续在下个扇区中找 */
        if (dir_entry_found == NULL)
        {
            block_idx++;
            continue;
        }

        /* 在此扇区中找到目录项后,清除该目录项并判断是否回收扇区,随后退出循环直接返回 */
        ASSERT(dir_entry_cnt >= 1);
        /* 除目录第1个扇区外,若该扇区上只有该目录项自己,则将整个扇区回收 */
        if (dir_entry_cnt == 1 && !is_dir_first_block)
        {
            /* a 在块位图中回收该块 */
            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);

            /* b 将块地址从数组i_sectors或索引表中去掉 */
            if (block_idx < 12)
            {
                dir_inode->i_sectors[block_idx] = 0;
            }
            else
            { // 在一级间接索引表中擦除该间接块地址
                /*先判断一级间接索引表中间接块的数量,如果仅有这1个间接块,连同间接索引表所在的块一同回收 */
                uint32_t indirect_blocks = 0;
                uint32_t indirect_block_idx = 12;
                while (indirect_block_idx < 140)
                {
                    if (all_blocks[indirect_block_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
                { // 间接索引表中就当前这1个间接块,直接把间接索引表所在的块回收,然后擦除间接索引表块地址
                    /* 回收间接索引表所在的块 */
                    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);

                    /* 将间接索引表地址清0 */
                    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);
        }

        /* 更新i结点信息并同步到硬盘 */
        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;
    }
    /* 所有块中未找到则返回false,若出现这种情况应该是serarch_file出错了 */
    return false;
}

函数声明,修改(myos/fs/dir.h

bool delete_dir_entry(struct partition *part, struct dir *pdir, uint32_t inode_no, void *io_buf);

sys_unlink用于根据传入路径,删除非目录文件。原理:首先调用 search_file搜索路径以返回文件的inode,判断该inode是否对应某个打开全局文件结构,如果是,则说明此文件正在被使用,那么就不应该被删除。如果不是,调用 delete_dir_entry删除这个文件在磁盘中的目录项,调用inode_release删除inode对应的文件,这就完成了删除。

修改(myos/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;
    }

    /* 检查是否在已打开文件列表(文件表)中 */
    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);

    /* 为delete_dir_entry申请缓冲区 */
    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; // 成功删除文件
}

函数声明,修改(myos/fs/fs.h

int32_t sys_unlink(const char *pathname);

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"

int main(void) {
   put_str("I am kernel\n");
   init_all();
   printf("/file1 delete %s!\n", sys_unlink("/file1") == 0 ? "done" : "fail");
   while(1);
   return 0;
}

为了方便调试,我们修改(myos/fs/fs.c/mount_partition),调试完毕需删除增加的代码

        printk("mount %s done!\n", part->name);
        /* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
            只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
        sys_free(sb_buf);

        printk("mount %s done!\n", part->name);
        printk("sdb1's block_bitmap_lba: %x\n", sb_buf->block_bitmap_lba);
        printk("sdb1's inode_bitmap_lba: %x\n", sb_buf->inode_bitmap_lba);
        printk("sdb1's inode_table_lba: %x\n", sb_buf->inode_table_lba);
        printk("sdb1's data_start_lba: %x\n", sb_buf->data_start_lba);
        /* 此处返回true是为了迎合主调函数list_traversal的实现,与函数本身功能无关。
            只有返回true时list_traversal才会停止遍历,减少了后面元素无意义的遍历.*/
        sys_free(sb_buf);

小节i:

创建目录文件涉及的工作,其实就是涉及到了inode与目录项

  1. 确认待创建的新目录在文件系统上不存在。

  2. 为新目录创建inode。

  3. 为新目录分配1个块存储该目录文件中的目录项。

  4. 在新目录中创建两个目录项“…”和“.”,这是每个目录都必须存在的两个目录项。

  5. 在新目录的父目录中添加新目录的目录项。

  6. 将以上资源的变更同步到磁盘。

sys_mkdir用于根据传入的路径创建目录文件。核心原理:1、调用search_file来确认待创建的新目录文件在文件系统上不存在;2、调用inode_bitmap_alloc来为新目录分配inode索引,并调用inode_init来初始化这个inode;3、调用block_bitmap_alloc来分配一个块用于承载该目录的目录文件。同时设定inode的i_sectors[0],并调用bitmap_sync同步块位图;4、清零缓冲区,然后写入.与…两个目录项,之后将缓冲区内容写入到目录文件中,这就完成了.与…两个目录项的创建;5、创建并设定自己的目录项,调用sync_dir_entry来将目录项同步到父目录中;6、inode_sync同步父目录的inode与自己的inode,bitmap_sync同步inode位图

修改(myos/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);
        /* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
        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;
    /* 目录名称后可能会有字符'/',所以最好直接用searched_record.searched_path,无'/' */
    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); // 初始化i结点

    uint32_t block_bitmap_idx = 0; // 用来记录block对应于block_bitmap中的索引
    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;
    ASSERT(block_bitmap_idx != 0);
    bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);

    /* 将当前目录的目录项'.'和'..'写入目录 */
    memset(io_buf, 0, SECTOR_SIZE * 2); // 清空io_buf
    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); // 清空io_buf
    if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf))
    { // sync_dir_entry中将block_bitmap通过bitmap_sync同步到硬盘
        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);

    /* 将inode位图同步到硬盘 */
    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); // 如果新文件的inode创建失败,之前位图中分配的inode_no也要恢复
    case 1:
        /* 关闭所创建目录的父目录 */
        dir_close(searched_record.parent_dir);
        break;
    }
    sys_free(io_buf);
    return -1;
}

函数声明,修改(myos/fs/fs.h

int32_t sys_mkdir(const char *pathname);

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"

int main(void) {
   put_str("I am kernel\n");
   init_all();
   printf("/dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
   printf("/dir1 create %s!\n", sys_mkdir("/dir1") == 0 ? "done" : "fail");
   printf("now, /dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
   int fd = sys_open("/dir1/subdir1/file2", O_CREAT|O_RDWR);
   if (fd != -1) {
      printf("/dir1/subdir1/file2 create done!\n");
      sys_write(fd, "Catch me if you can!\n", 21);
      sys_lseek(fd, 0, SEEK_SET);
      char buf[32] = {0};
      sys_read(fd, buf, 21); 
      printf("/dir1/subdir1/file2 says:\n%s", buf);
      sys_close(fd);
   }
   while(1);
   return 0;
}

小节j:

sys_opendir用于根据传入的路径打开目录。原理:调用search_file将路径转换为inode索引,然后调用dir_open创建目录结构并将对应的inode载入内存

修改(myos/fs/fs.c

/* 目录打开成功后返回目录指针,失败返回NULL */
struct dir *sys_opendir(const char *name)
{
    ASSERT(strlen(name) < MAX_PATH_LEN);
    /* 如果是根目录'/',直接返回&root_dir */
    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;
}

sys_closedir就是dir_close的封装

修改(myos/fs/fs.c

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

函数声明(myos/fs/fs.h

struct dir *sys_opendir(const char *name);
int32_t sys_closedir(struct dir *dir);

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"

int main(void)
{
    put_str("I am kernel\n");
    init_all();
    struct dir *p_dir = sys_opendir("/dir1/subdir1");
    if (p_dir)
    {
        printf("/dir1/subdir1 open done!\n");
        if (sys_closedir(p_dir) == 0)
        {
            printf("/dir1/subdir1 close done!\n");
        }
        else
        {
            printf("/dir1/subdir1 close fail!\n");
        }
    }
    else
    {
        printf("/dir1/subdir1 open fail!\n");
    }
    while (1)
        ;
    return 0;
}

小节k:

dir_read用于根据传入的目录指针,一次返回一个该目录下的目录项,比如该目录文件下目录项分布:a,空,b,空,c,第一次调用返回a,第二次调用返回b,第三次调用返回c…原理:目录内有个指向自己inode的指针,该inode内有i_sectors[ ],所以能在磁盘中找到该目录文件,将其读出到缓冲区然后按需设定遍历规则即可。

代码中dir_pos实际含义是已经返回过的目录项总大小,cur_dir_entry_pos的含义是此次调用遍历过程中,扫描过的非空目录项总大小。当扫描到的非空目录项时,此时若cur_dir_entry_posdir_pos相等,自然就可以判断出这个非空目录项应该返回。

修改(myos/fs/dir.c

/* 读取目录,成功返回1个目录项,失败返回NULL */
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; // 当前目录项的偏移,此项用来判断是否是之前已经返回过的目录项
    uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
    uint32_t dir_entrys_per_sec = SECTOR_SIZE / dir_entry_size; // 1扇区内可容纳的目录项个数
    /* 因为此目录内可能删除了某些文件或子目录,所以要遍历所有块 */
    while (block_idx < block_cnt)
    {
        if (dir->dir_pos >= dir_inode->i_size)
        {
            return NULL;
        }
        if (all_blocks[block_idx] == 0)
        { // 如果此块地址为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)
            { // 如果f_type不等于0,即不等于FT_UNKNOWN
                /* 判断是不是最新的目录项,避免返回曾经已经返回过的目录项 */
                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;
            }
            dir_entry_idx++;
        }
        block_idx++;
    }
    return NULL;
}

函数声明,修改(myos/fs/dir.h

struct dir_entry *dir_read(struct dir *dir);

sys_readdir,就是dir_read的封装

sys_rewinddir将目录的dir_pos置为0

修改(myos/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;
}

函数声明,修改(myos/fs/fs.h

struct dir_entry *sys_readdir(struct dir *dir);
void sys_rewinddir(struct dir *dir);

测试代码,(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"

int main(void)
{
    put_str("I am kernel\n");
    init_all();
    /********  测试代码  ********/
    struct dir *p_dir = sys_opendir("/dir1/subdir1");
    if (p_dir)
    {
        printf("/dir1/subdir1 open done!\ncontent:\n");
        char *type = NULL;
        struct dir_entry *dir_e = NULL;
        while ((dir_e = sys_readdir(p_dir)))
        {
            if (dir_e->f_type == FT_REGULAR)
            {
                type = "regular";
            }
            else
            {
                type = "directory";
            }
            printf("      %s   %s\n", type, dir_e->filename);
        }
        if (sys_closedir(p_dir) == 0)
        {
            printf("/dir1/subdir1 close done!\n");
        }
        else
        {
            printf("/dir1/subdir1 close fail!\n");
        }
    }
    else
    {
        printf("/dir1/subdir1 open fail!\n");
    }
    /********  测试代码  ********/
    while (1)
        ;
    return 0;
}

小节l:

dir_is_empty判断目录是否为空。原理:为空的目录对应的目录文件中只有两个目录项,分别是.和…。所以,我们直接判断目录对应的inode中的i_size是否等于两个目录项大小即可

dir_remove传入父目录与子目录指针,在指定父目录中删除子目录。原理:判断子目录为空,然后调用delete_dir_entry删除父目录的目录文件中的子目录目录项,最后调用inode_release删除子目录的目录文件(子目录目录文件中只应该有.与…)

修改(myso/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);
}

/* 在父目录parent_dir中删除child_dir */
int32_t dir_remove(struct dir *parent_dir, struct dir *child_dir)
{
    struct inode *child_dir_inode = child_dir->inode;
    /* 空目录只在inode->i_sectors[0]中有扇区,其它扇区都应该为空 */
    int32_t block_idx = 1;
    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;
    }

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

    /* 回收inode中i_secotrs中所占用的扇区,并同步inode_bitmap和block_bitmap */
    inode_release(cur_part, child_dir_inode->i_no);
    sys_free(io_buf);
    return 0;
}

函数声明,修改(myos/fs/dir.h

bool dir_is_empty(struct dir *dir);
int32_t dir_remove(struct dir *parent_dir, struct dir *child_dir);

sys_rmdir用于根据传入路径删除空目录。原理:先调用search_file查找这个路径对应目录的inode,如果存在且该inode对应的确实是个目录文件,然后调用dir_open将该inode调入内存并创建对应的struct dir,调用dir_is_empty判断该目录为空,最后调用dir_remove删除该目录。

修改(myos/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;
}

函数声明,修改(myos/fs/fs.h

int32_t sys_rmdir(const char *pathname);

测试代码(myos/kernel/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"

int main(void)
{
    put_str("I am kernel\n");
    init_all();
    /********  测试代码  ********/
    printf("/dir1 content before delete /dir1/subdir1:\n");
    struct dir *dir = sys_opendir("/dir1/");
    char *type = NULL;
    struct dir_entry *dir_e = NULL;
    while ((dir_e = sys_readdir(dir)))
    {
        if (dir_e->f_type == FT_REGULAR)
        {
            type = "regular";
        }
        else
        {
            type = "directory";
        }
        printf("      %s   %s\n", type, dir_e->filename);
    }
    printf("try to delete nonempty directory /dir1/subdir1\n");
    if (sys_rmdir("/dir1/subdir1") == -1)
    {
        printf("sys_rmdir: /dir1/subdir1 delete fail!\n");
    }

    printf("try to delete /dir1/subdir1/file2\n");
    if (sys_rmdir("/dir1/subdir1/file2") == -1)
    {
        printf("sys_rmdir: /dir1/subdir1/file2 delete fail!\n");
    }
    if (sys_unlink("/dir1/subdir1/file2") == 0)
    {
        printf("sys_unlink: /dir1/subdir1/file2 delete done\n");
    }

    printf("try to delete directory /dir1/subdir1 again\n");
    if (sys_rmdir("/dir1/subdir1") == 0)
    {
        printf("/dir1/subdir1 delete done!\n");
    }

    printf("/dir1 content after delete /dir1/subdir1:\n");
    sys_rewinddir(dir);
    while ((dir_e = sys_readdir(dir)))
    {
        if (dir_e->f_type == FT_REGULAR)
        {
            type = "regular";
        }
        else
        {
            type = "directory";
        }
        printf("      %s   %s\n", type, dir_e->filename);
    }

    /********  测试代码  ********/
    while (1)
        ;
    return 0;
}

小节m:

任务的工作目录

get_parent_dir_inode_nr传入子目录inode索引返回父目录的inode索引。原理:调用inode_open将子目录对应的inode加载到内存中,取出inode其中i_sectors[0]地址,加载这个目录文件到内存中,找到…对应的目录项,从中取出父目录对应的inode索引返回即可。

修改(myos/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);
    /* 目录中的目录项".."中包括父目录inode编号,".."位于目录的第0块 */
    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编号
}

get_child_dir_name通过传入的父目录inode索引与子目录inode索引,返回子目录的名字。原理:调用inode_open将父目录的inode载入内存,然后通过inode中的i_sectors[ ]加载父目录的目录文件到内存中,遍历其中的目录项,遍历过程中比对目录项的i_nr是否与子目录的inode索引相等,如果是,则拷贝名字到缓冲区中。

修改(myos/fs/fs.c

/* 在inode编号为p_inode_nr的目录中查找inode编号为c_inode_nr的子目录的名字,
 * 将名字存入缓冲区path.成功返回0,失败返-1 */
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);
    /* 填充all_blocks,将该目录的所占扇区地址全部写入all_blocks */
    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])
    { // 若包含了一级间接块表,将共读入all_blocks.
        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])
        { // 如果相应块不为空则读入相应块
            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用于解析当前正在运行进程或线程的绝对工作路径。原理:我们之前已经为PCB中添加了cwd_inode_nr用于表示任务工作目录inode索引,假设现在这个inode索引是正确的。首先调用get_parent_dir_inode_nr得到父目录inode索引,此时原来的cwd_inode_nr就变成了子目录inode索引,然后就可以调用get_child_dir_name得到子目录名称,然后将父目录inode索引转换成新的子目录索引,又调用get_parent_dir_inode_nr得到新的父目录索引…如此循环,缓冲区中就会存储反转的绝对路径。比如一个进程工作在/home/kanshan/test下,缓冲区就会存入/test/kanshan/home,所以我们最后把这个路径反转过来即可。

修改(myos/fs/fs.c

/* 把当前工作目录绝对路径写入buf, size是buf的大小.
 当buf为NULL时,由操作系统分配存储工作路径的空间并返回地址
 失败则返回NULL */
char *sys_getcwd(char *buf, uint32_t size)
{
    /* 确保buf不为空,若用户进程提供的buf为NULL,
    系统调用getcwd中要为用户进程通过malloc分配内存 */
    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); // 最大支持4096个inode
    /* 若当前目录是根目录,直接返回'/' */
    if (child_inode_nr == 0)
    {
        buf[0] = '/';
        buf[1] = 0;
        return buf;
    }

    memset(buf, 0, size);
    char full_path_reverse[MAX_PATH_LEN] = {0}; // 用来做全路径缓冲区

    /* 从下往上逐层找父目录,直到找到根目录为止.
     * 当child_inode_nr为根目录的inode编号(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中的路径是反着的,
     * 即子目录在前(左),父目录在后(右) ,
     * 现将full_path_reverse中的路径反置 */
    char *last_slash; // 用于记录字符串中最后一个斜杠地址
    while ((last_slash = strrchr(full_path_reverse, '/')))
    {
        uint16_t len = strlen(buf);
        strcpy(buf + len, last_slash);
        /* 在full_path_reverse中添加结束字符,做为下一次执行strcpy中last_slash的边界 */
        *last_slash = 0;
    }
    sys_free(io_buf);
    return buf;
}

sys_chdir传入一个目录的路径,然后改变当前正在运行的进程或线程的工作目录索引。原理:调用search_file将传入路径解析成一个对应的inode索引,然后直接去修改当前任务pcb的cwd_inode_nr为之前返回的inode索引就行了。

/* 更改当前工作目录为绝对路径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;
}

函数声明,修改(myos/fs/fs.h

char *sys_getcwd(char *buf, uint32_t size);
int32_t sys_chdir(const char *path);

测试代码(myos/kernle/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"

int main(void) {
   put_str("I am kernel\n");
   init_all();
/********  测试代码  ********/
   char cwd_buf[32] = {0};
   sys_getcwd(cwd_buf, 32);
   printf("cwd:%s\n", cwd_buf);
   sys_chdir("/dir1");
   printf("change cwd now\n");
   sys_getcwd(cwd_buf, 32);
   printf("cwd:%s\n", cwd_buf);
/********  测试代码  ********/
   while(1);
   return 0;
}

小节n:

获得文件属性

修改(myos/fs/fs.h),增加记录文件属性的结构体定义

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

sys_stat传入一个路径,调用search_file解析该路径,获得该路径对应的inode索引,然后调用inode_open将该inode调入内存,然后赋值struct stat对应的成员即可

修改(myos/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;
    }

    int32_t ret = -1; // 默认返回值
    struct path_search_record searched_record;
    memset(&searched_record, 0, sizeof(struct path_search_record)); // 记得初始化或清0,否则栈中信息不知道是什么
    int inode_no = search_file(path, &searched_record);
    if (inode_no != -1)
    {
        struct inode *obj_inode = inode_open(cur_part, inode_no); // 只为获得文件大小
        buf->st_size = obj_inode->i_size;
        inode_close(obj_inode);
        buf->st_filetype = searched_record.file_type;
        buf->st_ino = inode_no;
        ret = 0;
    }
    else
    {
        printk("sys_stat: %s not found\n", path);
    }
    dir_close(searched_record.parent_dir);
    return ret;
}

函数声明,修改(myos/fs/fs.h

int32_t sys_stat(const char *path, struct stat *buf);

测试代码(myos/kernle/main.c

#include "print.h"
#include "init.h"
#include "fs.h"
#include "stdio.h"

int main(void)
{
    put_str("I am kernel\n");
    init_all();
    /********  测试代码  ********/
    struct stat obj_stat;
    sys_stat("/", &obj_stat);
    printf("/`s info\n   i_no:%d\n   size:%d\n   filetype:%s\n",
           obj_stat.st_ino, obj_stat.st_size,
           obj_stat.st_filetype == 2 ? "directory" : "regular");
    sys_stat("/dir1", &obj_stat);
    printf("/dir1`s info\n   i_no:%d\n   size:%d\n   filetype:%s\n",
           obj_stat.st_ino, obj_stat.st_size,
           obj_stat.st_filetype == 2 ? "directory" : "regular");
    /********  测试代码  ********/
    while (1)
        ;
    return 0;
}
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值