操作系统真象还原实验记录之实验二十五:文件操作相关基础函数

本文详细介绍了文件系统的底层原理,包括文件描述符、inode操作、文件和目录相关函数等关键概念及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

操作系统真象还原实验记录之实验二十五:文件操作相关基础函数

1. 文件描述符

以打开文件open这个系统调用流程为例。

首先,根目录已经有两个FCB了,分别是"。" 和“。。”
然后我们的inode数组共4096个,inode[0]指向了根目录文件扇区地址,也就是超级块的data_start_lba。
现在我们假设根目录里又新增了两个FCB。
那么情况如下图。
在这里插入图片描述
这里列出他们是为了和inode结点打开队列以及打开文件表区分开来,也就是下面这个图

在这里插入图片描述

图中的文件表应该是打开文件表,里面的表项叫做文件结构,整个表就是文件结构数组。
当没有文件打开的时候,这个表为空,同时全局inode结点打开队列也为空。
注意:i结点打开队列每个分区结构体都有,早已定义。
此打开队列位于内存,是为了减少磁盘读取次数。

PCB里增加了文件描述符数组用于存储文件结构数组下标。
有了上述的基础后,就可以模拟open系统调用流程了。

当使用open打开文件2的时候,立即申请一个新的inode结点,然后将inode[4]写入到该新结点,该新结点加入全局打开inode结点队列。
紧接着在打开文件表里新建一个文件结构表项,根据根目录里的FCB3的信息来存储文件2相关信息于该文件结构表项。然后将新inode结点的地址写入fd_inode。
最后将新文件结构表项在打开文件表里的下标返回到PCB的文件描述符数组fd_table。从而完成open。

2. inode操作有关函数

首先回忆一下inode结构体

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

i_open_cnts
inode_tag
write_deny
这三个是inode在内存才有效。
inode在内存意味着内核池里存有该inode,且它的inode_tag在该分区全局打开队列中。

inode.h

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

2.1 inode有关函数(暂时)inode.c

#include "inode.h"
#include "fs.h"
#include "file.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
#include "interrupt.h"
#include "list.h"
#include "stdio-kernel.h"
#include "string.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写入到分区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);
   }
}

/* 根据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或减少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分配了内核空间 */
      struct task_struct* cur = running_thread();
      uint32_t* cur_pagedir_bak = cur->pgdir;
      cur->pgdir = NULL;
      sys_free(inode);		         // 释放inode的内核空间
      cur->pgdir = cur_pagedir_bak;
   }
   intr_set_status(old_status);
}


/* 初始化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++;
   }
}

struct inode_position 记录inode在磁盘是否跨扇区,所在扇区号以及扇区内字节偏移

inode_locate 根据i_no填写上述结构体
inode_sync 将内存的inode写入磁盘,也就是inode内存与磁盘的同步
inode_open 根据i_no返回内存中的inode结点地址。
注意:如果全局打开队列没有该inode,说明该inode不在内存,这个文件是第一次打开,所以要从磁盘读取该inode加入队列。
inode_close i_open_cnts减一。如果变成0,就从队列移除,并释放占用的内核内存。
inode_init

inode_open中,为新inode结点sys_malloc了一块内存,使用的是内核内存池与内核虚拟池,因为内核虚拟池返回的虚拟地址都是3GB+1MB以上,第六次分页实验内核页目录第769到1024项都已写好,也就是依次指向1MB到2MB的各个页表,所以所有用户进程769到1024个页目录项指向的页表都与内核的页目录3GB以上指向的页表一致,均是(1MB到2MB的页表),所以所有用户进程和内核线程对于3GB以上的相同虚拟地址,最后转换的都是同一个物理地址。也就是该inode共享。也就是内核内存池所有空间所有用户进程与内核线程共享。

2.3 其他

thread.h(增加)

#define MAX_FILES_OPEN_PER_PROC 8

/* 进程或线程的pcb,程序控制块 */
struct task_struct {
 略。。。
   int32_t fd_table[MAX_FILES_OPEN_PER_PROC];	// 已打开文件数组
  略。。。
};
thread.c(增加)
void init_thread(struct task_struct* pthread, char* name, int prio) {
略。。。
 /* 标准输入输出先空出来 */
   pthread->fd_table[0] = 0;
   pthread->fd_table[1] = 1;
   pthread->fd_table[2] = 2;
   /* 其余的全置为-1 */
   uint8_t fd_idx = 3;
   while (fd_idx < MAX_FILES_OPEN_PER_PROC) {
      pthread->fd_table[fd_idx] = -1;
      fd_idx++;
   }
略。。。
}

3. 文件相关的函数

3.1 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

file 就是打开文件表的文件结构表项
fd_pos 是当前具体的文件操作的文件内偏移地址。
显然这个不是FCB。

3.2 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"
#include "ioqueue.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);
}

pcb_fd_install 将文件描述符写入pcb的fd_table数组中
get_free_slot_in_global 从打开文件表获得一个空闲表项的下标

block_bitmap_alloc:在内存的分区的空闲块位图中找到空闲块,返回bit的下标
inode_bitmap_alloc:在内存分区的i结点位图找到空闲inode结点,返回下标

bitmap_sync:显然,内存里的分区的位图被修改了,就需要往磁盘里的空闲块或i结点位图更新同步。
由于磁盘读写单位是一个扇区,所以即使只是修改了一个位,也要更新该位所在的整个扇区。

该函数接受一个被修改的bit位在位图的下标。
由此下标便可获得相对位图的扇区偏移量。从而确定更新磁盘哪个扇区。
part->block_bitmap.bits是内存里的空闲块位图首地址。
part->sb->block_bitmap_lba是内存超级块结构体里记录的该分区空闲块在硬盘中的扇区号。

3.3 目录相关的函数

回忆目录和目录项结构体

/* 目录结构 */
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;	      // 文件类型
};

首先是格局
磁盘上已有一个扇区用作根目录了,inode[0]也记录着根目录。每个目录里的目录项存着i_no。
打开一个文件,本质就是打开对应的inode,即将inode加入全局打开队列,加入打开文件表,加入PCB的文件描述符数组。
根目录也是一个文件,而且根目录必须永久打开,因为任何文件对应的inode都要从根目录开始找,而根目录的i_no默认为0,存于超级块。
而被打开的文件也就是调用inode_open,会返回一个inode
因此需要一个dir类型全局变量root_dir永驻内存,来记录被打开的文件。
所以dir这个结构体专门用来记录内存中被打开的文件

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


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

root_dir:全局变量记录被打开的根目录的inode。
open_root_dir:打开根目录的inode,记录在root_dir。
dir_open:接受一个i_no和分区,打开该分区对应的inode,申请一个dir记录此打开的文件的inode,返回该dir地址。

dir_close:关闭目录,即关闭inode,不能关闭根目录。
create_dir_entry:内存中初始化目录项。

search_dir_entry


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

在磁盘的part分区内的pdir目录内寻找名为name的文件或目录,找到后返回true并将其目录项存入内存的目录项结构体dir_e,否则返回false。
代码思路如下:
首先有pdir,说明该目录已被打开且记录在了pdir中。
其次pdir目录的inode中有12个直接索引,一个间接索引。
这些索引反应了pdir目录文件的扇区数。一个目录文件最多12+128=140个扇区,扇区地址均在索引中。
目录文件的每个扇区装满了目录项,且目录项不会跨扇区。
这些扇区都在磁盘中,所以应该从磁盘读出整个目录于内存,然后在内存依次遍历所有目录项,检查name。

将inode的140个索引地址存入all_blocks,遍历all_blocks。
索引地址表示扇区号。
如果索引地址为0,那么表示不存在。
如果不为0,那么就读出该扇区,遍历里面的目录项的name。找到则保存于目录项dir_e,return true。

3.3.2 dir.c下半部


/* 将目录项 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);

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

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

    // 将 12 个直接块存入 all_blocks
    while(block_idx < 12) {
        all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
        block_idx++;
    }
	//这里原书的源码一定是写错了,我反复思考,原书写掉了下面的代码。如果没有下面的代码,只读前12个
	//all_block[12]永远是0,无论你是否已经新分配了扇区给i_sectors[12]。因为你分配了第13块扇区保存在	
	//inode里面,然后在第13块扇区填好第一个目录项后return了,你希望下次要填的目录项可以接着第13块扇区填,但是你代码每次只读了前12块扇区地址,i_sectors[12]的信息每次没有去读。
	//如果目录小,12个直接块没有填满,不会报错,如果一旦前12个填满,再填目录项代码每次都会是all_blocks[12] == 0且block_idx == 12,分配两个新扇区,导致每次i_sectors[12]都会被覆盖,每次第13块扇区的第一个目录项都写不进去。
	if (pdir->inode->i_sectors[12] != 0) {	// 若含有一级间接块表,128个间接块地址写入all_blocks
      ide_read(part->my_disk, pdir->inode->i_sectors[12], all_blocks + 12, 1);
   }


    // dir_e 用来在 io_buf 中遍历目录项
    struct dir_entry* dir_e = (struct dir_entry*)io_buf;
    int32_t block_bitmap_idx = -1;

    // 开始遍历所有块以寻找目录项空位, 若已有扇区中没有空闲位
    // 在不超过文件大小的情况下申请新扇区来存储新目录项
    block_idx = 0;
    while(block_idx < 140) {
        block_bitmap_idx = -1;

        if(all_blocks[block_idx] == 0) {
            block_lba = block_bitmap_alloc(cur_part);
            if(block_lba == -1) {
                printk("alloc block bitmap for sync_dir_entry failed\n");
                return false;
            }

            // 每分配一个块就同步一次 block_bitmap
            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;
                // 再分配一个块作为第 0 个间接块
                block_lba = block_bitmap_alloc(cur_part);
                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) {
                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;
}

将新目录项写入目录,
需要遍历所有目录项,直到找到空闲目录项位置。
目录最多有140个扇区,扇区内装着目录项,依次遍历这140个扇区,也就是遍历12个直接索引,一个间接索引
分以下这几个情况:
1.当遍历的是直接索引且不为0时,
意味着扇区已存在,也就是扇区内部含有目录项,但是也有可能有空位。那就需要读出这个扇区,遍历里面的目录项,寻找空位。没有空位继续遍历处理下一个扇区。
2.当遍历的是直接索引且为0时
这就需要在当前分区的空闲块位图中,新分配一个空闲扇区并同步磁盘的位图,然后将这个扇区地址写入该目录的inode的直接索引。
3.当遍历的是间接索引且为0时,第13块扇区不存在,那么就要分配两个扇区,一个用于作间接扇区A,里面最多可存128个直接索引,一个用作存目录项的文件扇区B,也就是第13块扇区。
A地址写入间接索引,B地址写入A
4.当遍历的是间接索引块A内的直接索引且为0时,分配新扇区B,将B写入A内的直接索引。
然后2、3、4情况都还没有写入新FCB于目录,所以最后有一段代码统一写入。
(这个函数最好画图理解,以后再多看几遍)
总结:这个函数在磁盘可能修改的东西有分区的空闲块位图,inode的前12个直接索引、以及间接索引;其次,还有间接块里的直接索引;最后磁盘里的目录文件还需要写入新的FCB。
上述修改中,位图、间接块的直接索引、目录里的新FCB在函数中均已做到同步。但是!!!inode的修改未同步。
别忘了inode_sync函数。

3.4 路径解析有关函数

fs.h

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

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

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

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

fs.c


/* 将最上层路径名称解析出来 */
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; 
}

/* 返回路径深度,比如/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;
}

/* 搜索文件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;
}
 

path_parse:
char* path_parse(char* pathname, char* name_store)
比如pathname = “/a/b/c”,那么此函数的name_store=“a”,返回的char* 为/b/c。

path_depth_cnt

search_file:主要就是这个函数,
static int search_file(const char* pathname, struct path_search_record* searched_record)
我们来模拟一下
当pathname为/a/b/c时,c为目录或文件的情况

首先先调一次path_parse,name=a,sub_path=/b/c。
searched_record->parent_dir为根目录文件。
然后进入while(name[0])
strcat使得searched_record->searched_path=/a
然后开始
if (search_dir_entry(cur_part, parent_dir, name, &dir_e))
在根目录中查找name=a的目录项,查找到就保存在dir_e中。
找到a的目录项后,再调一次path_parse,name=b,sub_path=/c,
然后parent_inode_no = parent_dir->inode->i_no;
parent_dir = dir_open(cur_part, dir_e.i_no);
在打开a目录项前,用parent_inode_no 保存a的父目录文件的i_no,再将searched_record->parent_dir置为a目录文件。
综上所述:
searched_record->searched_path=/a时,searched_record->parent_dir为根目录,在根目录寻找a的目录项。
searched_record->searched_path=/a/b时,searched_record->parent_dir为a目录,parent_inode_no =根目录的i_no,在a目录寻找b的目录项。找到b则关闭a目录打开b目录。
searched_record->searched_path=/a/b/c时,searched_record->parent_dir为b目录,parent_inode_no =a的i_no,在b目录中寻找c的目录项,然后打开c目录关闭b目录。

如果c是文件,直接return dir_e.i_no即可,searched_record不变。
如果c是目录,那么searched_record->parent_dir会等于c的目录,parent_inode_no =b的i_no,然后退出循环,所以函数的最后,关闭了c目录,利用parent_inode_no,打开了b目录,把searched_record->parent_dir修改回b的目录。

这里注意一个问题,此函数只负责在文件系统上检索文件,不负责侦错。主调函数依靠searched_record记录的内容来判断错误。
也就是说如果b是文件,那么/a/b/c这个格式就是错的,但是函数仍然会打开a目录,返回b文件的i_no,同时返回的searched_record中searched_path=/a/b,parent_dir为a目录,文件类型是文件。主调函数要根据searched_path来增加判断错误的代码。

4.总结

函数太多了,总结一下,以后再看细节

4.1有关inode的操作有

inode_locate:获得inode所在的扇区号以及扇区内的偏移,以inode_position结构体形式记录。

inode_open:根据i_no打开inode结点并返回该节点。

inode_close:关闭或减少inode的打开数

inode_init:初始化inode结点

4.2 有关文件的操作有

file file_table[MAX_FILE_OPEN] 全局打开文件表

get_free_slot_in_global:获取打开文件表空位,返回下标即文件描述符。

fd_install:文件描述符安装到自己的pcb中

inode_bitmap_alloc:分配一个i结点,返回i结点号
注意理解为什么这个不是inode操作函数,看看它的inode_open的区别就明白了。
inode_open这个是磁盘里文件存在,且已经分配了磁盘里4096个i结点中的一个,若打开该结点需要在内存中创建此结点副本。可以理解成该i结点磁盘存在,内存不存在,故调inode_open。
inode_bitmap_alloc是文件在磁盘刚刚创建,需要分配一个inode结点,也就是i结点磁盘里都不存在,故掉inode_bitmap_alloc。本质是文件操作。

block_bitmap_alloc:磁盘中分配一个扇区,返回扇区地址

bitmap_sync:将内存中bitmap第bit_idx位所在的512字节同步到磁盘,可以是空闲块位图,也可以是inode位图

4.3有关目录的操作有

open_root_dir:打开根目录,也就是用全局root_dir来记录根inode_open打开后返回的i_no

dir_open:打开i_no的目录并返回该inode

注意:这两个本质一样,核心都是inode_open,不过,他们打开的都是目录的inode,用内存里dir结构体专门保存inode_open返回的i_no;而不是向打开普通文件一样,记录在打开文件表里,然后文件描述符记录在PCB里。

search_dir_entry:在pdir的目录内寻找名为name的目录或文件,找到后返回true并将目录项记录在dir_e。

dir_close:关闭目录,释放dir,且inode_close。根目录除外

create_dir_entry:内存中初始化目录项p_de

sync_dir_entry:将目录项p_de写入父目录parent_dir中。

4.4 路径解析函数

path_parse:最上层路径名解析出来保存name_store,返回剩余子路径。

path_depth_cnt:返回路径深度

search_file:搜索文件pathname,找到则返回i_no。
此函数依靠path_parse获取目录名
search_dir_entry找到目录项(里面有i_no)
dir_open继续打开目录或直接返回普通文件i_no。
来完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值