在前面一节中介绍了文件系统的基本概念,接下来就要准备开始实现我们的文件系统,下面是为管理文件系统提供的几个基础数据结构。
超级块的结构
/* 超级块 */
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; // 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; // 根目录所在的I结点号
uint32_t dir_entry_size; // 目录项大小
uint8_t pad[460]; // 加上460字节,凑够512字节1扇区大小
} __attribute__((packed));
inode的结构
struct inode
{
uint32_t i_no; // inode编号
uint32_t i_size; // 此inode为文件时,表示文件的大小。为目录时,表示该目录下所有目录项大小之和
uint32_t i_open_cnts; // 文件被打开的次数
bool write_deny; // 写文件的标识,防止多个进行同时对一个文件写
uint32_t i_sectors[13]; // 一个文件只支持13个块,12个直接块,1个间接块。在这个文件系统中,块的大小直接等于1扇区
struct list_elem inode_tag;
};
目录和目录项的结构
enum file_types
{
FT_UNKNOWN,
FT_REGULAR,
FT_DIRECTORY
};
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;
enum file_types f_type;
};
有了这些结构之后,第一步要做的工作就是对硬盘上的分区进行格式化
创建的步骤如下
- 根据分区大小,计算分区文件系统各元信息需要的扇区数及位置
- 在内存中创建超级块,将上面的元信息写入超级块
- 将超级块写入磁盘
- 将元信息写入磁盘上各自的位置
- 将根目录写入磁盘
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); // inode位图占用的扇区数.最多支持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 = 0x19971234;
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);
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);
}
这个函数的主要功能实际上就是按照我们对文件系统的规划,将硬盘每一个分区中的信息进行了初始化。这些数据就是用来记录硬盘分区的基本信息,将下图中的信息确定并保留下来。
/* 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统 */
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)
{
if (dev_no == 0)
{
// 跨过第一个裸盘,在模拟器中的第二块硬盘才是存储文件系统的硬盘
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;
}
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 == 0x19971234)
{
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);
}
在成功创建出文件系统之后,输入了该文件系统中四个主分区的起始扇区地址和该分区的扇区总数。
有了文件系统之后,我们就能对磁盘上的数据进行有效的管理,接下来就要实现文件的创建与删除操作。