已完成实验
已完成实验链接
简介
实验 24. 创建并挂载文件系统
总结
-
创建文件系统: 初始化每一个分区的结构,把扇区划分为超级块,扇区位图,inode 位图,inode 表,根目录,空闲扇区
-
挂载分区: 创建一个分区结构体,在内存中存放要挂在的分区的超级块、扇区位图、inode 位图
主要代码
main.c
// 文件: main.c
// 时间: 2024-07-19
// 来自: ccj
// 描述: 内核从此处开始
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall.h"
#include "syscall-init.h"
#include "stdio.h"
#include "memory.h"
int main(void) {
put_str("I am kernel\n");
init_all();
while (1);
return 0;
}
init.c
fs.c
// 文件: fs.c
// 时间: 2024-08-06
// 来自: ccj
// 描述: 文件系统相关
// 1.格式化分区:把分区的超级块、扇区位图、inode位图、inode_table位图初始化
// 2.挂载分区:创建一个分区结构体,在内存中存放要挂在的分区的超级块、扇区位图、inode位图
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "dir.h"
#include "stdint.h"
#include "stdio-kernel.h"
#include "list.h"
#include "string.h"
#include "ide.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
struct partition* cur_part; // 默认情况下操作的是哪个分区
/// @brief 挂载分区链表中找到名为arg的分区
/// @param pelem 分区链表
/// @param arg 分区名
/// @return
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;
// 在内存中创建分区cur_part的超级块
cur_part->sb = (struct super_block*)sys_malloc(sizeof(struct super_block));
if (cur_part->sb == NULL) { PANIC("alloc memory failed!"); }
// 读入超级块
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
memset(sb_buf, 0, SECTOR_SIZE);
ide_read(hd, cur_part->start_lba + 1, sb_buf, 1);
// 把读入的超级块复制给内存中cur_part的超级块
memcpy(cur_part->sb, sb_buf, sizeof(struct super_block));
// 1. 复制扇区位图
// 1.1 申请位图内存
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!"); }
// 1.2 复制位图占用字节
cur_part->block_bitmap.btmp_bytes_len = sb_buf->block_bitmap_sects * SECTOR_SIZE;
// 1.3 把块位图复制到申请的内存块位图中
ide_read(hd, sb_buf->block_bitmap_lba, cur_part->block_bitmap.bits,
sb_buf->block_bitmap_sects);
// 2,复制inode位图
// 2.1 申请位图内存
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!"); }
// 2.2 复制位图占用字节
cur_part->inode_bitmap.btmp_bytes_len = sb_buf->inode_bitmap_sects * SECTOR_SIZE;
// 1.3 把inode位图复制到申请的内存块位图中
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停止遍历
return true;
}
return false; // 使list_traversal继续遍历
}
/// @brief 格式化分区,也就是初始化分区的元信息,创建文件系统
/// @param part 分区
static void partition_format(struct partition* part) {
uint32_t boot_sector_sects = 1; // 引导块 = 1个扇区
uint32_t super_block_sects = 1; // 超级块 = 1个扇区
// I结点位图占用的扇区数 文件数 / 4096(1个扇区512字节=4096比特)
uint32_t inode_bitmap_sects = DIV_ROUND_UP(MAX_FILES_PER_PART, BITS_PER_SECTOR);
// inode表占用的扇区数 总字节 / 512
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);
// 空闲扇区数 - 剩余的空闲扇区数的位图所占用的扇区数 = 扇区位图的位数
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;
// 第0块是引导块,第1块是超级块,第2块开始时扇区位图
sb.block_bitmap_lba = sb.part_lba_base + 2;
sb.block_bitmap_sects = block_bitmap_sects;
// 扇区位图过后是inode位图
sb.inode_bitmap_lba = sb.block_bitmap_lba + sb.block_bitmap_sects;
sb.inode_bitmap_sects = inode_bitmap_sects;
// inode位图过后是inode表
sb.inode_table_lba = sb.inode_bitmap_lba + sb.inode_bitmap_sects;
sb.inode_table_sects = inode_table_sects;
// inode表过后是数据
sb.data_start_lba = sb.inode_table_lba + sb.inode_table_sects;
sb.root_inode_no = 0; // 根节点的inode
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;
// 申请一段足够长的内存,来代表位图的情况,最后把buffer复制给位图
uint8_t* buf = (uint8_t*)sys_malloc(buf_size);
// 2.初始化块位图block_bitmap并写入sb.block_bitmap_lba
buf[0] |= 0x01; // 第0个位预留给根目录,写1表示占用
uint32_t block_bitmap_last_byte = block_bitmap_bit_len / 8; // 位图的字节数
uint8_t block_bitmap_last_bit = block_bitmap_bit_len % 8; // 位图最后小于1字节的比特数
// 2.1 先将位图最后一字节到其所在的扇区的结束全置为1,即超出实际块数的部分直接置为已占用
// block_bitmap_last_byte % 512 = 位图最后不满足1个扇区的字节数
// last_size是位图最后一个字节到这个字节的扇区尾部的字节数
uint32_t last_size = SECTOR_SIZE - (block_bitmap_last_byte % SECTOR_SIZE);
memset(&buf[block_bitmap_last_byte], 0xff, last_size);
// 2.2 再将上一步中覆盖的最后一字节内的有效位重新置0
uint8_t bit_idx = 0;
while (bit_idx <= block_bitmap_last_bit) { buf[block_bitmap_last_byte] &= ~(1 << bit_idx++); }
// 2.3 将buf写入扇区位图的起始扇区
ide_write(hd, sb.block_bitmap_lba, buf, sb.block_bitmap_sects);
// 3 将inode位图初始化并写入sb.inode_bitmap_lba
memset(buf, 0, buf_size);
// 3.1 第0个inode分给了根目录
buf[0] |= 0x1;
// 3.2 inode位图刚好4096字节 = 512*8,直接写入
ide_write(hd, sb.inode_bitmap_lba, buf, sb.inode_bitmap_sects);
// 4 将inode数组初始化并写入sb.inode_table_lba
memset(buf, 0, buf_size);
// 4.1 第0个inode分给了根目录
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;
// 4.2 写入sb.inode_table_lba
ide_write(hd, sb.inode_table_lba, buf, sb.inode_table_sects);
// 5. 将根目录初始化并写入sb.data_start_lba
memset(buf, 0, buf_size);
// 5.1 写入根目录的两个目录项.和..
struct dir_entry* p_de = (struct dir_entry*)buf;
// 5.1.1 初始化当前目录"."
memcpy(p_de->filename, ".", 1);
p_de->i_no = 0;
p_de->f_type = FT_DIRECTORY;
p_de++;
// 5.1.2 初始化当前目录父目录".."
memcpy(p_de->filename, "..", 2);
p_de->i_no = 0; // 根目录的父目录依然是根目录自己
p_de->f_type = FT_DIRECTORY;
// 5.2 写入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);
}
/// @brief 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统
void filesys_init() {
struct super_block* sb_buf = (struct super_block*)sys_malloc(SECTOR_SIZE);
if (sb_buf == NULL) { PANIC("alloc memory failed!"); }
printk("searching filesystem......\n");
uint8_t channel_no = 0;
while (channel_no < channel_cnt) {
uint8_t 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;
uint8_t part_idx = 0;
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 == 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);
}
super_block.h
// 文件: super_block.h
// 时间: 2024-08-06
// 来自: ccj
// 描述: 超级块数据结构,分区的第1个扇区,第0个为引导扇区
#ifndef __FS_SUPER_BLOCK_H
#define __FS_SUPER_BLOCK_H
#include "stdint.h"
/// @brief 超级块
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));
#endif
inode.h
// 文件: inode.h
// 时间: 2024-08-06
// 来自: ccj
// 描述: inode数据结构
#ifndef __FS_INODE_H
#define __FS_INODE_H
#include "stdint.h"
#include "list.h"
/// @brief 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;
};
#endif
dir.h
#ifndef __FS_DIR_H
#define __FS_DIR_H
#include "stdint.h"
#include "inode.h"
#include "ide.h"
#include "global.h"
#define MAX_FILE_NAME_LEN 16 // 最大文件名长度
/// @brief 目录结构
struct dir {
struct inode* inode;
uint32_t dir_pos; // 记录在目录内的偏移
uint8_t dir_buf[512]; // 目录的数据缓存
};
/// @brief 目录项结构
struct dir_entry {
char filename[MAX_FILE_NAME_LEN]; // 普通文件或目录名称
uint32_t i_no; // 普通文件或目录对应的inode编号
enum file_types f_type; // 文件类型
};
#endif