云计算百科
云计算领域专业知识百科平台

PA3之简易文件系统


  • 文章为本人自学记录的分享(非NJU学生),代码不保证正确,如果你是NJU的该实验课学员或者YSYX的A阶段学员,请自觉遵守学术诚信。
  • 如果本文造成不良影响请联系我撤回。
  • 实验为南京大学 计算机科学与技术系 计算机系统基础 课程实验。实验文档:https://ysyx.oscc.cc/docs/ics-pa/。

  • 简易文件系统

    关键实现

    要做的事情可以拆成三块:用户态(libos 的系统调用封装)、内核态(do_syscall 增加 SYS_open/read/write/close/lseek 分发)、文件系统实现(nanos-lite/src/fs.c 里实现 fs_open/fs_read/fs_write/fs_lseek/fs_close,并维护 open_offset)。
    (1)navy-apps/libs/libos/src/syscall.c

    int _open(const char *path, int flags, mode_t mode) {
    return _syscall_(SYS_open, (intptr_t)path, flags, mode);
    }

    int _read(int fd, void *buf, size_t count) {
    return _syscall_(SYS_read, fd, (intptr_t)buf, count);
    }

    int _close(int fd) {
    return _syscall_(SYS_close, fd, 0, 0);
    }

    off_t _lseek(int fd, off_t offset, int whence) {
    return _syscall_(SYS_lseek, fd, offset, whence);
    }

    (2)实现 nanos-lite/src/fs.c 的核心逻辑

    typedef struct {
    char *name;
    size_t size;
    size_t disk_offset;
    size_t open_offset; // 新增
    ReadFn read;
    WriteFn write;
    } Finfo;

    int fs_open(const char *pathname, int flags, int mode) {
    (void)flags;
    (void)mode;
    for (int fd = 0; fd < (int)(sizeof(file_table) / sizeof(file_table[0]));
    fd++) {
    if (strcmp(file_table[fd].name, pathname) == 0) {
    file_table[fd].open_offset = 0;
    return fd;
    }
    }
    assert(0 && "fs_open: file not found");
    return 1;
    }

    size_t fs_read(int fd, void *buf, size_t len) {
    if (fd == FD_STDIN)
    return 0; // todo assert
    if (fd == FD_STDOUT || fd == FD_STDERR)
    return 0;

    Finfo *f = &file_table[fd];
    if (f->open_offset >= f->size)
    return 0;

    size_t remain = f->size f->open_offset;
    if (len > remain)
    len = remain;

    size_t disk_off = f->disk_offset + f->open_offset;
    size_t r = ramdisk_read(buf, disk_off, len);
    f->open_offset += r;
    return r;
    }

    size_t fs_write(int fd, const void *buf, size_t len) {
    if (fd == FD_STDOUT || fd == FD_STDERR) {
    return serial_write(buf, 0, len);
    }
    if (fd == FD_STDIN)
    return 0;

    Finfo *f = &file_table[fd];
    if (f->open_offset >= f->size)
    return 0;

    size_t remain = f->size f->open_offset;
    if (len > remain)
    len = remain;

    size_t disk_off = f->disk_offset + f->open_offset;
    size_t w = ramdisk_write(buf, disk_off, len);
    f->open_offset += w;
    return w;
    }

    off_t fs_lseek(int fd, off_t offset, int whence) {
    Finfo *f = &file_table[fd];
    off_t new_off = 0;

    switch (whence) {
    case SEEK_SET:
    new_off = offset;
    break;
    case SEEK_CUR:
    new_off = (off_t)f->open_offset + offset;
    break;
    case SEEK_END:
    new_off = (off_t)f->size + offset;
    break;
    default:
    assert(0);
    }

    // 不允许越界:<0 设为 0;>size 设为 size
    if (new_off < 0)
    new_off = 0;
    if (new_off > (off_t)f->size)
    new_off = (off_t)f->size;

    f->open_offset = (size_t)new_off;
    return new_off;
    }

    int fs_close(int fd) { return 0; }

    (3)在 Nanos-lite 的 do_syscall 增加这些系统调用分发

    void do_syscall(Context *c) {
    uintptr_t a[4];
    a[0] = c->GPR1;
    a[1] = c->GPR2; // arg0
    a[2] = c->GPR3; // arg1
    a[3] = c->GPR4; // arg2

    intptr_t ret = 0;
    switch (a[0]) {
    case SYS_yield:
    yield();
    ret = 0;
    break;
    case SYS_exit:
    halt(c->GPRx);
    break;
    case SYS_open:
    ret = fs_open((const char *)a[1], (int)a[2], (int)a[3]);
    break;
    case SYS_read:
    ret = fs_read((int)a[1], (void *)(uintptr_t)a[2], (size_t)a[3]);
    break;
    case SYS_write:
    ret = fs_write((int)a[1], (const void *)(uintptr_t)a[2], (size_t)a[3]);
    break;
    case SYS_lseek:
    ret = fs_lseek((int)a[1], (off_t)a[2], (int)a[3]);
    break;
    case SYS_close:
    ret = fs_close((int)a[1]);
    break;
    case SYS_brk:
    ret = 0; // 这阶段:总是成功
    break;
    default:
    panic("Unhandled syscall ID = %d", a[0]);
    }
    c->GPRx = ret;
    }

    让loader使用文件并实现完整的文件系统

    (1)在loader中使用文件名来指定加载的程序

    static uintptr_t loader(PCB *pcb, const char *filename) {
    (void)pcb;

    int fd = fs_open(filename, 0, 0);

    Elf_Ehdr eh;
    fs_lseek(fd, 0, SEEK_SET);
    fs_read(fd, &eh, sizeof(eh));

    assert(eh.e_ident[0] == 0x7f && eh.e_ident[1] == 'E' &&
    eh.e_ident[2] == 'L' && eh.e_ident[3] == 'F');

    for (int i = 0; i < eh.e_phnum; i++) {
    Elf_Phdr ph;
    size_t ph_off = eh.e_phoff + (size_t)i * eh.e_phentsize;

    fs_lseek(fd, ph_off, SEEK_SET);
    fs_read(fd, &ph, sizeof(ph));

    if (ph.p_type != PT_LOAD)
    continue;

    if (ph.p_filesz > 0) {
    fs_lseek(fd, ph.p_offset, SEEK_SET);
    fs_read(fd, (void *)(uintptr_t)ph.p_vaddr, ph.p_filesz);
    }

    if (ph.p_memsz > ph.p_filesz) {
    memset((void *)((uintptr_t)ph.p_vaddr + ph.p_filesz), 0,
    ph.p_memsz ph.p_filesz);
    }
    }

    fs_close(fd);
    return (uintptr_t)eh.e_entry;
    }

    (2)init_proc()时传入文件名

    void init_proc() {
    switch_boot_pcb();

    Log("Initializing processes…");

    // load program here
    naive_uload(NULL, "/bin/file-test");
    }

    Context* schedule(Context *prev) {
    return NULL;
    }

    输出:

    虚拟文件系统

    读写函数指针+ramdisk的API实现一切皆文件

    // FD = File Descriptor; FB = Frame Buffer
    enum { FD_STDIN, FD_STDOUT, FD_STDERR, FD_FB };

    static Finfo file_table[] __attribute__((used)) = {
    [FD_STDIN] = {"stdin", 0, 0, 0, invalid_read, invalid_write},
    [FD_STDOUT] = {"stdout", 0, 0, 0, invalid_read, serial_write},
    [FD_STDERR] = {"stderr", 0, 0, 0, invalid_read, serial_write},
    #include "files.h"
    };

    size_t fs_read(int fd, void *buf, size_t len) {
    Finfo *f = &file_table[fd];

    // 设备文件:有 read 函数指针
    if (f->read != NULL) {
    size_t r = f->read(buf, f->open_offset, len);
    f->open_offset += r;
    return r;
    }

    // 普通文件(ramdisk)
    if (f->open_offset >= f->size)
    return 0;

    size_t remain = f->size f->open_offset;
    if (len > remain)
    len = remain;

    size_t disk_off = f->disk_offset + f->open_offset;
    size_t r = ramdisk_read(buf, disk_off, len);
    f->open_offset += r;
    return r;
    }

    size_t fs_write(int fd, const void *buf, size_t len) {
    Finfo *f = &file_table[fd];

    // 设备文件:有 write 函数指针
    if (f->write != NULL) {
    size_t w = f->write(buf, f->open_offset, len);
    f->open_offset += w;
    return w;
    }

    // 普通文件(ramdisk)
    if (f->open_offset >= f->size)
    return 0;

    size_t remain = f->size f->open_offset;
    if (len > remain)
    len = remain;

    size_t disk_off = f->disk_offset + f->open_offset;
    size_t w = ramdisk_write(buf, disk_off, len);
    f->open_offset += w;
    return w;
    }

    操作系统之上的IOE

    把串口抽象成文件

    size_t serial_write(const void *buf, size_t offset, size_t len) {
    const char *p = (const char *)buf;
    for (size_t i = 0; i < len; i++)
    putch(p[i]);
    return len;
    }

    实现gettimeofday

    在 Nanos-lite 中它通过 SYS_gettimeofday → AM_TIMER_UPTIME 实现,
    本质是把 AM 提供的时间读出来,包装成标准 Unix 接口

    // nanos-lite/src/syscall.c/do_syscall()
    case SYS_gettimeofday: {
    struct timeval *tv = (struct timeval *)(uintptr_t)a[1];
    AM_TIMER_UPTIME_T uptime;
    ioe_read(AM_TIMER_UPTIME, &uptime);
    if (tv) {
    tv->tv_sec = uptime.us / 1000000;
    tv->tv_usec = uptime.us % 1000000;
    }
    ret = 0;
    break;
    }

    // navy-apps/libs/libos/src/syscall.c
    int _gettimeofday(struct timeval *tv, struct timezone *tz) {
    return _syscall_(SYS_gettimeofday, (intptr_t)tv, (intptr_t)tz, 0);
    }

    // navy-apps/tests/timer-test/main.c
    #include <stdio.h>
    #include <sys/time.h>

    int main() {
    struct timeval last, now;
    gettimeofday(&last, NULL);
    while (1) {
    gettimeofday(&now, NULL);
    long diff_us = (now.tv_sec last.tv_sec) * 1000000L + (now.tv_usec last.tv_usec);
    if (diff_us >= 500000) { // 0.5 秒
    printf("0.5 second passed!\\n");
    last = now;
    }
    }
    return 0;
    }

    输出:

    实现NDL的时钟

    只需要在 NDL_GetTicks() 中调用 gettimeofday() 就行。

    // navy-apps/libs/libndl/NDL.c
    #include <sys/time.h>
    uint32_t NDL_GetTicks() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    uint64_t ms = (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
    return (uint32_t)ms;
    }

    // navy-apps/tests/timer-test/main.c
    #include <NDL.h>
    #include <stdio.h>

    int main() {
    NDL_Init(0);
    uint32_t last = NDL_GetTicks();
    while (1) {
    uint32_t now = NDL_GetTicks();
    if ((uint32_t)(now last) >= 500) { // 0.5s = 500ms,处理回绕用无符号差值
    printf("0.5 second passed!\\n");
    last = now;
    }
    }
    NDL_Quit();
    return 0;
    }

    输出照旧。

    把按键输入抽象成文件

    实现events_read()(在nanos-lite/src/device.c中定义)

    size_t events_read(void *buf, size_t offset, size_t len) {
    (void)offset; // 字符设备不需要 offset

    AM_INPUT_KEYBRD_T ev = io_read(AM_INPUT_KEYBRD);

    if (ev.keycode == AM_KEY_NONE)
    return 0;
    const char *name = keyname[ev.keycode];
    if (name == NULL)
    return 0;

    // kd / ku + 空格 + NAME + \\n
    int n = snprintf((char *)buf, len, "%s %s\\n", ev.keydown ? "kd" : "ku", name);
    if (n < 0)
    return 0;

    if ((size_t)n >= len)
    return len 1;
    return (size_t)n;
    }

    在VFS中添加对/dev/events的支持

    enum { FD_STDIN, FD_STDOUT, FD_STDERR, FD_EVENTS, FD_FB };

    static Finfo file_table[] __attribute__((used)) = {
    [FD_STDIN] = {"stdin", 0, 0, 0, invalid_read, invalid_write},
    [FD_STDOUT] = {"stdout", 0, 0, 0, invalid_read, serial_write},
    [FD_STDERR] = {"stderr", 0, 0, 0, invalid_read, serial_write},
    [FD_EVENTS] = {"/dev/events", 0, 0, 0, events_read, invalid_write},
    #include "files.h"
    };

    在NDL中实现NDL_PollEvent(), 从/dev/events中读出事件并写入到buf中.

    int NDL_Init(uint32_t flags) {
    if (getenv("NWM_APP")) {
    evtdev = 3;
    } else {
    // open 成功返回非负整数(文件句柄),失败返回-1
    evtdev = open("/dev/events", 0, 0);
    }
    return 0;
    }

    int NDL_PollEvent(char *buf, int len) {
    if (evtdev < 0)
    return 0;
    int n = read(evtdev, buf, len);
    return (n > 0) ? 1 : 0;
    }

    输出:

    分析:每句输出之间空一行是因为buff里自带\\n(一个事件以换行符\\n结束),而navy-apps/tests/event-test/main.c的printf又有\\n

    while (1) {
    char buf[64];
    if (NDL_PollEvent(buf, sizeof(buf))) {
    printf("receive event: %s\\n", buf);
    }
    }

    在NDL中获取屏幕大小

    (1)实现dispinfo_read()(我们认为这个文件不支持lseek, 可忽略offset,不过我这里考虑了多次连着读).

    size_t dispinfo_read(void *buf, size_t offset, size_t len) {
    AM_GPU_CONFIG_T cfg = io_read(AM_GPU_CONFIG);

    char info[64];
    int n = snprintf(info, sizeof(info), "WIDTH : %d\\nHEIGHT:%d\\n", cfg.width,
    cfg.height);
    if (n < 0)
    return 0;

    // 按“文件”语义裁剪
    size_t size = (size_t)n;
    if (offset >= size)
    return 0;
    if (len > size offset)
    len = size offset;

    memcpy(buf, info + offset, len);
    return len;
    }

    (2)在NDL中读出这个文件的内容, 从中解析出屏幕大小:NDL_Init时做

    int NDL_Init(uint32_t flags) {
    if (getenv("NWM_APP")) {
    evtdev = 3;
    } else {
    // open 成功返回非负整数(文件句柄),失败返回-1
    evtdev = open("/dev/events", 0, 0);
    }

    int fd = open("/proc/dispinfo", 0, 0);
    char buf[128] = {0};
    int n = read(fd, buf, sizeof(buf) 1);
    close(fd);

    // 按 README 的格式解析
    int w = 0, h = 0;
    sscanf(buf, "WIDTH : %d\\nHEIGHT:%d\\n", &w, &h);
    screen_w = w;
    screen_h = h;
    return 0;
    }

    (3)实现NDL_OpenCanvas()的功能:
    目前NDL_OpenCanvas()只需要记录画布的大小,具体参考下文。
    (4)不要忘了把 /proc/dispinfo 加进 file_table,这是把VGA文件化的前提

    // FD = File Descriptor; FB = Frame Buffer
    enum { FD_STDIN, FD_STDOUT, FD_STDERR, FD_EVENTS, FD_DISPINFO, FD_FB };

    /* This is the information about all files in disk. */
    static Finfo file_table[] __attribute__((used)) = {
    [FD_STDIN] = {"stdin", 0, 0, 0, invalid_read, invalid_write},
    [FD_STDOUT] = {"stdout", 0, 0, 0, invalid_read, serial_write},
    [FD_STDERR] = {"stderr", 0, 0, 0, invalid_read, serial_write},
    [FD_EVENTS] = {"/dev/events", 0, 0, 0, events_read, invalid_write},
    [FD_DISPINFO] = {"/proc/dispinfo", 0, 0, 0, dispinfo_read, invalid_write},
    #include "files.h"
    };

    把VGA显存抽象成文件

    (1)在init_fs()中对文件记录表中/dev/fb的大小进行初始化.

    void init_fs() {
    AM_GPU_CONFIG_T cfg = io_read(AM_GPU_CONFIG);
    file_table[FD_FB].size = cfg.width * cfg.height * 4;
    }

    (2)实现fb_write(), 用于把buf中的len字节写到屏幕上offset处.

    size_t fb_write(const void *buf, size_t offset, size_t len) {
    AM_GPU_CONFIG_T cfg = io_read(AM_GPU_CONFIG);
    size_t W = (size_t)cfg.width;

    // 像素对齐
    if (offset % 4 != 0)
    return 0;
    len = (len / 4) * 4; // 向下取整到4的倍数

    const uint8_t *p = (const uint8_t *)buf;
    size_t left = len;

    while (left > 0) {
    size_t pix = offset / 4;
    size_t x = pix % W;
    size_t y = pix / W;

    // 当前行还剩多少像素
    size_t row_left_pixels = W x;

    // 这次最多写多少字节(不跨行)
    size_t chunk = row_left_pixels * 4;
    if (chunk > left)
    chunk = left;

    int draw_w = (int)(chunk / 4);
    int draw_h = 1;
    //bool sync = (chunk == left); // 最后一块才sync
    bool sync = true; // 每次绘图后总是马上同步

    io_write(AM_GPU_FBDRAW, (int)x, (int)y, (void *)p, draw_w, draw_h, sync);

    p += chunk;
    offset += chunk;
    left -= chunk;
    }

    return len;
    }

    (3)在NDL中实现NDL_DrawRect(), 通过往/dev/fb中的正确位置写入像素信息来绘制图像.

    void NDL_OpenCanvas(int *w, int *h) {
    if (getenv("NWM_APP")) {
    int fbctl = 4;
    fbdev = 5;
    screen_w = *w; screen_h = *h;
    char buf[64];
    int len = sprintf(buf, "%d %d", screen_w, screen_h);
    // let NWM resize the window and create the frame buffer
    write(fbctl, buf, len);
    while (1) {
    // 3 = evtdev
    int nread = read(3, buf, sizeof(buf) 1);
    if (nread <= 0) continue;
    buf[nread] = '\\0';
    if (strcmp(buf, "mmap ok") == 0) break;
    }
    close(fbctl);
    }

    // 打开 /dev/fb(只做一次)
    if (fbdev < 0)
    fbdev = open("/dev/fb", 0, 0);

    // *w=*h=0 表示全屏
    if (*w == 0 && *h == 0) {
    *w = screen_w;
    *h = screen_h;
    }

    assert(*w <= screen_w && *h <= screen_h);

    // 居中贴(也可以改成 0,0 左上角贴)
    canvas_x = (screen_w *w) / 2;
    canvas_y = (screen_h *h) / 2;
    }

    // 向画布`(x, y)`坐标处绘制`w*h`的矩形图像, 并将该绘制区域同步到屏幕上
    // 图像像素按行优先方式存储在`pixels`中,
    // 每个像素用32位整数以`00RRGGBB`的方式描述颜色
    void NDL_DrawRect(uint32_t *pixels, int x, int y, int w, int h) {
    if (fbdev < 0)
    return;

    int sx = canvas_x + x;
    int sy = canvas_y + y;

    for (int row = 0; row < h; row++) {
    off_t off = ((off_t)(sy + row) * screen_w + sx) * 4;
    lseek(fbdev, off, SEEK_SET);
    write(fbdev, pixels + row * w, (size_t)w * 4);
    }
    }

    (4)把 /dev/fb 加进 file_table
    类似/proc/dispinfo

    输出:

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » PA3之简易文件系统
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!