《操作系统真象还原》第十四章
本篇对应书籍第十四章14.5--14.15
的内容
本章分成了两部分来记录,上一部分完成了文件系统原理的介绍,文件系统的创建,和基础设施的建设,这里该实现文件系统相关的文件操作了
创建文件
实现 file_create
创建文件需要考虑实现如下工作:
- 文件需要inode来描述大小、 位置等属性, 所以创建文件就要创建其inode。 这就涉及到向inode _ bitmap申请位图来获得inode号,因此inode_ bitmap会被更新,inode_table数组中的某项也会由新的inode填充。
- inode->i_sectors是文件具体存储的扇区地址,这需要向block_bitmap申请可用位来获得可用的块,因此block_bitmap会被更新,分区的数据区data_ start_Iba 以后的某个扇区会被分配。
- 新增加的文件必然存在于某个目录,所以该目录的 inode->i_ size 会增加个目录项的大小。 此新增加的文件对应的目录项需要写入该目录的 inode->i_ sectors[]中的某个扇区, 原有扇区可能已满,所以有可能要申请新扇区来存储目录项。
- 若其中某步操作失败, 需要回滚之前已成功的操作。
- inode _ bitmap、 block_bitmap、新文件的 inode 及文件所在目录的 inode, 这些位千内存中已经被改变的数据要同步到硬盘。
fs/file.c
/* 创建文件, 若成功则返回文件描述符, 否则返回 -1 */
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 failed\n");
rollback_step = 1;
goto rollback;
}
// 初始化 inode
inode_init(inode_no, new_file_inode);
// 返回的是 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));
// 初始化目录项 new_dir_entry
create_dir_entry(filename, inode_no, FT_REGULAR, &new_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. 将父目录 inode 的内容同步到硬盘
inode_sync(cur_part, parent_dir->inode, io_buf);
memset(io_buf, 0, 1024);
// c. 将新创建文件的 inode 内容同步到硬盘
inode_sync(cur_part, new_file_inode, io_buf);
// d. 将 inode_bitmap 位图同步到硬盘
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
// e. 将创建的文件 inode 添加到 open_inodes 链表
list_push(&cur_part->open_inodes, &new_file_inode->inode_tag);
// 记录此文件被打开的次数
new_file_inode->i_open_cnts = 1;
sys_free(io_buf);
// 将全局描述符下标安装到进程或线程自己的文件描述符数组 fd_table中,
// 成功返回下标, 失败返回-1
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:
// 如果新文件的 inode 创建失败
// 之前位图中分配的 inode_no 也要恢复
bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
break;
}
sys_free(io_buf);
return -1;
}
这里使用了goto和switch实现了累加回滚操作执行过程
函数执行先申请缓冲区,以防最后环节缓冲区空间不够导致瞎操作了半天
接着申请file_table数组下标,向数组中填充结构信息,接着创建目录项,将目录项填充,最后再将文件对硬盘的修改写入硬盘
创建新文件的顺序是:创建i节点,文件描述符fs,目录项
实现 sys_create
fs/fs.c
/* 打开或创建文件成功后, 返回文件描述符, 否则返回-1 */
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 directory 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;
// 其余为打开文件
}
// 此 fd 是指任务 pcb->fd_table 数组中的元素下标
// 并不是指全局 file_table 中的下标
return fd;
}
// 在磁盘上搜索文件系统, 若没有则格式化分区创建文件系统
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;
}
}
创建第一个文件
kernel/main.c
#include "print.h"
#include "init.h"
#include "debug.h"
#include "memory.h"
#include "../thread/thread.h"
#include "interrupt.h"
#include "../device/console.h"
#include "../userprog/process.h"
#include "../userprog/syscall-init.h"
#include "../lib/user/syscall.h"
#include "../lib/stdio.h"
#include "../fs/fs.h"
void k_thread_a(void*);
void k_thread_b(void*);
void u_prog_a(void);
void u_prog_b(void);
int prog_a_pid = 0, prog_b_pid = 0;
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
sys_open("/file1", O_CREAT);
while(1);
return 0;
}
/* 在线程中运行的函数 */
void k_thread_a(void* arg) {
void* addr1 = sys_malloc(256);
void* addr2 = sys_malloc(255);
void* addr3 = sys_malloc(254);
console_put_str(" thread_a malloc addr:0x");
console_put_int((int)addr1);
console_put_char(',');
console_put_int((int)addr2);
console_put_char(',');
console_put_int((int)addr3);
console_put_char('\n');
int cpu_delay = 100000;
while(cpu_delay-- > 0);
sys_free(addr1);
sys_free(addr2);
sys_free(addr3);
while(1);
}
/* 在线程中运行的函数 */
void k_thread_b(void* arg) {
void* addr1 = sys_malloc(256);
void* addr2 = sys_malloc(255);
void* addr3 = sys_malloc(254);
console_put_str(" thread_b malloc addr:0x");
console_put_int((int)addr1);
console_put_char(',');
console_put_int((int)addr2);
console_put_char(',');
console_put_int((int)addr3);
console_put_char('\n');
int cpu_delay = 100000;
while(cpu_delay-- > 0);
sys_free(addr1);
sys_free(addr2);
sys_free(addr3);
while(1);
}
/* 测试用户进程 */
void u_prog_a(void) {
void* addr1 = malloc(256);
void* addr2 = malloc(255);
void* addr3 = malloc(254);
printf(" prog_a malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);
int cpu_delay = 100000;
while(cpu_delay-- > 0);
free(addr1);
free(addr2);
free(addr3);
while(1);
}
/* 测试用户进程 */
void u_prog_b(void) {
void* addr1 = malloc(256);
void* addr2 = malloc(255);
void* addr3 = malloc(254);
printf(" prog_b malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);
int cpu_delay = 100000;
while(cpu_delay-- > 0);
free(addr1);
free(addr2);
free(addr3);
while(1);
}
运行 Bochs
BUILD_DIR = ./build
ENTRY_POINT = 0xc0001500
AS = nasm
CC = gcc
LD = ld
LIB = -I lib/ -I lib/kernel/ -I lib/user/ -I kernel/ -I device/
ASFLAGS = -f elf
CFLAGS = -Wall -m32 -fno-stack-protector $(LIB) -c -fno-builtin -W -Wstrict-prototypes -Wmissing-prototypes
LDFLAGS = -m elf_i386 -Ttext $(ENTRY_POINT) -e main -Map $(BUILD_DIR)/kernel.map
OBJS = $(BUILD_DIR)/main.o $(BUILD_DIR)/init.o $(BUILD_DIR)/interrupt.o \
$(BUILD_DIR)/timer.o $(BUILD_DIR)/kernel.o $(BUILD_DIR)/print.o \
$(BUILD_DIR)/debug.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/string.o \
$(BUILD_DIR)/bitmap.o $(BUILD_DIR)/thread.o $(BUILD_DIR)/list.o \
$(BUILD_DIR)/switch.o $(BUILD_DIR)/sync.o $(BUILD_DIR)/console.o \
$(BUILD_DIR)/keyboard.o $(BUILD_DIR)/ioqueue.o $(BUILD_DIR)/tss.o \
$(BUILD_DIR)/process.o $(BUILD_DIR)/syscall-init.o $(BUILD_DIR)/syscall.o \
$(BUILD_DIR)/stdio.o $(BUILD_DIR)/stdio-kernel.o $(BUILD_DIR)/ide.o \
$(BUILD_DIR)/fs.o $(BUILD_DIR)/inode.o $(BUILD_DIR)/file.o $(BUILD_DIR)/dir.o
############ C 代码编译 ##############
$(BUILD_DIR)/main.o: kernel/main.c lib/kernel/print.h \
lib/stdint.h kernel/init.h lib/string.h \
lib/kernel/stdio-kernel.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/init.o: kernel/init.c kernel/init.h lib/kernel/print.h \
lib/stdint.h kernel/interrupt.h device/timer.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/interrupt.o: kernel/interrupt.c kernel/interrupt.h \
lib/stdint.h kernel/global.h lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/timer.o: device/timer.c device/timer.h lib/stdint.h \
lib/kernel/io.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/debug.o: kernel/debug.c kernel/debug.h \
lib/kernel/print.h lib/stdint.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/string.o: lib/string.c lib/string.h \
kernel/debug.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/memory.o: kernel/memory.c kernel/memory.h \
lib/stdint.h lib/kernel/bitmap.h kernel/debug.h lib/string.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/bitmap.o: lib/kernel/bitmap.c lib/kernel/bitmap.h \
lib/string.h kernel/interrupt.h lib/kernel/print.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/thread.o: thread/thread.c thread/thread.h \
lib/stdint.h lib/string.h kernel/global.h kernel/memory.h \
kernel/debug.h kernel/interrupt.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/list.o: lib/kernel/list.c lib/kernel/list.h \
kernel/interrupt.h lib/stdint.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/sync.o: thread/sync.c thread/sync.h \
lib/stdint.h thread/thread.h kernel/debug.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/console.o: device/console.c device/console.h \
lib/kernel/print.h thread/sync.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/keyboard.o: device/keyboard.c device/keyboard.h \
lib/kernel/print.h lib/kernel/io.h kernel/interrupt.h \
kernel/global.h lib/stdint.h device/ioqueue.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ioqueue.o: device/ioqueue.c device/ioqueue.h \
kernel/interrupt.h kernel/global.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/tss.o: userprog/tss.c userprog/tss.h \
kernel/global.h thread/thread.h lib/kernel/print.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/process.o: userprog/process.c userprog/process.h \
lib/string.h kernel/global.h kernel/memory.h lib/kernel/print.h \
thread/thread.h kernel/interrupt.h kernel/debug.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall-init.o: userprog/syscall-init.c userprog/syscall-init.h \
lib/user/syscall.h lib/stdint.h lib/kernel/print.h \
kernel/interrupt.h thread/thread.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/syscall.o: lib/user/syscall.c lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio.o: lib/stdio.c lib/stdio.h lib/stdint.h lib/string.h lib/user/syscall.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/stdio-kernel.o : lib/kernel/stdio-kernel.c lib/kernel/stdio-kernel.h \
lib/stdio.h device/console.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/ide.o: device/ide.c device/ide.h lib/stdint.h kernel/debug.h \
lib/kernel/stdio-kernel.h lib/stdio.h kernel/global.h thread/sync.h \
lib/kernel/io.h device/timer.h kernel/interrupt.h lib/kernel/list.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/fs.o: fs/fs.c fs/fs.h lib/stdint.h kernel/global.h device/ide.h fs/inode.h fs/dir.h \
fs/super_block.h lib/kernel/stdio-kernel.h lib/string.h kernel/debug.h lib/kernel/list.h \
fs/file.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/inode.o: fs/inode.c fs/inode.h device/ide.h kernel/debug.h thread/thread.h \
kernel/memory.h lib/string.h lib/kernel/list.h kernel/interrupt.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/file.o: fs/file.c fs/file.h lib/kernel/stdio-kernel.h thread/thread.h device/ide.h \
fs/file.h kernel/global.h
$(CC) $(CFLAGS) $< -o $@
$(BUILD_DIR)/dir.o: fs/dir.c fs/dir.h device/ide.h fs/fs.h fs/inode.h kernel/memory.h lib/string.h lib/stdint.h \
lib/kernel/stdio-kernel.h kernel/debug.h fs/file.h kernel/memory.h lib/string.h kernel/debug.h
$(CC) $(CFLAGS) $< -o $@
############## 汇编代码编译 ###############
$(BUILD_DIR)/kernel.o: kernel/kernel.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/print.o: lib/kernel/print.asm
$(AS) $(ASFLAGS) $< -o $@
$(BUILD_DIR)/switch.o: thread/switch.asm
$(AS) $(ASFLAGS) $< -o $@
############## 链接所有目标文件 #############
$(BUILD_DIR)/kernel.bin: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@
.PHONY: mk_dir hd clean all
mk_dir:
if [ ! -d $(BUILD_DIR) ]; then mkdir $(BUILD_DIR); fi
hd:
sudo dd if=$(BUILD_DIR)/kernel.bin \
of=/home/steven/source/os/bochs/hd60M.img \
bs=512 count=200 seek=9 conv=notrunc
clean:
cd $(BUILD_DIR) && rm -f ./*
build: $(BUILD_DIR)/kernel.bin
all: mk_dir build hd
编译,第一次运行:
第二次运行:
文件的打开与关闭
有关文件读写的操作都要用到文件描述符,这里要改进一下sys_open来支持更多功能
文件的打开
fs/file.h
/* 打开编号为 inode_no 的 inode 对应的文件, 若成功则返回文件描述符, 否则返回-1 */
int32_t file_open(uint32_t inode_no, uint8_t flag);
/* 关闭文件 */
int32_t file_close(struct file* file);
fs/file.c
/* 打开编号为 inode_no 的 inode 对应的文件, 若成功则返回文件描述符, 否则返回-1 */
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);
// 每次打开文件, 要将 fd_pos 还原为 0, 即让文件内的指针指向开头
file_table[fd_idx].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)) {
// 进入临界区要关中断
enum intr_status old_status = intr_disable();
// 若当前没有其它进程写该文件, 将其占用
if(!(*write_deny)) {
// 置为 true, 避免多个进程同时写此文件
*write_deny = 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, 保持默认
// 将全局描述符下标安装到进程或线程自己的文件描述符数组 fd_table中,
// 成功返回下标, 失败返回 -1
return pcb_fd_install(fd_idx);
}
fs/fs.c
// 打开或创建文件成功后, 返回文件描述符, 否则返回 -1
int32_t sys_open(const char* pathname, uint8_t flags) {
...
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;
}
这里是改进sys_open函数,使其拥有打开文件的功能
文件的关闭
fs/file.c
/* 关闭文件 */
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;
}
就是关闭inode,恢复inode为未使用的状态
fs/fs.c
/* 将文件描述符转化为文件表的下标 */
static uint32_t fd_local2global(uint32_t local_fd) {
struct task_struct* cur = running_thread();
int32_t global_fd = cur->fd_table[local_fd];
ASSERT(global_fd >= 0 && global_fd < MAX_FILE_OPEN);
return (uint32_t) global_fd;
}
/* 关闭文件描述符 fd 指向的文件, 成功返回 0, 否则返回 -1 */
int32_t sys_close(int32_t fd) {
// 返回值默认为 -1, 即失败
int32_t ret = -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_close 函数实际上就是上面两个函数的封装,关闭文件实际上就是获取该文件结构(通过文件表下标获取,知道文件描述符数组下标即可实现),然后将该文件结构中的inode信息还原
运行 Bochs
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
uint32_t fd = sys_open("/file1", O_RDONLY);
printf("fd:%d\n", fd);
sys_close(fd);
printf("%d closed now\n", fd);
while(1);
return 0;
}
编译,运行:
实现文件写入
本节要实现系统调用sys_write的内核实现,让write支持文件描述符
实现 file_write
实现文件系统的套路:在file.c中添加file_xxx
的核心功能函数,然后再fs.c中套个壳(封装)
fs/file.c
/* 把 buf 中的 count 个字节写入 file, 成功则返回写入的字节数, 失败则返回 -1 */
int32_t file_write(struct file* file, const void* buf, uint32_t count) {
// 文件目前最大只支持 512*140 = 71680 字节
if((file->fd_inode->i_size + count) > (BLOCK_SIZE * 140)) {
printk("exceed max file_size 71680 bytes, write file failed\n");
return -1;
}
uint8_t* io_buf = sys_malloc(512);
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 中, 下面开始写数据
// 置 fd_pos 为文件大小 -1, 下面在写数据时随时更新
file->fd_pos = file->fd_inode->i_size - 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
inode_sync(cur_part, file->fd_inode, io_buf);
sys_free(all_blocks);
sys_free(io_buf);
return bytes_written;
}
先判断是否是第一次写入文件,如果是,则块地址未分配,为0,则申请块地址,填充到inode信息中去,将位图同步到硬盘中
接着判断写入前后占用块数,也就是写入前后扇区数有没有变化:(这里将数据收集到all_block中,收集完毕之后再进行写入)
-
如果无,则判断是否使用到了间接块
- 未使用间接块,则把使用的块记录到all_block中
- 使用到了间接块,则需要将间接块地址读进来添加到all_block中
-
如果有,分三种情况
-
若已经使用的扇区数在12块之内,新增了若干块后,文件大小还在12块之内,直接分配所需的块并把块地址写入i_sectors数组中即可。
先将原有地址收录到all_block中,在将未来要用的扇区分配好写入all_block
-
若已经使用的块数在12块之内,新增了若干块后,文件大小超过了12块,这种情况下所申请的块除了要写入i_sector 数组,还要创建一级间接块表并写入块地址。
先将原有地址收录到all_block中,分配一个块作为一级间接索引表,然后在将未来要用的扇区分配好写入all_block
-
若已经使用的扇区数超过了12块,这种情况下要在一级间接块表中创建间接块项,间接块项就是在一级间接块表中记录间接块地址的条目。
直接将间接块索引表读取到all_block中即可,新分配的块地址也直接写入间接索引表
-
实现 sys_write
fs/fs.c
/* 将 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[1204] = {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;
}
}
这里比较好看懂,如果是输出到标准输出,则用缓冲区存一下要输出的内容,console_put_str直接打印在终端上了,不然就输出到文件里
这里修改了系统调用sys_write,所以系统调用相关文件也需要进行修改
lib/user/syscall.c
/* 打印字符串str */
uint32_t write(int32_t fd, const void* buf, uint32_t count) {
return _syscall3(SYS_WRITE, fd, buf, count);
}
就是把参数数量变了,记得在syscall.h把声明也改一下
lib/stdio.c
/* 格式化输出字符串 format */
uint32_t printf(const char* format, ...) {
va_list args;
va_start(args, format); // 使 args 指向 format
char buf[1024] = {0}; // 用于存储拼接后的字符串
vsprintf(buf, format, args);
va_end(args);
return write(1, buf, strlen(buf)); // 在终端输出 buf 中的字符串
}
这里把write参数给改了
接着把userprog/syscall-init.c中的sys-write给去掉即可
#include "syscall-init.h"
#include "../lib/user/syscall.h"
#include "stdint.h"
#include "print.h"
#include "../thread/thread.h"
#include "../device/console.h"
#include "string.h"
#include "../kernel/memory.h"
#include "../fs/fs.h"
#define syscall_nr 32 // 最大支持的系统子功能调用数
typedef void* syscall;
syscall syscall_table[syscall_nr];
/* 返回当前任务的 pid */
uint32_t sys_getpid(void) {
return running_thread()->pid;
}
/* 初始化系统调用 */
void syscall_init(void) {
put_str("syscall_init start\n");
syscall_table[SYS_GETPID] = sys_getpid;
syscall_table[SYS_WRITE] = sys_write;
syscall_table[SYS_MALLOC] = sys_malloc;
syscall_table[SYS_FREE] = sys_free;
put_str("syscall_init done\n");
}
运行 Bochs
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
uint32_t fd = sys_open("/file1", O_RDWR);
printf("fd:%d\n", fd);
sys_write(fd, "hello,world\n", 12);
sys_close(fd);
printf("%d closed now\n", fd);
while(1);
return 0;
}
编译,运行:
printf("\n%d\n", 0x2ab * 512);
// 结果为 349696
读取文件
实现 file_read
fs/file.c
/* 从文件 file 中读取 count 个字节写入 buf, 返回读出的字节数, 若到文件尾则返回 -1 */
int32_t file_read(struct file* file, void* buf, uint32_t count) {
uint8_t* buf_dst = (uint8_t*) buf;
uint32_t size = count, size_left = size;
// 若要读取的字节数超过了文件可读的剩余量, 就用剩余量作为待读取的字节数
if((file->fd_pos + count) > file->fd_inode->i_size) {
size = file->fd_inode->i_size - file->fd_pos;
size_left = size;
if(size == 0) {
// 若到文件尾则返回 -1
return -1;
}
}
uint8_t* io_buf = sys_malloc(BLOCK_SIZE);
if(io_buf == NULL) {
printk("file_read: 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_read: sys_malloc for all_blocks failed\n");
return -1;
}
uint32_t block_read_start_idx = file->fd_pos / BLOCK_SIZE; // 数据所在块的起始地址
uint32_t block_read_end_idx = (file->fd_pos + size) / BLOCK_SIZE; // 数据所在块的终止地址
uint32_t read_blocks = block_read_end_idx - block_read_start_idx; // 如增量为 0, 表示数据在同一扇区
ASSERT(block_read_start_idx < 139 && block_read_end_idx < 139);
int32_t indirect_block_table; // 用来获取一级间接表地址
uint32_t block_idx; // 获取待读的块地址
// 开始构建 all_blocks 块地址数组, 专门存储用到的块地址
if(read_blocks == 0) {
// 在同一扇区内读数据, 不涉及到跨扇区读取
ASSERT(block_read_end_idx == block_read_start_idx);
if(block_read_end_idx < 12) {
// 待读的数据在 12 个直接块之内
block_idx = block_read_end_idx;
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
} else {
// 若用到了一级间接块表, 需要将表中间接块读进来
indirect_block_table = file->fd_inode->i_sectors[12];
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
}
} else {
// 若要读多个块
// 第一种情况: 起始块和终止块属于直接块
if(block_read_end_idx < 12) {
// 数据结束所在的块属于直接块
block_idx = block_read_start_idx;
while(block_idx <= block_read_end_idx) {
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
block_idx++;
}
} else if(block_read_start_idx < 12 && block_read_end_idx >= 12) {
// 第二种情况: 待读入的数据跨越直接块和间接块两类
// 先将直接块地址写入 all_blocks
block_idx = block_read_start_idx;
while(block_idx < 12) {
all_blocks[block_idx] = file->fd_inode->i_sectors[block_idx];
block_idx++;
}
// 确保已经分配了一级间接块表
ASSERT(file->fd_inode->i_sectors[12] != 0);
// 再将间接块地址写入 all_blocks
indirect_block_table = file->fd_inode->i_sectors[12];
// 将一级间接块表读进来写入到第13个块的位置之后
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
} else {
// 第三种情况, 数据在间接块中
// 确保已经分配了一级间接块表
ASSERT(file->fd_inode->i_sectors[12] != 0);
// 获取一级间接表地址
indirect_block_table = file->fd_inode->i_sectors[12];
// 将一级间接块表读进来写入到第 13 个块的位置之后
ide_read(cur_part->my_disk, indirect_block_table, all_blocks + 12, 1);
}
}
// 用到的块地址已经收集到 all_blocks 中, 下面开始读数据
uint32_t sec_idx; // 用来索引扇区
uint32_t sec_lba; // 扇区地址
uint32_t sec_off_bytes; // 扇区内字节偏移量
uint32_t sec_left_bytes; // 扇区内剩余字节量
uint32_t chunk_size; // 每次读取硬盘的数据块大小
uint32_t bytes_read = 0;
while(bytes_read < size) {
// 直到读完为止
sec_idx = file->fd_pos / BLOCK_SIZE;
sec_lba = all_blocks[sec_idx];
sec_off_bytes = file->fd_pos % BLOCK_SIZE;
sec_left_bytes = BLOCK_SIZE - sec_off_bytes;
// 待读入的数据大小
chunk_size = size_left < sec_left_bytes ? size_left : sec_left_bytes;
memset(io_buf, 0, BLOCK_SIZE);
ide_read(cur_part->my_disk, sec_lba, io_buf, 1);
memcpy(buf_dst, io_buf + sec_off_bytes, chunk_size);
buf_dst += chunk_size;
file->fd_pos += chunk_size;
bytes_read += chunk_size;
size_left -= chunk_size;
}
sys_free(all_blocks);
sys_free(io_buf);
return bytes_read;
}
读取文件同写入文件类似,先创建缓冲区,将用到的块地址都收集起来(三种情况),然后再将数据读取到缓冲区中
收集的时候分三种情况与写入的时候一样:
- 若起始块和终止块都在 12 块之内,直接读入 i_sectors 数组中即可。
- 若起始块在 12 块之内,结束块超过了 12 块,除了要读入 i_sector 数组,还要从一级间接块索引表中读取间接块地址。
- 若起始块超过了 12 块,这种情况下要在 级间接块索引表中读取间接块。
实现 sys_read
fs/fs.c
/* 从文件描述符 fd 指向的文件中读取 count 个字节到 buf, 若成功则返回读出的字节数, 到文件尾则返回 -1 */
int32_t sys_read(int32_t fd, void* buf, uint32_t count) {
if(fd < 0) {
printk("sys_read: fd error\n");
return -1;
}
ASSERT(buf != NULL);
uint32_t _fd = fd_local2global(fd);
return file_read(&file_table[_fd], buf, count);
}
运行 Bochs
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
uint32_t fd = sys_open("/file1", O_RDWR);
printf("open /file1, fd:%d\n", fd);
char buf[64] = {0};
int read_bytes = sys_read(fd, buf, 18);
printf("1_ read %d bytes:\n%s\n", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("2_ read %d bytes:\n%s", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("3_ read %d bytes:\n%s", read_bytes, buf);
printf("________ close file1 and reopen ________\n");
sys_close(fd);
fd = sys_open("/file1", O_RDWR);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 24);
printf("4_ read %d bytes:\n%s", read_bytes, buf);
while(1);
return 0;
}
在之前写入了两次hello,world\n
读取成功!!!
实现文件读写指针定位
实现 sys_lseek
fs/fs.h
/* 文件读写位置偏移量 */
enum whence {
SEEK_SET = 1, // 文件开始
SEEK_CUR, // 当前读写位置
SEEK_END // 文件最后一个字节的下一个字节
};
...
/* 重置用于文件读写操作的偏移指针, 成功时返回新的偏移量, 出错时返回 -1 */
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence);
fs/fs.c
/* 重置用于文件读写操作的偏移指针, 成功时返回新的偏移量, 出错时返回 -1 */
int32_t sys_lseek(int32_t fd, int32_t offset, uint8_t whence) {
if(fd < 0) {
printk("sys_lseek: fd error\n");
return -1;
}
ASSERT(whence > 0 && whence < 4);
uint32_t _fd = fd_local2global(fd);
struct file* pf = &file_table[_fd];
// 新的偏移量必须位于文件大小之内
int32_t new_pos = 0;
int32_t file_size = (int32_t) pf->fd_inode->i_size;
switch(whence) {
// SEEK_SET 新的读写位置是相对于文件开头再增加 offset 个位移量
case SEEK_SET:
new_pos = offset;
break;
// SEEK_CUR 新的读写位置是相对于当前的位置增加 offset 个位移量
// // offset 可正可负
case SEEK_CUR:
new_pos = (int32_t) pf->fd_pos + offset;
break;
// SEEK_END 新的读写位置是相对于文件尺寸再增加 offset 个位移量
// 此情况下, offset应该为负值
case SEEK_END:
new_pos = file_size + offset;
break;
}
if(new_pos < 0 || new_pos > (file_size - 1)) {
return -1;
}
pf->fd_pos = new_pos;
return pf->fd_pos;
}
通过参照物和偏移量移动指向文件的位置,原理很简单
运行 Bochs
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
uint32_t fd = sys_open("/file1", O_RDWR);
printf("open /file1, fd:%d\n", fd);
char buf[64] = {0};
int read_bytes = sys_read(fd, buf, 18);
printf("1_ read %d bytes:\n%s\n", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("2_ read %d bytes:\n%s", read_bytes, buf);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 6);
printf("3_ read %d bytes:\n%s", read_bytes, buf);
printf("________ SEEK_SET 0 ________\n");
sys_lseek(fd, 0, SEEK_SET);
memset(buf, 0, 64);
read_bytes = sys_read(fd, buf, 24);
printf("4_ read %d bytes:\n%s", read_bytes, buf);
sys_close(fd);
while(1);
return 0;
}
运行,编译:
之前这里第二次读取,读出来的是hello\n
这里通过sys_lseek将指针重置到文件头,重新读取,即是hello,world\nhello,world\n
实现文件删除功能
文件删除是文件创建的逆过程
涉及inode,inode位图,目录inode的size,目录项,数据块,数据块位图的回收
回收 inode
inode是文件系统的灵魂,删除文件最重要的就是回收文件对应的inode了,与inode相关资源有:
- inode 位图
- inode_table 文件表
- inode中的数据块
- 一级间接索引表本身扇区地址
fs/inode.c
/* 将硬盘分区 part 上的 inod e清空 */
void inode_delete(struct partition* part, uint32_t inode_no, void* io_buf) {
ASSERT(inode_no < 4096);
struct inode_position inode_pos;
// inode 位置信息会存入 inode_pos
inode_locate(part, inode_no, &inode_pos);
ASSERT(inode_pos.sec_lba <= (part->start_lba + part->sec_cnt));
char* inode_buf = (char*) io_buf;
if(inode_pos.two_sec) {
// inode 跨扇区, 读入 2 个扇区
// 将原硬盘上的内容先读出来
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
// 将 inode_buf 清 0
memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
// 用清 0 的内存数据覆盖磁盘
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 2);
} else {
// 未跨扇区, 只读入 1 个扇区就好
// 将原硬盘上的内容先读出来
ide_read(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
// 将 inode_buf 清 0
memset((inode_buf + inode_pos.off_size), 0, sizeof(struct inode));
// 用清 0 的内存数据覆盖磁盘
ide_write(part->my_disk, inode_pos.sec_lba, inode_buf, 1);
}
}
/* 回收 inode 的数据块和 inode 本身 */
void inode_release(struct partition* part, uint32_t inode_no) {
struct inode* inode_to_del = inode_open(part, inode_no);
ASSERT(inode_to_del->i_no == inode_no);
// 1. 回收 inode 占用的所有块
uint8_t block_idx = 0, block_cnt = 12;
uint32_t block_bitmap_idx;
// 12个直接块 + 128个间接块
uint32_t all_blocks[140] = {0};
// a. 先将前 12 个直接块存入 all_blocks
while(block_idx < 12) {
all_blocks[block_idx] = inode_to_del->i_sectors[block_idx];
block_idx++;
}
// b. 如果一级间接块表存在, 将其 128 个间接块读到 all_blocks[12~], 并释放一级间接块表所占的扇区
if(inode_to_del->i_sectors[12] != 0) {
ide_read(part->my_disk, inode_to_del->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
// 回收一级间接块表占用的扇区
block_bitmap_idx = inode_to_del->i_sectors[12] - part->sb->data_start_lba;
ASSERT(block_bitmap_idx > 0);
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
// c. inode 所有的块地址已经收集到 all_blocks 中, 下面逐个回收
block_idx = 0;
while (block_idx < block_cnt) {
if(all_blocks[block_idx] != 0) {
block_bitmap_idx = 0;
block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
ASSERT(block_bitmap_idx > 0);
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
}
block_idx++;
}
// 2. 回收该 inode 所占用的 inode
bitmap_set(&part->inode_bitmap, inode_no, 0);
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
/****** 以下inode_delete是调试用的 ******
* 此函数会在 inode_table 中将此 inode 清 0,
* 但实际上是不需要的, inode 分配是由 inode 位图控制的,
* 硬盘上的数据不需要清 0, 可以直接覆盖
* */
void* io_buf = sys_malloc(1024);
inode_delete(part, inode_no, io_buf);
sys_free(io_buf);
inode_close(inode_to_del);
}
删除目录项
与删除目录项相关的工作:
- 在文件所在的目录中擦除该文件的目录项,使其为 0。
- 根目录是必须存在的, 它是文件读写的根基, 不应该被清空,它至少要保留1个块。 如果目录项独占1个块, 并且该块不是根目录最后一个块的话, 将其回收。
- 目录inode的i_size是目录项大小的总和, 因此还要将i_size减去一个目录项的单位大小。
- 目录inode改变后, 要同步到硬盘。
fs/dir.c
/* 把分区 part 目录 pdir 中编号为 inode_no 的目录项删除 */
bool delete_dir_entry(struct partition* part, struct dir* pdir, uint32_t inode_no, void* io_buf) {
struct inode* dir_inode = pdir->inode;
uint32_t block_idx = 0, all_blocks[140] = {0};
// 收集目录全部块地址
while(block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
if(dir_inode->i_sectors[12]) {
ide_read(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
}
// 目录项在存储时保证不会跨扇区
uint32_t dir_entry_size = part->sb->dir_entry_size;
// 每扇区最大的目录项数目
uint32_t dir_entrys_per_sec = (SECTOR_SIZE / dir_entry_size);
struct dir_entry* dir_e = (struct dir_entry*) io_buf;
struct dir_entry* dir_entry_found = NULL;
uint8_t dir_entry_idx, dir_entry_cnt;
// 目录的第 1 个块
bool is_dir_first_block = false;
// 遍历所有块, 寻找目录项
block_idx = 0;
while(block_idx < 140) {
is_dir_first_block = false;
if(all_blocks[block_idx] == 0) {
block_idx++;
continue;
}
dir_entry_idx = dir_entry_cnt = 0;
memset(io_buf, 0, SECTOR_SIZE);
// 读取扇区, 获得目录项
ide_read(part->my_disk, all_blocks[block_idx], io_buf, 1);
// 遍历所有的目录项, 统计该扇区的目录项数量及是否有待删除的目录项
while(dir_entry_idx < dir_entrys_per_sec) {
if((dir_e + dir_entry_idx)->f_type != FT_UNKNOWN) {
if(!strcmp((dir_e + dir_entry_idx)->filename, ".")) {
// 如果是当前目录: "."
is_dir_first_block = true;
} else if (strcmp((dir_e + dir_entry_idx)->filename, ".") &&
strcmp((dir_e + dir_entry_idx)->filename, "..")) {
// 如果不是 "." 和 ".."
// 统计此扇区内的目录项个数, 用来判断删除目录项后是否回收该扇区
dir_entry_cnt++;
if((dir_e + dir_entry_idx)->i_no == inode_no) {
// 确保目录中只有一个编号为 inode_no 的 inode, 找到一次后 dir_entry_found 就不再是 NULL
ASSERT(dir_entry_found == NULL);
dir_entry_found = dir_e + dir_entry_idx;
// 找到后也继续遍历, 统计总共的目录项数
}
}
}
dir_entry_idx++;
}
// 若此扇区未找到该目录项, 继续在下个扇区中找
if (dir_entry_found == NULL) {
block_idx++;
continue;
}
// 在此扇区中找到目录项后, 清除该目录项并判断是否回收扇区, 随后退出循环直接返回
ASSERT(dir_entry_cnt >= 1);
// 除目录第 1 个扇区外, 若该扇区上只有该目录项自己, 则将整个扇区回收
if(dir_entry_cnt == 1 && !is_dir_first_block) {
// a. 在块位图中回收该块
uint32_t block_bitmap_idx = all_blocks[block_idx] - part->sb->data_start_lba;
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
// b. 将块地址从数组 i_sectors 或索引表中去掉
if(block_idx < 12) {
dir_inode->i_sectors[block_idx] = 0;
} else {
// 在一级间接索引表中擦除该间接块地址
// 先判断一级间接索引表中间接块的数量, 如果仅有这 1 个间接块, 连同间接索引表所在的块一同回收
uint32_t indirect_blocks = 0;
uint32_t indirect_block_idx = 12;
while(indirect_block_idx < 140) {
if(all_blocks[indirect_block_idx] != 0) {
indirect_blocks++;
}
}
// 包括当前间接块
ASSERT(indirect_blocks >= 1);
// 间接索引表中还包括其它间接块, 仅在索引表中擦除当前这个间接块地址
if(indirect_blocks > 1) {
all_blocks[block_idx] = 0;
ide_write(part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
} else {
// 间接索引表中就当前这 1 个间接块, 直接把间接块索引表所在的块回收, 然后擦除间接索引表块地址
// 回收间接索引表所在的块
block_bitmap_idx = dir_inode->i_sectors[12] - part->sb->data_start_lba;
bitmap_set(&part->block_bitmap, block_bitmap_idx, 0);
bitmap_sync(cur_part, block_bitmap_idx, BLOCK_BITMAP);
// 将间接索引表地址清 0
dir_inode->i_sectors[12] = 0;
}
}
} else {
// 仅将该目录项清空
memset(dir_entry_found, 0, dir_entry_size);
ide_write(part->my_disk, all_blocks[block_idx], io_buf, 1);
}
// 更新 inode 信息并同步到硬盘
ASSERT(dir_inode->i_size >= dir_entry_size);
dir_inode->i_size -= dir_entry_size;
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(part, dir_inode, io_buf);
return true;
}
// 所有块中未找到则返回 false, 若出现这种情况应该是 search_file 出错了
return false;
}
实现 sys_unlink
fs/fs.c
/* 删除文件(非目录), 成功返回 0, 失败返回 -1 */
int32_t sys_unlink(const char* pathname) {
ASSERT(strlen(pathname) < MAX_PATH_LEN);
// 先检查待删除的文件是否存在
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);
if(inode_no == -1) {
printk("file %s not found!\n", pathname);
dir_close(searched_record.parent_dir);
return -1;
}
if(searched_record.file_type == FT_DIRECTORY) {
printk("can`t delete a directory with unlink(), use rmdir() to instead\n");
dir_close(searched_record.parent_dir);
return -1;
}
// 检查是否在已打开文件列表(文件表)中
uint32_t file_idx = 0;
while(file_idx < MAX_FILE_OPEN) {
if (file_table[file_idx].fd_inode != NULL && (uint32_t) inode_no == file_table[file_idx].fd_inode->i_no) {
break;
}
file_idx++;
}
if(file_idx < MAX_FILE_OPEN) {
// 文件在已打开文件列表(文件表)中, 即文件已打开, 不允许删除
dir_close(searched_record.parent_dir);
printk("file %s is in use, not allow to delete!\n", pathname);
return -1;
}
ASSERT(file_idx == MAX_FILE_OPEN);
// 为 delete_dir_entry 申请缓冲区
void* io_buf = sys_malloc(SECTOR_SIZE + SECTOR_SIZE);
if (io_buf == NULL) {
dir_close(searched_record.parent_dir);
printk("sys_unlink: malloc for io_buf failed\n");
return -1;
}
struct dir* parent_dir = searched_record.parent_dir;
delete_dir_entry(cur_part, parent_dir, inode_no, io_buf);
inode_release(cur_part, inode_no);
sys_free(io_buf);
dir_close(searched_record.parent_dir);
return 0; // 成功删除文件
}
运行 Bochs
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
printf("/file1 delete %s!\n", sys_unlink("/file1") == 0 ? "done" : "fail");
while(1);
return 0;
}
再一次运行,就会显示fail!
创建目录
实现 sys_mkdir 创建目录
创建目录的工作是:
- 确认待创建的新目录在文件系统上不存在。
- 为新目录创建inode。
- 为新目录分配 1 个块存储该目录中的目录项。
- 在新目录中创建两个目录项 “.“和 “ …“, 这是每个目录都必须存在的两个目录项。
- 在新目录的父目录中添加新目录的目录项。
- 将以上资源的变更同步到硬盘。
fs/fs.c
/* 创建目录 pathname, 成功返回 0, 失败返回 -1 */
int32_t sys_mkdir(const char* pathname) {
// 用于操作失败时回滚各资源状态
uint8_t rollback_step = 0;
void* io_buf = sys_malloc(SECTOR_SIZE * 2);
if(io_buf == NULL) {
printk("sys_mkdir: sys_malloc for io_buf failed\n");
return -1;
}
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = -1;
inode_no = search_file(pathname, &searched_record);
if(inode_no != -1) {
// 如果找到了同名目录或文件, 失败返回
printk("sys_mkdir: file or directory %s exist!\n", pathname);
rollback_step = 1;
goto rollback;
} else {
// 若未找到, 也要判断是在最终目录没找到还是某个中间目录不存在
uint32_t pathname_depth = path_depth_cnt((char*) pathname);
uint32_t path_searched_depth = path_depth_cnt(searched_record.searched_path);
// 先判断是否把 pathname 的各层目录都访问到了, 即是否在某个中间目录就失败了
if(pathname_depth != path_searched_depth) {
// 说明并没有访问到全部的路径, 某个中间目录是不存在的
printk("sys_mkdir: can`t access %s, subpath %s is not exist\n", pathname, searched_record.searched_path);
rollback_step = 1;
goto rollback;
}
}
struct dir* parent_dir = searched_record.parent_dir;
// 目录名称后可能会有字符 '/', 所以最好直接用 searched_record.searched_path, 无 '/'
char* dirname = strrchr(searched_record.searched_path, '/') + 1;
inode_no = inode_bitmap_alloc(cur_part);
if (inode_no == -1) {
printk("sys_mkdir: allocate inode failed\n");
rollback_step = 1;
goto rollback;
}
struct inode new_dir_inode;
// 初始化 inode
inode_init(inode_no, &new_dir_inode);
// 用来记录 block 对应于 block_bitmap 中的索引
uint32_t block_bitmap_idx = 0;
int32_t block_lba = -1;
// 为目录分配一个块, 用来写入目录 . 和 ..
block_lba = block_bitmap_alloc(cur_part);
if (block_lba == -1) {
printk("sys_mkdir: block_bitmap_alloc for create directory failed\n");
rollback_step = 2;
goto rollback;
}
new_dir_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);
// 将当前目录的目录项 '.' 和 '..' 写入目录
// 清空 io_buf
memset(io_buf, 0, SECTOR_SIZE * 2);
struct dir_entry* p_de = (struct dir_entry*) io_buf;
// 初始化当前目录 '.'
memcpy(p_de->filename, ".", 1);
p_de->i_no = inode_no;
p_de->f_type = FT_DIRECTORY;
p_de++;
// 初始化当前目录 '..'
memcpy(p_de->filename, "..", 2);
p_de->i_no = parent_dir->inode->i_no;
p_de->f_type = FT_DIRECTORY;
ide_write(cur_part->my_disk, new_dir_inode.i_sectors[0], io_buf, 1);
new_dir_inode.i_size = 2 * cur_part->sb->dir_entry_size;
// 在父目录中添加自己的目录项
struct dir_entry new_dir_entry;
memset(&new_dir_entry, 0, sizeof(struct dir_entry));
// 在内存中初始化目录项 new_dir_entry
create_dir_entry(dirname, inode_no, FT_DIRECTORY, &new_dir_entry);
// 清空 io_buf
memset(io_buf, 0, SECTOR_SIZE*2);
// 将目录项 new_dir_entry 写入父目录 parent_dir 中
if(!sync_dir_entry(parent_dir, &new_dir_entry, io_buf)) {
// sync_dir_entry 中将 block_bitmap 通过 bitmap_sync 同步到硬盘
printk("sys_mkdir: sync_dir_entry to disk failed\n");
rollback_step = 2;
goto rollback;
}
// 父目录的 inode 同步到硬盘
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(cur_part, parent_dir->inode, io_buf);
// 将新创建目录的 inode 同步到硬盘
memset(io_buf, 0, SECTOR_SIZE * 2);
inode_sync(cur_part, &new_dir_inode, io_buf);
// 将 inode 位图同步到硬盘
bitmap_sync(cur_part, inode_no, INODE_BITMAP);
sys_free(io_buf);
// 关闭所创建目录的父目录
dir_close(searched_record.parent_dir);
return 0;
// 创建文件或目录需要创建相关的多个资源, 若某步失败则会执行到下面的回滚步骤
rollback:
switch(rollback_step) {
case 2:
// 如果新文件的 inode 创建失败, 之前位图中分配的 inode_no 也要恢复
bitmap_set(&cur_part->inode_bitmap, inode_no, 0);
case 1:
// 关闭所创建目录的父目录
dir_close(searched_record.parent_dir);
break;
}
sys_free(io_buf);
return -1;
}
和创建文件有点相似
运行 Bochs 功能验证
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
printf("/dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
printf("/dir1 create %s!\n", sys_mkdir("/dir1") == 0 ? "done" : "fail");
printf("now, /dir1/subdir1 create %s!\n", sys_mkdir("/dir1/subdir1") == 0 ? "done" : "fail");
int fd = sys_open("/dir1/subdir1/file2", O_CREAT|O_RDWR);
if (fd != -1) {
printf("/dir1/subdir1/file2 create done!\n");
sys_write(fd, "Catch me if you can!\n", 21);
sys_lseek(fd, 0, SEEK_SET);
char buf[32] = {0};
sys_read(fd, buf, 21);
printf("/dir1/subdir1/file2 says:\n%s", buf);
sys_close(fd);
}
while(1);
return 0;
}
编译,运行:
遍历目录
打开和关闭目录
fs/fs.c
/* 目录打开成功后返回目录指针, 失败返回 NULL */
struct dir* sys_opendir(const char* name) {
ASSERT(strlen(name) < MAX_PATH_LEN);
// 如果是根目录 '/', 直接返回 &root_dir
if(name[0] == '/' && (name[1] == 0 || name[1] == '.')) {
return &root_dir;
}
// 先检查待打开的目录是否存在
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(name, &searched_record);
struct dir* ret = NULL;
if(inode_no == -1) {
// 找不到目录
printk("In %s, sub path %s not exist\n", name, searched_record.searched_path);
} else {
if (searched_record.file_type == FT_REGULAR) {
printk("%s is regular file!\n", name);
} else if (searched_record.file_type == FT_DIRECTORY) {
ret = dir_open(cur_part, inode_no);
}
}
dir_close(searched_record.parent_dir);
return ret;
}
/* 成功关闭目录 dir 返回 0, 失败返回 -1 */
int32_t sys_closedir(struct dir* dir) {
int32_t ret = -1;
if(dir != NULL) {
dir_close(dir);
ret = 0;
}
return ret;
}
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
process_execute(u_prog_a, "u_prog_a");
process_execute(u_prog_b, "u_prog_b");
thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
struct dir* p_dir = sys_opendir("/dir1/subdir1");
if (p_dir) {
printf("/dir1/subdir1 open done!\n");
if (sys_closedir(p_dir) == 0) {
printf("/dir1/subdir1 close done!\n");
} else {
printf("/dir1/subdir1 close fail!\n");
}
} else {
printf("/dir1/subdir1 open fail!\n");
}
while(1);
return 0;
}
读取1个目录项
fs/dir.c
/* 读取目录, 成功返回 1个目录项, 失败返回 NULL */
struct dir_entry* dir_read(struct dir* dir) {
struct dir_entry* dir_e = (struct dir_entry*) dir->dir_buf;
struct inode* dir_inode = dir->inode;
uint32_t all_blocks[140] = {0}, block_cnt = 12;
uint32_t block_idx = 0, dir_entry_idx = 0;
while(block_idx < 12) {
all_blocks[block_idx] = dir_inode->i_sectors[block_idx];
block_idx++;
}
if(dir_inode->i_sectors[12] != 0) {
// 若含有一级间接块表
ide_read(cur_part->my_disk, dir_inode->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
}
block_idx = 0;
// 当前目录项的偏移, 此项用来判断是否是之前已经返回过的目录项
uint32_t cur_dir_entry_pos = 0;
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
// 1 扇区内可容纳的目录项个数
uint32_t dir_entrys_per_sec = SECTOR_SIZE / dir_entry_size;
// 因为此目录内可能删除了某些文件或子目录, 所以要遍历所有块
while (block_idx < block_cnt) {
if (dir->dir_pos >= dir_inode->i_size) {
return NULL;
}
if (all_blocks[block_idx] == 0) {
// 如果此块地址为0, 即空块, 继续读出下一块
block_idx++;
continue;
}
memset(dir_e, 0, SECTOR_SIZE);
ide_read(cur_part->my_disk, all_blocks[block_idx], dir_e, 1);
dir_entry_idx = 0;
// 遍历扇区内所有目录项
while (dir_entry_idx < dir_entrys_per_sec) {
if ((dir_e + dir_entry_idx)->f_type) {
// f_type != FT_UNKNOWN
// 判断是不是最新的目录项, 避免返回曾经已经返回过的目录项
if(cur_dir_entry_pos < dir->dir_pos) {
cur_dir_entry_pos += dir_entry_size;
dir_entry_idx++;
continue;
}
ASSERT(cur_dir_entry_pos == dir->dir_pos);
// 更新为新位置, 即下一个返回的目录项地址
dir->dir_pos += dir_entry_size;
return dir_e + dir_entry_idx;
}
dir_entry_idx++;
}
block_idx++;
}
return NULL;
}
实现 sys_readdir 和 sys_rewinddir
rewind:重绕
fs/fs.c
/* 读取目录 dir 的1个目录项, 成功后返回其目录项地址, 到目录尾时或出错时返回 NULL */
struct dir_entry* sys_readdir(struct dir* dir) {
ASSERT(dir != NULL);
return dir_read(dir);
}
/* 把目录 dir 的指针 dir_pos 置 0 */
void sys_rewinddir(struct dir* dir) {
dir->dir_pos = 0;
}
运行 Bochs 功能验证
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
struct dir* p_dir = sys_opendir("/dir1/subdir1");
if (p_dir) {
printf("/dir1/subdir1 open done!\ncontent:\n");
char* type = NULL;
struct dir_entry* dir_e = NULL;
while ((dir_e = sys_readdir(p_dir))) {
if (dir_e->f_type == FT_REGULAR) {
type = "regular";
} else {
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
if (sys_closedir(p_dir) == 0) {
printf("/dir1/subdir1 close done!\n");
} else {
printf("/dir1/subdir1 close fail!\n");
}
} else {
printf("/dir1/subdir1 open fail!\n");
}
/******** 测试代码 ********/
while(1);
return 0;
}
编译,运行:
删除目录
删除目录与判断空目录
在 Linux 中删除文件的时候,会提示目录非空,需要删除目录下的文件才能删除目录,删除非空目录实际上是采用递归的方式rm -r
fs/dir.c
/* 判断目录是否为空 */
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 */
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_sectors 中所占用的扇区, 并同步 inode_bitmap 和 block_bitmap
inode_release(cur_part, child_dir_inode->i_no);
sys_free(io_buf);
return 0;
}
实现 sys_rmdir
fs/fs.c
/* 删除空目录, 成功时返回 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;
}
main.c
int main() {
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
printf("/dir1 content before delete /dir1/subdir1:\n");
struct dir* dir = sys_opendir("/dir1/");
char* type = NULL;
struct dir_entry* dir_e = NULL;
while ((dir_e = sys_readdir(dir))) {
if (dir_e->f_type == FT_REGULAR) {
type = "regular";
} else {
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
printf("try to delete nonempty directory /dir1/subdir1\n");
if (sys_rmdir("/dir1/subdir1") == -1) {
printf("sys_rmdir: /dir1/subdir1 delete fail!\n");
}
printf("try to delete /dir1/subdir1/file2\n");
if (sys_rmdir("/dir1/subdir1/file2") == -1) {
printf("sys_rmdir: /dir1/subdir1/file2 delete fail!\n");
}
if (sys_unlink("/dir1/subdir1/file2") == 0) {
printf("sys_unlink: /dir1/subdir1/file2 delete done\n");
}
printf("try to delete directory /dir1/subdir1 again\n");
if (sys_rmdir("/dir1/subdir1") == 0) {
printf("/dir1/subdir1 delete done!\n");
}
printf("/dir1 content after delete /dir1/subdir1:\n");
sys_rewinddir(dir);
while ((dir_e = sys_readdir(dir))) {
if (dir_e->f_type == FT_REGULAR) {
type = "regular";
} else {
type = "directory";
}
printf(" %s %s\n", type, dir_e->filename);
}
/******** 测试代码 ********/
while(1);
return 0;
}
任务的工作目录
显示当前工作目录的原理和基础代码
每个目录中都有..
目录项,这个目录项表示父目录,那就进入父目录找有没有..
,这样依次找下去,最终找到根目录,这个过程中获取到的每个目录名加起来就是当前工作目录
cwd
current working dir
当前工作目录
fs/fs.c
/* 获得父目录的 inode 编号 */
static uint32_t get_parent_dir_inode_nr(uint32_t child_inode_nr, void* io_buf) {
struct inode* child_dir_inode = inode_open(cur_part, child_inode_nr);
// 目录中的目录项 ".." 中包括父目录 inode 编号, ".." 位于目录的第 0 块
uint32_t block_lba = child_dir_inode->i_sectors[0];
ASSERT(block_lba >= cur_part->sb->data_start_lba);
inode_close(child_dir_inode);
ide_read(cur_part->my_disk, block_lba, io_buf, 1);
struct dir_entry* dir_e = (struct dir_entry*) io_buf;
// 第 0 个目录项是 ".", 第 1 个目录项是 ".."
ASSERT(dir_e[1].i_no < 4096 && dir_e[1].f_type == FT_DIRECTORY);
// 返回父目录的 inode 编号
return dir_e[1].i_no;
}
/* 在 inode 编号为 p_inode_nr 的目录中查找 inode 编号为 c_inode_nr 的子目录的名字,
* 将名字存入缓冲区 path. 成功返回 0, 失败返 -1 */
static int get_child_dir_name(uint32_t p_inode_nr, uint32_t c_inode_nr, char* path, void* io_buf) {
struct inode* parent_dir_inode = inode_open(cur_part, p_inode_nr);
// 填充 all_blocks, 将该目录的所占扇区地址全部写入 all_blocks
uint8_t block_idx = 0;
uint32_t all_blocks[140] = {0}, block_cnt = 12;
while(block_idx < 12) {
all_blocks[block_idx] = parent_dir_inode->i_sectors[block_idx];
block_idx++;
}
if(parent_dir_inode->i_sectors[12]) {
// 若包含了一级间接块表, 将共读入 all_blocks.
ide_read(cur_part->my_disk, parent_dir_inode->i_sectors[12], all_blocks + 12, 1);
block_cnt = 140;
}
inode_close(parent_dir_inode);
struct dir_entry* dir_e = (struct dir_entry*) io_buf;
uint32_t dir_entry_size = cur_part->sb->dir_entry_size;
uint32_t dir_entrys_per_sec = (512 / dir_entry_size);
block_idx = 0;
// 遍历所有块
while (block_idx < block_cnt) {
if(all_blocks[block_idx]) {
// 如果相应块不为空则读入相应块
ide_read(cur_part->my_disk, all_blocks[block_idx], io_buf, 1);
uint8_t dir_e_idx = 0;
// 遍历每个目录项
while (dir_e_idx < dir_entrys_per_sec) {
if ((dir_e + dir_e_idx)->i_no == c_inode_nr) {
strcat(path, "/");
strcat(path, (dir_e + dir_e_idx)->filename);
return 0;
}
dir_e_idx++;
}
}
block_idx++;
}
return -1;
}
实现 sys_getcwd
为了实现当前工作目录,需要在进程PCB中添加成员来记录当前工作目录
thread/thread.h
uint32_t cwd_inode_nr; // 进程所在工作目录的inode编号
uint32_t stack_magic; // 栈的边界标记, 用于检测栈的溢出
thread/thread.c
void init_thread(struct task_struct* pthread, char* name, int prio) {
...
pthread->cwd_inode_nr = 0; // 以根目录做为默认工作路径
pthread->stack_magic = 0x19870916; // 自定义魔数
}
fs/fs.c
/* 把当前工作目录绝对路径写入 buf, size是 buf 的大小.
* 当 buf 为 NULL 时, 由操作系统分配存储工作路径的空间并返回地址
* 失败则返回 NULL */
char* sys_getcwd(char* buf, uint32_t size) {
// 确保 buf 不为空,若用户进程提供的 buf 为 NULL,
// 系统调用 getcwd 中要为用户进程通过 malloc 分配内存
ASSERT(buf != NULL);
void* io_buf = sys_malloc(SECTOR_SIZE);
if(io_buf == NULL) {
return NULL;
}
struct task_struct* cur_thread = running_thread();
int32_t parent_inode_nr = 0;
int32_t child_inode_nr = cur_thread->cwd_inode_nr;
// 最大支持 4096 个 inode
ASSERT(child_inode_nr >= 0 && child_inode_nr < 4096);
// 若当前目录是根目录, 直接返回 '/'
if(child_inode_nr == 0) {
buf[0] = '/';
buf[1] = 0;
return buf;
}
memset(buf, 0, size);
// 用来做全路径缓冲区
char full_path_reverse[MAX_PATH_LEN] = {0};
// 从下往上逐层找父目录, 直到找到根目录为止
// 当 child_inode_nr 为 0 时停止
while(child_inode_nr) {
parent_inode_nr = get_parent_dir_inode_nr(child_inode_nr, io_buf);
if (get_child_dir_name(parent_inode_nr, child_inode_nr, full_path_reverse, io_buf) == -1) {
sys_free(io_buf);
return NULL;
}
child_inode_nr = parent_inode_nr;
}
ASSERT(strlen(full_path_reverse) <= size);
// 至此 full_path_reverse 中的路径是反着的, 即子目录在前(左), 父目录在后(右)
// 用于记录字符串中最后一个斜杆地址
char* last_slash;
while ((last_slash = strrchr(full_path_reverse, '/'))) {
uint16_t len = strlen(buf);
strcpy(buf + len, last_slash);
// 在 full_path_reverse 中添加结束字符, 作为下一次执行 strcpy 中 last_slash 的边界
*last_slash = 0;
}
sys_free(io_buf);
return buf;
}
实现 sys_chdir 改变工作目录
fs/fs.c
/* 更改当前工作目录为绝对路径 path, 成功则返回 0,失败返回 -1 */
int32_t sys_chdir(const char* path) {
int32_t ret = -1;
struct path_search_record searched_record;
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(path, &searched_record);
if(inode_no != -1) {
if(searched_record.file_type == FT_DIRECTORY) {
running_thread()->cwd_inode_nr = inode_no;
return 0;
} else {
// path 不是目录
printk("sys_chdir: %s is regular file or other!\n", path);
}
}
dir_close(searched_record.parent_dir);
return ret;
}
运行 Bochs 功能验证
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
char cwd_buf[32] = {0};
sys_getcwd(cwd_buf, 32);
printf("cwd:%s\n", cwd_buf);
sys_chdir("/dir1");
printf("change cwd now\n");
sys_getcwd(cwd_buf, 32);
printf("cwd:%s\n", cwd_buf);
/******** 测试代码 ********/
while(1);
return 0;
}
运行,编译:
获取文件属性
ls命令查看文件属性的时候,会大量调用系统调用stat64,这是64位版本的函数,这里实现一个建议的stat
实现sys_stat
fs/fs.h
// 文件属性结构体
struct stat {
uint32_t st_ino; // inode 编号
uint32_t st_size; // 尺寸
enum file_types st_filetype; // 文件类型
};
...
int32_t sys_stat(const char* path, struct stat* buf);
fs/fs.c
/* 在 buf 中填充文件结构相关信息, 成功时返回 0, 失败返回 -1 */
int32_t sys_stat(const char* path, struct stat* buf) {
// 若直接查看根目录 '/'
if (!strcmp(path, "/") || !strcmp(path, "/.") || !strcmp(path, "/..")) {
buf->st_filetype = FT_DIRECTORY;
buf->st_ino = 0;
buf->st_size = root_dir.inode->i_size;
return 0;
}
// 默认返回值
int32_t ret = -1;
struct path_search_record searched_record;
// 记得初始化或清0,否则栈中信息不知道是什么
memset(&searched_record, 0, sizeof(struct path_search_record));
int inode_no = search_file(path, &searched_record);
if(inode_no != -1) {
// 只为获得文件大小
struct inode* obj_inode = inode_open(cur_part, inode_no);
buf->st_size = obj_inode->i_size;
inode_close(obj_inode);
buf->st_filetype = searched_record.file_type;
buf->st_ino = inode_no;
ret = 0;
} else {
printk("sys_stat: %s not found\n", path);
}
dir_close(searched_record.parent_dir);
return ret;
}
运行 Bochs 功能验证
kernel/main.c
int main() {
put_str("I am kernel\n");
init_all();
/******** 测试代码 ********/
struct stat obj_stat;
sys_stat("/", &obj_stat);
printf("/`s info\n i_no:%d\n size:%d\n filetype:%s\n", \
obj_stat.st_ino, obj_stat.st_size, \
obj_stat.st_filetype == 2 ? "directory" : "regular");
sys_stat("/dir1", &obj_stat);
printf("/dir1`s info\n i_no:%d\n size:%d\n filetype:%s\n", \
obj_stat.st_ino, obj_stat.st_size, \
obj_stat.st_filetype == 2 ? "directory" : "regular");
/******** 测试代码 ********/
while(1);
return 0;
}
编译,运行:
完整代码
inode.h
inode.c
dir.h
dir.c
file.h
file.c
fs.h
fs.c
勘误
file.c
int32_t block_lba = -1; // 块地址