操作系统真象还原实验记录之实验二十五:文件操作相关基础函数
1. 文件描述符
以打开文件open这个系统调用流程为例。
首先,根目录已经有两个FCB了,分别是"。" 和“。。”
然后我们的inode数组共4096个,inode[0]指向了根目录文件扇区地址,也就是超级块的data_start_lba。
现在我们假设根目录里又新增了两个FCB。
那么情况如下图。
这里列出他们是为了和inode结点打开队列以及打开文件表区分开来,也就是下面这个图
图中的文件表应该是打开文件表,里面的表项叫做文件结构,整个表就是文件结构数组。
当没有文件打开的时候,这个表为空,同时全局inode结点打开队列也为空。
注意:i结点打开队列每个分区结构体都有,早已定义。
此打开队列位于内存,是为了减少磁盘读取次数。
PCB里增加了文件描述符数组用于存储文件结构数组下标。
有了上述的基础后,就可以模拟open系统调用流程了。
当使用open打开文件2的时候,立即申请一个新的inode结点,然后将inode[4]写入到该新结点,该新结点加入全局打开inode结点队列。
紧接着在打开文件表里新建一个文件结构表项,根据根目录里的FCB3的信息来存储文件2相关信息于该文件结构表项。然后将新inode结点的地址写入fd_inode。
最后将新文件结构表项在打开文件表里的下标返回到PCB的文件描述符数组fd_table。从而完成open。
2. inode操作有关函数
首先回忆一下inode结构体
/* 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;
};
i_open_cnts
inode_tag
write_deny
这三个是inode在内存才有效。
inode在内存意味着内核池里存有该inode,且它的inode_tag在该分区全局打开队列中。
inode.h
struct inode* inode_open(struct partition* part, uint32_t inode_no);
void inode_sync(struct partition* part, struct inode* inode, void* io_buf);
void inode_init(uint32_t inode_no, struct inode* new_inode);
void inode_close(struct inode* inode);
2.1 inode有关函数(暂时)inode.c
#include "inode.h"
#include "fs.h"
#include "file.h"
#include "global.h"
#include "debug.h"
#include "memory.h"
#include "interrupt.h"
#include "list.h"
#include "stdio-kernel.h"
#include "string.h"
#include "super_block.h"
/* 用来存储inode位置 */
struct inode_position {
bool two_sec; // inode是否跨扇区
uint32_t sec_lba; // inode所在的扇区号
uint32_t off_size; // inode在扇区内的字节偏移量
};
/* 获取inode所在的扇区和扇区内的偏移量 */
static void inode_locate(struct partition* part, uint32_t inode_no, struct inode_position* inode_pos) {
/* inode_table在硬盘上是连续的 */
ASSERT(inode_no < 4096);
uint32_t inode_table_lba = part->sb->inode_table_lba;
uint32_t inode_size = sizeof(struct inode);
uint32_t off_size = inode_no * inode_size; // 第inode_no号I结点相对于inode_table_lba的字节偏移量
uint32_t off_sec = off_size / 512; // 第inode_no号I结点相对于inode_table_lba的扇区偏移量
uint32_t off_size_in_sec = off_size % 512; // 待查找的inode所在扇区中的起始地址
/* 判断此i结点是否跨越2个扇区 */
uint32_t left_in_sec = 512 - off_size_in_sec;
if (left_in_sec < inode_size ) { // 若扇区内剩下的空间不足以容纳一个inode,必然是I结点跨越了2个扇区
inode_pos->two_sec = true;
} else { // 否则,所查找的inode未跨扇区
inode_pos->two_sec = false;
}
inode_pos->sec_lba = inode_table_lba + off_sec;
inode_pos->off_size = off_size_in_sec;
}
/* 将inode写入到分区part */
void inode_sync(struct partition* part, struct inode* inode, void* io_buf) { // io_buf是用于硬盘io的缓冲区
uint8_t inode_no = inode->i_no;
struct inode_position inode_pos;
inode_locate(part, inode_no, &inode_pos); // inode位置信息会存入inode_pos
ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));
/* 硬盘中的inode中的成员inode_tag和i_open_cnts是不需要的,
* 它们只在内存中记录链表位置和被多少进程共享 */
struct inode pure_inode;
memcpy(&pure_inode, inode, sizeof(struct inode));
/* 以下inode的三个成员只存在于内存中,现在将inode同步到硬盘,清掉这三项即可 */
pure_inode.i_open_cnts = 0;
pure_inode.write_deny = false; // 置为false,以保证在硬盘中读出时为可写
pure_inode.inode_tag.prev = pure_inode.inode_tag.next = NULL;
char* inode_buf = (char*)io_buf;
if (inode_pos.two_sec) { // 若是跨了两个扇区,就要读出两个扇区再写入两个扇区
/* 读写硬盘是以扇区为单位,若写入的数据小于一扇区,要将原硬盘上的内容先读出来再和新数据拼成一扇区后再写入 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2); // inode在format中写入硬盘时是连续写入的,所以读入2块扇区
/* 开始将待写入的inode拼入到这2个扇区中的相应位置 */
memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
/* 将拼接好的数据再写入磁盘 */
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
} else { // 若只是一个扇区
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
memcpy((inode_buf + inode_pos.off_size), &pure_inode, sizeof(struct inode));
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
}
/* 根据i结点号返回相应的i结点 */
struct inode* inode_open(struct partition* part, uint32_t inode_no) {
/* 先在已打开inode链表中找inode,此链表是为提速创建的缓冲区 */
struct list_elem* elem = part->open_inodes.head.next;
struct inode* inode_found;
while (elem != &part->open_inodes.tail) {
inode_found = elem2entry(struct inode, inode_tag, elem);
if (inode_found->i_no == inode_no) {
inode_found->i_open_cnts++;
return inode_found;
}
elem = elem->next;
}
/*由于open_inodes链表中找不到,下面从硬盘上读入此inode并加入到此链表 */
struct inode_position inode_pos;
/* inode位置信息会存入inode_pos, 包括inode所在扇区地址和扇区内的字节偏移量 */
inode_locate(part, inode_no, &inode_pos);
/* 为使通过sys_malloc创建的新inode被所有任务共享,
* 需要将inode置于内核空间,故需要临时
* 将cur_pbc->pgdir置为NULL */
struct task_struct* cur = running_thread();
uint32_t* cur_pagedir_bak = cur->pgdir;
cur->pgdir = NULL;
/* 以上三行代码完成后下面分配的内存将位于内核区 */
inode_found = (struct inode*)sys_malloc(sizeof(struct inode));
/* 恢复pgdir */
cur->pgdir = cur_pagedir_bak;
char* inode_buf;
if (inode_pos.two_sec) { // 考虑跨扇区的情况
inode_buf = (char*)sys_malloc(1024);
/* i结点表是被partition_format函数连续写入扇区的,
* 所以下面可以连续读出来 */
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
} else { // 否则,所查找的inode未跨扇区,一个扇区大小的缓冲区足够
inode_buf = (char*)sys_malloc(512);
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
memcpy(inode_found, inode_buf + inode_pos.off_size, sizeof(struct inode));
/* 因为一会很可能要用到此inode,故将其插入到队首便于提前检索到 */
list_push(&part->open_inodes, &inode_found->inode_tag);
inode_found->i_open_cnts = 1;
sys_free(inode_buf);
return inode_found;
}
/* 关闭inode或减少inode的打开数 */
void inode_close(struct inode* inode) {
/* 若没有进程再打开此文件,将此inode去掉并释放空间 */
enum intr_status old_status = intr_disable();
if (--inode->i_open_cnts == 0) {
list_remove(&inode->inode_tag); // 将I结点从part->open_inodes中去掉
/* inode_open时为实现inode被所有进程共享,已经在sys_malloc为inode分配了内核空间 */
struct task_struct* cur = running_thread();
uint32_t* cur_pagedir_bak = cur->pgdir;
cur->pgdir = NULL;
sys_free(inode); // 释放inode的内核空间
cur->pgdir = cur_pagedir_bak;
}
intr_set_status(old_status);
}
/* 初始化new_inode */
void inode_init(uint32_t inode_no, struct inode* new_inode) {
new_inode->i_no = inode_no;
new_inode->i_size = 0;
new_inode->i_open_cnts = 0;
new_inode->write_deny = false;
/* 初始化块索引数组i_sector */
uint8_t sec_idx = 0;
while (sec_idx < 13) {
/* i_sectors[12]为一级间接块地址 */
new_inode->i_sectors[sec_idx] = 0;
sec_idx++;
}
}
struct inode_position 记录inode在磁盘是否跨扇区,所在扇区号以及扇区内字节偏移
inode_locate 根据i_no填写上述结构体
inode_sync 将内存的inode写入磁盘,也就是inode内存与磁盘的同步
inode_open 根据i_no返回内存中的inode结点地址。
注意:如果全局打开队列没有该inode,说明该inode不在内存,这个文件是第一次打开,所以要从磁盘读取该inode加入队列。
inode_close i_open_cnts减一。如果变成0,就从队列移除,并释放占用的内核内存。
inode_init
inode_open中,为新inode结点sys_malloc了一块内存,使用的是内核内存池与内核虚拟池,因为内核虚拟池返回的虚拟地址都是3GB+1MB以上,第六次分页实验内核页目录第769到1024项都已写好,也就是依次指向1MB到2MB的各个页表,所以所有用户进程769到1024个页目录项指向的页表都与内核的页目录3GB以上指向的页表一致,均是(1MB到2MB的页表),所以所有用户进程和内核线程对于3GB以上的相同虚拟地址,最后转换的都是同一个物理地址。也就是该inode共享。也就是内核内存池所有空间所有用户进程与内核线程共享。
2.3 其他
thread.h(增加)
#define MAX_FILES_OPEN_PER_PROC 8
/* 进程或线程的pcb,程序控制块 */
struct task_struct {
略。。。
int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 已打开文件数组
略。。。
};
thread.c(增加)
void init_thread(struct task_struct* pthread, char* name, int prio) {
略。。。
/* 标准输入输出先空出来 */
pthread->fd_table[0] = 0;
pthread->fd_table[1] = 1;
pthread->fd_table[2] = 2;
/* 其余的全置为-1 */
uint8_t fd_idx = 3;
while (fd_idx < MAX_FILES_OPEN_PER_PROC) {
pthread->fd_table[fd_idx] = -1;
fd_idx++;
}
略。。。
}
3. 文件相关的函数
3.1 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);
#endif
file 就是打开文件表的文件结构表项
fd_pos 是当前具体的文件操作的文件内偏移地址。
显然这个不是FCB。
3.2 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"
#include "ioqueue.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);
}
pcb_fd_install 将文件描述符写入pcb的fd_table数组中
get_free_slot_in_global 从打开文件表获得一个空闲表项的下标
block_bitmap_alloc:在内存的分区的空闲块位图中找到空闲块,返回bit的下标
inode_bitmap_alloc:在内存分区的i结点位图找到空闲inode结点,返回下标
bitmap_sync:显然,内存里的分区的位图被修改了,就需要往磁盘里的空闲块或i结点位图更新同步。
由于磁盘读写单位是一个扇区,所以即使只是修改了一个位,也要更新该位所在的整个扇区。
该函数接受一个被修改的bit位在位图的下标。
由此下标便可获得相对位图的扇区偏移量。从而确定更新磁盘哪个扇区。
part->block_bitmap.bits是内存里的空闲块位图首地址。
part->sb->block_bitmap_lba是内存超级块结构体里记录的该分区空闲块在硬盘中的扇区号。
3.3 目录相关的函数
回忆目录和目录项结构体
/* 目录结构 */
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; // 文件类型
};
首先是格局
磁盘上已有一个扇区用作根目录了,inode[0]也记录着根目录。每个目录里的目录项存着i_no。
打开一个文件,本质就是打开对应的inode,即将inode加入全局打开队列,加入打开文件表,加入PCB的文件描述符数组。
根目录也是一个文件,而且根目录必须永久打开,因为任何文件对应的inode都要从根目录开始找,而根目录的i_no默认为0,存于超级块。
而被打开的文件也就是调用inode_open,会返回一个inode
因此需要一个dir类型全局变量root_dir永驻内存,来记录被打开的文件。
所以dir这个结构体专门用来记录内存中被打开的文件
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.3.1 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;
}
/* 关闭目录 */
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;
}
root_dir:全局变量记录被打开的根目录的inode。
open_root_dir:打开根目录的inode,记录在root_dir。
dir_open:接受一个i_no和分区,打开该分区对应的inode,申请一个dir记录此打开的文件的inode,返回该dir地址。
dir_close:关闭目录,即关闭inode,不能关闭根目录。
create_dir_entry:内存中初始化目录项。
search_dir_entry
/* 在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;
}
在磁盘的part分区内的pdir目录内寻找名为name的文件或目录,找到后返回true并将其目录项存入内存的目录项结构体dir_e,否则返回false。
代码思路如下:
首先有pdir,说明该目录已被打开且记录在了pdir中。
其次pdir目录的inode中有12个直接索引,一个间接索引。
这些索引反应了pdir目录文件的扇区数。一个目录文件最多12+128=140个扇区,扇区地址均在索引中。
目录文件的每个扇区装满了目录项,且目录项不会跨扇区。
这些扇区都在磁盘中,所以应该从磁盘读出整个目录于内存,然后在内存依次遍历所有目录项,检查name。
将inode的140个索引地址存入all_blocks,遍历all_blocks。
索引地址表示扇区号。
如果索引地址为0,那么表示不存在。
如果不为0,那么就读出该扇区,遍历里面的目录项的name。找到则保存于目录项dir_e,return true。
3.3.2 dir.c下半部
/* 将目录项 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);
// 每扇区最大的目录项数目
uint32_t dir_entrys_per_sec = 512 / dir_entry_size;
int32_t block_lba = -1;
// 将该目录的所有扇区地址(12 个直接块 + 128 个间接块)存入 all_blocks
uint8_t block_idx = 0;
// all_blocks 保存目录所有的块
uint32_t all_blocks[140] = {0};
// 将 12 个直接块存入 all_blocks
while(block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
//这里原书的源码一定是写错了,我反复思考,原书写掉了下面的代码。如果没有下面的代码,只读前12个
//all_block[12]永远是0,无论你是否已经新分配了扇区给i_sectors[12]。因为你分配了第13块扇区保存在
//inode里面,然后在第13块扇区填好第一个目录项后return了,你希望下次要填的目录项可以接着第13块扇区填,但是你代码每次只读了前12块扇区地址,i_sectors[12]的信息每次没有去读。
//如果目录小,12个直接块没有填满,不会报错,如果一旦前12个填满,再填目录项代码每次都会是all_blocks[12] == 0且block_idx == 12,分配两个新扇区,导致每次i_sectors[12]都会被覆盖,每次第13块扇区的第一个目录项都写不进去。
if (pdir->inode->i_sectors[12] != 0) { // 若含有一级间接块表,128个间接块地址写入all_blocks
ide_read(part->my_disk, pdir->inode->i_sectors[12], all_blocks + 12, 1);
}
// dir_e 用来在 io_buf 中遍历目录项
struct dir_entry* dir_e = (struct dir_entry*)io_buf;
int32_t block_bitmap_idx = -1;
// 开始遍历所有块以寻找目录项空位, 若已有扇区中没有空闲位
// 在不超过文件大小的情况下申请新扇区来存储新目录项
block_idx = 0;
while(block_idx < 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;
// 再分配一个块作为第 0 个间接块
block_lba = block_bitmap_alloc(cur_part);
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) {
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;
}
将新目录项写入目录,
需要遍历所有目录项,直到找到空闲目录项位置。
目录最多有140个扇区,扇区内装着目录项,依次遍历这140个扇区,也就是遍历12个直接索引,一个间接索引
分以下这几个情况:
1.当遍历的是直接索引且不为0时,
意味着扇区已存在,也就是扇区内部含有目录项,但是也有可能有空位。那就需要读出这个扇区,遍历里面的目录项,寻找空位。没有空位继续遍历处理下一个扇区。
2.当遍历的是直接索引且为0时
这就需要在当前分区的空闲块位图中,新分配一个空闲扇区并同步磁盘的位图,然后将这个扇区地址写入该目录的inode的直接索引。
3.当遍历的是间接索引且为0时,第13块扇区不存在,那么就要分配两个扇区,一个用于作间接扇区A,里面最多可存128个直接索引,一个用作存目录项的文件扇区B,也就是第13块扇区。
A地址写入间接索引,B地址写入A
4.当遍历的是间接索引块A内的直接索引且为0时,分配新扇区B,将B写入A内的直接索引。
然后2、3、4情况都还没有写入新FCB于目录,所以最后有一段代码统一写入。
(这个函数最好画图理解,以后再多看几遍)
总结:这个函数在磁盘可能修改的东西有分区的空闲块位图,inode的前12个直接索引、以及间接索引;其次,还有间接块里的直接索引;最后磁盘里的目录文件还需要写入新的FCB。
上述修改中,位图、间接块的直接索引、目录里的新FCB在函数中均已做到同步。但是!!!inode的修改未同步。
别忘了inode_sync函数。
3.4 路径解析有关函数
fs.h
#define MAX_PATH_LEN 512 // 路径最大长度
/* 文件类型 */
enum file_types {
FT_UNKNOWN, // 不支持的文件类型
FT_REGULAR, // 普通文件
FT_DIRECTORY // 目录
};
/* 打开文件的选项 */
enum oflags {
O_RDONLY, // 只读
O_WRONLY, // 只写
O_RDWR, // 读写
O_CREAT = 4 // 创建
};
/* 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方" */
struct path_search_record {
char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
struct dir* parent_dir; // 文件或目录所在的直接父目录
enum file_types file_type; // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};
extern struct partition* cur_part;
void filesys_init(void);
char* path_parse(char* pathname, char* name_store);
int32_t path_depth_cnt(char* pathname);
int32_t sys_open(const char* pathname, uint8_t flags);
fs.c
/* 将最上层路径名称解析出来 */
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;
}
/* 返回路径深度,比如/a/b/c,深度为3 */
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;
}
/* 搜索文件pathname,若找到则返回其inode号,否则返回-1 */
static int search_file(const char* pathname, struct path_search_record* searched_record) {
/* 如果待查找的是根目录,为避免下面无用的查找,直接返回已知根目录信息 */
if (!strcmp(pathname, "/") || !strcmp(pathname, "/.") || !strcmp(pathname, "/..")) {
searched_record->parent_dir = &root_dir;
searched_record->file_type = FT_DIRECTORY;
searched_record->searched_path[0] = 0; // 搜索路径置空
return 0;
}
uint32_t path_len = strlen(pathname);
/* 保证pathname至少是这样的路径/x且小于最大长度 */
ASSERT(pathname[0] == '/' && path_len > 1 && path_len < MAX_PATH_LEN);
char* sub_path = (char*)pathname;
struct dir* parent_dir = &root_dir;
struct dir_entry dir_e;
/* 记录路径解析出来的各级名称,如路径"/a/b/c",
* 数组name每次的值分别是"a","b","c" */
char name[MAX_FILE_NAME_LEN] = {0};
searched_record->parent_dir = parent_dir;
searched_record->file_type = FT_UNKNOWN;
uint32_t parent_inode_no = 0; // 父目录的inode号
sub_path = path_parse(sub_path, name);
while (name[0]) { // 若第一个字符就是结束符,结束循环
/* 记录查找过的路径,但不能超过searched_path的长度512字节 */
ASSERT(strlen(searched_record->searched_path) < 512);
/* 记录已存在的父目录 */
strcat(searched_record->searched_path, "/");
strcat(searched_record->searched_path, name);
/* 在所给的目录中查找文件 */
if (search_dir_entry(cur_part, parent_dir, name, &dir_e)) {
memset(name, 0, MAX_FILE_NAME_LEN);
/* 若sub_path不等于NULL,也就是未结束时继续拆分路径 */
if (sub_path) {
sub_path = path_parse(sub_path, name);
}
if (FT_DIRECTORY == dir_e.f_type) { // 如果被打开的是目录
parent_inode_no = parent_dir->inode->i_no;
dir_close(parent_dir);
parent_dir = dir_open(cur_part, dir_e.i_no); // 更新父目录
searched_record->parent_dir = parent_dir;
continue;
} else if (FT_REGULAR == dir_e.f_type) { // 若是普通文件
searched_record->file_type = FT_REGULAR;
return dir_e.i_no;
}
} else { //若找不到,则返回-1
/* 找不到目录项时,要留着parent_dir不要关闭,
* 若是创建新文件的话需要在parent_dir中创建 */
return -1;
}
}
/* 执行到此,必然是遍历了完整路径并且查找的文件或目录只有同名目录存在 */
dir_close(searched_record->parent_dir);
/* 保存被查找目录的直接父目录 */
searched_record->parent_dir = dir_open(cur_part, parent_inode_no);
searched_record->file_type = FT_DIRECTORY;
return dir_e.i_no;
}
path_parse:
char* path_parse(char* pathname, char* name_store)
比如pathname = “/a/b/c”,那么此函数的name_store=“a”,返回的char* 为/b/c。
path_depth_cnt
search_file:主要就是这个函数,
static int search_file(const char* pathname, struct path_search_record* searched_record)
我们来模拟一下
当pathname为/a/b/c时,c为目录或文件的情况
首先先调一次path_parse,name=a,sub_path=/b/c。
searched_record->parent_dir为根目录文件。
然后进入while(name[0])
strcat使得searched_record->searched_path=/a
然后开始
if (search_dir_entry(cur_part, parent_dir, name, &dir_e))
在根目录中查找name=a的目录项,查找到就保存在dir_e中。
找到a的目录项后,再调一次path_parse,name=b,sub_path=/c,
然后parent_inode_no = parent_dir->inode->i_no;
parent_dir = dir_open(cur_part, dir_e.i_no);
在打开a目录项前,用parent_inode_no 保存a的父目录文件的i_no,再将searched_record->parent_dir置为a目录文件。
综上所述:
searched_record->searched_path=/a时,searched_record->parent_dir为根目录,在根目录寻找a的目录项。
searched_record->searched_path=/a/b时,searched_record->parent_dir为a目录,parent_inode_no =根目录的i_no,在a目录寻找b的目录项。找到b则关闭a目录打开b目录。
searched_record->searched_path=/a/b/c时,searched_record->parent_dir为b目录,parent_inode_no =a的i_no,在b目录中寻找c的目录项,然后打开c目录关闭b目录。
如果c是文件,直接return dir_e.i_no即可,searched_record不变。
如果c是目录,那么searched_record->parent_dir会等于c的目录,parent_inode_no =b的i_no,然后退出循环,所以函数的最后,关闭了c目录,利用parent_inode_no,打开了b目录,把searched_record->parent_dir修改回b的目录。
这里注意一个问题,此函数只负责在文件系统上检索文件,不负责侦错。主调函数依靠searched_record记录的内容来判断错误。
也就是说如果b是文件,那么/a/b/c这个格式就是错的,但是函数仍然会打开a目录,返回b文件的i_no,同时返回的searched_record中searched_path=/a/b,parent_dir为a目录,文件类型是文件。主调函数要根据searched_path来增加判断错误的代码。
4.总结
函数太多了,总结一下,以后再看细节
4.1有关inode的操作有
inode_locate:获得inode所在的扇区号以及扇区内的偏移,以inode_position结构体形式记录。
inode_open:根据i_no打开inode结点并返回该节点。
inode_close:关闭或减少inode的打开数
inode_init:初始化inode结点
4.2 有关文件的操作有
file file_table[MAX_FILE_OPEN] 全局打开文件表
get_free_slot_in_global:获取打开文件表空位,返回下标即文件描述符。
fd_install:文件描述符安装到自己的pcb中
inode_bitmap_alloc:分配一个i结点,返回i结点号
注意理解为什么这个不是inode操作函数,看看它的inode_open的区别就明白了。
inode_open这个是磁盘里文件存在,且已经分配了磁盘里4096个i结点中的一个,若打开该结点需要在内存中创建此结点副本。可以理解成该i结点磁盘存在,内存不存在,故调inode_open。
inode_bitmap_alloc是文件在磁盘刚刚创建,需要分配一个inode结点,也就是i结点磁盘里都不存在,故掉inode_bitmap_alloc。本质是文件操作。
block_bitmap_alloc:磁盘中分配一个扇区,返回扇区地址
bitmap_sync:将内存中bitmap第bit_idx位所在的512字节同步到磁盘,可以是空闲块位图,也可以是inode位图
4.3有关目录的操作有
open_root_dir:打开根目录,也就是用全局root_dir来记录根inode_open打开后返回的i_no
dir_open:打开i_no的目录并返回该inode
注意:这两个本质一样,核心都是inode_open,不过,他们打开的都是目录的inode,用内存里dir结构体专门保存inode_open返回的i_no;而不是向打开普通文件一样,记录在打开文件表里,然后文件描述符记录在PCB里。
search_dir_entry:在pdir的目录内寻找名为name的目录或文件,找到后返回true并将目录项记录在dir_e。
dir_close:关闭目录,释放dir,且inode_close。根目录除外
create_dir_entry:内存中初始化目录项p_de
sync_dir_entry:将目录项p_de写入父目录parent_dir中。
4.4 路径解析函数
path_parse:最上层路径名解析出来保存name_store,返回剩余子路径。
path_depth_cnt:返回路径深度
search_file:搜索文件pathname,找到则返回i_no。
此函数依靠path_parse获取目录名
search_dir_entry找到目录项(里面有i_no)
dir_open继续打开目录或直接返回普通文件i_no。
来完成