已完成实验
已完成实验链接
简介
实验 25. 创建文件
总结
inode 就是文件
- i_no 就是 inode 号
- i_sectors 是块地址数组,表示这个文件的内容是那些块构成的.
- 如果是文件,那么块的内容是文件的内容
- 如果是目录,那么这些块的内容是一个个目录项
dir_entry 目录项
- 是目录文件的数据块的内容
文件描述符如何转换为 inode
- 首先在内核有一个 所有线程打开的文件表 file_tbale
struct file file_table[MAX_FILE_OPEN];
- 这个表的每一项是一个文件结构体
/// @brief 文件结构
struct file {
uint32_t fd_pos; // 记录当前文件操作的偏移地址
uint32_t fd_flag;
struct inode\* fd_inode;
};
- 然后每个线程或进程 pcb 有一个文件描述符数组
struct task_struct {
...
int32_t fd_table[MAX_FILES_OPEN_PER_PROC];
...
};
- 其中 索引 0 标准输入 索引 1 标准输出 索引 2 标准错误
- 其他文件描述符假如是 4,那么假设文件描述符数组 fd_table[4] = 7
- 那么拿着 7 去找所有线程打开的文件表 file_tbale[7]的 file 结构体
- file 结构体就能定位到 inode,也可以 fseek,也能判断读写 flags
主要代码
thread.h
+#define MAX_FILES_OPEN_PER_PROC 8
+
/// @brief 进程或线程的pcb process control block 4096字节
/// 一个pcb包含1个中断栈,1个线程栈,
struct task_struct {
uint32_t* self_kstack; // pcb中线程栈的地址
pid_t pid;
enum task_status status; // 状态
char name[16]; //
uint8_t priority; // 线程优先级
uint8_t ticks; // 每次在处理器上执行的时间嘀嗒数
uint32_t elapsed_ticks; // 处理器上执行的时间嘀嗒总数
+ int32_t fd_table[MAX_FILES_OPEN_PER_PROC]; // 文件描述符数组
+
struct list_elem general_tag; // 放入就绪队列
struct list_elem all_list_tag; // 放入全部队列
// 用户进程使用 内核线程不使用
uint32_t* pgdir; // 指向用户的页目录项
struct virtual_addr userprog_vaddr; // 用户进程的虚拟地址池
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
uint32_t stack_magic; // pcb魔数,用于检测栈的溢出
};
thread.c
/// @brief 初始化pcb
/// @param pthread pcb
/// @param name 线程名
/// @param prio 优先级
void init_thread(struct task_struct* pthread, char* name, int prio) {
// 清0
memset(pthread, 0, sizeof(*pthread));
pthread->pid = allocate_pid(); // 设置pid
// 设置状态
if (pthread == main_thread) { // 如果是主线程pcb
pthread->status = TASK_RUNNING;
} else {
pthread->status = TASK_READY;
}
// 初始化名字、优先级、时钟、总时钟、魔数
strcpy(pthread->name, name);
pthread->priority = prio;
pthread->ticks = prio;
pthread->elapsed_ticks = 0;
pthread->pgdir = NULL;
pthread->stack_magic = 0x19870916; // 自定义的魔数
+ // 文教描述符数组
+ pthread->fd_table[0] = 0; // 标准输入
+ pthread->fd_table[1] = 1; // 标准输出
+ pthread->fd_table[2] = 2; // 标准错误输出
+ uint8_t fd_idx = 3; // 剩下都是-1
+ while (fd_idx < MAX_FILES_OPEN_PER_PROC) {
+ pthread->fd_table[fd_idx] = -1;
+ fd_idx++;
+ }
+
// self_kstack是线程自己在内核态下使用的栈顶地址
pthread->self_kstack = (uint32_t*)((uint32_t)pthread + PG_SIZE);
}
inode.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;
};
inode.c
/// @brief inode位置信息
struct inode_position {
bool two_sec; // inode是否跨扇区
uint32_t sec_lba; // inode所在的扇区号
uint32_t off_size; // inode在扇区内的字节偏移量
};
/// @brief 打开inode
/// 先从分区已打开inode中找,再从硬盘找,找到后inode的打开次数+1
/// @param part 分区
/// @param inode_no inode号
/// @return inode的数据结构指针 注意:这个指针指向的结构体在堆内存
struct inode* inode_open(struct partition* part, uint32_t inode_no);
/// @brief 同步inode
/// 把内存中inode的信息同步到硬盘
/// @param part 分区
/// @param inode inode指针
/// @param io_buf 从硬盘读出的扇区数据缓存
void inode_sync(struct partition* part, struct inode* inode, void* io_buf);
/// @brief 关闭inode
/// 如果打开多次,那么次数-1,如果次数为0,那么删除在内存中的indoe
/// @param inode inode指针
void inode_close(struct inode* inode);
/// @brief 删除inode
/// 把硬盘中inode表中的inode结构体全部归0
/// 感觉不太需要,只要把inode位图中的位给0就可以代表了
/// @param part 分区
/// @param inode_no inode号
/// @param io_buf 从硬盘读出的扇区数据缓存
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf);
/// @brief 释放inode
/// 在硬盘inode位图中标志该inode为空闲,并在块位图中标记他的所有块为空闲
/// @param part 分区
/// @param inode_no inode号
void inode_release(struct partition* part, uint32_t inode_no);
/// @brief 初始化inode
/// 除了inode号全部默认值
/// @param inode_no inode号
/// @param new_inode inode指针
void inode_init(uint32_t inode_no, struct inode* new_inode);
dir.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; // 文件类型
};
dir.c
struct dir root_dir; // 根目录
/// @brief 打开根目录
/// 打开inode0
/// @param part
void open_root_dir(struct partition* part);
/// @brief 打开目录
/// 打开inode号的目录,返回目录指针
/// 堆内存申请的结构体,返回这个结构体的指针
/// @param part 分区
/// @param inode_no 目录inode号
/// @return 目录指针
struct dir* dir_open(struct partition* part, uint32_t inode_no);
/// @brief 关闭目录
/// 关闭inode号的目录,释放目录结构体
/// @param dir
void dir_close(struct dir* dir);
/// @brief 寻找文件
/// 在part分区内的pdir目录内寻找名为name的文件或目录
/// @param part 分区
/// @param pdir 目录
/// @param name 文件或目录名
/// @param dir_e 保存结果
/// @return 找到后返回true并将其目录项存入dir_e,否则返回false
bool search_dir_entry(struct partition* part, struct dir* pdir, const char* name, struct dir_entry* dir_e);
/// @brief 创建目录项
/// 赋值目录项指针指向的目录项的文件名,inode号,文件类型
/// @param filename 文件名
/// @param inode_no inode号
/// @param file_type 文件类型
/// @param p_de 目录项指针
void create_dir_entry(char* filename, uint32_t inode_no, uint8_t file_type, struct dir_entry* p_de);
/// @brief 同步目录项
/// 在硬盘中将目录项p_de写入父目录parent_dir的数据块中,io_buf由主调函数提供
/// @param parent_dir 父目录指针
/// @param p_de 目录项指针
/// @param io_buf 缓冲区由外部提供
/// @return 成功返回true,失败返回false
bool sync_dir_entry(struct dir* parent_dir, struct dir_entry* p_de, void* io_buf);
file.h
/// @brief 文件结构
struct file {
uint32_t fd_pos; // 记录当前文件操作的偏移地址,以0为起始,最大为文件大小-1
uint32_t fd_flag;
struct inode* fd_inode;
};
/// @brief 标准输入输出描述符
enum std_fd {
stdin_no, // 0 标准输入
stdout_no, // 1 标准输出
stderr_no // 2 标准错误
};
/// @brief 位图类型
enum bitmap_type {
INODE_BITMAP, // inode位图
BLOCK_BITMAP // 块位图
};
#define MAX_FILE_OPEN 32 // 系统可打开的最大文件数
file.c
/// @brief 全局打开的文件表
struct file file_table[MAX_FILE_OPEN];
/// @brief 从全局打开的文件表中获取一个空闲位置
/// @return 成功返回下标,失败返回-1
int32_t get_free_slot_in_global(void)
/// @brief 分配一个描述符索引
/// 将全局描述符下标安装到进程或线程自己的文件描述符数组fd_table中
/// @param globa_fd_idx 全局描述符下标
/// @return 成功返回下标,失败返回-1
int32_t pcb_fd_install(int32_t globa_fd_idx)
/// @brief 在块位图中分配一个位
/// @param part 分区
/// @return 扇区地址
int32_t block_bitmap_alloc(struct partition* part)
/// @brief 同步内存位图bit_idx比特位所在的扇区中到硬盘
/// @param part 分区
/// @param bit_idx 比特偏移
/// @param btmp_type 位图类型
void bitmap_sync(struct partition* part, uint32_t bit_idx, uint8_t btmp_type)
/// @brief 创建文件
/// @param parent_dir 父目录
/// @param filename 文件名
/// @param flag 打开文件的选项
/// @return 若成功则返回文件描述符,否则返回-1
int32_t file_create(struct dir* parent_dir, char* filename, uint8_t flag)
fs.h
#define MAX_FILES_PER_PART 4096 // 每个分区所支持最大创建的文件数
#define BITS_PER_SECTOR 4096 // 每扇区的位数 512字节 = 4096比特
#define SECTOR_SIZE 512 // 扇区字节大小
#define BLOCK_SIZE SECTOR_SIZE // 块字节大小
#define MAX_PATH_LEN 512 // 路径最大长度
/// @brief 文件类型
enum file_types {
FT_UNKNOWN, // 不支持的文件类型
FT_REGULAR, // 普通文件
FT_DIRECTORY // 目录
};
/// @brief 打开文件的选项
enum oflags {
O_RDONLY, // 只读
O_WRONLY, // 只写
O_RDWR, // 读写
O_CREAT = 4 // 创建
};
/// @brief 用来记录查找文件过程中已找到的上级路径,也就是查找文件过程中"走过的地方"
struct path_search_record {
char searched_path[MAX_PATH_LEN]; // 查找过程中的父路径
struct dir* parent_dir; // 文件或目录所在的直接父目录
enum file_types file_type; // 找到的是普通文件还是目录,找不到将为未知类型(FT_UNKNOWN)
};
fs.c
/// @brief 将最上层路径名称解析出来
/// @param pathname 路径 /a/b/c
/// @param name_store a
/// @return /b/c
static int search_file(const char* pathname, struct path_search_record* searched_record)
/// @brief 查找文件
/// @param pathname 文件名
/// @param searched_record 保存结果
/// @return 若找到则返回其inode号,否则返回-1
static int search_file(const char* pathname, struct path_search_record* searched_record)
/// @brief 打开或创建文件
/// @param pathname 文件路径
/// @param flags
/// @return 成功后,返回文件描述符,否则返回-1
int32_t sys_open(const char* pathname, uint8_t flags)
/// @brief 在磁盘上搜索文件系统,若没有则格式化分区创建文件系统
void filesys_init() {
...
// 确定默认操作的分区
char default_part[8] = "sdb1";
// 挂载分区
list_traversal(&partition_list, mount_partition, (int)default_part);
// 将当前分区的根目录打开
open_root_dir(cur_part);
// 初始化文件表
uint32_t fd_idx = 0;
while (fd_idx < MAX_FILE_OPEN) { file_table[fd_idx++].fd_inode = NULL; }
}
init.c
// 文件: init.c
// 时间: 2024-07-22
// 来自: ccj
// 描述: 内核所有初始化操作
#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "timer.h"
#include "memory.h"
#include "thread.h"
#include "keyboard.h"
#include "console.h"
#include "tss.h"
#include "syscall-init.h"
#include "ide.h"
#include "fs.h"
/// @brief 内核所有初始化
void init_all() {
put_str("init all\n");
idt_init(); // 初始化中断
timer_init(); // 调快时钟、注册时钟中断来调度线程
mem_init(); // 初始化内存管理系统
thread_init(); // 初始化线程
console_init(); // 控制台初始化最好放在开中断之前
keyboard_init(); // 键盘初始化
tss_init(); // tss初始化
syscall_init(); // 初始化系统调用
intr_enable(); // 打开中断
ide_init(); // 初始化硬盘
filesys_init(); // 初始化文件系统
}
main.c
int main(void) {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "user_prog_a");
process_execute(u_prog_b, "user_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "argA ");
thread_start("k_thread_b", 31, k_thread_b, "argB ");
sys_open("/file1", O_CREAT);
while (1);
return 0;
}