十一、文件系统

inode、间接块索引表、文件控制块FCB

硬盘读写单位是扇区,因此一个块是由多个扇区组成的,块大小是扇区大小的整数倍。块是文件系统的读写单位,因此文件至少要占一个块,当文件体积大于一个块时,文件要拆分为多个块来存储。
文件组织形式是对各个文件而言的,UNIX操作系统中的索引结构-inode。采用索引结构的文件系统,文件中的块依然可以分散到不连续的零散空间中,保留了磁盘高利用率的优点,更重要的是文件系统为每个文件的所有块建立了一个索引表,索引表就是块地址数组,即每个数组元素就是块的地址,数组元素下标是文件块的索引,第n个数组元素指向文件中的第n个块,这样访问任意一个块时候,只要从数组中获得块地址即可。必须为每一个文件都单独配备一个这样的元信息数据结构,因此 UNIX文件系统中,一个文件必须对应一个 inode。
当文件很大时,采取的策略是:每个索引表共有 15个索引项,暂时称此索引表为老索引表。前12个索引项是文件的前12个块地址,他们是文件的直接块。若文件大于12个块,那就再建立个新的索引表,新索引表称为一级间接块索引表,表中容纳256个块地址。此表占用一个物理块来存储,该物理块的地址存储到老索引表的第 13 个索引项中。这样文件就有12+256=268块,当文件块大于256块时,建立二级间接块索引表,各表项存储的是一级间接块索引表的地址,这样就可以有 256* 256个块了,然后在老索引表中的第14个索引项存储二级间接块索引表所在块的地址。这样总共可以有 (12+256+256*256) 个块。不行的话,在创建三级间接块索引表,表中各表项存储的二级间接块索引表的地址,这样就可以 256*256*256 个块,三级间接块索引表的地址储存在老索引表的第15个索引项中。文件最大可以(12+256+256*256+256*256*256)个块。
inode结构中,几乎囊括了一个文件的所有信息,权限、属性、时间、大小等等。
只要用于管理、控制文件相关信息的数据结构都被称为 FCB,即文件控制块,inode 也是这种结构,因此inode 也是FCB 的一种。注意:inode 是文件在文件系统上的元信息,要通过文件系统获得文件的实体,必须先要找到文件的 inode。
Linux中,文件系统是针对各个分区来管理磁盘空间的。在每一个分区中,inode 的数量是固定的,inode 的数量等于文件的数量,分区中所有文件的 inode 通过一个大表格来维护,此表格称为 inode_table ,用数组来表示,数组元素的下标便是文件 inode 编号。
一个分区的大小是固定的,但是文件大小不一,若两个 16GB 的分区,一个分区中储存 1M大小 的文件一个,另一个分区中 储存文件大小 1G 的文件也是一个储存,它们的 inode 利用率是一样的,但是磁盘空间利用率不一样。所以一个分区的利用率分为 inode 利用率和磁盘空间利用率。

目录项与目录简介

Linux中,目录和文件都用 inode 来表示,因此目录也是文件,只是目录是包含了文件的文件。所以有了目录文件和普通文件,是文件就要有 inode。如果inode 表示的普通文件,此 inode 指向的数据块中的内容应该是普通文件自己的数据。 inode 表示的目录文件,则指向的数据块中的内容应该是要么普通文件的目录项,要么是目录文件的目录项。
不管文件是 普通文件 还是 目录文件,所有的都存在于某个目录中。所以目录项内容分为普通文件和目录文件。
所以目录相当与一个文件列表,每个文件在目录中都有一个entry,每个entry 的内容至少包括 文件名、文件类型、文件的inode 编号。想想 ls -lai 这个命令出来的内容。
文件名:inode中并没有文件名,所以文件名存在于目录项中
文件类型:表明这个文件的类型是目录文件还是普通文件
inode编号:通过inode 编号可以在inode_table 中找到 inode的地址,这样可以找到该文件的实体块。
创建文件的本质就是创建了目录项和 inode。
总结:文件系统中的目录和普通文件统一用 inode 表示。inode表示的文件是普通文件,还是目录文件,取决于 inode 所指向的数据块中实际内容是什么。inode 是文件的实质,但它并不能直接引用,必须通过文件名找到文件名所在的目录项,然后从目录项中获得 inode 的编号,然后用编号到inode_table 数组中找相关的 inode,最后找到文件的数据块。

超级块与文件系统布局

超级快就是文件系统元信息的配置文件,它是在分区创建文件系统时创建的,所有有关文件系统元信息的配置都在超级块中,包括:inode_table 的地址和大小、inode 位图的地址和大小、根目录的地址和大小,空闲块位图的地址和大小。每一个分区都有一个超级块,超级块的位置和大小是固定的,在各个分区的第2个扇区,一个扇区大小。
文件系统是针对各个分区来管理的,inode 代表了文件,因此各分区都有自己的inode 数组。各分区可创建的最大文件数是在为分区创建文件系统(格式化)时设置的。所以各分区的inode_table 的数组长度是等于最大文件数的,我们用位图来管理 inode 的使用情况。
在分区的开始是OBR块,里面主要是内核加载器。通过OBR可以进入保护模式、开启分页模式、加载内核到内存。第二个块是超级块,后面是inode 位图和inode_table 数组,后面是根目录和空闲块区域,根目录和空闲块区域才是储存数据的区域。 

创建文件系统

创建超级块、i结点、目录项

在创建文件系统前,一些基础数据结构要先创建:超级块、inode 和 目录项。
//超级块(硬盘中和内存中都有一份)
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));
//inode 结构(硬盘中,通过读入一个扇区到内存来修改inode_table的值然后同步)
truct inode {
   uint32_t i_no;    // inode编号
/* 当此inode是文件时,i_size是指文件大小,
若此inode是目录,i_size是指该目录下所有目录项大小之和*/
   uint32_t i_size;
   uint32_t i_open_cnts;   // 记录此文件被打开的次数
   bool write_deny;	   // 写文件不能并行,进程写文件前检查此标识
   uint32_t i_sectors[13];
   struct list_elem inode_tag;
};
//目录项(硬盘中)
struct dir_entry {
   char filename[MAX_FILE_NAME_LEN];  // 普通文件或目录名称
   uint32_t i_no;					 // 普通文件或目录对应的inode编号
   enum file_types f_type;			 // 文件类型
};

//目录(内存中)

struct dir 
{
   struct inode* inode;   //指针,用于指向内存中的inode
   uint32_t dir_pos;	  // 记录在目录内的偏移
   uint8_t dir_buf[512];  // 目录的数据缓存
};

创建分区元信息

高级格式化分区,也就是初始化分区的元信息,具体包括:超级块位置和大小、空闲块位图的位置和大小、inode 位图的位置和大小、inode 数组的位置和大小、空闲块起始地址、根目录起始地址。 partition_format(struct partition* part),具体实现是:1,首先要根据分区 part 的大小,计算出来分区文件系统各元信息需要的扇区数和位置。2,在内存中创建超级块,将以上步骤计算的元信息写入超级块,将超级块写入磁盘,将元信息写入磁盘上各自的位置,将根目录写入磁盘。
注意的是:分区挂载的目的是为了使用分区上的数据,需要对这些数据增删查改,所以需要对相对应的位图进行操作,位图的操作是在内存中进行。我们的空闲块位图的处理方法比较简单粗暴,在将启动扇区、inode位图、inode数组写入硬盘后剩余的扇区为空闲扇区,然后向上除整得到空闲块位图的扇区数,然后用总空闲扇区的数量减去位图数量得到了。这样做的结果就造成了最后一扇区的最后有些位是多余的,所以我们要提前将这些多余的位设置为1,然后写入硬盘。
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;
   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, 所以位图中的位全都代表inod
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值