代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
接下来就是实现文件和目录了
1.文件相关函数
文件操作相关的函数定义在 fs/file.c 和 fs/file.h 中
新建/fs/file.h
#ifndef __FS_FILE_H
#define __FS_FILE_H
#include "stdint.h"
#include "ide.h"
#include "dir.h"
#include "global.h"
/* 文件结构 */
struct file {
uint32_t fd_pos; // 记录当前文件操作的偏移地址,以0为起始,最大为文件大小-1
uint32_t fd_flag;
struct inode* fd_inode;
};
/* 标准输入输出描述符 */
enum std_fd {
stdin_no, // 0 标准输入
stdout_no, // 1 标准输出
stderr_no // 2 标准错误
};
/* 位图类型 */
enum bitmap_type {
INODE_BITMAP, // inode位图
BLOCK_BITMAP // 块位图
};
#define MAX_FILE_OPEN 32 // 系统可打开的最大文件数
extern struct file file_table[MAX_FILE_OPEN];
int32_t inode_bitmap_alloc(struct partition* part);
int32_t block_bitmap_alloc(struct partition* part);
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag);
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp);
int32_t get_free_slot_in_global(void);
int32_t pcb_fd_install(int32_t globa_fd_idx);
#endifstruct file就是所说的文件结构,其中的fd_pos用于记录当前文件操作的偏移地址,该值最小是0,最 大为文件大小减1,fd_flag是文件操作标识,如O_RDONLY,fd_inode是inode指针,用来指向inode队 列(part-> open_inodes)中的inode。
enum std_fd是标准文件描述符,在Linux中0表示标准输入, 1表示标准输出,2表示标准错误,未打算实现标准错误,因此只定义了前两个,stdin_no表示标准输入,其值为0,stdout_no表示标准输出,其值为1。
enum bitmap_type是位图类型,包括INODE_BITMAP和BLOCK_BITMAP,我们以后在往硬盘上同步位图的时候会用到此结构。
宏MAX FILE OPEN的值是32,这是系统可打开的最大文件数。
新建/fs/file.c
#include "file.h"
#include "fs.h"
#include "super_block.h"
#include "inode.h"
#include "stdio-kernel.h"
#include "memory.h"
#include "debug.h"
#include "interrupt.h"
#include "string.h"
#include "thread.h"
#include "global.h"
#define DEFAULT_SECS 1
/* 文件表 */
struct file file_table[MAX_FILE_OPEN];
/* 从文件表file_table中获取一个空闲位,成功返回下标,失败返回-1 */
int32_t get_free_slot_in_global(void) {
uint32_t fd_idx = 3;
while (fd_idx < MAX_FILE_OPEN) {
if (file_table[fd_idx].fd_inode == NULL) {
break;
}
fd_idx++;
}
if (fd_idx == MAX_FILE_OPEN) {
printk("exceed max open files\n");
return -1;
}
return fd_idx;
}
/* 将全局描述符下标安装到进程或线程自己的文件描述符数组fd_table中,
* 成功返回下标,失败返回-1 */
int32_t pcb_fd_install(int32_t globa_fd_idx) {
struct task_struct* cur = running_thread();
uint8_t local_fd_idx = 3; // 跨过stdin,stdout,stderr
while (local_fd_idx < MAX_FILES_OPEN_PER_PROC) {
if (cur->fd_table[local_fd_idx] == -1) { // -1表示free_slot,可用
cur->fd_table[local_fd_idx] = globa_fd_idx;
break;
}
local_fd_idx++;
}
if (local_fd_idx == MAX_FILES_OPEN_PER_PROC) {
printk("exceed max open files_per_proc\n");
return -1;
}
return local_fd_idx;
}
/* 分配一个i结点,返回i结点号 */
int32_t inode_bitmap_alloc(struct partition* part) {
int32_t bit_idx = bitmap_scan(&part->inode_bitmap, 1);
if (bit_idx == -1) {
return -1;
}
bitmap_set(&part->inode_bitmap, bit_idx, 1);
return bit_idx;
}
/* 分配1个扇区,返回其扇区地址 */
int32_t block_bitmap_alloc(struct partition* part) {
int32_t bit_idx = bitmap_scan(&part->block_bitmap, 1);
if (bit_idx == -1) {
return -1;
}
bitmap_set(&part->block_bitmap, bit_idx, 1);
/* 和inode_bitmap_malloc不同,此处返回的不是位图索引,而是具体可用的扇区地址 */
return (part->sb->data_start_lba + bit_idx);
}
/* 将内存中bitmap第bit_idx位所在的512字节同步到硬盘 */
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type) {
uint32_t off_sec = bit_idx / 4096; // 本i结点索引相对于位图的扇区偏移量
uint32_t off_size = off_sec * BLOCK_SIZE; // 本i结点索引相对于位图的字节偏移量
uint32_t sec_lba;
uint8_t* bitmap_off;
/* 需要被同步到硬盘的位图只有inode_bitmap和block_bitmap */
switch (btmp_type) {
case INODE_BITMAP:
sec_lba = part->sb->inode_bitmap_lba + off_sec;
bitmap_off = part->inode_bitmap.bits + off_size;
break;
case BLOCK_BITMAP:
sec_lba = part->sb->block_bitmap_lba + off_sec;
bitmap_off = part->block_bitmap.bits + off_size;
break;
}
ide_write(part->my_disk, sec_lba, bitmap_off, 1);
}file_table 是文件表,也就是文件结构数组,它的长度是 MAX_FILE_OPEN,也就是最多可同时打开MAX_FILE_OPEN次文件。
get_free_slot_in_global函数功能是从文件表file_table中获取一个空闲位,成功则返回空闲位下标, 失败则返回-1。 实现原理是遍历file_table,找出fd_inode为null的数组元素,该元素表示为空,将其下标返回即可。 另外,file_table中的前3个成员预留给标准输入、标准输出及标准错误,以后需要用到它们。
peb_fd_install函数接受1个参数,全局描述符下标globa_fd_idx,也就是数组file_table的下标。 函数功能是将globa_fd_idx安装到进程或线程自己的文件描述符数组fd_table中,成功则返回fd_table中空位的下标,失败则返回-1。 位于pcb中的fd_table,前3个元素是标准文件描述符,分别表示标准输入、标准输出和标准错误,不能占用,其余的都是可分配的描述符,我们已在初始化线程时将这些可分配的描述符置为-1。 因此只要fd_table数组元素为-1就表示空位、可分配的文件描述符,具体实现是while (local_fd_idx < MAX_FILES_OPEN_PER_PROC)循环。
inode_bitmap_alloc函数接受1个参数,分区part,功能是分配一个i结点,返回i结点号。就是位图操作。
block_bitmap_alloc函数接受1个参数,分区part,功能是分配1个扇区,返回其扇区地址。 其实现也是位图操作。
函数bitmap_sync接受3个参数,分区part、位索引bit_idx、位图类型btmp_type,功能是将内存中bitmap第bit_idx位所在的512字节同步到硬盘。 因为硬盘是以扇区为读写单位,所以内存中的数据也要一次操作1扇区,函数开头要确定待同步的位属于哪一个512字节中,uint32_t off_sec = bit_idx / 4096用 bit_idx/4096计算第bit_idx位相对于位图的以扇区为单位的偏移量,结果存入off_sec,也就是说第bit_idx位属于位图中第off_sec个扇区大小的内存中,不过off_sec用于计算写入硬盘的扇区地址,下一行将off_sec乘以BLOCK_SIZE获得该位相对于位图的字节偏移量,结果存入off_size, off_size才用于待写入硬盘的内存数据,它是第bit_idx位所在位图中以 512 字节为单位的起始地址。文件系统中目前有两种位图,inode 位图及块位图,下面的 switch 结构通过位图类型 btmp_type来判断同步哪种位图,分别计算出位图的扇区地址 sec_lba 和字节偏移 量bitmap off,bitmap off是待同步数据的起始地址,最后通过ide write将位图写入硬盘。
2.目录相关函数
新建/fs/dir.c
#include "dir.h"
#include "stdint.h"
#include "inode.h"
#include "file.h"
#include "fs.h"
#include "stdio-kernel.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
#include "string.h"
#include "interrupt.h"
#include "super_block.h"
struct dir root_dir; // 根目录
/* 打开根目录 */
void open_root_dir(struct partition* part) {
root_dir.inode = inode_open(part, part->sb->root_inode_no);
root_dir.dir_pos = 0;
}
/* 在分区part上打开i结点为inode_no的目录并返回目录指针 */
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;
}
/* 在part分区内的pdir目录内寻找名为name的文件或目录,
* 找到后返回true并将其目录项存入dir_e,否则返回false */
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) {
/************* 根目录不能关闭 ***************
*1 根目录自打开后就不应该关闭,否则还需要再次open_root_dir();
*2 root_dir所在的内存是低端1M之内,并非在堆中,free会出问题 */
if (dir == &root_dir) {
/* 不做任何处理直接返回*/
return;
}
inode_close(dir->inode);
sys_free(dir);
}
/* 在内存中初始化目录项p_de */
void create_dir_entry(char* filename, uint32_t inode_no, uint8_t file_type, struct dir_entry* p_de) {
ASSERT(strlen(filename) <= MAX_FILE_NAME_LEN);
/* 初始化目录项 */
memcpy(p_de->filename, filename, strlen(filename));
p_de->i_no = inode_no;
p_de->f_type = file_type;
}
/* 将目录项p_de写入父目录parent_dir中,io_buf由主调函数提供 */
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;
}
程序开头定义了 root_dir,它是分区的根目录。
open_root_dir函数接受一个参数,分区part,功能是打开分区part的根目录。 先调用 inode_open打开根目录的inode,然后将其赋值给根目录的inode指针。随后将根目录的dir_pos置为0。
dir_open函数接受两个参数,分区part和inode编号inode_no,功能是在分区part上打开i结点为inode_no的目录并返回目录指针。 此函数与open_root_dir类似,区别是根目录root_dir是提前定义好的全局变量,免去了为其申请内存的过程,这里要为其他目录单独申请内存,也就是struct dir* pdir = (struct dir*)sys_malloc(sizeof(struct dir))代码的功能。
search_dir_entry函数接受4个参数,分区part、目录指针pdir、文件名name、目录项指针dir_e,函数功能是在part分区内的pdir目录内寻找名为name的文件或目录,找到后返回 true 并将其目录项存入 dir_e,否则返回 false。函数开头定义了变量block_cnt,表示inode总的块数,其值为140,即12个直接块+128个一级间接块。 接下来uint32_t* all_blocks = (uint32_t*)sys_malloc(48 + 512)为这140个扇区地址申请内存,返回地址赋值给 all_blocks,这样做是为了方便检索此 inode 的全部扇区地址,我们以后对此目录inode所在的扇区地址都统一从all_blocks中获取,因此while (block_idx < 12)先将目录inode的i_sectors中的前12个扇区地址录入到all_blocks中,随后在if (pdir->inode->i_sectors[12] != 0)判断是否有一 级间接块索引表,只要i_sectors[12]不为0就表示有一级间接块索引表,如果有,就从硬盘的扇区地址 i_sectors[12]处获取1扇区数据,此数据是128个间接块地址,将其复制到all_blocks+12字节处。 至此,all_blocks 被写满了,其存储的是目录pdir的所有扇区地址。
我们在处理 inode_table 时,是将它连续写在多个扇区中,也就是除最后一个扇区外,其他几个扇区都写满了inode,从而导致了inode跨扇区的情况,以至于在获取inode的时候要做额外判断,比较麻烦。 吸取经验教训,我们在往目录中写目录项的时候,写入的都是完整的目录项,避免了目录项跨扇区的情况,因此在实际搜索目录项的时候每次只从硬盘读取一扇区就好了,所以在uint8_t* buf = (uint8_t*)sys_malloc(SECTOR_SIZE),我们为缓冲区buf申请的内存大小是SECTOR_SIZE,即1扇区。 struct dir_entry* p_de = (struct dir_entry*)buf将缓冲区转换为目录项struct dir_entry类型,赋值给p_de,后面将开始用p_de遍历buf。 然后计算一扇区内容纳的目录项数,结果存入变量dir_entry_cnt。
由于我们不知道目录项在目录的哪个扇区中存在,所以在while (block_idx < block_cnt)的while循环开始,我们在该目录所有的扇区中查找目录项。 目录的所有扇区地址已经被收录到all_blocks中,在if (all_blocks[block_idx] == 0)判断,如果扇区地址为0,这说明未分配扇区,跳过,继续下一次循环。这里要说明一下,尽管我们将来往目录中写目录项时,是优先在inode->i_sectors扇区数组中靠前的扇区中写入,前面的扇区无法容纳完整目录项后才会占用新的扇区。但将来我们还会实现删除文件和目录的功能,删除文件的顺序可是随机的,这取决于文件所在的扇区。若目录中的文件或子目录过多,占用了多个扇区,执行删除文件时,有可能被删除的文件位于前面扇区中,后面扇区中的文件还在,比如目录a中先生成了10个文件,假如这10个文件位于扇区地址i_sector[0]处,正巧这个扇区中只能容纳10个目录项,后来又在目录a中生成了第11个文件,该文件占用的扇区地址是i_sector[1],现在将前10个文件全部删除,删除后,扇区i_sector[0]上已无文件,因此将i_sector[0]回收,那时会将i_sector[0]赋值为0,但目录a的扇区地址i_sector[1]不为0,该扇区中还有文件,所以扇区地址为0时还要继续在后面的索引块中查找。
回到代码,若在if (all_blocks[block_idx] == 0) 判断all_blocks[block_idx]不为0,这表示已分配扇区地址了,于是在ide_read(part->my_disk, all_blocks[block_idx], buf, 1)从该扇区地址all_blocks[block_idx]读入1扇区数据到buf,在while (dir_entry_idx < dir_entry_cnt)循环用目录项指针p_de遍历该扇区内的所有目录项,在if (!strcmp(p_de->filename, name))比较目录项的p-de->filename是否和待查找的文件名name相等,若相等则表示找到该文件,然后memcpy(dir_e, p_de, dir_entry_size)将该目录项p_de复制到参数dir_e中,之后释放缓冲区和all_blocks,成功返回。否则,更新为下一个目录项继续在该扇区中查找。若该扇区中未找到该文件的话,使block_idx++,更新为all_blocks中的下一个扇区,读取新的扇区,重复以上查找过程。如果到最后都未找到该文件,释放buf和all_blocks,返回false。
dir_close函数接受1个参数,目录指针dir,,功能是关闭目录dir,关闭目录的本质是关闭目录的inode并释放目录占用的内存,也就是inode_close(dir->inode)和sys_free(dir)。 根目录不能被真正地关闭,不做任何处理直接返回。 原因是首先根目录始终应该是打开的,它是所有目录的父目录,查找文件时必须要从根目录开始找。 其次是根目录root_dir占用的是静态内存,它位于低端 1MB 之内,并非是在堆中申请的,不能将其释放。
create_dir_entry函数接受4个参数,文件名filename、 inode编号inode_no、文件类型file_type、目录项指针p_de。 功能是在内存中创建目录项p_de。 函数的实现就是在初始化目录项p_de,将文件名拷贝到目录项 p_de->filename 中,用inode_no为p_de->i_no 赋值,用file_type为p_de->f_type 赋值。
sync_dir_entry函数接受3个参数,父目录parent_dir、目录项p_de、缓冲区io_buf,功能是将目录项p_de写入父目录parent_dir中,其中io_buf由主调函数提供。
当inode是目录时,其i_size是目录中目录项的大小之和,父目录的大小是dir_inode->i_size,uint32_t dir_size = dir_inode->i_size将其赋值给变量dir_size。目录项大小记录在超级块中,uint32_t dir_entry_size = cur_part->sb->dir_entry_size获取了超级块的大小,存入变量dir_entry_size。uint32_t dir_entrys_per_sec = (512 / dir_entry_size)计算1扇区可容纳的完整的目录项数,结果写入变量dir_entrys_per_sec,此处就是在函数search_dir_entry 中所说的,写入目录项时已保证目录项不会跨扇区。
while (block_idx < 12)循环将目录的 12个直接块地址收集到数组 all_blocks,下面将优先检查这 12 个扇区。struct dir_entry* dir_e = (struct dir_entry*)io_buf使目录项指针dir_e指向缓冲区io_buf,这里为目录项指针变量起名为dir_e是避免与参数p_de冲突。
由于删除文件时会造成目录中存在空洞,也就是文件系统内的碎片,所以在写入文件时,,要逐个目录项查找空位,避免一味在目录的末尾添加目录项,而前面所有文件已被删除时,却还占用多个扇区的情况,所以while (block_idx < 140)开始从头在这12个扇区中找空闲目录项位置。虽然我们只在all_blocks中收集了12个直接块的地址,但依然要遍历140个扇区,因为要是在这12个扇区中找不到目录项空位时,在文件大小未超过140个扇区的情况下,我们还会为其分配新扇区以容纳目录项。 代码if (all_blocks[block_idx] == 0)先判断扇区是否分配,若未分配,在block_lba = block_bitmap_alloc(cur_part)通过函数block_bitmap_alloc为其分配一扇区,扇区地址写入变量block_lba。 由于 block_bitmap_alloc仅是操作内存中的块位图,为保持数据同步,现在要将块位图同步到硬盘,于是在block_bitmap_idx = block_lba – cur_part->sb->data_start_lba计算block_lba相对于data_start_lba的偏移,bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP)调用bitmap_sync将块位图同步到硬盘。
if (block_idx < 12)判断当前为空的块是直接块,还是间接块,若块索引小于12,则属于直接块,故在dir_inode->i_sectors[block_idx] = all_blocks[block_idx] = block_lba将分配的扇区地址写入i_sectors[block_idx]和all_blocks[block_idx]。 若正好是第12个块,即一级间接块索引表地址为空,下面就该创建间接块了,在dir_inode->i_sectors[12] = block_lba将刚才分配的扇区地址block_lba作为一级间接块索引表的地址写入i_sectors[12],在block_lba = block_bitmap_alloc(cur_part)重新再分配一扇区,此时block_lba更新为新分配的扇区地址,该扇区地址将作为第0个间接块。 如果block_lba为-1,也就是分配扇区失败了,执行一些回滚操作。 如果分配成功的话,接着将块位图同步到硬盘。 然后在all_blocks[12] = block_lba将新分配的扇区地址更新到all_blocks[12],这是第0个间接块的地址,随后在ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1)调用ide_write将间接块地址写入一级间接块索引表所在的扇区。
在else语句,若block_idx已经超过了12,这说明已经遍历到间接块了,将最初分配的扇区地址block_lba录入all_blocks,然后在ide_write(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1)将间接块地址写入一级间接块索引表所在的扇区。 最后将目录项写入扇区,然后dir_inode->i_size += dir_entry_size更新目录大小,使目录的i_size加上1个目录项大小dir_entry_size。
ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1)开始处理块已存在,不需要分配块的情况,也就是要在该扇区中寻找空闲的目录项,先在ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1)将该扇区读到io_buf中,接着在while (dir_entry_idx < dir_entrys_per_sec)通过while循环遍历dir_entrys_per_sec个目录项,if ((dir_e + dir_entry_idx)->f_type == FT_UNKNOWN)判断若目录项的f_type为FT_UNKNOWN,这表示该目录项未分配,可以用,于是在memcpy(dir_e + dir_entry_idx, p_de, dir_entry_size)将目录项p_de写入io_buf,接着调用ide_write将目录项同步到硬盘,最后使目录的i_size加上1个目录项大小dir_entry_size。 如果所有的扇区都满了,直接输出”directory is full!”并以false返回。
对应/fs/dir.h
#ifndef __FS_DIR_H
#define __FS_DIR_H
#include "stdint.h"
#include "inode.h"
#include "fs.h"
#include "ide.h"
#include "global.h"
#define MAX_FILE_NAME_LEN 16 // 最大文件名长度
/* 目录结构 */
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; // 普通文件或目录对应的inode编号
enum file_types f_type; // 文件类型
};
extern struct dir root_dir; // 根目录
void open_root_dir(struct partition* part);
struct dir* dir_open(struct partition* part, uint32_t inode_no);
void dir_close(struct dir* dir);
bool search_dir_entry(struct partition* part, struct dir* pdir, const char* name, struct dir_entry* dir_e);
void create_dir_entry(char* filename, uint32_t inode_no, uint8_t file_type, struct dir_entry* p_de);
bool sync_dir_entry(struct dir* parent_dir, struct dir_entry* p_de, void* io_buf);
#endif
3.参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


