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));
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]; // 目录的数据缓存
};
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, 所以位图中的位全都代表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);
}
当 inode 指代的是普通文件时,成员大小是文件的具体字节大小。当inode 指代的是目录时,成员大小是是此目录下所有目录项的大小之和。
windows系统的分区盘符都是直接摆在那里的,但是Linux却不一样,分区使用的时候要拿出来,不使用的时候可以收起来。
Linux中内核所在的分区是默认分区,自系统启动后就以该分区为默认分区,该分区的根目录是固定存在的,要想使用其他新分区的话,需要用mount 命令手动将新的分区挂载到默认分区的某个目录下。尽管其他分区有自己的根目录,但是默认分区的根目录才是所有分区的父目录。分区不用的时候就要 umount 命令卸载。
挂载分区的实质就是把该分区文件系统的元信息从硬盘上读出来加载到内存中,这样硬盘资源的变化都用内存中元信息来跟踪。如果对硬盘资源有了操作,就要计时的将内存中的元信息同步写入到硬盘以持久化。
struct file {
uint32_t fd_pos; // 记录当前文件操作的偏移地址,以0为起始,最大为文件大小-1
uint32_t fd_flag;
struct inode* fd_inode;
};
我们要实现文件及目录的创建、打开、读写操作,要首先写好相关的函数:inode 操作相关函数、文件相关函数、目录相关函数、路径解析相关函数
static void inode_locate(struct partition* part, uint32_t inode_no, struct inode_position* inode_pos)
void inode_sync(struct partition* part, struct inode* inode, void* io_buf);
struct inode* inode_open(struct partition* part, uint32_t inode_no);
void inode_close(struct inode* inode)
int32_t get_free_slot_in_global(void)
int32_t pcb_fd_install(int32_t globa_fd_idx)
int32_t inode_bitmap_alloc(struct partition* part)
int32_t block_bitmap_alloc(struct partition* part)
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type)
void open_root_dir(struct partition* part) {
root_dir.inode = inode_open(part, part->sb->root_inode_no);
root_dir.dir_pos = 0;
}
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;
}
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;
}
void dir_close(struct dir* dir) {
if (dir == &root_dir) {
return;
}
inode_close(dir->inode);
sys_free(dir);
}
void create_dir_entry(char* filename, uint32_t inode_no, uint8_t file_type, struct dir_entry* p_de) {
memcpy(p_de->filename, filename, strlen(filename));
p_de->i_no = inode_no;
p_de->f_type = file_type;
}
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); // dir_size应该是dir_entry_size的整数倍
uint32_t dir_entrys_per_sec = (512 / dir_entry_size); // 每扇区最大的目录项数目
int32_t block_lba = -1;
/* 将该目录的所有扇区地址(12个直接块+ 128个间接块)存入all_blocks */
uint8_t block_idx = 0;
uint32_t all_blocks[140] = {0}; // all_blocks保存目录所有的块
/* 将12个直接块存入all_blocks */
while (block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
struct dir_entry* dir_e = (struct dir_entry*)io_buf; // dir_e用来在io_buf中遍历目录项
int32_t block_bitmap_idx = -1;
/* 开始遍历所有块以寻找目录项空位,若已有扇区中没有空闲位,
* 在不超过文件大小的情况下申请新扇区来存储新目录项 */
block_idx = 0;
while (block_idx < 140) { // 文件(包括目录)最大支持12个直接块+128个间接块=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;
block_lba = block_bitmap_alloc(cur_part); // 再分配一个块做为第0个间接块
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) { // FT_UNKNOWN为0,无论是初始化或是删除文件后,都会将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;
}
//功能: 将最上层路径名称解析出来储存到 name_store
//实现:比如我们要解析:/a/b/c、///a/b/c,调用函数后 name_store的值为 'a',返回 “/b/c ”。
static 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;
}
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;
}
在打开文件之前,文件系统要确认文件是否在磁盘上存在。在创建文件之前,文件系统要确认文件所在的目录中是否已有同名文件存在。
//用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方"
//用这个结构的目的是想获得路径中“断链”的部分,如查找 a/b/c ,若找不到的话,我们想知道是c不存在,还是b或者a不存在。成员 searched_path[] 就是查找过程中不存在的路径,如果是 c 不存在,seached_path 的值为 “a/b/c”,若 b 不存在,值为 “ /a/b ”,并且找不到的话是未知类型 FT_UNKOEN。如果找得到的话,search_path[] 的值是直接父目录,file_type是 目录或者文件。
struct path_search_record {
char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
struct dir* parent_dir; // 文件或目录所在的直接父目录
enum file_types file_type; // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};
//功能:搜索文件pathname,若找到则返回其inode号,否则返回-1
//功能:在目录parent_dir 中以模式 flag 去创建普通文件 filename,若成功则返回文件描述符,即 pcb->fb_table 中的下标,否则返回-1
//实现:创建文件需要的操作:1,每个文件需要一个 inode 来描述大小、属性等等,所以创建文件要在 inode_table 中申请一个空闲位,所以我们我们需要现在 内存inode位图 中 ,因此要同步硬盘的位图。2,申请好 inode 后需要 空闲数据块 来储存数据,所以在 内存空闲数据位图 申请一个空闲位,并要同步硬盘。3,新文件必定要属于某一个目录,所以该目录的 inode->i_size 要增加个目录项的大小,并且新加的目录项还要写入该目录的 inode->i_sectors[] 中的某个扇区中,原有扇区可能已满,所以要申请新扇区来储存目录项。4,还要注意 inode_table、block_bitmap、新文件的inode 及文件所在目录的inode 这些同步问题。
因此我们创建文件的顺序:创建新文件i结点-》文件描述符fd-》目录项。
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag) {
/* 后续操作的公共缓冲区 */
void* io_buf = sys_malloc(1024);
if (io_buf == NULL) {
printk("in file_creat: sys_malloc for io_buf failed\n");
return -1;
}
uint8_t rollback_step = 0; // 用于操作失败时回滚各资源状态
/* 为新文件分配inode */
int32_t inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1) {
printk("in file_creat: allocate inode failed\n");
return -1;
}
/* 此inode要从堆中申请内存,不可生成局部变量(函数退出时会释放)
* 因为file_table数组中的文件描述符的inode指针要指向它.*/
struct inode* new_file_inode = (struct inode*)sys_malloc(sizeof(struct inode));
if (new_file_inode == NULL) {
printk("file_create: sys_malloc for inode failded\n");
rollback_step = 1;
goto rollback;
}
inode_init(inode_no, new_file_inode); // 初始化i结点
/* 返回的是file_table数组的下标 */
int fd_idx = get_free_slot_in_global();
if (fd_idx == -1) {
printk("exceed max open files\n");
rollback_step = 2;
goto rollback;
}
file_table[fd_idx].fd_inode = new_file_inode;
file_table[fd_idx].fd_pos = 0;
file_table[fd_idx].fd_flag = flag;
file_table[fd_idx].fd_inode->write_deny = false;
struct dir_entry new_dir_entry;
memset(&new_dir_entry, 0, sizeof(struct dir_entry));
create_dir_entry(filename, inode_no, FT_REGULAR, &new_dir_entry); // create_dir_entry只是内存操作不出意外,不会返回失败
/* 同步内存数据到硬盘 */
/* a 在目录parent_dir下安装目录项new_dir_entry, 写入硬盘后返回true,否则false */
if (!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
printk("sync dir_entry to disk failed\n");
rollback_step = 3;
goto rollback;
}
memset(io_buf, 0, 1024);
/* b 将父目录i结点的内容同步到硬盘 */
inode_sync(cur_part, parent_dir->inode, io_buf);
memset(io_buf, 0, 1024);
/* c 将新创建文件的i结点内容同步到硬盘 */
inode_sync(cur_part, new_file_inode, io_buf);
/* d 将inode_bitmap位图同步到硬盘 */
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
/* e 将创建的文件i结点添加到open_inodes链表 */
list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
new_file_inode->i_open_cnts = 1;
sys_free(io_buf);
return pcb_fd_install(fd_idx);
/*创建文件需要创建相关的多个资源,若某步失败则会执行到下面的回滚步骤 */
rollback:
switch (rollback_step) {
case 3:
/* 失败时,将file_table中的相应位清空 */
memset(&file_table[fd_idx], 0, sizeof(struct file));
case 2:
sys_free(new_file_inode);
case 1:
/* 如果新文件的i结点创建失败,之前位图中分配的inode_no也要恢复 */
bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
break;
}
sys_free(io_buf);
return -1;
}
sys_open() 是 open()函数的内核级实现(系统调用)。create()函数可以创建文件,open()的功能很多,也可以创建文件。
//功能:接受两个参数:pathname 是待打开的文件,为绝对路径。flags 是打开标识。目录以字符 “/” 结尾,因此程序开头判断pathname 是否为目录,是对 pathname 的最后一个字判断,这个函数只支持文件打开,不支持目录打开。
//实现:
1,我们这个函数仅仅支持普通文件的打开和创建,所以要首先判断这个文件是不是目录文件:仅仅用最后的 / 来判断。如果是目录文件的话直接返回。
2,然后判断这个文件存不存在,如果存在先判断是不是目录文件,因为目录文件和普通文件不能同名,可能我们的路径最后没有 / ,但是是个目录。如果是目录就报错。如果不是目录就说明这个普通文件存在,接着判断 flags 的值是否有创建,有创建则报错退出,没有创建则返回文件标识符。
3,如果这个文件不存在,先要判断在路径哪一层断开的,如果是父层断开的,开始判断flags的值有没有创建,有创建就创建后返回标识符,没有创建报错返回。如果是中间层就断开了就报错返回。
int32_t sys_open(const char* pathname, uint8_t flags) {
/* 对目录要用dir_open,这里只有open普通文件 */
if (pathname[strlen(pathname) - 1] == '/') {
printk("can`t open a directory %s\n",pathname);
return -1;
}
ASSERT(flags <= 7);
int32_t fd = -1; // 默认为找不到
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
/* 记录目录深度.帮助判断中间某个目录不存在的情况 */
uint32_t pathname_depth = path_depth_cnt((char*)pathname);
/* 先检查文件是否存在 */
int inode_no = search_file(pathname, &searched_record);
bool found = inode_no != -1 ? true : false;
if (searched_record.file_type == FT_DIRECTORY) {
printk("can`t open a direcotry with open(), use opendir() to instead\n");
dir_close(searched_record.parent_dir);
return -1;
}
uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);
/* 先判断是否把pathname的各层目录都访问到了,即是否在某个中间目录就失败了 */
if (pathname_depth != path_searched_depth) { // 说明并没有访问到全部的路径,某个中间目录是不存在的
printk("cannot access %s: Not a directory, subpath %s is`t exist\n", \
pathname, searched_record.searched_path);
dir_close(searched_record.parent_dir);
return -1;
}
/* 若是在最后一个路径上没找到,并且并不是要创建文件,直接返回-1 */
if (!found && !(flags & O_CREAT)) {
printk("in path %s, file %s is`t exist\n", \
searched_record.searched_path, \
(strrchr(searched_record.searched_path, '/') + 1));
dir_close(searched_record.parent_dir);
return -1;
} else if (found && flags & O_CREAT) { // 若要创建的文件已存在
printk("%s has already exist!\n", pathname);
dir_close(searched_record.parent_dir);
return -1;
}
switch (flags & O_CREAT) {
case O_CREAT:
printk("creating file\n");
fd = file_create(searched_record.parent_dir, (strrchr(pathname, '/') + 1), flags);
dir_close(searched_record.parent_dir); break; default;
// 其余为打开文件 fd = file_open(inode_no, flags);
}
/* 此fd是指任务pcb->fd_table数组中的元素下标,
* 并不是指全局file_table中的下标 */
return fd;
}
有关文件读写的函数都要用文件描述符作为参数,所以文件的读写操作通常是在文件被打开返回描述符之后。
//功能:打开编号为inode_no的inode对应的文件,若成功则返回文件描述符,否则返回-1
//实现:一个文件无论打开多少次每次都要对应一个文件结构,所以首先我们要获得一个文件结构的下标,这样就可以得到文件描述符了。在文件结构中储存着这个文件的基本信息:inode、权限、偏移地址。如果我们是以读和创建的方式打开文件的,返回文件描述符。如果是以写的方式打开文件的,就要检查inode中的write_deny位了,因为为了避免多个任务同时写文件而引起相互覆盖的混乱,所以我们要只保证同一时间只有一个任务在写文件,所以如果write_deny是0,表示没有任务写,我们把write_deny置1即可。如果write_deny的值是1,表示其他任务提前以写的方式执行了这个函数已经将write_deny置1了,所以报错退出。
所以顺序是:文件结构-》inode-》write_deny位-》fd
int32_t file_open(uint32_t inode_no, uint8_t flag) {
int fd_idx = get_free_slot_in_global();
if (fd_idx == -1) {
printk("exceed max open files\n");
return -1;
}
file_table[fd_idx].fd_inode = inode_open(cur_part, inode_no);
file_table[fd_idx].fd_pos = 0; // 每次打开文件,要将fd_pos还原为0,即让文件内的指针指向开头
file_table[fd_idx].fd_flag = flag;
bool* write_deny = &file_table[fd_idx].fd_inode->write_deny;
if (flag & O_WRONLY || flag & O_RDWR) { // 只要是关于写文件,判断是否有其它进程正写此文件
// 若是读文件,不考虑write_deny
/* 以下进入临界区前先关中断 */
enum intr_status old_status = intr_disable();
if (!(*write_deny)) { // 若当前没有其它进程写该文件,将其占用.
*write_deny = true; // 置为true,避免多个进程同时写此文件
intr_set_status(old_status); // 恢复中断
} else { // 直接失败返回
intr_set_status(old_status);
printk("file can`t be write now, try again later\n");
return -1;
}
} // 若是读文件或创建文件,不用理会write_deny,保持默认
return pcb_fd_install(fd_idx);
}
//实现:首先将 内存中的inode的write_deny 的置0,表示这个写操作已经完成了,其他任务可以用了。然后将文件结构清零,使这个项可以被使用。然后 inode_close(file->fd_inode) :如果这个 内存堆中inode 打开数减一不为零,表示有其他认为在打开这个文件,则这个堆继续保留使用。若为零,表示没有堆使用了,则回收这个堆。最后回收文件描述符,使其可用。所以顺序是:write_deny-》文件结构-》inode-》fd
int32_t file_close(struct file* file) {
if (file == NULL) {
return -1;
}
file->fd_inode->write_deny = false;
inode_close(file->fd_inode);
file->fd_inode = NULL; // 使文件结构可用
return 0;
}
int32_t sys_close(int32_t fd) {
int32_t ret = -1; // 返回值默认为-1,即失败
if (fd > 2) {
uint32_t _fd = fd_local2global(fd);
ret = file_close(&file_table[_fd]);
running_thread()->fd_table[fd] = -1; // 使该文件描述符位可用
}
return ret;
}
sys_write 是系统调用 write 的内核实现。
//功能:把buf中的count个字节写入file,成功则返回写入的字节数,失败则返回-1
//实现:普通文件的数据是训练连续写入块中的,目录文件中目录项可以是离散的。如果是第一次写入数据要根据 内存空闲块位图 为其分配一个块地址,复制到 内存inode 中,然后同步。接着根据 内存inode 开始计算这个文件过去占了几个数据块了,根据 将要写入的字节数 判断将来会是几个数据块,然后算出还需要分配几个数据块出来。如果不需要分配数据块,就要判断文件已经占用的数据块数量是否大于12了。如果小于12,则直接可以算出 内存inode->i_sectors[,,],存入 all_blocks[block_idx]。如果大于12,则可以通过fd_inode->i_sectors[13],将 一级间接块的地址读出到all_blocks[]中。如果还需要分配 数据块,则要判断将来的总共使用的数据块是否大于12了。如果将来总共的数据块小于12,那么目前已经使用的数据块的最后一个块一般都不满,则要将块地址读入all_blocks[]中,然后在申请几个块满足需要,并将块地址写入all_blocks[]中。如果旧数据在12个块之内,但是将来总共的数据块在12个块之上,就需要申请间接块了。先将第11个块地址存入all_blocks[ ] 中,然后由 内存空闲块位图 申请一个块地址 作为间接块,地址存入 内存inode 中,然后申请将来需要的数据块,块地址存入 inode->i_sectors[ ] 和 all_blocks[ ] 中和 间接块中。注意元信息和 inode 、间接块的同步。如果旧数据就已经大于12了,需要 将间接块地址读入 all_blocks[ ] 中,然后申请空闲数据块写入到 all_blocks[ ]。此时可以 写数据块地址 已经收集到 all_blocks 中,下面开始写数据。all_blocks[ ]中包含可以继续使用的,含有剩余空间的块地址,以及新数据要占用的新的块地址。因此第一次写数据时候要把该块中的数据读出来,将新数据写入该块的空闲区域,之后新老数据一起写入硬盘,这样就实现了追加的功能。
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
if ((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140)) { // 文件目前最大只支持512*140=71680字节
printk("exceed max file_size 71680 bytes, write file failed\n");
return -1;
}
uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
if (io_buf == NULL) {
printk("file_write: sys_malloc for io_buf failed\n");
return -1;
}
uint32_t* all_blocks = (uint32_t*)sys_malloc(BLOCK_SIZE + 48); // 用来记录文件所有的块地址
if (all_blocks == NULL) {
printk("file_write: sys_malloc for all_blocks failed\n");
return -1;
}
const uint8_t* src = buf; // 用src指向buf中待写入的数据
uint32_t bytes_written = 0; // 用来记录已写入数据大小
uint32_t size_left = count; // 用来记录未写入数据大小
int32_t block_lba = -1; // 块地址
uint32_t block_bitmap_idx = 0; // 用来记录block对应于block_bitmap中的索引,做为参数传给bitmap_sync
uint32_t sec_idx; // 用来索引扇区
uint32_t sec_lba; // 扇区地址
uint32_t sec_off_bytes; // 扇区内字节偏移量
uint32_t sec_left_bytes; // 扇区内剩余字节量
uint32_t chunk_size; // 每次写入硬盘的数据块大小
int32_t indirect_block_table; // 用来获取一级间接表地址
uint32_t block_idx; // 块索引
/* 判断文件是否是第一次写,如果是,先为其分配一个块 */
if (file->fd_inode->i_sectors[0] == 0) {
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("file_write: block_bitmap_alloc failed\n");
return -1;
}
file->fd_inode->i_sectors[0] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
ASSERT(block_bitmap_idx != 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
/* 写入count个字节前,该文件已经占用的块数 */
uint32_t file_has_used_blocks = file->fd_inode->i_size / BLOCK_SIZE + 1;
/* 存储count字节后该文件将占用的块数 */
uint32_t file_will_use_blocks = (file->fd_inode->i_size + count) / BLOCK_SIZE + 1;
ASSERT(file_will_use_blocks <= 140);
/* 通过此增量判断是否需要分配扇区,如增量为0,表示原扇区够用 */
uint32_t add_blocks = file_will_use_blocks - file_has_used_blocks;
/* 开始将文件所有块地址收集到all_blocks,(系统中块大小等于扇区大小)
* 后面都统一在all_blocks中获取写入扇区地址 */
if (add_blocks == 0) {
/* 在同一扇区内写入数据,不涉及到分配新扇区 */
if (file_has_used_blocks <= 12 ) { // 文件数据量将在12块之内
block_idx = file_has_used_blocks - 1; // 指向最后一个已有数据的扇区
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
} else {
/* 未写入新数据之前已经占用了间接块,需要将间接块地址读进来 */
ASSERT(file->fd_inode->i_sectors[12] != 0);
indirect_block_table = file->fd_inode->i_sectors[12];
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
}
} else {
/* 若有增量,便涉及到分配新扇区及是否分配一级间接块表,下面要分三种情况处理 */
/* 第一种情况:12个直接块够用*/
if (file_will_use_blocks <= 12 ) {
/* 先将有剩余空间的可继续用的扇区地址写入all_blocks */
block_idx = file_has_used_blocks - 1;
ASSERT(file->fd_inode->i_sectors[block_idx] != 0);
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
/* 再将未来要用的扇区分配好后写入all_blocks */
block_idx = file_has_used_blocks; // 指向第一个要分配的新扇区
while (block_idx < file_will_use_blocks) {
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("file_write: block_bitmap_alloc for situation 1 failed\n");
return -1;
}
/* 写文件时,不应该存在块未使用但已经分配扇区的情况,当文件删除时,就会把块地址清0 */
ASSERT(file->fd_inode->i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
block_idx++; // 下一个分配的新扇区
}
} else if (file_has_used_blocks <= 12 && file_will_use_blocks > 12) {
/* 第二种情况: 旧数据在12个直接块内,新数据将使用间接块*/
/* 先将有剩余空间的可继续用的扇区地址收集到all_blocks */
block_idx = file_has_used_blocks - 1; // 指向旧数据所在的最后一个扇区
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
/* 创建一级间接块表 */
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("file_write: block_bitmap_alloc for situation 2 failed\n");
return -1;
}
ASSERT(file->fd_inode->i_sectors[12] == 0); // 确保一级间接块表未分配
/* 分配一级间接块索引表 */
indirect_block_table = file->fd_inode->i_sectors[12] = block_lba;
block_idx = file_has_used_blocks; // 第一个未使用的块,即本文件最后一个已经使用的直接块的下一块
while (block_idx < file_will_use_blocks) {
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("file_write: block_bitmap_alloc for situation 2 failed\n");
return -1;
}
if (block_idx < 12) { // 新创建的0~11块直接存入all_blocks数组
ASSERT(file->fd_inode->i_sectors[block_idx] == 0); // 确保尚未分配扇区地址
file->fd_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba;
} else { // 间接块只写入到all_block数组中,待全部分配完成后一次性同步到硬盘
all_blocks[block_idx] = block_lba;
}
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
block_idx++; // 下一个新扇区
}
ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
} else if (file_has_used_blocks > 12) {
/* 第三种情况:新数据占据间接块*/
ASSERT(file->fd_inode->i_sectors[12] != 0); // 已经具备了一级间接块表
indirect_block_table = file->fd_inode->i_sectors[12]; // 获取一级间接表地址
/* 已使用的间接块也将被读入all_blocks,无须单独收录 */
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 获取所有间接块地址
block_idx = file_has_used_blocks; // 第一个未使用的间接块,即已经使用的间接块的下一块
while (block_idx < file_will_use_blocks) {
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("file_write: block_bitmap_alloc for situation 3 failed\n");
return -1;
}
all_blocks[block_idx++] = block_lba;
/* 每分配一个块就将位图同步到硬盘 */
block_bitmap_idx = block_lba - cur_part->sb->data_start_lba;
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
ide_write(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1); // 同步一级间接块表到硬盘
}
}
bool first_write_block = true; // 含有剩余空间的扇区标识
/* 块地址已经收集到all_blocks中,下面开始写数据 */
file->fd_pos = file->fd_inode->i_size - 1; // 置fd_pos为文件大小-1,下面在写数据时随时更新
while (bytes_written < count) { // 直到写完所有数据
memset(io_buf, 0, BLOCK_SIZE);
sec_idx = file->fd_inode->i_size / BLOCK_SIZE;
sec_lba = all_blocks[sec_idx];
sec_off_bytes = file->fd_inode->i_size % BLOCK_SIZE;
sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
/* 判断此次写入硬盘的数据大小 */
chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;
if (first_write_block) {
ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
first_write_block = false;
}
memcpy(io_buf + sec_off_bytes, src, chunk_size);
ide_write(cur_part->my_disk, sec_lba, io_buf, 1);
printk("file write at lba 0x%x\n", sec_lba); //调试,完成后去掉
src += chunk_size; // 将指针推移到下个新数据
file->fd_inode->i_size += chunk_size; // 更新文件大小
file->fd_pos += chunk_size;
bytes_written += chunk_size;
size_left -= chunk_size;
}
inode_sync(cur_part, file->fd_inode, io_buf);
sys_free(all_blocks);
sys_free(io_buf);
return bytes_written;
}
//功能:将buf中连续count个字节写入文件描述符fd,成功则返回写入的字节数,失败返回-1
int32_t sys_write(int32_t fd, const void* buf, uint32_t count) {
if (fd < 0) {
printk("sys_write: fd error\n");
return -1;
}
if (fd == stdout_no) {
char tmp_buf[1024] = {0};
memcpy(tmp_buf, buf, count);
console_put_str(tmp_buf);
return count;
}
uint32_t _fd = fd_local2global(fd);
struct file* wr_file = &file_table[_fd];
if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR) {
uint32_t bytes_written = file_write(wr_file, buf, count);
return bytes_written;
} else {
console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
return -1;
}
}
我们删除空目录,在删除目录时先判断目录是否为空,不允许删除非空目录
//功能:判断目录是否为空
//实现:若目录下只有.和..这两个目录项则目录为空
bool dir_is_empty(struct dir* dir) {
struct inode* dir_inode = dir->inode;
/* 若目录下只有.和..这两个目录项则目录为空 */
return (dir_inode->i_size == cur_part->sb->dir_entry_size * 2);
}
//功能:在父目录parent_dir中删除child_dir
//实现:我们要删除的child_dir 必须是个空目录,空目录只在inode->i_sectors[0]中有扇区,其它扇区都应该为空
int32_t dir_remove(struct dir* parent_dir, struct dir* child_dir) {
struct inode* child_dir_inode = child_dir->inode;
/* 空目录只在inode->i_sectors[0]中有扇区,其它扇区都应该为空 */
int32_t block_idx = 1;
while (block_idx < 13) {
ASSERT(child_dir_inode->i_sectors[block_idx] == 0);
block_idx++;
}
void* io_buf = sys_malloc(SECTOR_SIZE * 2);
if (io_buf == NULL) {
printk("dir_remove: malloc for io_buf failed\n");
return -1;
}
/* 在父目录parent_dir中删除子目录child_dir对应的目录项 */
delete_dir_entry(cur_part, parent_dir, child_dir_inode->i_no, io_buf);
/* 回收inode中i_secotrs中所占用的扇区,并同步inode_bitmap和block_bitmap */
inode_release(cur_part, child_dir_inode->i_no);
sys_free(io_buf);
return 0;
}
//功能: 删除空目录,成功时返回0,失败时返回-1
//实现:先要确定目录是否存在,然后确定这个是目录还是普通文件,最后确定目录是否为空,最后删除
int32_t sys_rmdir(const char* pathname) {
/* 先检查待删除的文件是否存在 */
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(pathname, &searched_record);
ASSERT(inode_no != 0);
int retval = -1; // 默认返回值
if (inode_no == -1) {
printk("In %s, sub path %s not exist\n", pathname, searched_record.searched_path);
} else {
if (searched_record.file_type == FT_REGULAR) {
printk("%s is regular file!\n", pathname);
} else {
struct dir* dir = dir_open(cur_part, inode_no);
if (!dir_is_empty(dir)) { // 非空目录不可删除
printk("dir %s is not empty, it is not allowed to delete a nonempty directory!\n", pathname);
} else {
if (!dir_remove(searched_record.parent_dir, dir)) {
retval = 0;
}
}
dir_close(dir);
}
}
dir_close(searched_record.parent_dir);
return retval;
}