Gos —— 文件系统

inode

索引表

磁盘的IO是非常慢的,所以引入的块的概念,其是多个扇区组成的,也是文件系统的基本读写单位,所以一个文件至少要占一个块。当文件的大小过大的时候,其会占用多个块,而这些块可能在不同的地方。而其中记录哪个块对应哪个文件的元信息就被称之为文件分配表(FAT)

FAT采用索引来组织,其为每个文件的所有块建立一个索引表,索引表就是块地址数组。每个数组元素就是块的地址,数组元素下标就是文件块的索引。这样我们想要访问哪个块就可以查索引表快速获得块的起始地址。包含此索引表的索引结构被称之为inode
在这里插入图片描述
索引表总共有15个索引项,其中前12项被称之为直接索引,而包含直接索引的索引表被称之为直接块索引表。如果文件大于12个块的话,我们就建立新的块索引表,这个被叫做一级间接块索引表,之后从第13项开始存储的就是一级间接块索引表的起始位置。每个一级间接块总共有256项,可以容纳256个块。而直接块索引的第14项则是二级间接块索引表,其一个表也是256项,不过其中每一项都是一个一级间接块索引表的地址,所以二级间接块索引表支持256*256个文件块。同理,第15项是三级间接块索引表
在这里插入图片描述

inode 结构

而inode结构中,除了索引表之外还有文件的其他信息,比如说文件名字、文件大小、执行权限等等,这就构成了如下图的结构:
在这里插入图片描述
用代码表示便是如下的结构:

struct inode
{
    uint32_t inode_no;        //inode编号
    uint32_t inode_size;      //当inode表示目录,其表示目录项下文件大小和
                              //若为文件,则表示文件大小
    uint32_t inode_open_cnts; //记录此文件被打开的次数
    bool write_deny;          //写文件标志,因为不能同时写

    uint32_t inode_sectors[13]; //数据块指针,0~11表示直接块,12表示一级间接块指针
                                //简单实现一下,不需要13的二级间接块指针
                                //也不需要14的三级间接块指针

    struct list_elem inode_tag; //在list链表中的标志位,这个list表示已打开的inode列表
};

超级块

这样我们对于每个文件都有其物理表示了,要注意的就是文件只是概念上的。而对于文件系统来说,我们需要在某个固定的地方获得文件系统的元信息配置,其需要表示这个分区的inode有多少,这个inode位图地址及大小,根目录位置、空间块位图地址和大小等等,这就是超级块。用代码表示如下,这些属性只有62字节,为了筹齐一个扇区,我们需要定义一个空闲数组,方便我们到时候的读取硬盘中分区超级块块初始化信息。

//超级块,存储着文件系统元信息的配置
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;   //super块位图本身起始扇区地址
    uint32_t block_bitmap_sects; //扇区位图本身占用的扇区数量

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

    uint32_t inode_table_lba;   //inode表起始扇区lba
    uint32_t inode_table_sects; //inode表占用的扇区数量

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

    uint8_t pad[460]; //加上460字节,凑够一个扇区
} __attribute__((packed));

超级块位于磁盘中。硬盘自从MBR引导扇区之后,剩余的空闲内容便被划分为一个个分区。在每个分区的起始位置,便是这个分区的一些元信息,主要包含操作系统引导块、超级块、空闲块位图、inode位图等等,详情如下图:
在这里插入图片描述

创建文件系统

创建文件系统需要我们初始化文件系统的元信息,其实也就是初始化上图中的内容,主要分为以下几个步骤:

  1. 根据分区part大小,计算分区文件系统元信息需要的扇区数及位置,我们假设每个分区最多支持4096个文件,所以我们就可以根据inode的位图需要多少磁盘块、一级inode表需要多少磁盘块,就能计算出我们已经使用的块数和空闲的块数。
    //block_bitmap_init,一个块大小是一扇区,用来表示磁盘的一个块被没被使用
    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);                       //inode节点位图占用的扇区数
    uint32_t inode_table_sects = DIV_ROUND_UP(((sizeof(struct inode) * MAX_FILES_PER_PART)), SECTOR_SIZE); //计算inode表所占用的扇区数

    uint32_t used_sects = boot_sector_sects + super_block_sects + inode_bitmap_sects + inode_table_sects; //已使用的扇区数
    uint32_t free_sects = part->sec_cnt - used_sects;                                                     //剩余扇区数

    //处理空闲块和空闲块位图,每个扇区位图可以表示4096个空闲扇区
    uint32_t block_bitmap_sects;                                     //空闲块最终占用的位图数
    block_bitmap_sects = DIV_ROUND_UP(free_sects, BITS_PER_SECTOR);  //空闲块位图所占用的扇区数
    uint32_t block_bitmap_bit_len = free_sects - block_bitmap_sects; //真正的空闲块数量
    block_bitmap_sects = DIV_ROUND_UP(block_bitmap_bit_len, BITS_PER_SECTOR);
  1. 内存创建超级块,初始化超级块的信息,以上元信息写入写入超级块。这个时候磁盘还是空闲的,所以根据上面计算出来的信息,我们很容易就算出超级块各项的信息。
  2. 将超级块写入磁盘。这个时候超级块的信息已经初始化完了,我们就可以把这部分信息先写入磁盘保存。
  3. 写入文件系统其他元信息,主要是块位图、inode位图以及inode表
  4. 写入根目录到磁盘中,根目录是文件系统的开始,一开始主要包含...这两项

执行完这几步其实我们的文件系统已经创建完成,剩下的就是各个数据结构的操作函数。

inode 操作解析

打开inode

/**
 * @brief 根据inode节点号返回inode节点
 * @param part 分区指针
 * @param inode_no inode号
 * @return 这个inode节点号代表的inode
 */
struct inode *inode_open(struct partition *part, uint32_t inode_no);

对于inode来说,其本身的信息存储在是存储在inode表中的,表中的每个元素都是一个文件的inode信息,我们就拿根目录来举例。根目录的inode号为0,其也是inode表的第一个元素,那么我们我们根据inode号就可以计算出其在硬盘中的地址。之后,我们在内核内存池给inode号分配一个内存空间,从硬盘读取内容到这个内存区域中,再加入我们维护的一个inode队列中这样下次查找的时候,直接从这个队列中获取就不用读磁盘。

而我们去打开inode则是分为两种情况。一个是先去判断已打开的inode队列中是否有这个要打开的inode;如果没有就去磁盘读取这个inode的信息,之后把这个inode添加到队列中。
在这里插入图片描述

回收inode

/**
 * @brief 回收inode数据块和inode本身
 * 
 * @param part 分区指针
 * @param inode_no 要回收的inode的inode号
 */
void inode_release(struct partition *part, uint32_t inode_no)

inode中分为直接块和超级块,所以我们可以先获取这个inode中所有的块,把它们放到一起,之后我们先把块位图清空并同步到磁盘,之后再根据inode号清空此inode表项。同时要注意这个步骤我们要清除内存中队列这个inode节点的信息。

    //# 1.获取inode信息
    struct inode *delete_inode = inode_open(part, inode_no);
    ASSERT(delete_inode->inode_no == inode_no);

    //# 2.回收inode占用的所有块
    uint8_t block_idx = 0;
    uint8_t block_cnt = 12; //块的数量
    uint32_t block_bitmap_idx = 0;
    uint32_t all_blocks[140] = {0};
	
	...

    //逐个回收直接块和间接块所占的空间
    block_idx = 0;
    while (block_idx < block_cnt)
    {
        //因为这里没读入间接表那个块,所以all_blocks[12] = 0
        if (all_blocks[block_idx] != 0)
        {
            block_bitmap_idx = 0;
            block_bitmap_idx = all_blocks[block_idx] - part->su_block->data_start_lba;
            ASSERT(block_bitmap_idx > 0);
            bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
            bitmap_sync(current_partition, block_bitmap_idx, BLOCK_BITMAP);
        }
        block_idx++;
    }

    //# 3.回收该indeo所占用的inode
    bitmap_set(&part->inode_bitmap, inode_no, 0);
    bitmap_sync(current_partition, inode_no, BLOCK_BITMAP);

    //# 4.释放资源
    ...

inode同步到磁盘

/**
 * @brief 将inode写入到分区part
 * @param part 分区指针
 * @param inode 待同步的inode指针
 * @param io_buf 主调函数提供的操作缓冲区
 */
void inode_sync(struct partition *part, struct inode *inode, void *io_buf);
  1. 根据inode号在inode表中得到这个inode在磁盘上的偏移
    //# 1.定位该inode在磁盘中的位置
    uint8_t inode_no = inode->inode_no;
    struct inode_position inode_pos;
    inode_locate(part, inode_no, &inode_pos);
  1. 有些内容是不需要在磁盘中存在的,所以在这里我们可以清空其中的内容
    //# 2.清空磁盘中不需要的三项 inode_open_cnts write_deny inode_tag
    ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));
    struct inode pure_inode;
    memcpy(&pure_inode, inode, sizeof(struct inode));
    pure_inode.inode_open_cnts = 0;
    pure_inode.write_deny = false;
    pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;
  1. 之后的过程就是从磁盘中读取这个inode元信息所在的扇区到一个缓冲区,然后把我们新的inode数据写入这个缓冲区,再同步到磁盘之中。
    //读出两个删除中的数据,然后拼接写入
    ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
    //再写入原来的位置
    memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
    ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);

目录

在操作系统的设计时,目录其实本质也是一个文件,所以其本身也是需要inode节点的。而目录文件中存储的内容就是该目录下的文件,其本身可以看做一个列表,每个列表元素的主要元素就是描述其包含文件的元信息。

inode编号文件名文件类型
12345.此目录
12346上一级目录
12347test.c文件
12348project目录

用代码表示这几项便如下:

//目录项结构,连接文件名和inode节点的数据结构
struct dir_entry
{
    char filename[MAX_FILE_NAME_LEN]; //普通文件或目录名称
    uint32_t inode_no;                //文件或目录对应的inode编号
    enum file_types file_type;        //文件类型
};

创建目录

/**
 * @brief 创建目录pathname
 * 
 * @param pathname 要创建的目录
 * @return int32_t 成功返回0,失败返回-1
 */
int32_t sys_mkdir(const char *pathname)

创建目录主要分为以下过程:

  1. 我们传入的是一个字符串表示的文件路径,这里假设我们创建的路径是/home/ik,首先我们先判断一下这个路径是否存在。这个过程其实就是从根目录的目录文件表查有没有home这个目录,之后可以获得home这个目录的inode号,根据inode号在inode表中查home的目录文件表是否有ik这个目录。
    //# 1.先判断这个路径是否存在
    inode_no = search_file(pathname, &searched_record);
  1. 要开始创建目录了,这个过程我们先给这个目录去分配一个inode结构表示其元信息。
    //# 2.得到目录名称,不带路径的那种,创建其inode结构
    char *dirname = strrchr(searched_record.searched_path, '/') + 1;
    inode_no = inode_bitmap_alloc(current_partition);
  1. 给这个目录文件分配一个块用于实际存储,首先写入. 和…这两项,完善目录文件表。
    //# 3.为目录分配一个块,用来写入. 和 ..
    block_lba = block_bitmap_alloc(current_partition);
    ...
    //# 4.将.和..写入写入目录
    memset(io_buf, 0, SECTOR_SIZE * 2);
    struct dir_entry *pdir_e = (struct dir_entry *)io_buf;
    //初始化当前目录.
    memcpy(pdir_e->filename, ".", 1);
    pdir_e->inode_no = inode_no;
    pdir_e->file_type = FT_DIRECTORY;
    pdir_e++;

    //初始化当前目录..
    memcpy(pdir_e->filename, "..", 2);
    pdir_e->inode_no = parent_dir->inode->inode_no;
    pdir_e->file_type = FT_DIRECTORY;
  1. 把这个块中的内容写入磁盘
    //同步到磁盘
    ide_write(current_partition->my_disk, new_dir_inode.inode_sectors[0], io_buf, 1);
  1. 把当前的目录写入父目录的目录文件表中并保存到磁盘
    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);
	sync_dir_entry(parent_dir, &new_dir_entry, io_buf);
  1. 父目录的inode同步到硬盘
    //# 6.父目录的inode同步到硬盘
    inode_sync(current_partition, parent_dir->inode, io_buf);
  1. 新创建的inode同步到硬盘
    //# 7.新创建的inode同步到硬盘
    inode_sync(current_partition, &new_dir_inode, io_buf);
  1. 这个目录的inode位图信息同步到磁盘中
    //# 8.inode位图同步到硬盘
    bitmap_sync(current_partition, inode_no, INODE_BITMAP);

打开目录

inode作为文件的实体,所以打开目录的本质其实也就是是打开一个inode结构,将其添加到内核维护的inode队列。当然,这个目录本身也是要在内存中,存在的,所以分配一块内存空间。

/**
 * @brief 在分区part上打开inode节点为inode_no的目录并返回目录指针
 * @param part 分区指针
 * @param inode_no inode节点号
 * @return 返回dir目录指针
 */
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;
}

关闭目录

关闭目录就是相反的过程,释放内存,之后关闭目录文件。这里要注意的是多个进程可能都共享一个文件,所以文件时有一个引用计数的选项,当引用计数为0时,inode_close函数才会真正的去关闭这个inode。

/**
 * @brief 关闭dir指针所指向的目录
 * @param dir 目录指针
 */
void dir_close(struct dir *dir)
{
    if (dir == &root_dir)
    {
        return;
    }
    inode_close(dir->inode);
    sys_free(dir);
}

文件

在操作系统中,多个进程可以同时打开一个文件,对文件的不同位置进行操作,这肯定就需要一个叫做文件偏移量的属性。刚刚说文件的物理表示其实是inode,但是inode中并不存储文件偏移量这种属性。一旦存储这个属性,那么就会导致inode记载的信息随着进程数的增加记录的内容增多,这些信息都会随着inode加载到磁盘中,磁盘空间约占越多。所以我们还需要一种结构来存储这类信息,这个信息最好是在内存中,这样我们打开文件的时候这个文件结构存在并且随着文件的关闭这个数据结构,这就是文件结构。用代码表示就如下:

struct file
{
    uint32_t fd_pos;        //文件偏移量
    uint32_t fd_flag;       //文件操作标识如O_RDONLY等
    struct inode *fd_inode; //inode指针
};

inode是文件在磁盘上的物理实体,文件结构则是文件的概念表示。一个文件结构可以指向多个inode实体,一个inode实体则只可以表示一个文件。它们的关系如下图:
在这里插入图片描述

文件描述符

文件描述符(file descripitor)是用来描述文件的一个数据结构,其所描述的对象是文件的操作。每个进程都有一个进程文件表,其本质其实就是文件描述符数组,文件描述符中的元素便是内核文件表中的文件描述符的下标,而在操作系统中通过打开文件获得的文件描述符其实就是这个文件描述符数组的下标,我们查找的时候,根据进程文件表的下标拿到内核文件表中的内容,这个内容就是上面的文件结构。它们三者的关系如下图:
在这里插入图片描述

这样我们如果在进程A中查找文件描述符为2的文件,那么我们可以得到这个文件在内核文件表中的下标就是0,我们就可以得到我们要操作的inode节点信息了。

常见文件操作解析

创建文件

/**
 * @brief 创建文件
 * @param parent_dir 父目录的指针
 * @param file_name  此文件的文件名
 * @param flag 文件标志位
 * @return 成功返回进程中的文件描述符,失败返回-1
 */
int32_t file_create(struct dir *parent_dir, char *file_name, uint8_t flag);

我们现在来梳理创建文件的过程。主要有以下几个步骤:

  1. 创建文件的实体inode并对其进行初始化。这个过程其实就是inode位图中去分配一个块。这个时候我们只是在内存中创建了这么一个结构,并没有将其同步到硬盘中。
    int32_t inode_no = inode_bitmap_alloc(current_partition); //分配一个位图为0的节点号
    struct inode *new_file_inode = (struct inode *)sys_malloc(sizeof(struct inode));
    //初始化inode信息
    inode_init(inode_no, new_file_inode);
  1. 将这个文件的信息存入内核文件表,建立内核文件表项和inode节点的映射关系
    //得到内核文件表的一个空闲下标
    int fd_idx = get_free_slot_in_global();
    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;
  1. 创建一个目录项实体,作为这个文件在文件目录中的表示。
    struct dir_entry new_dir_entry;
    create_dir_entry(file_name, inode_no, FT_REGULAR, &new_dir_entry);
  1. 同步内存目录项数据到磁盘
	//目录项new_dir_entry写入磁盘的parent_dir目录项
	sync_dir_entry(parent_dir, &new_dir_entry, io_buf);
	//父目录的inode被更改了,同步到磁盘中
    inode_sync(current_partition, parent_dir->inode, io_buf);
    //此文件的inode同步到磁盘中
    inode_sync(current_partition, new_file_inode, io_buf);
    //将inode_bitmap同步到磁盘
    bitmap_sync(current_partition, inode_no, INODE_BITMAP);

    //此文件加入内核打开的文件中
    list_push(&current_partition->open_inodes, &new_file_inode->inode_tag);
    new_file_inode->inode_open_cnts = 1;

打开文件

内核层面打开文件
/**
 * @brief 打开编号为inode_no的inode对应的文件
 * @param inode_no inode号
 * @param flag 文件操作标识
 * @return 成功返回文件描述符,失败返回-1
 * @note write_deny设置的时候保证原子性
 */
int32_t file_open(uint32_t inode_no, uint8_t flag);

打开文件的时候,需要传入我们要打开的文件的inode号,也就是指定是磁盘中的哪个文件,之后我们要做的步骤如下:

  1. 打开这个inode结构,将inode信息加载到内存并得到这个inode结构的inode号
	inode_open(current_partition, inode_no);
  1. 将此inode结构加入到内核文件表,内核文件表和inode是一一对应的关系。
    //初始化file_table中fd_idx项
    file_table[fd_idx].fd_inode = inode_open(current_partition, inode_no); //得到这个inode节点号对应的inode结点指针
    file_table[fd_idx].fd_pos = 0;
    file_table[fd_idx].fd_flag = flag;
  1. 之后判断我们进程是否是要对这个文件进行读写,我们需要保证对于一个进程同一时间只能有一个进程在读写。
    bool *write_deny = &file_table[fd_idx].fd_inode->write_deny;

    //判断文件是否是关于写文件,因为不能多个进程同时写一个文件
    if (flag & O_WRONLY || flag & O_RDWR)
    {
        enum intr_status old_status = intr_get_status();
        if (!(*write_deny))
        {
            //此进程占有此文件
            *write_deny = true;
            intr_set_status(old_status);
        }
        else
        {
            intr_set_status(old_status);
            printk("file_open: can't open this file, maybe occured by other process!\n");
            return -1;
        }
    }
  1. 将此文件的描述符加入到进程文件表之中,这一步就是看进程文件表哪个表现还是空的,把内核文件表的下标写入。
    //建立进程文件描述符和内核文件描述符的映射关系
    pcb_fd_install(fd_idx);
用户层面打开文件

对于用户层面大家可以想一下我们平时使用open函数打开文件的过程,其会传入文件的路径信息以及对文件的权限信息。

/**
 * @brief 打开或创建文件
 * @param path_name 文件路径
 * @param flags 文件操作标识信息
 * @return 成功返回文件描述符,失败返回-1
 */
int32_t sys_open(const char *path_name, uint8_t flags);

这个过程主要分为以下几步:

  1. 判断打开的文件是不是目录文件,这个判断文件路径的最后一个是不是'/'就可以做到了。
	path_name[strlen(path_name) - 1] == '/'
  1. 根据文件路径找到这个文件的inode号。假设当前路径是/test/test.c,首先我们对文件进行解析,首先解析出来的就是根目录/,其是一个目录文件,根据目录的信息,我们直到根目录下面有关目录文件表如下:
inode编号文件名文件类型
12345.此目录
12346上一级目录
12347main.c文件
12348test目录

然后解析我们要寻找的目录test,其inode号为12348,我们根据inode号打开test这个目录文件,读取其目录文件表,在其中找到文件test.c,返回test.c的inode号12351,这个过程就是search_file函数做的事情。

inode编号文件名文件类型
12349.此目录
12350上一级目录
12351test.c文件
    int inode_no = search_file(path_name, &searched_record);

3.拿到了inode号之后,我们就可以判断文件权限标志了,是要不存在就创建还是单纯的要打开这个文件:

 switch (flags & O_CREAT)
    {
    case O_CREAT:
        printk("create files: %s\n", path_name);
        fd = file_create(searched_record.parent_dir, (strrchr(path_name, '/') + 1), flags);
        dir_close(searched_record.parent_dir);
        break;
    //其余均为打开已存在文件
    //主要有O_RDONLY、O_WRONLY、O_RWRD
    default:
        fd = file_open(inode_no, flags);
        break;
    }

关闭文件

这个过程被封装到两个函数中去做了,一个是file_close主要做的是内核层面的关闭文件操作,我们直接交给inode_close去做。用户层面则是注重把文件描述符移出进程文件表这个过程,在sys_close,我们根据进程中的文件描述符查表可以得到内核文件表中的表项,之后我们调用file_close关闭就可以了。

/**
 * @brief 关闭文件
 * @param file 待关闭的文件指针
 * @return 成功返回0,失败返回-1
 */
int32_t file_close(struct file *file)
{
    file->fd_inode->write_deny = false;
    inode_close(file->fd_inode);
    file->fd_inode = NULL;
    return 0;
}

/**
 * @brief 关闭文件描述符fd指向的文件
 * 
 * @param fd 文件描述符
 * @return int32_t 成功返回0,失败返回-1
 */
int32_t sys_close(int32_t fd)
{
    int32_t ret = -1;
    if (fd > 2)
    {
        uint32_t fd_ = fd_local2global(fd);
        ret = file_close(&file_table[fd_]);
        running_thread()->fd_table[fd] = -1;
    }
    return ret;
}

读写文件

/**
 * @brief 从文件file读取count个字节到buf中
 * 
 * @param file 文件指针
 * @param buf 存储读出数据的缓冲区
 * @param count 读出的字节数
 * @return int32_t 成功返回读出的字节数,失败返回-1
 */
int32_t file_read(struct file *file, void *buf, uint32_t count);

文件读写的逻辑差不多,我们这里就拿读文件举例。主要分为以下几个过程:

  1. 首先是要计算我们要读取的字节数+当前的偏移量是不是比整个文件都长。如果读的大小大于文件的字节数就有多少读多少。
    //# 1.如果要读取的字节数+文件偏移量是要大于文件总共的字节数的,那就有多少读多少
    if ((file->fd_pos + count) > file->fd_inode->inode_size)
    {
        size = file->fd_inode->inode_size - file->fd_pos;
        size_left = size;
    }
  1. 根据文件指针我们很容易知道要操作的是哪个inode,所以我们就申请一个缓冲区存放inode中的块信息。
    //# 2.申请一个缓冲区以及存放inode表的all_blocks
    uint8_t *io_buf = sys_malloc(BLOCK_SIZE);
    uint32_t *all_blocks = (uint32_t *)sys_malloc(BLOCK_SIZE + 48);
    ...
  1. 之后我们就可以根据当前的文件偏移量得到要读取的位置在哪个块内,然后从这个位置,每次开始读取一个扇区。
    //# 开始读取数据了
    uint32_t sec_idx;        //扇区索引
    uint32_t sec_lba;        //扇区地址
    uint32_t sec_off_bytes;  //扇区内字节偏移量
    uint32_t sec_left_bytes; //扇区内剩余字节量
    uint32_t 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(current_partition->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;
    }

参考文献

[1] 操作系统真相还原

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值