From bbcac5cf62b29d32e550527c28c63ef520c20b86 Mon Sep 17 00:00:00 2001 From: Zhiyuan Shao Date: Mon, 8 May 2023 15:37:19 +0800 Subject: [PATCH] init commit of lab4_1 --- LICENSE.txt | 7 +- Makefile | 4 +- hostfs_root/hostfile.txt | 2 + kernel/hostfs.c | 273 ++++++++++++++++ kernel/hostfs.h | 37 +++ kernel/kernel.c | 7 + kernel/proc_file.c | 162 ++++++++++ kernel/proc_file.h | 32 ++ kernel/process.c | 4 + kernel/process.h | 4 + kernel/ramdev.c | 107 +++++++ kernel/ramdev.h | 32 ++ kernel/rfs.c | 606 +++++++++++++++++++++++++++++++++++ kernel/rfs.h | 87 +++++ kernel/syscall.c | 86 +++++ kernel/syscall.h | 8 + kernel/vfs.c | 517 ++++++++++++++++++++++++++++++ kernel/vfs.h | 189 +++++++++++ spike_interface/spike_file.c | 17 +- spike_interface/spike_file.h | 9 +- user/app_file.c | 63 ++++ user/app_two_long_loops.c | 28 -- user/user_lib.c | 49 +++ user/user_lib.h | 16 + util/hash_table.c | 60 ++++ util/hash_table.h | 29 ++ util/string.c | 49 ++- util/string.h | 15 +- util/types.h | 30 ++ 29 files changed, 2480 insertions(+), 49 deletions(-) create mode 100644 hostfs_root/hostfile.txt create mode 100644 kernel/hostfs.c create mode 100644 kernel/hostfs.h create mode 100644 kernel/proc_file.c create mode 100644 kernel/proc_file.h create mode 100644 kernel/ramdev.c create mode 100644 kernel/ramdev.h create mode 100644 kernel/rfs.c create mode 100644 kernel/rfs.h create mode 100644 kernel/vfs.c create mode 100644 kernel/vfs.h create mode 100644 user/app_file.c delete mode 100644 user/app_two_long_loops.c create mode 100644 util/hash_table.c create mode 100644 util/hash_table.h diff --git a/LICENSE.txt b/LICENSE.txt index ffe4849..b58b8e0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -4,9 +4,12 @@ Copyright License The PKE software is: Copyright (c) 2021, Zhiyuan Shao (zyshao@hust.edu.cn), - Yi Gui (gy163email@163.com), - Yan Jiao (773709579@qq.com), + Guo Li (2925441676@qq.com), Huazhong University of Science and Technology + Liang Shi (lshi@cs.ecnu.edu.cn), + Longshan Xu (1981888213@qq.com), + Yangxue Ou (3386215144@qq.com), + East China Normal University Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Makefile b/Makefile index 2dde5bf..589db71 100644 --- a/Makefile +++ b/Makefile @@ -68,9 +68,7 @@ USER_CPPS := user/*.c USER_CPPS := $(wildcard $(USER_CPPS)) USER_OBJS := $(addprefix $(OBJ_DIR)/, $(patsubst %.c,%.o,$(USER_CPPS))) - - -USER_TARGET := $(OBJ_DIR)/app_two_long_loops +USER_TARGET := $(OBJ_DIR)/app_file #------------------------targets------------------------ $(OBJ_DIR): @-mkdir -p $(OBJ_DIR) diff --git a/hostfs_root/hostfile.txt b/hostfs_root/hostfile.txt new file mode 100644 index 0000000..cfbea73 --- /dev/null +++ b/hostfs_root/hostfile.txt @@ -0,0 +1,2 @@ +This is an apple. +Apples are good for our health. diff --git a/kernel/hostfs.c b/kernel/hostfs.c new file mode 100644 index 0000000..82415e9 --- /dev/null +++ b/kernel/hostfs.c @@ -0,0 +1,273 @@ +/* + * Interface functions between VFS and host-fs. added @lab4_1. + */ +#include "hostfs.h" + +#include "pmm.h" +#include "spike_interface/spike_file.h" +#include "spike_interface/spike_utils.h" +#include "util/string.h" +#include "util/types.h" +#include "vfs.h" + +/**** host-fs vinode interface ****/ +const struct vinode_ops hostfs_i_ops = { + .viop_read = hostfs_read, + .viop_write = hostfs_write, + .viop_create = hostfs_create, + .viop_lseek = hostfs_lseek, + .viop_lookup = hostfs_lookup, + + .viop_hook_open = hostfs_hook_open, + .viop_hook_close = hostfs_hook_close, + + .viop_write_back_vinode = hostfs_write_back_vinode, +}; + +/**** hostfs utility functions ****/ +// +// append hostfs to the fs list. +// +int register_hostfs() { + struct file_system_type *fs_type = (struct file_system_type *)alloc_page(); + fs_type->type_num = HOSTFS_TYPE; + fs_type->get_superblock = hostfs_get_superblock; + + for (int i = 0; i < MAX_SUPPORTED_FS; i++) { + if (fs_list[i] == NULL) { + fs_list[i] = fs_type; + return 0; + } + } + return -1; +} + +// +// append new device under "name" to vfs_dev_list. +// +struct device *init_host_device(char *name) { + // find rfs in registered fs list + struct file_system_type *fs_type = NULL; + for (int i = 0; i < MAX_SUPPORTED_FS; i++) { + if (fs_list[i] != NULL && fs_list[i]->type_num == HOSTFS_TYPE) { + fs_type = fs_list[i]; + break; + } + } + if (!fs_type) + panic("init_host_device: No HOSTFS file system found!\n"); + + // allocate a vfs device + struct device *device = (struct device *)alloc_page(); + // set the device name and index + strcpy(device->dev_name, name); + // we only support one host-fs device + device->dev_id = 0; + device->fs_type = fs_type; + + // add the device to the vfs device list + for (int i = 0; i < MAX_VFS_DEV; i++) { + if (vfs_dev_list[i] == NULL) { + vfs_dev_list[i] = device; + break; + } + } + + return device; +} + +// +// recursive call to assemble a path. +// +void path_backtrack(char *path, struct dentry *dentry) { + if (dentry->parent == NULL) { + return; + } + path_backtrack(path, dentry->parent); + strcat(path, "/"); + strcat(path, dentry->name); +} + +// +// obtain the absolute path for "dentry", from root to file. +// +void get_path_string(char *path, struct dentry *dentry) { + strcpy(path, H_ROOT_DIR); + path_backtrack(path, dentry); +} + +// +// allocate a vfs inode for an host fs file. +// +struct vinode *hostfs_alloc_vinode(struct super_block *sb) { + struct vinode *vinode = default_alloc_vinode(sb); + vinode->inum = -1; + vinode->i_fs_info = NULL; + vinode->i_ops = &hostfs_i_ops; + return vinode; +} + +int hostfs_write_back_vinode(struct vinode *vinode) { return 0; } + +// +// populate the vfs inode of an hostfs file, according to its stats. +// +int hostfs_update_vinode(struct vinode *vinode) { + spike_file_t *f = vinode->i_fs_info; + if ((int64)f < 0) { // is a direntry + vinode->type = H_DIR; + return -1; + } + + struct stat stat; + spike_file_stat(f, &stat); + + vinode->inum = stat.st_ino; + vinode->size = stat.st_size; + vinode->nlinks = stat.st_nlink; + vinode->blocks = stat.st_blocks; + + if (S_ISDIR(stat.st_mode)) { + vinode->type = H_DIR; + } else if (S_ISREG(stat.st_mode)) { + vinode->type = H_FILE; + } else { + sprint("hostfs_lookup:unknown file type!"); + return -1; + } + + return 0; +} + +/**** vfs-host-fs interface functions ****/ +// +// read a hostfs file. +// +ssize_t hostfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, + int *offset) { + spike_file_t *pf = (spike_file_t *)f_inode->i_fs_info; + if (pf < 0) { + sprint("hostfs_read: invalid file handle!\n"); + return -1; + } + int read_len = spike_file_read(pf, r_buf, len); + // obtain current offset + *offset = spike_file_lseek(pf, 0, 1); + return read_len; +} + +// +// write a hostfs file. +// +ssize_t hostfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, + int *offset) { + spike_file_t *pf = (spike_file_t *)f_inode->i_fs_info; + if (pf < 0) { + sprint("hostfs_write: invalid file handle!\n"); + return -1; + } + int write_len = spike_file_write(pf, w_buf, len); + // obtain current offset + *offset = spike_file_lseek(pf, 0, 1); + return write_len; +} + +// +// lookup a hostfs file, and establish its vfs inode in PKE vfs. +// +struct vinode *hostfs_lookup(struct vinode *parent, struct dentry *sub_dentry) { + // get complete path string + char path[MAX_PATH_LEN]; + get_path_string(path, sub_dentry); + + spike_file_t *f = spike_file_open(path, O_RDWR, 0); + + struct vinode *child_inode = hostfs_alloc_vinode(parent->sb); + child_inode->i_fs_info = f; + hostfs_update_vinode(child_inode); + + child_inode->ref = 0; + return child_inode; +} + +// +// creates a hostfs file, and establish its vfs inode. +// +struct vinode *hostfs_create(struct vinode *parent, struct dentry *sub_dentry) { + char path[MAX_PATH_LEN]; + get_path_string(path, sub_dentry); + + spike_file_t *f = spike_file_open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if ((int64)f < 0) { + sprint("hostfs_create cannot create the given file.\n"); + return NULL; + } + + struct vinode *new_inode = hostfs_alloc_vinode(parent->sb); + new_inode->i_fs_info = f; + + if (hostfs_update_vinode(new_inode) != 0) return NULL; + + new_inode->ref = 0; + return new_inode; +} + +// +// reposition read/write file offset +// +int hostfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, + int *offset) { + spike_file_t *f = (spike_file_t *)f_inode->i_fs_info; + if (f < 0) { + sprint("hostfs_lseek: invalid file handle!\n"); + return -1; + } + + *offset = spike_file_lseek(f, new_offset, whence); + if (*offset >= 0) + return 0; + return -1; +} + +/**** vfs-hostfs hook interface functions ****/ +// +// open a hostfs file (after having its vfs inode). +// +int hostfs_hook_open(struct vinode *f_inode, struct dentry *f_dentry) { + if (f_inode->i_fs_info != NULL) return 0; + + char path[MAX_PATH_LEN]; + get_path_string(path, f_dentry); + spike_file_t *f = spike_file_open(path, O_RDWR, 0); + if ((int64)f < 0) { + sprint("hostfs_hook_open cannot open the given file.\n"); + return -1; + } + + f_inode->i_fs_info = f; + return 0; +} + +// +// close a hostfs file. +// +int hostfs_hook_close(struct vinode *f_inode, struct dentry *dentry) { + spike_file_t *f = (spike_file_t *)f_inode->i_fs_info; + spike_file_close(f); + return 0; +} + +/**** vfs-hostfs file system type interface functions ****/ +struct super_block *hostfs_get_superblock(struct device *dev) { + // set the data for the vfs super block + struct super_block *sb = alloc_page(); + sb->s_dev = dev; + + struct vinode *root_inode = hostfs_alloc_vinode(sb); + root_inode->type = H_DIR; + + struct dentry *root_dentry = alloc_vfs_dentry("/", root_inode, NULL); + sb->s_root = root_dentry; + + return sb; +} diff --git a/kernel/hostfs.h b/kernel/hostfs.h new file mode 100644 index 0000000..61449d3 --- /dev/null +++ b/kernel/hostfs.h @@ -0,0 +1,37 @@ +#ifndef _HOSTFS_H_ +#define _HOSTFS_H_ +#include "vfs.h" + +#define HOSTFS_TYPE 1 + +// dinode type +#define H_FILE FILE_I +#define H_DIR DIR_I + +// root directory +#define H_ROOT_DIR "./hostfs_root" + +// hostfs utility functin declarations +int register_hostfs(); +struct device *init_host_device(char *name); +void get_path_string(char *path, struct dentry *dentry); +struct vinode *hostfs_alloc_vinode(struct super_block *sb); +int hostfs_write_back_vinode(struct vinode *vinode); +int hostfs_update_vinode(struct vinode *vinode); + +// hostfs interface function declarations +ssize_t hostfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, + int *offset); +ssize_t hostfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, + int *offset); +struct vinode *hostfs_lookup(struct vinode *parent, struct dentry *sub_dentry); +struct vinode *hostfs_create(struct vinode *parent, struct dentry *sub_dentry); +int hostfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, + int *offset); +int hostfs_hook_open(struct vinode *f_inode, struct dentry *f_dentry); +int hostfs_hook_close(struct vinode *f_inode, struct dentry *dentry); +struct super_block *hostfs_get_superblock(struct device *dev); + +extern const struct vinode_ops hostfs_node_ops; + +#endif diff --git a/kernel/kernel.c b/kernel/kernel.c index d3f10f3..b30b793 100644 --- a/kernel/kernel.c +++ b/kernel/kernel.c @@ -11,6 +11,10 @@ #include "sched.h" #include "memlayout.h" #include "spike_interface/spike_utils.h" +#include "util/types.h" +#include "vfs.h" +#include "rfs.h" +#include "ramdev.h" // // trap_sec_start points to the beginning of S-mode trap segment (i.e., the entry point of @@ -67,6 +71,9 @@ int s_start(void) { // added @lab3_1 init_proc_pool(); + // init file system, added @lab4_1 + fs_init(); + sprint("Switch to user mode...\n"); // the application code (elf) is first loaded into memory, and then put into execution // added @lab3_1 diff --git a/kernel/proc_file.c b/kernel/proc_file.c new file mode 100644 index 0000000..9c5f5fa --- /dev/null +++ b/kernel/proc_file.c @@ -0,0 +1,162 @@ +/* + * Interface functions between file system and kernel/processes. added @lab4_1 + */ + +#include "proc_file.h" + +#include "hostfs.h" +#include "pmm.h" +#include "process.h" +#include "ramdev.h" +#include "rfs.h" +#include "riscv.h" +#include "spike_interface/spike_file.h" +#include "spike_interface/spike_utils.h" +#include "util/functions.h" +#include "util/string.h" + +// +// initialize file system +// +void fs_init(void) { + // initialize the vfs + vfs_init(); + + // register hostfs and mount it as the root + if( register_hostfs() < 0 ) panic( "fs_init: cannot register hostfs.\n" ); + struct device *hostdev = init_host_device("HOSTDEV"); + vfs_mount("HOSTDEV", MOUNT_AS_ROOT); + + // register and mount rfs + if( register_rfs() < 0 ) panic( "fs_init: cannot register rfs.\n" ); + struct device *ramdisk0 = init_rfs_device("RAMDISK0"); + rfs_format_dev(ramdisk0); + vfs_mount("RAMDISK0", MOUNT_DEFAULT); +} + +// +// initialize a proc_file_management data structure for a process. +// return the pointer to the page containing the data structure. +// +proc_file_management *init_proc_file_management(void) { + proc_file_management *pfiles = (proc_file_management *)alloc_page(); + pfiles->cwd = vfs_root_dentry; // by default, cwd is the root + pfiles->nfiles = 0; + + for (int fd = 0; fd < MAX_FILES; ++fd) + pfiles->opened_files[fd].status = FD_NONE; + + sprint("FS: created a file management struct for a process.\n"); + return pfiles; +} + +// +// reclaim the open-file management data structure of a process. +// note: this function is not used as PKE does not actually reclaim a process. +// +void reclaim_proc_file_management(proc_file_management *pfiles) { + free_page(pfiles); + return; +} + +// +// get an opened file from proc->opened_file array. +// return: the pointer to the opened file structure. +// +struct file *get_opened_file(int fd) { + struct file *pfile = NULL; + + // browse opened file list to locate the fd + for (int i = 0; i < MAX_FILES; ++i) { + pfile = &(current->pfiles->opened_files[i]); // file entry + if (i == fd) break; + } + if (pfile == NULL) panic("do_read: invalid fd!\n"); + return pfile; +} + +// +// open a file named as "pathname" with the permission of "flags". +// return: -1 on failure; non-zero file-descriptor on success. +// +int do_open(char *pathname, int flags) { + struct file *opened_file = NULL; + if ((opened_file = vfs_open(pathname, flags)) == NULL) return -1; + + int fd = 0; + if (current->pfiles->nfiles >= MAX_FILES) { + panic("do_open: no file entry for current process!\n"); + } + struct file *pfile; + for (fd = 0; fd < MAX_FILES; ++fd) { + pfile = &(current->pfiles->opened_files[fd]); + if (pfile->status == FD_NONE) break; + } + + // initialize this file structure + memcpy(pfile, opened_file, sizeof(struct file)); + + ++current->pfiles->nfiles; + return fd; +} + +// +// read content of a file ("fd") into "buf" for "count". +// return: actual length of data read from the file. +// +int do_read(int fd, char *buf, uint64 count) { + struct file *pfile = get_opened_file(fd); + + if (pfile->readable == 0) panic("do_read: no readable file!\n"); + + char buffer[count + 1]; + int len = vfs_read(pfile, buffer, count); + buffer[count] = '\0'; + strcpy(buf, buffer); + return len; +} + +// +// write content ("buf") whose length is "count" to a file "fd". +// return: actual length of data written to the file. +// +int do_write(int fd, char *buf, uint64 count) { + struct file *pfile = get_opened_file(fd); + + if (pfile->writable == 0) panic("do_write: cannot write file!\n"); + + int len = vfs_write(pfile, buf, count); + return len; +} + +// +// reposition the file offset +// +int do_lseek(int fd, int offset, int whence) { + struct file *pfile = get_opened_file(fd); + return vfs_lseek(pfile, offset, whence); +} + +// +// read the vinode information +// +int do_stat(int fd, struct istat *istat) { + struct file *pfile = get_opened_file(fd); + return vfs_stat(pfile, istat); +} + +// +// read the inode information on the disk +// +int do_disk_stat(int fd, struct istat *istat) { + struct file *pfile = get_opened_file(fd); + return vfs_disk_stat(pfile, istat); +} + +// +// close a file +// +int do_close(int fd) { + struct file *pfile = get_opened_file(fd); + return vfs_close(pfile); +} diff --git a/kernel/proc_file.h b/kernel/proc_file.h new file mode 100644 index 0000000..25e8d16 --- /dev/null +++ b/kernel/proc_file.h @@ -0,0 +1,32 @@ +#ifndef _PROC_FILE_H_ +#define _PROC_FILE_H_ + +#include "spike_interface/spike_file.h" +#include "util/types.h" +#include "vfs.h" + +// +// file operations +// +int do_open(char *pathname, int flags); +int do_read(int fd, char *buf, uint64 count); +int do_write(int fd, char *buf, uint64 count); +int do_lseek(int fd, int offset, int whence); +int do_stat(int fd, struct istat *istat); +int do_disk_stat(int fd, struct istat *istat); +int do_close(int fd); + +void fs_init(void); + +// data structure that manages all openned files in a PCB +typedef struct proc_file_management_t { + struct dentry *cwd; // vfs dentry of current working directory + struct file opened_files[MAX_FILES]; // opened files array + int nfiles; // the number of files opened by a process +} proc_file_management; + +proc_file_management *init_proc_file_management(void); + +void reclaim_proc_file_management(proc_file_management *pfiles); + +#endif diff --git a/kernel/process.c b/kernel/process.c index f4c74b6..ddbbc35 100644 --- a/kernel/process.c +++ b/kernel/process.c @@ -151,6 +151,10 @@ process* alloc_process() { procs[i].total_mapped_region = 4; + // initialize files_struct + procs[i].pfiles = init_proc_file_management(); + sprint("in alloc_proc. build proc_file_management successfully.\n"); + // return after initialization. return &procs[i]; } diff --git a/kernel/process.h b/kernel/process.h index 0a1bd9c..5f4c63b 100644 --- a/kernel/process.h +++ b/kernel/process.h @@ -2,6 +2,7 @@ #define _PROC_H_ #include "riscv.h" +#include "proc_file.h" typedef struct trapframe_t { // space to store context (all common registers) @@ -89,6 +90,9 @@ typedef struct process_t { // accounting. added @lab3_3 int tick_count; + + // file system. added @lab4_1 + proc_file_management *pfiles; }process; // switch to run user app diff --git a/kernel/ramdev.c b/kernel/ramdev.c new file mode 100644 index 0000000..9504ad5 --- /dev/null +++ b/kernel/ramdev.c @@ -0,0 +1,107 @@ +/* + * Utility functions operating the devices. support only RAM disk device. added @lab4_1. + */ + +#include "ramdev.h" +#include "vfs.h" +#include "pmm.h" +#include "riscv.h" +#include "util/types.h" +#include "util/string.h" +#include "spike_interface/spike_utils.h" +#include "rfs.h" + +struct rfs_device *rfs_device_list[MAX_RAMDISK_COUNT]; + +// +// write the content stored in "buff" to the "blkno"^th block of disk. +// +int ramdisk_write(struct rfs_device *rfs_device, int blkno){ + if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT ) + panic("ramdisk_write: write block No %d out of range!\n", blkno); + void * dst = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE); + memcpy(dst, rfs_device->iobuffer, RAMDISK_BLOCK_SIZE); + return 0; +} + +// +// read the "blkno"^th block from the RAM disk and store its content into buffer. +// +int ramdisk_read(struct rfs_device *rfs_device, int blkno){ + if ( blkno < 0 || blkno >= RAMDISK_BLOCK_COUNT ) + panic("ramdisk_read: read block No out of range!\n"); + void * src = (void *)((uint64)rfs_device->d_address + blkno * RAMDISK_BLOCK_SIZE); + memcpy(rfs_device->iobuffer, src, RAMDISK_BLOCK_SIZE); + return 0; +} + +// +// alloc RAMDISK_BLOCK_COUNT continuous pages (blocks) for the RAM Disk +// setup an vfs node, initialize RAM disk device, and attach the device with the vfs node. +// +struct device *init_rfs_device(const char *dev_name) { + // find rfs in registered fs list + struct file_system_type *fs_type = NULL; + for (int i = 0; i < MAX_SUPPORTED_FS; i++) { + if (fs_list[i] != NULL && fs_list[i]->type_num == RFS_TYPE) { + fs_type = fs_list[i]; + break; + } + } + if (!fs_type) { + panic("No RFS file system found!\n"); + } + + // alloc blocks for the RAM Disk + void *curr_addr = NULL; + void *last_addr = NULL; + void *ramdisk_addr = NULL; + for ( int i = 0; i < RAMDISK_BLOCK_COUNT; ++ i ){ + last_addr = curr_addr; + curr_addr = alloc_page(); + if ( last_addr != NULL && last_addr - curr_addr != PGSIZE ){ + panic("RAM Disk0: address is discontinuous!\n"); + } + } + ramdisk_addr = curr_addr; + + // find a free rfs device + struct rfs_device **rfs_device = NULL; + int device_id = 0; + for (int i = 0; i < MAX_RAMDISK_COUNT; i++) { + if (rfs_device_list[i] == NULL) { + rfs_device = &rfs_device_list[i]; + device_id = i; + break; + } + } + if (!rfs_device) { + panic("RAM Disk0: no free device!\n"); + } + + *rfs_device = (struct rfs_device *)alloc_page(); + (*rfs_device)->d_blocks = RAMDISK_BLOCK_COUNT; + (*rfs_device)->d_blocksize = RAMDISK_BLOCK_SIZE; + (*rfs_device)->d_write = ramdisk_write; + (*rfs_device)->d_read = ramdisk_read; + (*rfs_device)->d_address = ramdisk_addr; + (*rfs_device)->iobuffer = alloc_page(); + + // allocate a vfs device + struct device * device = (struct device *)alloc_page(); + // set the device name and index + strcpy(device->dev_name, dev_name); + device->dev_id = device_id; + device->fs_type = fs_type; + + // add the device to the vfs device list + for(int i = 0; i < MAX_VFS_DEV; i++) { + if (vfs_dev_list[i] == NULL) { + vfs_dev_list[i] = device; + break; + } + } + + sprint("%s: base address of %s is: %p\n",dev_name, dev_name, ramdisk_addr); + return device; +} \ No newline at end of file diff --git a/kernel/ramdev.h b/kernel/ramdev.h new file mode 100644 index 0000000..9ab9466 --- /dev/null +++ b/kernel/ramdev.h @@ -0,0 +1,32 @@ +#ifndef _RAMDEV_H_ +#define _RAMDEV_H_ + +#include "riscv.h" +#include "util/types.h" + +#define RAMDISK_BLOCK_COUNT 128 +#define RAMDISK_BLOCK_SIZE PGSIZE + +#define MAX_RAMDISK_COUNT 10 + +#define RAMDISK_FREE 0 +#define RAMDISK_USED 1 + +struct rfs_device { + void *d_address; // the ramdisk base address + int d_blocks; // the number of blocks of the device + int d_blocksize; // the blocksize (bytes) per block + void *iobuffer; // iobuffer for write/read + int (*d_write)(struct rfs_device *rdev, int blkno); // device write funtion + int (*d_read)(struct rfs_device *rdev, int blkno); // device read funtion +}; + +#define dop_write(rdev, blkno) ((rdev)->d_write(rdev, blkno)) +#define dop_read(rdev, blkno) ((rdev)->d_read(rdev, blkno)) + +struct device *init_rfs_device(const char *dev_name); +struct rfs_device *alloc_rfs_device(void); + +extern struct rfs_device *rfs_device_list[MAX_RAMDISK_COUNT]; + +#endif diff --git a/kernel/rfs.c b/kernel/rfs.c new file mode 100644 index 0000000..753b0a2 --- /dev/null +++ b/kernel/rfs.c @@ -0,0 +1,606 @@ +/* + * RFS (Ramdisk File System) is a customized simple file system installed in the + * RAM disk. added @lab4_1. + * Layout of the file system: + * + * ******** RFS MEM LAYOUT (112 BLOCKS) **************** + * superblock | disk inodes | bitmap | free blocks * + * 1 block | 10 blocks | 1 | 100 * + * ***************************************************** + * + * The disk layout of rfs is similar to the fs in xv6. + */ +#include "rfs.h" + +#include "pmm.h" +#include "ramdev.h" +#include "spike_interface/spike_utils.h" +#include "util/string.h" +#include "vfs.h" + +/**** vinode inteface ****/ +const struct vinode_ops rfs_i_ops = { + .viop_read = rfs_read, + .viop_write = rfs_write, + .viop_create = rfs_create, + .viop_lseek = rfs_lseek, + .viop_disk_stat = rfs_disk_stat, + .viop_lookup = rfs_lookup, + + .viop_write_back_vinode = rfs_write_back_vinode, +}; + +/**** rfs utility functions ****/ +// +// register rfs to the fs list supported by PKE. +// +int register_rfs() { + struct file_system_type *fs_type = (struct file_system_type *)alloc_page(); + fs_type->type_num = RFS_TYPE; + fs_type->get_superblock = rfs_get_superblock; + + for (int i = 0; i < MAX_SUPPORTED_FS; i++) { + if (fs_list[i] == NULL) { + fs_list[i] = fs_type; + return 0; + } + } + return -1; +} + +// +// format "dev" with rfs. note the "dev" should be a ram disk device. +// +int rfs_format_dev(struct device *dev) { + struct rfs_device *rdev = rfs_device_list[dev->dev_id]; + + // ** first, format the superblock + // build a new superblock + struct super_block *super = (struct super_block *)rdev->iobuffer; + super->magic = RFS_MAGIC; + super->size = + 1 + RFS_MAX_INODE_BLKNUM + 1 + RFS_MAX_INODE_BLKNUM * RFS_DIRECT_BLKNUM; + // only direct index blocks + super->nblocks = RFS_MAX_INODE_BLKNUM * RFS_DIRECT_BLKNUM; + super->ninodes = RFS_BLKSIZE / RFS_INODESIZE * RFS_MAX_INODE_BLKNUM; + + // write the superblock to RAM Disk0 + if (rfs_w1block(rdev, RFS_BLK_OFFSET_SUPER) != 0) // write to device + panic("RFS: failed to write superblock!\n"); + + // ** second, set up the inodes and write them to RAM disk + // build an empty inode disk block which has RFS_BLKSIZE/RFS_INODESIZE(=32) + // disk inodes + struct rfs_dinode *p_dinode = (struct rfs_dinode *)rdev->iobuffer; + for (int i = 0; i < RFS_BLKSIZE / RFS_INODESIZE; ++i) { + p_dinode->size = 0; + p_dinode->type = R_FREE; + p_dinode->nlinks = 0; + p_dinode->blocks = 0; + p_dinode = (struct rfs_dinode *)((char *)p_dinode + RFS_INODESIZE); + } + + // write RFS_MAX_INODE_BLKNUM(=10) empty inode disk blocks to RAM Disk0 + for (int inode_block = 0; inode_block < RFS_MAX_INODE_BLKNUM; ++inode_block) { + if (rfs_w1block(rdev, RFS_BLK_OFFSET_INODE + inode_block) != 0) + panic("RFS: failed to initialize empty inodes!\n"); + } + + // build root directory inode (ino = 0) + struct rfs_dinode root_dinode; + root_dinode.size = 0; + root_dinode.type = R_DIR; + root_dinode.nlinks = 1; + root_dinode.blocks = 1; + root_dinode.addrs[0] = RFS_BLK_OFFSET_FREE; + + // write root directory inode to RAM Disk0 (ino = 0) + if (rfs_write_dinode(rdev, &root_dinode, 0) != 0) { + sprint("RFS: failed to write root inode!\n"); + return -1; + } + + // ** third, write freemap to disk + int *freemap = (int *)rdev->iobuffer; + memset(freemap, 0, RFS_BLKSIZE); + freemap[0] = 1; // the first data block is used for root directory + + // write the bitmap to RAM Disk0 + if (rfs_w1block(rdev, RFS_BLK_OFFSET_BITMAP) != 0) { // write to device + sprint("RFS: failed to write bitmap!\n"); + return -1; + } + + sprint("RFS: format %s done!\n", dev->dev_name); + return 0; +} + +// ** Note: If you use the following four functions interchangeably, +// ** be sure to watch out for IOBUFFER BEING OVERWRITTEN !!! + +// +// call ramdisk_read via the device structure. +// read the "n_block"^th block from RAM disk to the iobuffer of rfs_dev. +// +int rfs_r1block(struct rfs_device *rfs_dev, int n_block) { + return dop_read(rfs_dev, n_block); +} + +// +// call ramdisk_write via the device structure. +// write iobuffer of rfs_dev to RAM disk at the "n_block"^th block. +// +int rfs_w1block(struct rfs_device *rfs_dev, int n_block) { + return dop_write(rfs_dev, n_block); +} + +// +// read disk inode from RAM disk +// +struct rfs_dinode *rfs_read_dinode(struct rfs_device *rdev, int n_inode) { + int n_block = n_inode / (RFS_BLKSIZE / RFS_INODESIZE) + RFS_BLK_OFFSET_INODE; + int offset = n_inode % (RFS_BLKSIZE / RFS_INODESIZE); + + // call ramdisk_read defined in dev.c + if (dop_read(rdev, n_block) != 0) return NULL; + struct rfs_dinode *dinode = (struct rfs_dinode *)alloc_page(); + memcpy(dinode, (char *)rdev->iobuffer + offset * RFS_INODESIZE, + sizeof(struct rfs_dinode)); + return dinode; +} + +// +// write disk inode to RAM disk. +// note: we need first read the "disk" block containing the "n_inode"^th inode, +// modify it, and write the block back to "disk" eventually. +// +int rfs_write_dinode(struct rfs_device *rdev, const struct rfs_dinode *dinode, + int n_inode) { + int n_block = n_inode / (RFS_BLKSIZE / RFS_INODESIZE) + RFS_BLK_OFFSET_INODE; + int offset = n_inode % (RFS_BLKSIZE / RFS_INODESIZE); + + // call ramdisk_read defined in dev.c + dop_read(rdev, n_block); + memcpy(rdev->iobuffer + offset * RFS_INODESIZE, dinode, + sizeof(struct rfs_dinode)); + // call ramdisk_write defined in dev.c + int ret = dop_write(rdev, n_block); + + return ret; +} + +// +// allocate a block from RAM disk +// +int rfs_alloc_block(struct super_block *sb) { + int free_block = -1; + // think of s_fs_info as freemap information + int *freemap = (int *)sb->s_fs_info; + for (int block = 0; block < sb->nblocks; ++block) { + if (freemap[block] == 0) { // find a free block + freemap[block] = 1; + free_block = RFS_BLK_OFFSET_FREE + block; + break; + } + } + if (free_block == -1) panic("rfs_alloc_block: no more free block!\n"); + return free_block; +} + +// +// free a block in RAM disk +// +int rfs_free_block(struct super_block *sb, int block_num) { + int *freemap = (int *)sb->s_fs_info; + freemap[block_num - RFS_BLK_OFFSET_FREE] = 0; + return 0; +} + +// +// add a new directory entry to a directory +// +int rfs_add_direntry(struct vinode *dir, const char *name, int inum) { + if (dir->type != DIR_I) { + sprint("rfs_add_direntry: not a directory!\n"); + return -1; + } + + struct rfs_device *rdev = rfs_device_list[dir->sb->s_dev->dev_id]; + int n_block = dir->addrs[dir->size / RFS_BLKSIZE]; + if (rfs_r1block(rdev, n_block) != 0) { + sprint("rfs_add_direntry: failed to read block %d!\n", n_block); + return -1; + } + + // prepare iobuffer + char *addr = (char *)rdev->iobuffer + dir->size % RFS_BLKSIZE; + struct rfs_direntry *p_direntry = (struct rfs_direntry *)addr; + p_direntry->inum = inum; + strcpy(p_direntry->name, name); + + // write the modified (parent) directory block back to disk + if (rfs_w1block(rdev, n_block) != 0) { + sprint("rfs_add_direntry: failed to write block %d!\n", n_block); + return -1; + } + + // update its parent dir state + dir->size += sizeof(struct rfs_direntry); + + // write the parent dir inode back to disk + if (rfs_write_back_vinode(dir) != 0) { + sprint("rfs_add_direntry: failed to write back parent dir inode!\n"); + return -1; + } + + return 0; +} + +// +// alloc a new (and empty) vinode +// +struct vinode *rfs_alloc_vinode(struct super_block *sb) { + struct vinode *vinode = default_alloc_vinode(sb); + vinode->i_ops = &rfs_i_ops; + return vinode; +} + +// +// convert vfs inode to disk inode, and write it back to disk +// +int rfs_write_back_vinode(struct vinode *vinode) { + // copy vinode info to disk inode + struct rfs_dinode dinode; + dinode.size = vinode->size; + dinode.nlinks = vinode->nlinks; + dinode.blocks = vinode->blocks; + dinode.type = vinode->type; + for (int i = 0; i < RFS_DIRECT_BLKNUM; ++i) { + dinode.addrs[i] = vinode->addrs[i]; + } + + struct rfs_device *rdev = rfs_device_list[vinode->sb->s_dev->dev_id]; + if (rfs_write_dinode(rdev, &dinode, vinode->inum) != 0) { + sprint("rfs_free_write_back_inode: failed to write back disk inode!\n"); + return -1; + } + + return 0; +} + +// +// update vinode info by reading disk inode +// +int rfs_update_vinode(struct vinode *vinode) { + struct rfs_device *rdev = rfs_device_list[vinode->sb->s_dev->dev_id]; + struct rfs_dinode *dinode = rfs_read_dinode(rdev, vinode->inum); + if (dinode == NULL) { + sprint("rfs_update_vinode: failed to read disk inode!\n"); + return -1; + } + vinode->size = dinode->size; + vinode->nlinks = dinode->nlinks; + vinode->blocks = dinode->blocks; + vinode->type = dinode->type; + for (int i = 0; i < RFS_DIRECT_BLKNUM; ++i) { + vinode->addrs[i] = dinode->addrs[i]; + } + free_page(dinode); + + return 0; +} + +/**** vfs-rfs file interface functions ****/ +// +// read the content (for "len") of a file ("f_inode"), and copy the content +// to "r_buf". +// +ssize_t rfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, + int *offset) { + // obtain disk inode from vfs inode + if (f_inode->size < *offset) + panic("rfs_read:offset should less than file size!"); + + if (f_inode->size < (*offset + len)) len = f_inode->size - *offset; + + char buffer[len + 1]; + + // compute how many blocks we need to read + int align = *offset % RFS_BLKSIZE; + int block_offset = *offset / RFS_BLKSIZE; + int buf_offset = 0; + + int readtimes = (align + len) / RFS_BLKSIZE; + int remain = (align + len) % RFS_BLKSIZE; + + struct rfs_device *rdev = rfs_device_list[f_inode->sb->s_dev->dev_id]; + + // read first block + rfs_r1block(rdev, f_inode->addrs[block_offset]); + int first_block_len = (readtimes == 0 ? len : RFS_BLKSIZE - align); + memcpy(buffer + buf_offset, rdev->iobuffer + align, first_block_len); + buf_offset += first_block_len; + block_offset++; + readtimes--; + + // readtimes < 0 means that the file has only one block (and not full), + // so our work is done + // otherwise... + if (readtimes >= 0) { + // read in complete blocks + while (readtimes != 0) { + rfs_r1block(rdev, f_inode->addrs[block_offset]); + memcpy(buffer + buf_offset, rdev->iobuffer, RFS_BLKSIZE); + buf_offset += RFS_BLKSIZE; + block_offset++; + readtimes--; + } + + // read in the remaining data + if (remain > 0) { + rfs_r1block(rdev, f_inode->addrs[block_offset]); + memcpy(buffer + buf_offset, rdev->iobuffer, remain); + } + } + + buffer[len] = '\0'; + strcpy(r_buf, buffer); + + *offset += len; + return len; +} + +// +// write the content of "w_buf" (lengthed "len") to a file ("f_inode"). +// +ssize_t rfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, + int *offset) { + if (f_inode->size < *offset) { + panic("rfs_write:offset should less than file size!"); + } + + // compute how many blocks we need to write + int align = *offset % RFS_BLKSIZE; + int writetimes = (len + align) / RFS_BLKSIZE; + int remain = (len + align) % RFS_BLKSIZE; + + int buf_offset = 0; + int block_offset = *offset / RFS_BLKSIZE; + + struct rfs_device *rdev = rfs_device_list[f_inode->sb->s_dev->dev_id]; + + // write first block + if (align != 0) { + rfs_r1block(rdev, f_inode->addrs[block_offset]); + int first_block_len = (writetimes == 0 ? len : RFS_BLKSIZE - align); + memcpy(rdev->iobuffer + align, w_buf, first_block_len); + rfs_w1block(rdev, f_inode->addrs[block_offset]); + + buf_offset += first_block_len; + block_offset++; + writetimes--; + } + + // writetimes < 0 means that the file has only one block (and not full), + // so our work is done + // otherwise... + if (writetimes >= 0) { + // write complete blocks + while (writetimes != 0) { + if (block_offset == f_inode->blocks) { // need to create new block + // allocate a free block for the file + f_inode->addrs[block_offset] = rfs_alloc_block(f_inode->sb); + f_inode->blocks++; + } + + memcpy(rdev->iobuffer, w_buf + buf_offset, RFS_BLKSIZE); + rfs_w1block(rdev, f_inode->addrs[block_offset]); + + buf_offset += RFS_BLKSIZE; + block_offset++; + writetimes--; + } + + // write the remaining data + if (remain > 0) { + if (block_offset == f_inode->blocks) { + f_inode->addrs[block_offset] = rfs_alloc_block(f_inode->sb); + ++f_inode->blocks; + } + memcpy(rdev->iobuffer, w_buf + buf_offset, remain); + rfs_w1block(rdev, f_inode->addrs[block_offset]); + } + } + + // update file size + f_inode->size = + (f_inode->size < *offset + len ? *offset + len : f_inode->size); + + *offset += len; + return len; +} + +// +// lookup a directory entry("sub_dentry") under "parent". +// note that this is a one level lookup ,and the vfs layer will call this +// function several times until the final file is found. +// return: if found, return its vinode, otherwise return NULL +// +struct vinode *rfs_lookup(struct vinode *parent, struct dentry *sub_dentry) { + struct rfs_direntry *p_direntry = NULL; + struct vinode *child_vinode = NULL; + + int total_direntrys = parent->size / sizeof(struct rfs_direntry); + int one_block_direntrys = RFS_BLKSIZE / sizeof(struct rfs_direntry); + + struct rfs_device *rdev = rfs_device_list[parent->sb->s_dev->dev_id]; + + // browse the dir entries contained in a directory file + for (int i = 0; i < total_direntrys; ++i) { + if (i % one_block_direntrys == 0) { // read in the disk block at boundary + rfs_r1block(rdev, parent->addrs[i / one_block_direntrys]); + p_direntry = (struct rfs_direntry *)rdev->iobuffer; + } + if (strcmp(p_direntry->name, sub_dentry->name) == 0) { // found + child_vinode = rfs_alloc_vinode(parent->sb); + child_vinode->inum = p_direntry->inum; + if (rfs_update_vinode(child_vinode) != 0) + panic("rfs_lookup: read inode failed!"); + break; + } + ++p_direntry; + } + return child_vinode; +} + +// +// create a file with "sub_dentry->name" at directory "parent" in rfs. +// return the vfs inode of the file being created. +// +struct vinode *rfs_create(struct vinode *parent, struct dentry *sub_dentry) { + struct rfs_device *rdev = rfs_device_list[parent->sb->s_dev->dev_id]; + + // ** find a free disk inode to store the file that is going to be created + struct rfs_dinode *free_dinode = NULL; + int free_inum = 0; + for (int i = 0; i < (RFS_BLKSIZE / RFS_INODESIZE * RFS_MAX_INODE_BLKNUM); + ++i) { + free_dinode = rfs_read_dinode(rdev, i); + if (free_dinode->type == R_FREE) { // found + free_inum = i; + break; + } + free_page(free_dinode); + } + + if (free_dinode == NULL) + panic("rfs_create: no more free disk inode, we cannot create file.\n" ); + + // initialize the states of the file being created + + // TODO (lab4_1): implement the code for populating the disk inode (free_dinode) + // of a new file being created. + // hint: members of free_dinode to be filled are: + // size, should be zero for a new file. + // type, see kernel/rfs.h and find the type for a rfs file. + // nlinks, i.e., the number of links. + // blocks, i.e., its block count. + // Note: DO NOT DELETE CODE BELOW PANIC. + panic("You need to implement the code of populating a disk inode in lab4_1.\n" ); + + // DO NOT REMOVE ANY CODE BELOW. + // allocate a free block for the file + free_dinode->addrs[0] = rfs_alloc_block(parent->sb); + + // ** write the disk inode of file being created to disk + rfs_write_dinode(rdev, free_dinode, free_inum); + free_page(free_dinode); + + // ** build vfs inode according to dinode + struct vinode *new_vinode = rfs_alloc_vinode(parent->sb); + new_vinode->inum = free_inum; + rfs_update_vinode(new_vinode); + + // ** append the new file as a direntry to its parent dir + int result = rfs_add_direntry(parent, sub_dentry->name, free_inum); + if (result == -1) { + sprint("rfs_create: rfs_add_direntry failed"); + return NULL; + } + + return new_vinode; +} + +// +// there are two types of seek (specify by whence): LSEEK_SET, SEEK_CUR +// LSEEK_SET: set the file pointer to the offset +// LSEEK_CUR: set the file pointer to the current offset plus the offset +// return 0 if success, otherwise return -1 +// +int rfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, int *offset) { + int file_size = f_inode->size; + + switch (whence) { + case LSEEK_SET: + if (new_offset < 0 || new_offset > file_size) { + sprint("rfs_lseek: invalid offset!\n"); + return -1; + } + *offset = new_offset; + break; + case LSEEK_CUR: + if (*offset + new_offset < 0 || *offset + new_offset > file_size) { + sprint("rfs_lseek: invalid offset!\n"); + return -1; + } + *offset += new_offset; + break; + default: + sprint("rfs_lseek: invalid whence!\n"); + return -1; + } + + return 0; +} + +// +// read disk inode information from disk +// +int rfs_disk_stat(struct vinode *vinode, struct istat *istat) { + struct rfs_device *rdev = rfs_device_list[vinode->sb->s_dev->dev_id]; + struct rfs_dinode *dinode = rfs_read_dinode(rdev, vinode->inum); + if (dinode == NULL) { + sprint("rfs_disk_stat: read dinode failed!\n"); + return -1; + } + + istat->st_inum = 1; + istat->st_inum = vinode->inum; // get inode number from vinode + + istat->st_size = dinode->size; + istat->st_type = dinode->type; + istat->st_nlinks = dinode->nlinks; + istat->st_blocks = dinode->blocks; + free_page(dinode); + return 0; +} + +/**** vfs-rfs file system type interface functions ****/ +struct super_block *rfs_get_superblock(struct device *dev) { + struct rfs_device *rdev = rfs_device_list[dev->dev_id]; + + // read super block from ramdisk + if (rfs_r1block(rdev, RFS_BLK_OFFSET_SUPER) != 0) + panic("RFS: failed to read superblock!\n"); + + struct rfs_superblock d_sb; + memcpy(&d_sb, rdev->iobuffer, sizeof(struct rfs_superblock)); + + // set the data for the vfs super block + struct super_block *sb = alloc_page(); + sb->magic = d_sb.magic; + sb->size = d_sb.size; + sb->nblocks = d_sb.nblocks; + sb->ninodes = d_sb.ninodes; + sb->s_dev = dev; + + if( sb->magic != RFS_MAGIC ) + panic("rfs_get_superblock: wrong ramdisk device!\n"); + + // build root dentry and root inode + struct vinode *root_inode = rfs_alloc_vinode(sb); + root_inode->inum = 0; + rfs_update_vinode(root_inode); + + struct dentry *root_dentry = alloc_vfs_dentry("/", root_inode, NULL); + sb->s_root = root_dentry; + + // save the bitmap in the s_fs_info field + if (rfs_r1block(rdev, RFS_BLK_OFFSET_BITMAP) != 0) + panic("RFS: failed to read bitmap!\n"); + void *bitmap = alloc_page(); + memcpy(bitmap, rdev->iobuffer, RFS_BLKSIZE); + sb->s_fs_info = bitmap; + + return sb; +} diff --git a/kernel/rfs.h b/kernel/rfs.h new file mode 100644 index 0000000..4d118ae --- /dev/null +++ b/kernel/rfs.h @@ -0,0 +1,87 @@ +#ifndef _RFS_H_ +#define _RFS_H_ + +#include "ramdev.h" +#include "riscv.h" +#include "util/types.h" +#include "vfs.h" + +#define RFS_TYPE 0 +#define RFS_MAGIC 0xBEAF +#define RFS_BLKSIZE PGSIZE +#define RFS_INODESIZE 128 // block size must be divisible by this value +#define RFS_MAX_INODE_BLKNUM 10 +#define RFS_MAX_FILE_NAME_LEN 28 +#define RFS_DIRECT_BLKNUM DIRECT_BLKNUM + +// rfs block offset +#define RFS_BLK_OFFSET_SUPER 0 +#define RFS_BLK_OFFSET_INODE 1 +#define RFS_BLK_OFFSET_BITMAP 11 +#define RFS_BLK_OFFSET_FREE 12 + +// dinode type +#define R_FILE FILE_I +#define R_DIR DIR_I +#define R_FREE 2 + +// file system super block +struct rfs_superblock { + int magic; // magic number of the + int size; // size of file system image (blocks) + int nblocks; // number of data blocks + int ninodes; // number of inodes. +}; + +// disk inode +struct rfs_dinode { + int size; // size of the file (in bytes) + int type; // one of R_FREE, R_FILE, R_DIR + int nlinks; // number of hard links to this file + int blocks; // number of blocks + int addrs[RFS_DIRECT_BLKNUM]; // direct blocks +}; + +// directory entry +struct rfs_direntry { + int inum; // inode number + char name[RFS_MAX_FILE_NAME_LEN]; // file name +}; + +// directory memory cache (used by opendir/readdir/closedir) +struct rfs_dir_cache { + int block_count; + struct rfs_direntry *dir_base_addr; +}; + +// rfs utility functin declarations +int register_rfs(); +int rfs_format_dev(struct device *dev); + +int rfs_r1block(struct rfs_device *rfs_dev, int n_block); +int rfs_w1block(struct rfs_device *rfs_dev, int n_block); +struct rfs_dinode *rfs_read_dinode(struct rfs_device *rdev, int n_inode); +int rfs_write_dinode(struct rfs_device *rdev, const struct rfs_dinode *dinode, + int n_inode); +int rfs_alloc_block(struct super_block *sb); +int rfs_free_block(struct super_block *sb, int block_num); +int rfs_add_direntry(struct vinode *dir, const char *name, int inum); + +struct vinode *rfs_alloc_vinode(struct super_block *sb); +int rfs_write_back_vinode(struct vinode *vinode); +int rfs_update_vinode(struct vinode *vinode); + +// rfs interface function declarations +ssize_t rfs_read(struct vinode *f_inode, char *r_buf, ssize_t len, int *offset); +ssize_t rfs_write(struct vinode *f_inode, const char *w_buf, ssize_t len, + int *offset); +struct vinode *rfs_lookup(struct vinode *parent, struct dentry *sub_dentry); +struct vinode *rfs_create(struct vinode *parent, struct dentry *sub_dentry); +int rfs_lseek(struct vinode *f_inode, ssize_t new_offset, int whence, int *offset); +int rfs_disk_stat(struct vinode *vinode, struct istat *istat); + +struct super_block *rfs_get_superblock(struct device *dev); + +extern const struct vinode_ops rfs_i_ops; + +#endif diff --git a/kernel/syscall.c b/kernel/syscall.c index d6591c4..3660321 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -13,6 +13,7 @@ #include "pmm.h" #include "vmm.h" #include "sched.h" +#include "proc_file.h" #include "spike_interface/spike_utils.h" @@ -94,6 +95,76 @@ ssize_t sys_user_yield() { return 0; } +// +// open file +// +ssize_t sys_user_open(char *pathva, int flags) { + char* pathpa = (char*)user_va_to_pa((pagetable_t)(current->pagetable), pathva); + return do_open(pathpa, flags); +} + +// +// read file +// +ssize_t sys_user_read(int fd, char *bufva, uint64 count) { + int i = 0; + while (i < count) { // count can be greater than page size + uint64 addr = (uint64)bufva + i; + uint64 pa = lookup_pa((pagetable_t)current->pagetable, addr); + uint64 off = addr - ROUNDDOWN(addr, PGSIZE); + uint64 len = count - i < PGSIZE - off ? count - i : PGSIZE - off; + uint64 r = do_read(fd, (char *)pa + off, len); + i += r; if (r < len) return i; + } + return count; +} + +// +// write file +// +ssize_t sys_user_write(int fd, char *bufva, uint64 count) { + int i = 0; + while (i < count) { // count can be greater than page size + uint64 addr = (uint64)bufva + i; + uint64 pa = lookup_pa((pagetable_t)current->pagetable, addr); + uint64 off = addr - ROUNDDOWN(addr, PGSIZE); + uint64 len = count - i < PGSIZE - off ? count - i : PGSIZE - off; + uint64 r = do_write(fd, (char *)pa + off, len); + i += r; if (r < len) return i; + } + return count; +} + +// +// lseek file +// +ssize_t sys_user_lseek(int fd, int offset, int whence) { + return do_lseek(fd, offset, whence); +} + +// +// read vinode +// +ssize_t sys_user_stat(int fd, struct istat *istat) { + struct istat * pistat = (struct istat *)user_va_to_pa((pagetable_t)(current->pagetable), istat); + return do_stat(fd, pistat); +} + +// +// read disk inode +// +ssize_t sys_user_disk_stat(int fd, struct istat *istat) { + struct istat * pistat = (struct istat *)user_va_to_pa((pagetable_t)(current->pagetable), istat); + return do_disk_stat(fd, pistat); +} + +// +// close file +// +ssize_t sys_user_close(int fd) { + return do_close(fd); +} + // // [a0]: the syscall number; [a1] ... [a7]: arguments to the syscalls. // returns the code of success, (e.g., 0 means success, fail for otherwise) @@ -113,6 +184,21 @@ long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, l return sys_user_fork(); case SYS_user_yield: return sys_user_yield(); + // added @lab4_1 + case SYS_user_open: + return sys_user_open((char *)a1, a2); + case SYS_user_read: + return sys_user_read(a1, (char *)a2, a3); + case SYS_user_write: + return sys_user_write(a1, (char *)a2, a3); + case SYS_user_lseek: + return sys_user_lseek(a1, a2, a3); + case SYS_user_stat: + return sys_user_stat(a1, (struct istat *)a2); + case SYS_user_disk_stat: + return sys_user_disk_stat(a1, (struct istat *)a2); + case SYS_user_close: + return sys_user_close(a1); default: panic("Unknown syscall %ld \n", a0); } diff --git a/kernel/syscall.h b/kernel/syscall.h index e0a1a16..8ac161e 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -14,6 +14,14 @@ // added @lab3_1 #define SYS_user_fork (SYS_user_base + 4) #define SYS_user_yield (SYS_user_base + 5) +// added @lab4_1 +#define SYS_user_open (SYS_user_base + 17) +#define SYS_user_read (SYS_user_base + 18) +#define SYS_user_write (SYS_user_base + 19) +#define SYS_user_lseek (SYS_user_base + 20) +#define SYS_user_stat (SYS_user_base + 21) +#define SYS_user_disk_stat (SYS_user_base + 22) +#define SYS_user_close (SYS_user_base + 23) long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, long a6, long a7); diff --git a/kernel/vfs.c b/kernel/vfs.c new file mode 100644 index 0000000..5d52d11 --- /dev/null +++ b/kernel/vfs.c @@ -0,0 +1,517 @@ +/* + * VFS (Virtual File System) interface utilities. added @lab4_1. + */ + +#include "vfs.h" + +#include "pmm.h" +#include "spike_interface/spike_utils.h" +#include "util/string.h" +#include "util/types.h" +#include "util/hash_table.h" + +struct dentry *vfs_root_dentry; // system root direntry +struct super_block *vfs_sb_list[MAX_MOUNTS]; // system superblock list +struct device *vfs_dev_list[MAX_VFS_DEV]; // system device list in vfs layer +struct hash_table dentry_hash_table; +struct hash_table vinode_hash_table; + +// +// initializes the dentry hash list and vinode hash list +// +int vfs_init() { + int ret; + ret = hash_table_init(&dentry_hash_table, dentry_hash_equal, dentry_hash_func, + NULL, NULL, NULL); + if (ret != 0) return ret; + + ret = hash_table_init(&vinode_hash_table, vinode_hash_equal, vinode_hash_func, + NULL, NULL, NULL); + if (ret != 0) return ret; + return 0; +} + +// +// mount a file system from the device named "dev_name" +// PKE does not support mounting a device at an arbitrary directory as in Linux, +// but can only mount a device in one of the following two ways (according to +// the mnt_type parameter) : +// 1. when mnt_type = MOUNT_AS_ROOT +// Mount the device AS the root directory. +// that is, mount the device under system root direntry:"/". +// In this case, the device specified by parameter dev_name will be used as +// the root file system. +// 2. when mnt_type = MOUNT_DEFAULT +// Mount the device UNDER the root directory. +// that is, mount the device to "/DEVICE_NAME" (/DEVICE_NAME will be +// automatically created) folder. +// +struct super_block *vfs_mount(const char *dev_name, int mnt_type) { + // device pointer + struct device *p_device = NULL; + + // find the device entry in vfs_device_list named as dev_name + for (int i = 0; i < MAX_VFS_DEV; ++i) { + p_device = vfs_dev_list[i]; + if (p_device && strcmp(p_device->dev_name, dev_name) == 0) break; + } + if (p_device == NULL) panic("vfs_mount: cannot find the device entry!\n"); + + // add the super block into vfs_sb_list + struct file_system_type *fs_type = p_device->fs_type; + struct super_block *sb = fs_type->get_superblock(p_device); + + // add the root vinode into vinode_hash_table + hash_put_vinode(sb->s_root->dentry_inode); + + int err = 1; + for (int i = 0; i < MAX_MOUNTS; ++i) { + if (vfs_sb_list[i] == NULL) { + vfs_sb_list[i] = sb; + err = 0; + break; + } + } + if (err) panic("vfs_mount: too many mounts!\n"); + + // mount the root dentry of the file system to right place + if (mnt_type == MOUNT_AS_ROOT) { + vfs_root_dentry = sb->s_root; + + // insert the mount point into hash table + hash_put_dentry(sb->s_root); + } else if (mnt_type == MOUNT_DEFAULT) { + if (!vfs_root_dentry) + panic("vfs_mount: root dentry not found, please mount the root device first!\n"); + + struct dentry *mnt_point = sb->s_root; + + // set the mount point directory's name to device name + char *dev_name = p_device->dev_name; + strcpy(mnt_point->name, dev_name); + + // by default, it is mounted under the vfs root directory + mnt_point->parent = vfs_root_dentry; + + // insert the mount point into hash table + hash_put_dentry(sb->s_root); + } else { + panic("vfs_mount: unknown mount type!\n"); + } + + return sb; +} + +// +// open a file located at "path" with permission of "flags". +// if the file does not exist, and O_CREAT bit is set in "flags", the file will +// be created. +// return: the file pointer to the opened file. +// +struct file *vfs_open(const char *path, int flags) { + struct dentry *parent = vfs_root_dentry; // we start the path lookup from root. + char miss_name[MAX_PATH_LEN]; + + // path lookup. + struct dentry *file_dentry = lookup_final_dentry(path, &parent, miss_name); + + // file does not exist + if (!file_dentry) { + int creatable = flags & O_CREAT; + + // create the file if O_CREAT bit is set + if (creatable) { + char basename[MAX_PATH_LEN]; + get_base_name(path, basename); + + // a missing directory exists in the path + if (strcmp(miss_name, basename) != 0) { + sprint("vfs_open: cannot create file in a non-exist directory!\n"); + return NULL; + } + + // create the file + file_dentry = alloc_vfs_dentry(basename, NULL, parent); + struct vinode *new_inode = viop_create(parent->dentry_inode, file_dentry); + if (!new_inode) panic("vfs_open: cannot create file!\n"); + + file_dentry->dentry_inode = new_inode; + new_inode->ref++; + hash_put_dentry(file_dentry); + hash_put_vinode(new_inode); + } else { + sprint("vfs_open: cannot find the file!\n"); + return NULL; + } + } + + if (file_dentry->dentry_inode->type != FILE_I) { + sprint("vfs_open: cannot open a directory!\n"); + return NULL; + } + + // get writable and readable flags + int writable = 0; + int readable = 0; + switch (flags & MASK_FILEMODE) { + case O_RDONLY: + writable = 0; + readable = 1; + break; + case O_WRONLY: + writable = 1; + readable = 0; + break; + case O_RDWR: + writable = 1; + readable = 1; + break; + default: + panic("fs_open: invalid open flags!\n"); + } + + struct file *file = alloc_vfs_file(file_dentry, readable, writable, 0); + + // additional open operations for a specific file system + // hostfs needs to conduct actual file open. + if (file_dentry->dentry_inode->i_ops->viop_hook_open) { + if (file_dentry->dentry_inode->i_ops-> + viop_hook_open(file_dentry->dentry_inode, file_dentry) < 0) { + sprint("vfs_open: hook_open failed!\n"); + } + } + + return file; +} + +// +// read content from "file" starting from file->offset, and store it in "buf". +// return: the number of bytes actually read +// +ssize_t vfs_read(struct file *file, char *buf, size_t count) { + if (!file->readable) { + sprint("vfs_read: file is not readable!\n"); + return -1; + } + if (file->f_dentry->dentry_inode->type != FILE_I) { + sprint("vfs_read: cannot read a directory!\n"); + return -1; + } + // actual reading. + return viop_read(file->f_dentry->dentry_inode, buf, count, &(file->offset)); +} + +// +// write content in "buf" to "file", at file->offset. +// return: the number of bytes actually written +// +ssize_t vfs_write(struct file *file, const char *buf, size_t count) { + if (!file->writable) { + sprint("vfs_write: file is not writable!\n"); + return -1; + } + if (file->f_dentry->dentry_inode->type != FILE_I) { + sprint("vfs_read: cannot write a directory!\n"); + return -1; + } + // actual writing. + return viop_write(file->f_dentry->dentry_inode, buf, count, &(file->offset)); +} + +// +// reposition read/write file offset +// return: the new offset on success, -1 on failure. +// +ssize_t vfs_lseek(struct file *file, ssize_t offset, int whence) { + if (file->f_dentry->dentry_inode->type != FILE_I) { + sprint("vfs_read: cannot seek a directory!\n"); + return -1; + } + + if (viop_lseek(file->f_dentry->dentry_inode, offset, whence, &(file->offset)) != 0) { + sprint("vfs_lseek: lseek failed!\n"); + return -1; + } + + return file->offset; +} + +// +// read the vinode information +// +int vfs_stat(struct file *file, struct istat *istat) { + istat->st_inum = file->f_dentry->dentry_inode->inum; + istat->st_size = file->f_dentry->dentry_inode->size; + istat->st_type = file->f_dentry->dentry_inode->type; + istat->st_nlinks = file->f_dentry->dentry_inode->nlinks; + istat->st_blocks = file->f_dentry->dentry_inode->blocks; + return 0; +} + +// +// read the inode information on the disk +// +int vfs_disk_stat(struct file *file, struct istat *istat) { + return viop_disk_stat(file->f_dentry->dentry_inode, istat); +} + +// +// close a file at vfs layer. +// +int vfs_close(struct file *file) { + if (file->f_dentry->dentry_inode->type != FILE_I) { + sprint("vfs_close: cannot close a directory!\n"); + return -1; + } + + struct dentry *dentry = file->f_dentry; + struct vinode *inode = dentry->dentry_inode; + + // additional close operations for a specific file system + // hostfs needs to conduct actual file close. + if (inode->i_ops->viop_hook_close) { + if (inode->i_ops->viop_hook_close(inode, dentry) != 0) { + sprint("vfs_close: hook_close failed!\n"); + } + } + + dentry->d_ref--; + // if the dentry is not pointed by any opened file, free the dentry + if (dentry->d_ref == 0) { + // free the dentry + hash_erase_dentry(dentry); + free_vfs_dentry(dentry); + inode->ref--; + // no other opened hard link + if (inode->ref == 0) { + // write back the inode and free it + if (viop_write_back_vinode(inode) != 0) + panic("vfs_close: free inode failed!\n"); + hash_erase_vinode(inode); + free_page(inode); + } + } + + file->status = FD_NONE; + return 0; +} + +// +// lookup the "path" and return its dentry (or NULL if not found). +// the lookup starts from parent, and stop till the full "path" is parsed. +// return: the final dentry if we find it, NULL for otherwise. +// +struct dentry *lookup_final_dentry(const char *path, struct dentry **parent, + char *miss_name) { + char path_copy[MAX_PATH_LEN]; + strcpy(path_copy, path); + + // split the path, and retrieves a token at a time. + // note: strtok() uses a static (local) variable to store the input path + // string at the first time it is called. thus it can out a token each time. + // for example, when input path is: /RAMDISK0/test_dir/ramfile2 + // strtok() outputs three tokens: 1)RAMDISK0, 2)test_dir and 3)ramfile2 + // at its three continuous invocations. + char *token = strtok(path_copy, "/"); + struct dentry *this = *parent; + + while (token != NULL) { + *parent = this; + this = hash_get_dentry((*parent), token); // try hash first + if (this == NULL) { + // if not found in hash, try to find it in the directory + this = alloc_vfs_dentry(token, NULL, *parent); + // lookup subfolder/file in its parent directory. note: + // hostfs and rfs will take different procedures for lookup. + struct vinode *found_vinode = viop_lookup((*parent)->dentry_inode, this); + if (found_vinode == NULL) { + // not found in both hash table and directory file on disk. + free_page(this); + strcpy(miss_name, token); + return NULL; + } + + struct vinode *same_inode = hash_get_vinode(found_vinode->sb, found_vinode->inum); + if (same_inode != NULL) { + // the vinode is already in the hash table (i.e. we are opening another hard link) + this->dentry_inode = same_inode; + same_inode->ref++; + free_page(found_vinode); + } else { + // the vinode is not in the hash table + this->dentry_inode = found_vinode; + found_vinode->ref++; + hash_put_vinode(found_vinode); + } + + hash_put_dentry(this); + } + + // get next token + token = strtok(NULL, "/"); + } + return this; +} + +// +// get the base name of a path +// +void get_base_name(const char *path, char *base_name) { + char path_copy[MAX_PATH_LEN]; + strcpy(path_copy, path); + + char *token = strtok(path_copy, "/"); + char *last_token = NULL; + while (token != NULL) { + last_token = token; + token = strtok(NULL, "/"); + } + + strcpy(base_name, last_token); +} + +// +// alloc a (virtual) file +// +struct file *alloc_vfs_file(struct dentry *file_dentry, int readable, int writable, + int offset) { + struct file *file = alloc_page(); + file->f_dentry = file_dentry; + file_dentry->d_ref += 1; + + file->readable = readable; + file->writable = writable; + file->offset = 0; + file->status = FD_OPENED; + return file; +} + +// +// alloc a (virtual) dir entry +// +struct dentry *alloc_vfs_dentry(const char *name, struct vinode *inode, + struct dentry *parent) { + struct dentry *dentry = (struct dentry *)alloc_page(); + strcpy(dentry->name, name); + dentry->dentry_inode = inode; + if (inode) inode->ref++; + + dentry->parent = parent; + dentry->d_ref = 0; + return dentry; +} + +// +// free a (virtual) dir entry, if it is not referenced by any file +// +int free_vfs_dentry(struct dentry *dentry) { + if (dentry->d_ref > 0) { + sprint("free_vfs_dentry: dentry is still in use!\n"); + return -1; + } + free_page((void *)dentry); + return 0; +} + +// dentry generic hash table method implementation +int dentry_hash_equal(void *key1, void *key2) { + struct dentry_key *dentry_key1 = key1; + struct dentry_key *dentry_key2 = key2; + if (strcmp(dentry_key1->name, dentry_key2->name) == 0 && + dentry_key1->parent == dentry_key2->parent) { + return 1; + } + return 0; +} + +size_t dentry_hash_func(void *key) { + struct dentry_key *dentry_key = key; + char *name = dentry_key->name; + + size_t hash = 5381; + int c; + + while ((c = *name++)) hash = ((hash << 5) + hash) + c; // hash * 33 + c + + hash = ((hash << 5) + hash) + (size_t)dentry_key->parent; + return hash % HASH_TABLE_SIZE; +} + +// dentry hash table interface +struct dentry *hash_get_dentry(struct dentry *parent, char *name) { + struct dentry_key key = {.parent = parent, .name = name}; + return (struct dentry *)dentry_hash_table.virtual_hash_get(&dentry_hash_table, + &key); +} + +int hash_put_dentry(struct dentry *dentry) { + struct dentry_key *key = alloc_page(); + key->name = dentry->name; + key->parent = dentry->parent; + + int ret = dentry_hash_table.virtual_hash_put(&dentry_hash_table, key, dentry); + if (ret != 0) + free_page(key); + return ret; +} + +int hash_erase_dentry(struct dentry *dentry) { + struct dentry_key key = {.parent = dentry->parent, .name = dentry->name}; + return dentry_hash_table.virtual_hash_erase(&dentry_hash_table, &key); +} + +// vinode generic hash table method implementation +int vinode_hash_equal(void *key1, void *key2) { + struct vinode_key *vinode_key1 = key1; + struct vinode_key *vinode_key2 = key2; + if (vinode_key1->inum == vinode_key2->inum && vinode_key1->sb == vinode_key2->sb) { + return 1; + } + return 0; +} + +size_t vinode_hash_func(void *key) { + struct vinode_key *vinode_key = key; + return vinode_key->inum % HASH_TABLE_SIZE; +} + +// vinode hash table interface +struct vinode *hash_get_vinode(struct super_block *sb, int inum) { + if (inum < 0) return NULL; + struct vinode_key key = {.sb = sb, .inum = inum}; + return (struct vinode *)vinode_hash_table.virtual_hash_get(&vinode_hash_table, + &key); +} + +int hash_put_vinode(struct vinode *vinode) { + if (vinode->inum < 0) return -1; + struct vinode_key *key = alloc_page(); + key->sb = vinode->sb; + key->inum = vinode->inum; + + int ret = vinode_hash_table.virtual_hash_put(&vinode_hash_table, key, vinode); + if (ret != 0) free_page(key); + return ret; +} + +int hash_erase_vinode(struct vinode *vinode) { + if (vinode->inum < 0) return -1; + struct vinode_key key = {.sb = vinode->sb, .inum = vinode->inum}; + return vinode_hash_table.virtual_hash_erase(&vinode_hash_table, &key); +} + +// +// shared (default) actions on allocating a vfs inode. +// +struct vinode *default_alloc_vinode(struct super_block *sb) { + struct vinode *vinode = (struct vinode *)alloc_page(); + vinode->blocks = 0; + vinode->inum = 0; + vinode->nlinks = 0; + vinode->ref = 0; + vinode->sb = sb; + vinode->size = 0; + return vinode; +} + +struct file_system_type *fs_list[MAX_SUPPORTED_FS]; diff --git a/kernel/vfs.h b/kernel/vfs.h new file mode 100644 index 0000000..b9673ce --- /dev/null +++ b/kernel/vfs.h @@ -0,0 +1,189 @@ +#ifndef _VFS_H_ +#define _VFS_H_ + +#include "util/types.h" + +#define MAX_VFS_DEV 10 // the maximum number of vfs_dev_list +#define MAX_DENTRY_NAME_LEN 30 // the maximum length of dentry name +#define MAX_DEVICE_NAME_LEN 30 // the maximum length of device name +#define MAX_MOUNTS 10 // the maximum number of mounts +#define MAX_DENTRY_HASH_SIZE 100 // the maximum size of dentry hash table +#define MAX_PATH_LEN 30 // the maximum length of path +#define MAX_SUPPORTED_FS 10 // the maximum number of supported file systems + +#define DIRECT_BLKNUM 10 // the number of direct blocks + +/**** vfs initialization function ****/ +int vfs_init(); + +/**** vfs interfaces ****/ + +// device interfaces +struct super_block *vfs_mount(const char *dev_name, int mnt_type); + +// file interfaces +struct file *vfs_open(const char *path, int flags); +ssize_t vfs_read(struct file *file, char *buf, size_t count); +ssize_t vfs_write(struct file *file, const char *buf, size_t count); +ssize_t vfs_lseek(struct file *file, ssize_t offset, int whence); +int vfs_stat(struct file *file, struct istat *istat); +int vfs_disk_stat(struct file *file, struct istat *istat); +int vfs_close(struct file *file); + +/**** vfs abstract object types ****/ +// system root direntry +extern struct dentry *vfs_root_dentry; + +// vfs abstract dentry +struct dentry { + char name[MAX_DENTRY_NAME_LEN]; + int d_ref; + struct vinode *dentry_inode; + struct dentry *parent; + struct super_block *sb; +}; + + +// dentry constructor and destructor +struct dentry *alloc_vfs_dentry(const char *name, struct vinode *inode, + struct dentry *parent); +int free_vfs_dentry(struct dentry *dentry); + +// ** dentry hash table ** +extern struct hash_table dentry_hash_table; + +// dentry hash table key type +struct dentry_key { + struct dentry *parent; + char *name; +}; + +// generic hash table method implementation +int dentry_hash_equal(void *key1, void *key2); +size_t dentry_hash_func(void *key); + +// dentry hash table interface +struct dentry *hash_get_dentry(struct dentry *parent, char *name); +int hash_put_dentry(struct dentry *dentry); +int hash_erase_dentry(struct dentry *dentry); + +// data structure of an openned file +struct file { + int status; + int readable; + int writable; + int offset; + struct dentry *f_dentry; +}; + +// file constructor and destructor(use free_page to destruct) +struct file *alloc_vfs_file(struct dentry *dentry, int readable, int writable, + int offset); + +// abstract device entry in vfs_dev_list +struct device { + char dev_name[MAX_DEVICE_NAME_LEN]; // the name of the device + int dev_id; // the id of the device (the meaning of an id is interpreted by + // the specific file system, all we need to know is that it is + // a unique identifier) + struct file_system_type *fs_type; // the file system type in the device +}; + +// device list in vfs layer +extern struct device *vfs_dev_list[MAX_VFS_DEV]; + +// supported file system types +struct file_system_type { + int type_num; // the number of the file system type + struct super_block *(*get_superblock)(struct device *dev); +}; + +extern struct file_system_type *fs_list[MAX_SUPPORTED_FS]; + +// general-purpose super_block structure +struct super_block { + int magic; // magic number of the file system + int size; // size of file system image (blocks) + int nblocks; // number of data blocks + int ninodes; // number of inodes. + struct dentry *s_root; // root dentry of inode + struct device *s_dev; // device of the superblock + void *s_fs_info; // filesystem-specific info. for rfs, it points bitmap +}; + +// abstract vfs inode +struct vinode { + int inum; // inode number of the disk inode + int ref; // reference count + int size; // size of the file (in bytes) + int type; // one of FILE_I, DIR_I + int nlinks; // number of hard links to this file + int blocks; // number of blocks + int addrs[DIRECT_BLKNUM]; // direct blocks + void *i_fs_info; // filesystem-specific info (see s_fs_info) + struct super_block *sb; // super block of the vfs inode + const struct vinode_ops *i_ops; // vfs inode operations +}; + +struct vinode_ops { + // file operations + ssize_t (*viop_read)(struct vinode *node, char *buf, ssize_t len, + int *offset); + ssize_t (*viop_write)(struct vinode *node, const char *buf, ssize_t len, + int *offset); + struct vinode *(*viop_create)(struct vinode *parent, struct dentry *sub_dentry); + int (*viop_lseek)(struct vinode *node, ssize_t new_off, int whence, int *off); + int (*viop_disk_stat)(struct vinode *node, struct istat *istat); + struct vinode *(*viop_lookup)(struct vinode *parent, + struct dentry *sub_dentry); + + // write back inode to disk + int (*viop_write_back_vinode)(struct vinode *node); + + // hook functions + // In the vfs layer, we do not assume that hook functions will do anything, + // but simply call them (when they are defined) at the appropriate time. + // Hook functions exist because the fs layer may need to do some additional + // operations (such as allocating additional data structures) at some critical + // times. + int (*viop_hook_open)(struct vinode *node, struct dentry *dentry); + int (*viop_hook_close)(struct vinode *node, struct dentry *dentry); +}; + +// vinode operation interface +// the implementation depends on the vinode type and the specific file system + +// virtual file system inode interfaces +#define viop_read(node, buf, len, offset) (node->i_ops->viop_read(node, buf, len, offset)) +#define viop_write(node, buf, len, offset) (node->i_ops->viop_write(node, buf, len, offset)) +#define viop_create(node, name) (node->i_ops->viop_create(node, name)) +#define viop_lseek(node, new_off, whence, off) (node->i_ops->viop_lseek(node, new_off, whence, off)) +#define viop_disk_stat(node, istat) (node->i_ops->viop_disk_stat(node, istat)) +#define viop_lookup(parent, sub_dentry) (parent->i_ops->viop_lookup(parent, sub_dentry)) +#define viop_write_back_vinode(node) (node->i_ops->viop_write_back_vinode(node)) + +// vinode hash table +extern struct hash_table vinode_hash_table; + +// vinode hash table key type +struct vinode_key { + int inum; + struct super_block *sb; +}; + +// generic hash table method implementation +int vinode_hash_equal(void *key1, void *key2); +size_t vinode_hash_func(void *key); + +// vinode hash table interface +struct vinode *hash_get_vinode(struct super_block *sb, int inum); +int hash_put_vinode(struct vinode *vinode); +int hash_erase_vinode(struct vinode *vinode); + +// other utility functions +struct vinode *default_alloc_vinode(struct super_block *sb); +struct dentry *lookup_final_dentry(const char *path, struct dentry **parent, + char *miss_name); +void get_base_name(const char *path, char *base_name); + +#endif diff --git a/spike_interface/spike_file.c b/spike_interface/spike_file.c index 43061eb..39fff46 100644 --- a/spike_interface/spike_file.c +++ b/spike_interface/spike_file.c @@ -14,8 +14,6 @@ #include "spike_interface/spike_utils.h" //#include "../kernel/config.h" -#define MAX_FILES 128 -#define MAX_FDS 128 static spike_file_t* spike_fds[MAX_FDS]; spike_file_t spike_files[MAX_FILES] = {[0 ... MAX_FILES - 1] = {-1, 0}}; @@ -128,3 +126,18 @@ ssize_t spike_file_read(spike_file_t* f, void* buf, size_t size) { ssize_t spike_file_lseek(spike_file_t* f, size_t ptr, int dir) { return frontend_syscall(HTIFSYS_lseek, f->kfd, ptr, dir, 0, 0, 0, 0); } + +spike_file_t* spike_file_get(int fd) { + spike_file_t* f; + if (fd < 0 || fd >= MAX_FDS || (f = atomic_read(&spike_fds[fd])) == NULL) + return 0; + + long old_cnt; + do { + old_cnt = atomic_read(&f->refcnt); + if (old_cnt == 0) + return 0; + } while (atomic_cas(&f->refcnt, old_cnt, old_cnt+1) != old_cnt); + + return f; +} diff --git a/spike_interface/spike_file.h b/spike_interface/spike_file.h index 72c6e32..952e56e 100644 --- a/spike_interface/spike_file.h +++ b/spike_interface/spike_file.h @@ -6,16 +6,16 @@ #include "util/types.h" -typedef struct file { +typedef struct file_t { int kfd; // file descriptor of the host file uint32 refcnt; } spike_file_t; extern spike_file_t spike_files[]; -#define O_RDONLY 00 -#define O_WRONLY 01 -#define O_RDWR 02 +#define MAX_FILES 128 +#define MAX_FDS 128 + #define ENOMEM 12 /* Out of memory */ #define stdin (spike_files + 0) @@ -60,5 +60,6 @@ void spike_file_init(void); int spike_file_dup(spike_file_t* f); int spike_file_truncate(spike_file_t* f, off_t len); int spike_file_stat(spike_file_t* f, struct stat* s); +spike_file_t* spike_file_get(int fd); #endif diff --git a/user/app_file.c b/user/app_file.c new file mode 100644 index 0000000..ec3acbf --- /dev/null +++ b/user/app_file.c @@ -0,0 +1,63 @@ +#include "user_lib.h" +#include "util/string.h" +#include "util/types.h" + +int main(int argc, char *argv[]) { + int fd; + int MAXBUF = 512; + char buf[MAXBUF]; + char str[] = "hello world"; + int fd1, fd2; + + printu("\n======== Test 1: read host file ========\n"); + printu("read: /hostfile.txt\n"); + + fd = open("/hostfile.txt", O_RDONLY); + printu("file descriptor fd: %d\n", fd); + + read_u(fd, buf, MAXBUF); + printu("read content: \n%s\n", buf); + + close(fd); + + printu("\n======== Test 2: create/write rfs file ========\n"); + printu("write: /RAMDISK0/ramfile\n"); + + fd = open("/RAMDISK0/ramfile", O_RDWR | O_CREAT); + printu("file descriptor fd: %d\n", fd); + + write_u(fd, buf, strlen(buf)); + printu("write content: \n%s\n", buf); + close(fd); + + printu("\n======== Test 3: read rfs file ========\n"); + printu("read: /RAMDISK0/ramfile\n"); + + fd = open("/RAMDISK0/ramfile", O_RDWR); + printu("file descriptor fd: %d\n", fd); + + read_u(fd, buf, MAXBUF); + printu("read content: \n%s\n", buf); + close(fd); + + printu("\n======== Test 4: open twice ========\n"); + + fd1 = open("/RAMDISK0/ramfile", O_RDWR | O_CREAT); + fd2 = open("/RAMDISK0/ramfile", O_RDWR | O_CREAT); + + printu("file descriptor fd1(ramfile): %d\n", fd1); + printu("file descriptor fd2(ramfile): %d\n", fd2); + + write_u(fd1, str, strlen(str)); + printu("write content: \n%s\n", str); + + read_u(fd2, buf, MAXBUF); + printu("read content: \n%s\n", buf); + + close(fd1); + close(fd2); + + printu("\nAll tests passed!\n\n"); + exit(0); + return 0; +} diff --git a/user/app_two_long_loops.c b/user/app_two_long_loops.c deleted file mode 100644 index 2568485..0000000 --- a/user/app_two_long_loops.c +++ /dev/null @@ -1,28 +0,0 @@ -/* - * The application of lab3_3. - * parent and child processes never give up their processor during execution. - */ - -#include "user/user_lib.h" -#include "util/types.h" - -int main(void) { - uint64 pid = fork(); - uint64 rounds = 100000000; - uint64 interval = 10000000; - uint64 a = 0; - if (pid == 0) { - printu("Child: Hello world! \n"); - for (uint64 i = 0; i < rounds; ++i) { - if (i % interval == 0) printu("Child running %ld \n", i); - } - } else { - printu("Parent: Hello world! \n"); - for (uint64 i = 0; i < rounds; ++i) { - if (i % interval == 0) printu("Parent running %ld \n", i); - } - } - - exit(0); - return 0; -} diff --git a/user/user_lib.c b/user/user_lib.c index 3fcb85d..7e59d98 100644 --- a/user/user_lib.c +++ b/user/user_lib.c @@ -76,3 +76,52 @@ int fork() { void yield() { do_user_call(SYS_user_yield, 0, 0, 0, 0, 0, 0, 0); } + +// +// lib call to open +// +int open(const char *pathname, int flags) { + return do_user_call(SYS_user_open, (uint64)pathname, flags, 0, 0, 0, 0, 0); +} + +// +// lib call to read +// +int read_u(int fd, void * buf, uint64 count){ + return do_user_call(SYS_user_read, fd, (uint64)buf, count, 0, 0, 0, 0); +} + +// +// lib call to write +// +int write_u(int fd, void *buf, uint64 count) { + return do_user_call(SYS_user_write, fd, (uint64)buf, count, 0, 0, 0, 0); +} + +// +// lib call to seek +// +int lseek_u(int fd, int offset, int whence) { + return do_user_call(SYS_user_lseek, fd, offset, whence, 0, 0, 0, 0); +} + +// +// lib call to read file information +// +int stat_u(int fd, struct istat *istat) { + return do_user_call(SYS_user_stat, fd, (uint64)istat, 0, 0, 0, 0, 0); +} + +// +// lib call to read file information from disk +// +int disk_stat_u(int fd, struct istat *istat) { + return do_user_call(SYS_user_disk_stat, fd, (uint64)istat, 0, 0, 0, 0, 0); +} + +// +// lib call to close +// +int close(int fd) { + return do_user_call(SYS_user_close, fd, 0, 0, 0, 0, 0, 0); +} diff --git a/user/user_lib.h b/user/user_lib.h index 63e2e25..58fedb5 100644 --- a/user/user_lib.h +++ b/user/user_lib.h @@ -2,9 +2,25 @@ * header file to be used by applications. */ +#ifndef _USER_LIB_H_ +#define _USER_LIB_H_ +#include "util/types.h" +#include "kernel/proc_file.h" + int printu(const char *s, ...); int exit(int code); void* naive_malloc(); void naive_free(void* va); int fork(); void yield(); + +// added @ lab4_1 +int open(const char *pathname, int flags); +int read_u(int fd, void *buf, uint64 count); +int write_u(int fd, void *buf, uint64 count); +int lseek_u(int fd, int offset, int whence); +int stat_u(int fd, struct istat *istat); +int disk_stat_u(int fd, struct istat *istat); +int close(int fd); + +#endif diff --git a/util/hash_table.c b/util/hash_table.c new file mode 100644 index 0000000..1f5217b --- /dev/null +++ b/util/hash_table.c @@ -0,0 +1,60 @@ +#include "util/hash_table.h" +#include "util/types.h" +#include "kernel/pmm.h" + +static int default_equal(void *key1, void *key2) { return key1 == key2; } + +static int default_put(struct hash_table *hash_table, void *key, void *value) { + struct hash_node *node = (struct hash_node *)alloc_page(); + if (hash_table->virtual_hash_get(hash_table, key) != NULL) return -1; + node->key = key; + node->value = value; + + size_t index = hash_table->virtual_hash_func(key); + struct hash_node *head = hash_table->head + index; + + node->next = head->next; + head->next = node; + return 0; +} + +static void *defalut_get(struct hash_table *hash_table, void *key) { + size_t index = hash_table->virtual_hash_func(key); + struct hash_node *head = hash_table->head + index; + struct hash_node *node = head->next; + while (node) { + if (hash_table->virtual_hash_equal(node->key, key)) return node->value; + node = node->next; + } + return NULL; +} + +static int default_erase(struct hash_table *hash_table, void *key) { + size_t index = hash_table->virtual_hash_func(key); + struct hash_node *head = hash_table->head + index; + while (head->next && !hash_table->virtual_hash_equal(head->next->key, key)) + head = head->next; + if (head->next) { + struct hash_node *node = head->next; + head->next = node->next; + free_page(node); + return 0; + } else + return -1; +} + +int hash_table_init(struct hash_table *list, + int (*equal)(void *key1, void *key2), + size_t (*func)(void *key), + int (*put)(struct hash_table *hash_table, void *key, void *value), + void *(*get)(struct hash_table *hash_table, void *key), + int (*erase)(struct hash_table *hash_table, void *key)) { + for (int i = 0; i < HASH_TABLE_SIZE; i++) list->head[i].next = NULL; + if (func == NULL) return -1; + list->virtual_hash_func = func; + list->virtual_hash_equal = equal ? equal : default_equal; + list->virtual_hash_put = put ? put : default_put; + list->virtual_hash_get = get ? get : defalut_get; + list->virtual_hash_erase = erase ? erase : default_erase; + return 0; +} \ No newline at end of file diff --git a/util/hash_table.h b/util/hash_table.h new file mode 100644 index 0000000..a9802bd --- /dev/null +++ b/util/hash_table.h @@ -0,0 +1,29 @@ +#ifndef _HASH_TABLE_H +#define _HASH_TABLE_H +#include "util/types.h" + +#define HASH_TABLE_SIZE 128 + +struct hash_node { + struct hash_node *next; + void *key; + void *value; +}; + +// this is a generic hash linked table for KERNEL SPACE +struct hash_table { + struct hash_node head[HASH_TABLE_SIZE]; + int (*virtual_hash_equal)(void *key1, void *key2); + size_t (*virtual_hash_func)(void *key); + int (*virtual_hash_put)(struct hash_table *hash_table, void *key, void *value); + void *(*virtual_hash_get)(struct hash_table *hash_table, void *key); + int (*virtual_hash_erase)(struct hash_table *hash_table, void *key); +}; + +int hash_table_init(struct hash_table *list, int (*virtual_hash_equal)(void *key1, void *key2), + size_t (*virtual_hash_func)(void *key), + int (*virtual_hash_put)(struct hash_table *hash_table, void *key, void *value), + void *(*virtual_hash_get)(struct hash_table *hash_table, void *key), + int (*virtual_hash_erase)(struct hash_table *hash_table, void *key)); + +#endif \ No newline at end of file diff --git a/util/string.c b/util/string.c index c1cc9ca..8cdad59 100644 --- a/util/string.c +++ b/util/string.c @@ -1,10 +1,10 @@ // See LICENSE for license details. -#include -#include - #include "string.h" +#include +#include + void* memcpy(void* dest, const void* src, size_t len) { const char* s = src; char* d = dest; @@ -62,6 +62,47 @@ char* strcpy(char* dest, const char* src) { return dest; } +char *strchr(const char *p, int ch) +{ + char c; + c = ch; + for (;; ++p) { + if (*p == c) + return ((char *)p); + if (*p == '\0') + return (NULL); + } +} + +char* strtok(char* str, const char* delim) { + static char* current; + if (str != NULL) current = str; + if (current == NULL) return NULL; + + char* start = current; + while (*start != '\0' && strchr(delim, *start) != NULL) start++; + + if (*start == '\0') { + current = NULL; + return current; + } + + char* end = start; + while (*end != '\0' && strchr(delim, *end) == NULL) end++; + + if (*end != '\0') { + *end = '\0'; + current = end + 1; + } else + current = NULL; + return start; +} + +char *strcat(char *dst, const char *src) { + strcpy(dst + strlen(dst), src); + return dst; +} + long atol(const char* str) { long res = 0; int sign = 0; @@ -107,4 +148,4 @@ char* safestrcpy(char* s, const char* t, int n) { ; *s = 0; return os; -} \ No newline at end of file +} diff --git a/util/string.h b/util/string.h index f9ee4e5..ac48765 100644 --- a/util/string.h +++ b/util/string.h @@ -3,13 +3,16 @@ #include -void* memcpy(void* dest, const void* src, size_t len); -void* memset(void* dest, int byte, size_t len); +void *memcpy(void* dest, const void* src, size_t len); +void *memset(void* dest, int byte, size_t len); size_t strlen(const char* s); int strcmp(const char* s1, const char* s2); -char* strcpy(char* dest, const char* src); +char *strcpy(char* dest, const char* src); +char *strchr(const char *p, int ch); +char *strtok(char* str, const char* delim); +char *strcat(char *dst, const char *src); long atol(const char* str); -void* memmove(void* dst, const void* src, size_t n); -char* safestrcpy(char* s, const char* t, int n); +void *memmove(void* dst, const void* src, size_t n); +char *safestrcpy(char* s, const char* t, int n); -#endif \ No newline at end of file +#endif diff --git a/util/types.h b/util/types.h index eebb44b..73c12d7 100644 --- a/util/types.h +++ b/util/types.h @@ -16,8 +16,38 @@ typedef int bool; typedef signed long ssize_t; typedef unsigned long size_t; + #define NULL ((void *)0) #define TRUE 1 #define FALSE 0 +#define LSEEK_SET 0 +#define LSEEK_CUR 1 + +#define MOUNT_DEFAULT 0 +#define MOUNT_AS_ROOT 1 + +#define FILE_I 0 +#define DIR_I 1 + +#define MASK_FILEMODE 0x003 + +#define O_RDONLY 00 // read-only access +#define O_WRONLY 01 // write-only access +#define O_RDWR 02 // read-write +#define O_CREAT 0100 // create + +#define FD_NONE 0 +#define FD_OPENED 1 + +#define MAX_FILE_NAME_LEN 32 + +struct istat { + int st_inum; + int st_size; + int st_type; + int st_nlinks; + int st_blocks; +}; + #endif