实现完整的文件系统
完整文件系统只需要实现下面的几个函数:
打开文件
fs_open
:- 遍历文件表, 查找与
pathname
匹配的文件. - 如果找到匹配的文件, 初始化文件的打开偏移量为0, 并返回文件描述符.
- 如果未找到匹配的文件, 返回错误.
- 遍历文件表, 查找与
读取文件
fs_read
:- 检查文件描述符是否有效.
- 根据文件的读取函数指针调用相应的读取函数.
- 更新文件的打开偏移量.
写入文件
fs_write
:- 检查文件描述符是否有效.
- 根据文件的写入函数指针调用相应的写入函数.
- 更新文件的打开偏移量.
修改文件偏移量
fs_lseek
:- 检查文件描述符是否有效.
- 根据
whence
参数(SEEK_SET
,SEEK_CUR
,SEEK_END
)更新文件的打开偏移量.
关闭文件
fs_close
:- 检查文件描述符是否有效.
- 重置文件的打开偏移量.
打开关闭操作只需要进行检查与初始化.
读写普通文件可以直接复用ramdisk_xxx
函数, 如果是虚拟文件则调用专属的读写函数.
稍微复杂一点点的只有对文件偏移量的维护, 这里就需要仔细阅读文档, 知道偏移量在什么情况下会改变, 以及偏移量的作用.
部分代码
typedef struct {
size_t open_offset; // 文件的打开偏移量
} SEEKinfo; // 偏移量信息结构体
static SEEKinfo file_offset[ARRLEN(file_table)] = {0}; // 初始化文件偏移量数组
int fs_open(const char *pathname, int flags, int mode) {
Debug("%s", pathname); // 输出调试信息
for (int i = 0; i < ARRLEN(file_table); i++) { // 遍历文件表
if (strcmp(pathname, file_table[i].name) == 0) { // 查找匹配的文件
Debug("file fd:%s(%d, at %d) found. Openning...", pathname, i, (int)(file_table[i].disk_offset)); // 输出调试信息
file_offset[i].open_offset = 0; // 初始化文件的打开偏移量
return i; // 返回文件描述符
}
}
panic("file:%s not found", pathname); // 文件未找到, 抛出错误
return -1; // 返回错误码
}
size_t fs_read(int fd, void *buf, size_t len) {
Assert(buf != NULL, "File operation error: read the NULL"); // 检查缓冲区是否为空
Assert(fd < ARRLEN(file_table), "file fd:%d not found", fd); // 检查文件描述符是否有效
Debug("file fd:%d found. Reading (%d)...", fd, len); // 输出调试信息
size_t read_len = 0; // 初始化读取长度
if (file_offset[fd].open_offset >= file_table[fd].size && fd >= FD_END) {return 0;} // 检查是否超出文件大小
if(file_table[fd].read != NULL) { // 如果文件有读取函数
read_len = file_table[fd].read(buf, file_table[fd].disk_offset + file_offset[fd].open_offset, len); // 调用读取函数
} else { // 否则使用默认读取函数
read_len = ramdisk_read(buf, file_table[fd].disk_offset + file_offset[fd].open_offset, len); // 调用默认读取函数
file_offset[fd].open_offset += read_len; // 更新文件的打开偏移量
}
return read_len; // 返回读取长度
}
size_t fs_write(int fd, const void *buf, size_t len) {
Assert(fd < ARRLEN(file_table), "file fd:%d not found", fd); // 检查文件描述符是否有效
Debug("file fd:%d found. Writing (%d)...", fd, len); // 输出调试信息
size_t write_len = 0; // 初始化写入长度
if (file_offset[fd].open_offset >= file_table[fd].size && fd >= FD_END) {assert(0); return 0;} // 检查是否超出文件大小
if(file_table[fd].write != NULL) { // 如果文件有写入函数
write_len = file_table[fd].write(buf, file_table[fd].disk_offset + file_offset[fd].open_offset, len); // 调用写入函数
} else { // 否则使用默认写入函数
write_len = ramdisk_write(buf, file_table[fd].disk_offset + file_offset[fd].open_offset, len); // 调用默认写入函数
file_offset[fd].open_offset += write_len; // 更新文件的打开偏移量
}
return write_len; // 返回写入长度
}
size_t fs_lseek(int fd, size_t offset, int whence) {
Assert(fd < ARRLEN(file_table), "file fd:%d not found", fd); // 检查文件描述符是否有效
Debug("file fd:%d found. Seting...", fd); // 输出调试信息
switch (whence) { // 根据 whence 参数更新文件的打开偏移量
case SEEK_SET: file_offset[fd].open_offset = offset; break; // 从文件开头定位
case SEEK_CUR: file_offset[fd].open_offset += offset; break; // 从当前位置定位
case SEEK_END: file_offset[fd].open_offset = file_table[fd].size + offset; break; // 从文件末尾定位
default: break;
}
Debug("Offset:%d", (int)(file_offset[fd].open_offset)); // 输出调试信息
return file_offset[fd].open_offset; // 返回新的文件偏移量
}
int fs_close(int fd) {
Assert(fd < ARRLEN(file_table), "file fd:%d not found", fd); // 检查文件描述符是否有效
Debug("file fd:%d found. Closing...", fd); // 输出调试信息
file_offset[fd].open_offset = 0; // 重置文件的打开偏移量
return 0; // 返回成功
}
把串口抽象成文件
文档中已经提到了, 我们只需要提供相应的接口, 就能使文件系统能操控设备, 在框架代码中则是一个特殊的读写函数, 被保存在device.c
中.
实现下面的函数后, 我们只需要让文件系统写入时, 不使用ramdisk_write
, 改为serial_write
即可.
size_t serial_write(const void *buf, size_t offset, size_t len) {
int i = 0;
while (i != len) {
putch(((char *)buf)[i++]);
}
return len;
}
上文已经实现了自动调用虚拟文件接口的功能了.
我们只需要将其写入file_table
中即可:
static Finfo file_table[] __attribute__((used)) = {
[FD_STDIN ] = {"stdin", 0, 0, invalid_read, invalid_write },
[FD_STDOUT ] = {"stdout", 0, 0, invalid_read, serial_write },
[FD_STDERR ] = {"stderr", 0, 0, invalid_read, serial_write },
#include "files.h"
};
实现gettimeofday
使用io_read
读取AM_TIMER_UPTIME
寄存器获得时间, 按照手册的描述存入struct timeval *tv, struct timezone *tz
.
int mygettimeofday(struct timeval *tv, struct timezone *tz) {
AM_TIMER_UPTIME_T t = io_read(AM_TIMER_UPTIME);
if (tv) {
tv->tv_usec = t.us % (1000 * 1000);
tv->tv_sec = t.us / (1000 * 1000);
}
if (tz) {
tz->tz_minuteswest = -480;
tz->tz_dsttime = DST_NONE;
}
return 0;
}
实现NDL的时钟
PA3噩梦开始, 写不完的lib
static uint64_t NDL_start_time = 0;
static struct timeval tv;
static struct timezone tz;
uint32_t NDL_GetTicks() {
gettimeofday(&tv, &tz);
return tv.tv_usec / 1000 + tv.tv_sec * 1000 - NDL_start_time;
}
把按键输入抽象成文件
规定按键events的格式为:
k[d/u] [$keyname]\n
k
表示按键事件, d
代表按下, u
代表抬起, keyname
是按键名.
提示
如果你想追求效率, 可以不用字符表示, 直接使用二进制编码.
使用字符只是方便调试.
我们从寄存器读取状态后, 即可写到指针对应的地址内:
size_t events_read(void *buf, size_t offset, size_t len) {
AM_INPUT_KEYBRD_T key = io_read(AM_INPUT_KEYBRD);
if (key.keycode == AM_KEY_NONE) {
return -1;
}
return snprintf(buf, len, "k%c %s\n", key.keydown ? 'd' : 'u', keyname[key.keycode]);
}
完成后添加新的虚拟文件:
static Finfo file_table[] __attribute__((used)) = {
[FD_STDIN ] = { "stdin", 0, 0, invalid_read, invalid_write },
[FD_STDOUT ] = { "stdout", 0, 0, invalid_read, serial_write },
[FD_STDERR ] = { "stderr", 0, 0, invalid_read, serial_write },
[FD_EVENTS ] = { "/dev/events", 0, 0, events_read, invalid_write }, // new
#include "files.h"
};
VGA
在NDL中获取屏幕大小
与按键输入基本一直, 自己定义传输的信息格式, 然后完成对应的读取函数.
size_t dispinfo_read(void *buf, size_t offset, size_t len) {
AM_GPU_CONFIG_T gpu = io_read(AM_GPU_CONFIG);
return snprintf(buf, len, "WIDTH : %d\nHEIGHT : %d\n", gpu.width, gpu.height);
}
提示
记得向file_table
添加新的文件
把VGA显存抽象成文件
fb_write
实现
首先明确一点, fb
实际上是am
中是一块连续的内存空间, 映射到二位空间组成图像, 也就是说想要绘制矩形空间, 就得“一行一行”的写入, 换行时得重新计算偏移量, 那么我们可以规定: fb只支持单行写入, width、offset与len必须满足条件: (offset / sizeof(uint32_t) % width + len / sizeof(uint32_t)) < width
.
size_t fb_write(const void *buf, size_t offset, size_t len) {
AM_GPU_CONFIG_T cfg = io_read(AM_GPU_CONFIG);
int x = offset / sizeof(uint32_t) % cfg.width;
int y = offset / sizeof(uint32_t) / cfg.width;
assert(x + len/sizeof(uint32_t) < cfg.width);
io_write(AM_GPU_FBDRAW, x, y, (void*)buf, len/sizeof(uint32_t), 1, true);
return len;
}
提示
记得向file_table
添加新的文件
NDL_DrawRect
& 居中显示
编程题, 直接贴代码
static int screen_w = 0, screen_h = 0;
static int cave_w = 0, cave_h = 0;
void NDL_OpenCanvas(int *w, int *h) {
if (*w == 0 || *h == 0) {
*w = screen_w;
*h = screen_h;
}
cave_h = *h;
cave_w = *w;
...
}
void NDL_DrawRect(uint32_t *pixels, int x, int y, int w, int h) {
x = (screen_w - cave_w) / 2 + x;
y = (screen_h - cave_h) / 2 + y;
int fd = open("/dev/fb", 0);
for (int i = 0; i < h; i ++) {
lseek(fd, (x + (y + i) * screen_w) * 4, SEEK_SET);
write(fd, &(pixels[w * i]), w * 4);
}
close(fd);
}
实现更多的fixedptc API
给ai秒解
fixedpt
类型乘除int
类型
这种类型很简单, 因为本身就是定点运算, 乘除整数直接乘除即可.
static inline fixedpt fixedpt_muli(fixedpt A, int B) {
return (A * B);
}
static inline fixedpt fixedpt_divi(fixedpt A, int B) {
return (A / B);
}
fixedpt
类型之间乘除
我们可以先假设被乘/除数的小数部分都是0, 也就是没有小数部分.
那么与上方fixedpt
类型乘除int
类型相比, 仅仅只是被乘/除数位移了8位, 考虑小数部分的话实际也是成立的, 那么我们只需要在乘除运算中插入位移保证“小数点”位置就行.
提示
乘除法位移的先后不同为了保证精度不丢失
// 先乘后位移
static inline fixedpt fixedpt_mul(fixedpt A, fixedpt B) {
return ((fixedpt)(((fixedptd)A * (fixedptd)B) >> FIXEDPT_FBITS));
}
// 先位移后除
static inline fixedpt fixedpt_div(fixedpt A, fixedpt B) {
return ((fixedpt)(((fixedptd)A << FIXEDPT_FBITS) / (fixedptd)B));
}
其他运算
- abs:
fixedpt
本身就是int
类型, 可以直接使用比较判断, 然后进行反转 - floor: 向下取整, 直接清零小数部分即可
- ceil: 判断小数部分是否为零, 不是的话清零小数部分后加1
static inline fixedpt fixedpt_abs(fixedpt A) {
return (A < 0 ? -A : A);
}
static inline fixedpt fixedpt_floor(fixedpt A) {
return (A & ~FIXEDPT_FMASK);
}
static inline fixedpt fixedpt_ceil(fixedpt A) {
return ((A & FIXEDPT_FMASK) ? ((A + FIXEDPT_ONE) & ~FIXEDPT_FMASK) : A);
}
miniSDL
直接贴代码了, 没修改的代码省略, 有时间补上解析, 标记一下先
// TODO: Documentation to be improved
// navy-apps/libs/libminiSDL/src/video.c
void SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) {
assert(dst && src);
assert(dst->format->BitsPerPixel == src->format->BitsPerPixel);
SDL_Rect dst_r = { .x = 0, .y = 0, .w = dst->w, .h = dst->h },
src_r = { .x = 0, .y = 0, .w = src->w, .h = src->h };
if (srcrect) {
src_r = *srcrect;
}
if (dstrect) {
dst_r = *dstrect;
}
switch (dst->format->BitsPerPixel) {
case 32:
for (uint32_t i = 0; i < src_r.h; i ++) {
for (uint32_t j = 0; j < src_r.w; j ++) {
((uint32_t *)dst->pixels)[(i + dst_r.y) * dst->w + j + dst_r.x] =
((uint32_t *)src->pixels)[(i + src_r.y) * src->w + j + src_r.x];
}
}
break;
case 8:
for (uint32_t i = 0; i < src_r.h; i ++) {
for (uint32_t j = 0; j < src_r.w; j ++) {
dst->pixels[(i + dst_r.y) * dst->w + j + dst_r.x] =
src->pixels[(i + src_r.y) * src->w + j + src_r.x];
}
}
break;
default: break;
}
}
void SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, uint32_t color) {
SDL_Rect dst_r = { .x = 0, .y = 0, .w = dst->w, .h = dst->h };
if (dstrect) {
dst_r = *dstrect;
}
switch (dst->format->BitsPerPixel) {
case 32:
for (int i = 0; i < dst_r.h; i ++) {
for (int j = 0; j < dst_r.w; j ++) {
((uint32_t *)dst->pixels)[(i + dst_r.y) * dst->w + j + dst_r.x] = color;
}
}
break;
case 8:
for (int i = 0; i < dst_r.h; i ++) {
for (int j = 0; j < dst_r.w; j ++) {
dst->pixels[(i + dst_r.y) * dst->w + j + dst_r.x] = color;
}
}
break;
default: break;
}
}
void SDL_UpdateRect(SDL_Surface *s, int x, int y, int w, int h) {
if (w == 0 || h == 0) {
w = s->w;
h = s->h;
}
uint32_t local_pixels[WINDOW_W * WINDOW_H];
switch (s->format->BitsPerPixel) {
case 32: NDL_DrawRect((uint32_t *)(s->pixels), x, y, w, h); break;
case 8:
for (int i = 0; i < h; i ++) {
for (int j = 0; j < w; j ++) {
local_pixels[i * w + j] = s->format->palette->colors[s->pixels[(i + y) * s->w + j + x]].val;
}
}
NDL_DrawRect(local_pixels, x, y, w, h);
break;
default: break;
}
}
// navy-apps/libs/libminiSDL/src/timer.c
uint32_t SDL_GetTicks() {
return NDL_GetTicks();
}
// navy-apps/libs/libminiSDL/src/event.c
#define KEYNUM sizeof(keyname) / sizeof(keyname[0])
uint8_t key_status[KEYNUM] = {0};
int SDL_PollEvent(SDL_Event *ev) {
char buf[64];
if (NDL_PollEvent(buf, sizeof(buf))) {
if (buf[0] == 'k') {
char key_type;
char key_buf[32];
assert(sscanf(buf, "k%c %s", &key_type, key_buf) == 2);
switch (key_type) {
case 'u': ev->type = SDL_KEYUP; break;
case 'd': ev->type = SDL_KEYDOWN; break;
default: assert(0); break;
}
for (int i = 1; i < KEYNUM; i ++) {
if (strcmp(keyname[i], key_buf) == 0) {
ev->key.keysym.sym = i;
key_status[i] = !ev->type;
return 1;
}
}
}
}
}
int SDL_WaitEvent(SDL_Event *ev) {
while (true) {
if (SDL_PollEvent(ev)) {
return 1;
}
}
return 0;
}
uint8_t* SDL_GetKeyState(int *numkeys) {
return key_status;
}
// navy-apps/libs/libSDL_image/src/image.c
SDL_Surface* IMG_Load(const char *filename) {
FILE* fp = fopen(filename, "r");
assert(fp);
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *img_mem = SDL_malloc(size * sizeof(char));
assert(img_mem);
fread(img_mem, size, 1, fp);
SDL_Surface * IMG = STBIMG_LoadFromMemory(img_mem, size);
SDL_free(img_mem);
fclose(fp);
assert(IMG);
return IMG;
}