简易文件系统
关键实现
要做的事情可以拆成三块:用户态(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
输出:

网硕互联帮助中心





评论前必须登录!
注册