《操作系统真象还原》第十四章(2)

《操作系统真象还原》第十四章

本篇对应书籍第十四章14.5--14.15的内容

本章分成了两部分来记录,上一部分完成了文件系统原理的介绍,文件系统的创建,和基础设施的建设,这里该实现文件系统相关的文件操作了

创建文件

实现 file_create

创建文件需要考虑实现如下工作:

  1. 文件需要inode来描述大小、 位置等属性, 所以创建文件就要创建其inode。 这就涉及到向inode _ bitmap申请位图来获得inode号,因此inode_ bitmap会被更新,inode_table数组中的某项也会由新的inode填充。
  2. inode->i_sectors是文件具体存储的扇区地址,这需要向block_bitmap申请可用位来获得可用的块,因此block_bitmap会被更新,分区的数据区data_ start_Iba 以后的某个扇区会被分配。
  3. 新增加的文件必然存在于某个目录,所以该目录的 inode->i_ size 会增加个目录项的大小。 此新增加的文件对应的目录项需要写入该目录的 inode->i_ sectors[]中的某个扇区, 原有扇区可能已满,所以有可能要申请新扇区来存储目录项。
  4. 若其中某步操作失败, 需要回滚之前已成功的操作。
  5. 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中
  • 如果有,分三种情况

    1. 若已经使用的扇区数在12块之内,新增了若干块后,文件大小还在12块之内,直接分配所需的块并把块地址写入i_sectors数组中即可。

      先将原有地址收录到all_block中,在将未来要用的扇区分配好写入all_block

    2. 若已经使用的块数在12块之内,新增了若干块后,文件大小超过了12块,这种情况下所申请的块除了要写入i_sector 数组,还要创建一级间接块表并写入块地址。

      先将原有地址收录到all_block中,分配一个块作为一级间接索引表,然后在将未来要用的扇区分配好写入all_block

    3. 若已经使用的扇区数超过了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;
}

读取文件同写入文件类似,先创建缓冲区,将用到的块地址都收集起来(三种情况),然后再将数据读取到缓冲区中

收集的时候分三种情况与写入的时候一样:

  1. 若起始块和终止块都在 12 块之内,直接读入 i_sectors 数组中即可。
  2. 若起始块在 12 块之内,结束块超过了 12 块,除了要读入 i_sector 数组,还要从一级间接块索引表中读取间接块地址。
  3. 若起始块超过了 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相关资源有:

  1. inode 位图
  2. inode_table 文件表
  3. inode中的数据块
  4. 一级间接索引表本身扇区地址
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);
}

在这里插入图片描述

删除目录项

与删除目录项相关的工作:

  1. 在文件所在的目录中擦除该文件的目录项,使其为 0。
  2. 根目录是必须存在的, 它是文件读写的根基, 不应该被清空,它至少要保留1个块。 如果目录项独占1个块, 并且该块不是根目录最后一个块的话, 将其回收。
  3. 目录inode的i_size是目录项大小的总和, 因此还要将i_size减去一个目录项的单位大小。
  4. 目录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 创建目录

创建目录的工作是:

  1. 确认待创建的新目录在文件系统上不存在。
  2. 为新目录创建inode。
  3. 为新目录分配 1 个块存储该目录中的目录项。
  4. 在新目录中创建两个目录项 “.“和 “ …“, 这是每个目录都必须存在的两个目录项。
  5. 在新目录的父目录中添加新目录的目录项。
  6. 将以上资源的变更同步到硬盘。
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;         // 块地址
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值