手写操作系统(五十)-文件和目录相关函数

代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。

接下来就是实现文件和目录了

1.文件相关函数

文件操作相关的函数定义在 fs/file.c 和 fs/file.h 中

新建/fs/file.h

#ifndef __FS_FILE_H
#define __FS_FILE_H
#include "stdint.h"
#include "ide.h"
#include "dir.h"
#include "global.h"

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

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

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

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

extern struct file file_table[MAX_FILE_OPEN];
int32_t inode_bitmap_alloc(struct partition* part);
int32_t block_bitmap_alloc(struct partition* part);
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag);
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp);
int32_t get_free_slot_in_global(void);
int32_t pcb_fd_install(int32_t globa_fd_idx);
#endif

struct file就是所说的文件结构,其中的fd_pos用于记录当前文件操作的偏移地址,该值最小是0,最 大为文件大小减1,fd_flag是文件操作标识,如O_RDONLY,fd_inode是inode指针,用来指向inode队 列(part-> open_inodes)中的inode。

enum std_fd是标准文件描述符,在Linux中0表示标准输入, 1表示标准输出,2表示标准错误,未打算实现标准错误,因此只定义了前两个,stdin_no表示标准输入,其值为0,stdout_no表示标准输出,其值为1。

enum bitmap_type是位图类型,包括INODE_BITMAP和BLOCK_BITMAP,我们以后在往硬盘上同步位图的时候会用到此结构。

宏MAX FILE OPEN的值是32,这是系统可打开的最大文件数。

新建/fs/file.c

#include "file.h"
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "stdio-kernel.h"
#include "memory.h"
#include "debug.h"
#include "interrupt.h"
#include "string.h"
#include "thread.h"
#include "global.h"

#define DEFAULT_SECS 1

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

/* 从文件表file_table中获取一个空闲位,成功返回下标,失败返回-1 */
int32_t get_free_slot_in_global(void) {
   uint32_t fd_idx = 3;
   while (fd_idx < MAX_FILE_OPEN) {
      if (file_table[fd_idx].fd_inode == NULL) {
     break;
      }
      fd_idx++;
   }
   if (fd_idx == MAX_FILE_OPEN) {
      printk("exceed max open files\n");
      return -1;
   }
   return fd_idx;
}

/* 将全局描述符下标安装到进程或线程自己的文件描述符数组fd_table中,
 * 成功返回下标,失败返回-1 */
int32_t pcb_fd_install(int32_t globa_fd_idx) {
   struct task_struct* cur = running_thread();
   uint8_t local_fd_idx = 3; // 跨过stdin,stdout,stderr
   while (local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
      if (cur->fd_table[local_fd_idx] == -1) {  // -1表示free_slot,可用
     cur->fd_table[local_fd_idx] = globa_fd_idx;
     break;
      }
      local_fd_idx++;
   }
   if (local_fd_idx == MAX_FILES_OPEN_PER_PROC) {
      printk("exceed max open files_per_proc\n");
      return -1;
   }
   return local_fd_idx;
}

/* 分配一个i结点,返回i结点号 */
int32_t inode_bitmap_alloc(struct partition* part) {
   int32_t bit_idx = bitmap_scan(&part->inode_bitmap, 1);
   if (bit_idx == -1) {
      return -1;
   }
   bitmap_set(&part->inode_bitmap, bit_idx, 1);
   return bit_idx;
}
   
/* 分配1个扇区,返回其扇区地址 */
int32_t block_bitmap_alloc(struct partition* part) {
   int32_t bit_idx = bitmap_scan(&part->block_bitmap, 1);
   if (bit_idx == -1) {
      return -1;
   }
   bitmap_set(&part->block_bitmap, bit_idx, 1);
   /* 和inode_bitmap_malloc不同,此处返回的不是位图索引,而是具体可用的扇区地址 */
   return (part->sb->data_start_lba + bit_idx);
} 

/* 将内存中bitmap第bit_idx位所在的512字节同步到硬盘 */
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type) {
   uint32_t off_sec = bit_idx / 4096;  // 本i结点索引相对于位图的扇区偏移量
   uint32_t off_size = off_sec * BLOCK_SIZE;  // 本i结点索引相对于位图的字节偏移量
   uint32_t sec_lba;
   uint8_t* bitmap_off;

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

      case BLOCK_BITMAP: 
     sec_lba = part->sb->block_bitmap_lba + off_sec;
     bitmap_off = part->block_bitmap.bits + off_size;
     break;
   }
   ide_write(part->my_disk, sec_lba, bitmap_off, 1);
}

file_table 是文件表,也就是文件结构数组,它的长度是 MAX_FILE_OPEN,也就是最多可同时打开MAX_FILE_OPEN次文件。

get_free_slot_in_global函数功能是从文件表file_table中获取一个空闲位,成功则返回空闲位下标, 失败则返回-1。 实现原理是遍历file_table,找出fd_inode为null的数组元素,该元素表示为空,将其下标返回即可。 另外,file_table中的前3个成员预留给标准输入、标准输出及标准错误,以后需要用到它们。

peb_fd_install函数接受1个参数,全局描述符下标globa_fd_idx,也就是数组file_table的下标。 函数功能是将globa_fd_idx安装到进程或线程自己的文件描述符数组fd_table中,成功则返回fd_table中空位的下标,失败则返回-1。 位于pcb中的fd_table,前3个元素是标准文件描述符,分别表示标准输入、标准输出和标准错误,不能占用,其余的都是可分配的描述符,我们已在初始化线程时将这些可分配的描述符置为-1。 因此只要fd_table数组元素为-1就表示空位、可分配的文件描述符,具体实现是while (local_fd_idx < MAX_FILES_OPEN_PER_PROC)循环。

inode_bitmap_alloc函数接受1个参数,分区part,功能是分配一个i结点,返回i结点号。就是位图操作。

block_bitmap_alloc函数接受1个参数,分区part,功能是分配1个扇区,返回其扇区地址。 其实现也是位图操作。

函数bitmap_sync接受3个参数,分区part、位索引bit_idx、位图类型btmp_type,功能是将内存中bitmap第bit_idx位所在的512字节同步到硬盘。 因为硬盘是以扇区为读写单位,所以内存中的数据也要一次操作1扇区,函数开头要确定待同步的位属于哪一个512字节中,uint32_t off_sec = bit_idx / 4096用 bit_idx/4096计算第bit_idx位相对于位图的以扇区为单位的偏移量,结果存入off_sec,也就是说第bit_idx位属于位图中第off_sec个扇区大小的内存中,不过off_sec用于计算写入硬盘的扇区地址,下一行将off_sec乘以BLOCK_SIZE获得该位相对于位图的字节偏移量,结果存入off_size, off_size才用于待写入硬盘的内存数据,它是第bit_idx位所在位图中以 512 字节为单位的起始地址。文件系统中目前有两种位图,inode 位图及块位图,下面的 switch 结构通过位图类型 btmp_type来判断同步哪种位图,分别计算出位图的扇区地址 sec_lba 和字节偏移 量bitmap off,bitmap off是待同步数据的起始地址,最后通过ide write将位图写入硬盘。

 

2.目录相关函数

新建/fs/dir.c

#include "dir.h"
#include "stdint.h"
#include "inode.h"
#include "file.h"
#include "fs.h"
#include "stdio-kernel.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
#include "string.h"
#include "interrupt.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;
}

/* 在分区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;
}

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

/* 关闭目录 */
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);
}

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

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

/* 将目录项p_de写入父目录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;
}

程序开头定义了 root_dir,它是分区的根目录。

open_root_dir函数接受一个参数,分区part,功能是打开分区part的根目录。 先调用 inode_open打开根目录的inode,然后将其赋值给根目录的inode指针。随后将根目录的dir_pos置为0。

dir_open函数接受两个参数,分区part和inode编号inode_no,功能是在分区part上打开i结点为inode_no的目录并返回目录指针。 此函数与open_root_dir类似,区别是根目录root_dir是提前定义好的全局变量,免去了为其申请内存的过程,这里要为其他目录单独申请内存,也就是struct dir* pdir = (struct dir*)sys_malloc(sizeof(struct dir))代码的功能。

search_dir_entry函数接受4个参数,分区part、目录指针pdir、文件名name、目录项指针dir_e,函数功能是在part分区内的pdir目录内寻找名为name的文件或目录,找到后返回 true 并将其目录项存入 dir_e,否则返回 false。函数开头定义了变量block_cnt,表示inode总的块数,其值为140,即12个直接块+128个一级间接块。 接下来uint32_t* all_blocks = (uint32_t*)sys_malloc(48 + 512)为这140个扇区地址申请内存,返回地址赋值给 all_blocks,这样做是为了方便检索此 inode 的全部扇区地址,我们以后对此目录inode所在的扇区地址都统一从all_blocks中获取,因此while (block_idx < 12)先将目录inode的i_sectors中的前12个扇区地址录入到all_blocks中,随后在if (pdir->inode->i_sectors[12] != 0)判断是否有一 级间接块索引表,只要i_sectors[12]不为0就表示有一级间接块索引表,如果有,就从硬盘的扇区地址 i_sectors[12]处获取1扇区数据,此数据是128个间接块地址,将其复制到all_blocks+12字节处。 至此,all_blocks 被写满了,其存储的是目录pdir的所有扇区地址。

我们在处理 inode_table 时,是将它连续写在多个扇区中,也就是除最后一个扇区外,其他几个扇区都写满了inode,从而导致了inode跨扇区的情况,以至于在获取inode的时候要做额外判断,比较麻烦。 吸取经验教训,我们在往目录中写目录项的时候,写入的都是完整的目录项,避免了目录项跨扇区的情况,因此在实际搜索目录项的时候每次只从硬盘读取一扇区就好了,所以在uint8_t* buf = (uint8_t*)sys_malloc(SECTOR_SIZE),我们为缓冲区buf申请的内存大小是SECTOR_SIZE,即1扇区。 struct dir_entry* p_de = (struct dir_entry*)buf将缓冲区转换为目录项struct dir_entry类型,赋值给p_de,后面将开始用p_de遍历buf。 然后计算一扇区内容纳的目录项数,结果存入变量dir_entry_cnt。

由于我们不知道目录项在目录的哪个扇区中存在,所以在while (block_idx < block_cnt)的while循环开始,我们在该目录所有的扇区中查找目录项。 目录的所有扇区地址已经被收录到all_blocks中,在if (all_blocks[block_idx] == 0)判断,如果扇区地址为0,这说明未分配扇区,跳过,继续下一次循环。这里要说明一下,尽管我们将来往目录中写目录项时,是优先在inode->i_sectors扇区数组中靠前的扇区中写入,前面的扇区无法容纳完整目录项后才会占用新的扇区。但将来我们还会实现删除文件和目录的功能,删除文件的顺序可是随机的,这取决于文件所在的扇区。若目录中的文件或子目录过多,占用了多个扇区,执行删除文件时,有可能被删除的文件位于前面扇区中,后面扇区中的文件还在,比如目录a中先生成了10个文件,假如这10个文件位于扇区地址i_sector[0]处,正巧这个扇区中只能容纳10个目录项,后来又在目录a中生成了第11个文件,该文件占用的扇区地址是i_sector[1],现在将前10个文件全部删除,删除后,扇区i_sector[0]上已无文件,因此将i_sector[0]回收,那时会将i_sector[0]赋值为0,但目录a的扇区地址i_sector[1]不为0,该扇区中还有文件,所以扇区地址为0时还要继续在后面的索引块中查找。

回到代码,若在if (all_blocks[block_idx] == 0) 判断all_blocks[block_idx]不为0,这表示已分配扇区地址了,于是在ide_read(part->my_disk, all_blocks[block_idx], buf, 1)从该扇区地址all_blocks[block_idx]读入1扇区数据到buf,在while (dir_entry_idx < dir_entry_cnt)循环用目录项指针p_de遍历该扇区内的所有目录项,在if (!strcmp(p_de->filename, name))比较目录项的p-de->filename是否和待查找的文件名name相等,若相等则表示找到该文件,然后memcpy(dir_e, p_de, dir_entry_size)将该目录项p_de复制到参数dir_e中,之后释放缓冲区和all_blocks,成功返回。否则,更新为下一个目录项继续在该扇区中查找。若该扇区中未找到该文件的话,使block_idx++,更新为all_blocks中的下一个扇区,读取新的扇区,重复以上查找过程。如果到最后都未找到该文件,释放buf和all_blocks,返回false。

dir_close函数接受1个参数,目录指针dir,,功能是关闭目录dir,关闭目录的本质是关闭目录的inode并释放目录占用的内存,也就是inode_close(dir->inode)和sys_free(dir)。 根目录不能被真正地关闭,不做任何处理直接返回。 原因是首先根目录始终应该是打开的,它是所有目录的父目录,查找文件时必须要从根目录开始找。 其次是根目录root_dir占用的是静态内存,它位于低端 1MB 之内,并非是在堆中申请的,不能将其释放。

create_dir_entry函数接受4个参数,文件名filename、 inode编号inode_no、文件类型file_type、目录项指针p_de。 功能是在内存中创建目录项p_de。 函数的实现就是在初始化目录项p_de,将文件名拷贝到目录项 p_de->filename 中,用inode_no为p_de->i_no 赋值,用file_type为p_de->f_type 赋值。

sync_dir_entry函数接受3个参数,父目录parent_dir、目录项p_de、缓冲区io_buf,功能是将目录项p_de写入父目录parent_dir中,其中io_buf由主调函数提供。

当inode是目录时,其i_size是目录中目录项的大小之和,父目录的大小是dir_inode->i_size,uint32_t dir_size = dir_inode->i_size将其赋值给变量dir_size。目录项大小记录在超级块中,uint32_t dir_entry_size = cur_part->sb->dir_entry_size获取了超级块的大小,存入变量dir_entry_size。uint32_t dir_entrys_per_sec = (512 / dir_entry_size)计算1扇区可容纳的完整的目录项数,结果写入变量dir_entrys_per_sec,此处就是在函数search_dir_entry 中所说的,写入目录项时已保证目录项不会跨扇区。

while (block_idx < 12)循环将目录的 12个直接块地址收集到数组 all_blocks,下面将优先检查这 12 个扇区。struct dir_entry* dir_e = (struct dir_entry*)io_buf使目录项指针dir_e指向缓冲区io_buf,这里为目录项指针变量起名为dir_e是避免与参数p_de冲突。

由于删除文件时会造成目录中存在空洞,也就是文件系统内的碎片,所以在写入文件时,,要逐个目录项查找空位,避免一味在目录的末尾添加目录项,而前面所有文件已被删除时,却还占用多个扇区的情况,所以while (block_idx < 140)开始从头在这12个扇区中找空闲目录项位置。虽然我们只在all_blocks中收集了12个直接块的地址,但依然要遍历140个扇区,因为要是在这12个扇区中找不到目录项空位时,在文件大小未超过140个扇区的情况下,我们还会为其分配新扇区以容纳目录项。 代码if (all_blocks[block_idx] == 0)先判断扇区是否分配,若未分配,在block_lba = block_bitmap_alloc(cur_part)通过函数block_bitmap_alloc为其分配一扇区,扇区地址写入变量block_lba。 由于 block_bitmap_alloc仅是操作内存中的块位图,为保持数据同步,现在要将块位图同步到硬盘,于是在block_bitmap_idx = block_lba – cur_part->sb->data_start_lba计算block_lba相对于data_start_lba的偏移,bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP)调用bitmap_sync将块位图同步到硬盘。

if (block_idx < 12)判断当前为空的块是直接块,还是间接块,若块索引小于12,则属于直接块,故在dir_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba将分配的扇区地址写入i_sectors[block_idx]和all_blocks[block_idx]。 若正好是第12个块,即一级间接块索引表地址为空,下面就该创建间接块了,在dir_inode->i_sectors[12] = block_lba将刚才分配的扇区地址block_lba作为一级间接块索引表的地址写入i_sectors[12],在block_lba = block_bitmap_alloc(cur_part)重新再分配一扇区,此时block_lba更新为新分配的扇区地址,该扇区地址将作为第0个间接块。 如果block_lba为-1,也就是分配扇区失败了,执行一些回滚操作。 如果分配成功的话,接着将块位图同步到硬盘。 然后在all_blocks[12] = block_lba将新分配的扇区地址更新到all_blocks[12],这是第0个间接块的地址,随后在ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1)调用ide_write将间接块地址写入一级间接块索引表所在的扇区。

在else语句,若block_idx已经超过了12,这说明已经遍历到间接块了,将最初分配的扇区地址block_lba录入all_blocks,然后在ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1)将间接块地址写入一级间接块索引表所在的扇区。 最后将目录项写入扇区,然后dir_inode->i_size += dir_entry_size更新目录大小,使目录的i_size加上1个目录项大小dir_entry_size。

ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1)开始处理块已存在,不需要分配块的情况,也就是要在该扇区中寻找空闲的目录项,先在ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1)将该扇区读到io_buf中,接着在while (dir_entry_idx < dir_entrys_per_sec)通过while循环遍历dir_entrys_per_sec个目录项,if ((dir_e + dir_entry_idx)->f_type == FT_UNKNOWN)判断若目录项的f_type为FT_UNKNOWN,这表示该目录项未分配,可以用,于是在memcpy(dir_e + dir_entry_idx, p_de, dir_entry_size)将目录项p_de写入io_buf,接着调用ide_write将目录项同步到硬盘,最后使目录的i_size加上1个目录项大小dir_entry_size。 如果所有的扇区都满了,直接输出”directory is full!”并以false返回。

 

对应/fs/dir.h

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

extern struct dir root_dir;             // 根目录
void open_root_dir(struct partition* part);
struct dir* dir_open(struct partition* part, uint32_t inode_no);
void dir_close(struct dir* dir);
bool search_dir_entry(struct partition* part, struct dir* pdir, const char* name, struct dir_entry* dir_e);
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);
#endif

 

 

3.参考

郑钢著操作系统真象还原

田宇著一个64位操作系统的设计与实现

丁渊著ORANGE’S:一个操作系统的实现

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇