14.2 创建文件系统
14.2.1 创建超级块、i结点、目录项
超级块
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-05-07 10:18:02* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-05-07 11:24:50* @FilePath: /OS/chapter14/14.2/fs/super_block.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_H
#include "global.h"
#include "stdint.h"/*超级块*/
struct super_block{uint32_t magic; //用来标识文件系统类型,支持多文件系统的操作系统通过此标志来识别文件系统的类型uint32_t sec_cnt; //本分区总共的扇区数uint32_t inode_cnt; //本分区中ino数量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));#endif // !__FS_SUPER_BLOCK_H
inode节点
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-05-07 10:26:42* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-05-07 10:35:50* @FilePath: /OS/chapter14/14.2/fs/inode.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef __FS_INODE_H
#define __FS_INODE_H#include "stdint.h"
#include "list.h"/*inode结构*/
struct inode{uint32_t i_no; //inode编号/*当次inode是文件时,i_size是指文件大小,若是目录,则是指该目录下所有目录项大小之和*/uint32_t i_size;uint32_t i_open_cnts; //记录此文件被打开的次数bool write_deny; //写文件不能并行,进程写文件前检查此标识/* 咱们的块大小就是 1 扇区 ,i_sectors[0-11]是直接块,i_sectors[12]用来存储一级间接块指针*//*不过稍微不同的是扇区大小是 512 字节,并且块地址用 4 字节来表示,因此咱们支持的一级间接块数量是 128 个,即咱们总共支持 128+12= 140 个块(扇区)*/uint32_t i_sectors[13];/*由于 inode 是从硬盘上保存的 , 文件被打开时, 肯定是先要从硬盘上载入其 inode,但硬盘比较慢, 为了避免下次再打开该文件时还要从硬盘上重复载入 inode,应该在该文件第一次被打开时就将其 inode加入到 内存缓存中,每次打开一个文件时,先在此缓冲中查找相关的 inode , 如果有就直接使用, 否则再从硬盘上读取 inode,然后再加入此缓存。 这个内存缓存就是“己打开的 inode 队列”*/struct list_elem inode_tag;
};#endif // !__FS_INODE_H
目录
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-05-07 10:35:58* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-05-07 15:41:12* @FilePath: /OS/chapter14/14.2/fs/dir.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef _FS_DIR_H
#define _FS_DIR_H
#include "stdint.h"
#include "inode.h"
#include "fs.h"
#include "global.h"#define MAX_FILE_NAME_LEN 16 //最大文件名长度/*目录结构*/
struct dir{struct inode* inode;uint32_t dir_pos; //记录在目录内的偏移uint8_t dirr_buf[512]; //目录的数据缓存
};/*目录项结构*/
struct dir_entry{char filename[MAX_FILE_NAME_LEN]; //普通文件或目录名称uint32_t i_no; //普通文件或目录对应的inode编号enum file_types f_type; //文件类型
};#endif // !_FS_DIR_H
注:目录的数据结构struct dir
只会存在于内存之中,因为它管理的是对一个目录文件的操作(比如打开一个目录文件,就会在内存中创建这样一个结构体)。
文件类型的定义
/** @Author: Adward-DYX 1654783946@qq.com* @Date: 2024-05-07 11:06:32* @LastEditors: Adward-DYX 1654783946@qq.com* @LastEditTime: 2024-05-07 15:29:03* @FilePath: /OS/chapter14/14.2/fs/fs.h* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE*/
#ifndef _FS_FS_H
#define _FS_FS_H
#include "stdint.h"#define MAX_FILES_PER_PART 4096 //每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096 //每扇区的位数
#define SECTOR_SIZE 512 //扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE //块字节大小/*文件类型*/
enum file_types{FT_UNKNOWN, //不支持的文件类型FT_REGULAR, //普通文件FT_DIRECTORY //目录
};void filesys_init(void);
#endif // !_FS_FS_H
14.2.2 创建文件系统
这里书上的创建的文件系统有一定的问题,我这里直接是学习的另外一个博主的地址如下:《操作系统真象还原》 第十四章 文件系统-CSDN博客
其代码如下:
#include "stdint.h"
#include "fs.h"
#include "inode.h"
#include "ide.h"
#include "memory.h"
#include "super_block.h"
#include "dir.h"
#include "stdio-kernel.h"
#include "string.h"/* 格式化分区,也就是初始化分区的元信息,创建文件系统 */
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 now_total_free_sects = free_sects; // 定义一个现在总的可用扇区数uint32_t prev_block_bitmap_sects = 0; // 之前的块位图扇区数uint32_t block_bitmap_sects = DIV_ROUND_UP(now_total_free_sects, BITS_PER_SECTOR); // 初始估算uint32_t block_bitmap_bit_len;while (block_bitmap_sects != prev_block_bitmap_sects) {prev_block_bitmap_sects = block_bitmap_sects;/* block_bitmap_bit_len是位图中位的长度,也是可用块的数量 */block_bitmap_bit_len = now_total_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; //数据区的起始就是inode数组的结束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, 所以位图中的位全都代表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); // 先清空缓冲区bufstruct inode* i = (struct inode*)buf; i->i_size = sb.dir_entry_size * 2; // .和..i->i_no = 0; // 根目录占inode数组中第0个inodei->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);
}
下面再写一个函数filesys_init
遍历所有分区,如果该分区没有文件系统就调用partition_format
来创建文件系统
/*在磁盘上搜索文件系统,若没有则格式化分区创建文件系统*/
void filesys_init(void){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){ //跨过裸盘hd60M.imgdev_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;}/*** channels数组是全局变量,默认值为0,disk属于其嵌套结构,* partition又为disk的嵌套结构,因此partition中的成员默认也为0* 若partition未初始化,则partition中的成员扔为0* 下面处理存在的分区*/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 == 0x19590318){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);/*确定默认操作的分区*/char default_part[8] = "sdb1";/*挂载分区*/list_traversal(&partition_list,mount_partition,(int)default_part);
}
14.2.3 挂载分区
Linux 内核所在的分区是默认分区,自系统启动后就以该分区为默认分区,该分区的根目录是固定存在的,要想使用其他新分区的话,需要用 mount 命令手动把新的分区挂载到默认分区的某个目录下,这就是上面所说的“拿”出来。尽管其他分区都有自己的根目录,但是默认分区的根目录才是所有分区的父目录,因此挂载分区之后,整个路径树就像一串葡萄。分区不用的时候还可以通过 umount 命令卸载,这就是上面所说的“收”起来。
挂载分区的实质是把该分区文件系统的元信息从硬盘上读出来加载到内存中,这样硬盘资源的变化都用内存中元信息来跟踪,如果有写操作,及时将内存中的元信息同步写入到硬盘以持久化 。
struct partition* cur_part; //默认情况下操作的是那个分区/*在分区链表中找到名为part_name的分区,并将其赋值给cur_part*/
static bool mount_partition(struct list_elem* pelem, int arg){char* part_name = (char*)arg;struct partition* part = elem2entry(struct partition, part_tag, pelem);if(!strcmp(part->name,part_name)){cur_part = part;struct disk* hd = cur_part->my_disk;/*sb_buf用来存储从硬盘上读入的超级块*/struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);/*在内存中创建分区cur_part的超级块*/cur_part->sb = (struct super_block*)sys_malloc(sizeof(struct super_block));if(cur_part->sb == NULL){PANIC("alloc memory failed!");}/*读入超级块*/memset(sb_buf, 0, SECTOR_SIZE);ide_read(hd, cur_part->start_lba+1,sb_buf,1);/*把sb_buf中的超级块的信息复制到分区的超级块sb中*/memcpy(cur_part->sb,sb_buf,sizeof(struct super_block));/********************* 将硬盘上的块位图读入到内存中 ***************************************/cur_part->block_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->block_bitmap_sects * SECTOR_SIZE);if(cur_part->block_bitmap.bits == NULL)PANIC("alloc memory failed!");cur_part->block_bitmap.btmp_bytes_len = sb_buf->block_bitmap_sects * SECTOR_SIZE;/*从硬盘上读入块位图到分区的block_bitmao.bits*/ide_read(hd,sb_buf->block_bitmap_lba,cur_part->block_bitmap.bits,sb_buf->block_bitmap_sects);/********************************************************************************************//********************* 将硬盘上的inode位图读入到内存中 ***************************************/cur_part->inode_bitmap.bits = (uint8_t*)sys_malloc(sb_buf->inode_bitmap_sects * SECTOR_SIZE);if(cur_part->inode_bitmap.bits == NULL)PANIC("alloc memory failed!");cur_part->inode_bitmap.btmp_bytes_len = sb_buf->inode_bitmap_sects * SECTOR_SIZE;/*从硬盘上读入inode位图到分区的inode_bitmap.bits*/ide_read(hd,sb_buf->inode_bitmap_lba,cur_part->inode_bitmap.bits,sb_buf->inode_bitmap_sects);/********************************************************************************************/list_init(&cur_part->open_inodes);printk("mount %s done!\n", part->name);/*** 此处返回true是为了迎合主调函数list_traversal的实现* 与函数本身功能无关* 只有返回true时list_traversal才会停止遍历* 减少了后面元素无意义的遍历*/return true;}return false; //使得list_traversal继续遍历
}
结果如下: