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

PA3之精彩纷呈的应用程序


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

  • 定点算术

    通过整数运算指令来实现实数的逻辑, 而无需在硬件上引入FPU。
    fixedpt的类型本质是int32_t(量化误差 ≤ 1/256)
    A = a * 2^8

    神奇的fixedpt_rconst

    #define fixedpt_rconst(R) ((fixedpt)((R) * FIXEDPT_ONE + ((R) >= 0 ? 0.5 : 0.5)))

    利用编译期常量,编译器会在编译期把 ®*256 ± 0.5 算成一个整数常量,最后生成的目标代码里就是“加载立即数”,运行时不会出现任何浮点指令

    实现更多的fixedptc API

    /* Multiplies a fixedpt number with an integer, returns the result. */
    static inline fixedpt fixedpt_muli(fixedpt A, int B) {
    return (fixedpt)((int64_t)A * (int64_t)B);
    }

    /* Divides a fixedpt number with an integer, returns the result. */
    static inline fixedpt fixedpt_divi(fixedpt A, int B) {
    // 这里不做 B==0 的 UB 防护
    return (fixedpt)((int64_t)A / (int64_t)B);
    }

    /* Multiplies two fixedpt numbers, returns the result. */
    static inline fixedpt fixedpt_mul(fixedpt A, fixedpt B) {
    return (fixedpt)(((int64_t)A * (int64_t)B) >> FIXEDPT_FBITS);
    }

    /* Divides two fixedpt numbers, returns the result. */
    static inline fixedpt fixedpt_div(fixedpt A, fixedpt B) {
    return (fixedpt)(((int64_t)A << FIXEDPT_FBITS) / (int64_t)B);
    }

    static inline fixedpt fixedpt_abs(fixedpt A) {
    if (A == INT32_MIN)
    return INT32_MAX;
    return (A < 0) ? A : A;
    }

    // greatest integral value <= x
    static inline fixedpt fixedpt_floor(fixedpt A) {
    const int32_t one = (int32_t)FIXEDPT_ONE;
    int32_t ip = (int32_t)(A / one); // toward zero
    int32_t r = (int32_t)(A % one); // same sign as A

    if (r == 0)
    return (fixedpt)(ip * one);
    if (A < 0)
    return (fixedpt)((ip 1) * one); // 负数且有小数:floor 要更负
    return (fixedpt)(ip * one); // 正数:直接截断就是 floor
    }

    // least integral value >= x
    static inline fixedpt fixedpt_ceil(fixedpt A) {
    const int32_t one = (int32_t)FIXEDPT_ONE;
    int32_t ip = (int32_t)(A / one); // toward zero
    int32_t r = (int32_t)(A % one);

    if (r == 0)
    return (fixedpt)(ip * one);
    if (A > 0)
    return (fixedpt)((ip + 1) * one); // 正数且有小数:ceil 要更大
    return (fixedpt)(ip * one); // 负数:toward zero 就是 ceil
    }

    Navy作为基础设施


    未解决bug:在main打断点,结果还是不断输出Maximum number of clients reached,然后就是WSL直接卡死。

    Navy中的应用程序

    NSlider (NJU Slider)

    转换报错

    ⟩ bash convert.sh
    convert-im6.q16: attempt to perform an operation not allowed by the security policy `PDF' @ error/constitute.c/IsCoderAuthorized/426.
    convert-im6.q16: no images defined `slides.bmp' @ error/convert.c/ConvertImageCommand/3229.
    mv: cannot stat '*.bmp': No such file or directory

    在ai帮助下修改了navy-apps/apps/nslider/slides/convert.sh

    #!/bin/bash

    # convert slides.pdf \\
    # -sharpen "0x1.0" \\
    # -type truecolor -resize 400×300\\! slides.bmp

    # mkdir -p $NAVY_HOME/fsimg/share/slides/
    # rm $NAVY_HOME/fsimg/share/slides/*
    # mv *.bmp $NAVY_HOME/fsimg/share/slides/
    pdftoppm -png -r 150 slides.pdf slides
    i=0
    for f in slides-*.png; do
    convert "$f" -resize 400×300\\! "slides-$i.bmp"
    i=$((i+1))
    done
    rm -f slides-*.png
    mkdir -p "$NAVY_HOME/fsimg/share/slides/"
    rm -f "$NAVY_HOME/fsimg/share/slides/"*
    mv slides-*.bmp "$NAVY_HOME/fsimg/share/slides/"

    SDL_BlitSurface():把 src 这张“图”(surface)里的像素拷贝一块,到 dst 这张“图”里的某个位置去。
    SDL_UpdateRect:刷新

    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);
    assert(dst->format->BitsPerPixel == 32); // 先只支持 32bpp

    int sx = 0, sy = 0, w = src->w, h = src->h;
    if (srcrect) {
    sx = srcrect->x;
    sy = srcrect->y;
    w = srcrect->w;
    h = srcrect->h;
    }

    int dx = 0, dy = 0;
    if (dstrect) {
    dx = dstrect->x;
    dy = dstrect->y;
    }

    if (sx < 0) {
    w += sx;
    dx -= sx;
    sx = 0;
    }
    if (sy < 0) {
    h += sy;
    dy -= sy;
    sy = 0;
    }
    if (dx < 0) {
    w += dx;
    sx -= dx;
    dx = 0;
    }
    if (dy < 0) {
    h += dy;
    sy -= dy;
    dy = 0;
    }

    if (sx + w > src->w)
    w = src->w sx;
    if (sy + h > src->h)
    h = src->h sy;
    if (dx + w > dst->w)
    w = dst->w dx;
    if (dy + h > dst->h)
    h = dst->h dy;
    if (w <= 0 || h <= 0)
    return;

    int bpp = src->format->BytesPerPixel; // 32bpp -> 4
    for (int row = 0; row < h; row++) {
    uint8_t *sp = (uint8_t *)src->pixels + (sy + row) * src->pitch + sx * bpp;
    uint8_t *dp = (uint8_t *)dst->pixels + (dy + row) * dst->pitch + dx * bpp;
    memcpy(dp, sp, (size_t)w * bpp);
    }

    if (dstrect) {
    dstrect->x = dx;
    dstrect->y = dy;
    dstrect->w = w;
    dstrect->h = h;
    }
    }

    void SDL_UpdateRect(SDL_Surface *s, int x, int y, int w, int h) {
    assert(s && s->format->BitsPerPixel == 32);

    if (w == 0 && h == 0) {
    x = 0;
    y = 0;
    w = s->w;
    h = s->h;
    }

    if (x < 0) {
    w += x;
    x = 0;
    }
    if (y < 0) {
    h += y;
    y = 0;
    }
    if (x + w > s->w)
    w = s->w x;
    if (y + h > s->h)
    h = s->h y;
    if (w <= 0 || h <= 0)
    return;

    const int bpp = s->format->BytesPerPixel; // 4 bytes per pixel
    uint32_t *p = (uint32_t *)((uint8_t *)s->pixels + y * s->pitch + x * bpp);
    NDL_DrawRect(p, x, y, w, h);
    }

    // navy-apps/libs/libminiSDL/src/event.c
    static uint8_t lookup_key(const char *name) {
    for (int i = 0; i < (int)(sizeof(keyname) / sizeof(keyname[0])); i++) {
    if (strcmp(keyname[i], name) == 0)
    return (uint8_t)i; // i == SDLK_*
    }
    return (uint8_t)SDLK_NONE;
    }

    int SDL_WaitEvent(SDL_Event *event) {
    assert(event);

    char buf[64];
    while (1) {
    if (!NDL_PollEvent(buf, sizeof(buf))) {
    continue;
    }

    // "kd XXX\\n" or "ku XXX\\n"
    if (buf[0] == 'k' && (buf[1] == 'd' || buf[1] == 'u') && buf[2] == ' ') {
    int is_down = (buf[1] == 'd');

    char name[32];
    int n = 0;
    for (int i = 3; buf[i] && buf[i] != '\\n' && n < (int)sizeof(name) 1;
    i++) {
    name[n++] = buf[i];
    }
    name[n] = '\\0';

    event->key.type = is_down ? SDL_KEYDOWN : SDL_KEYUP;
    event->key.keysym.sym = lookup_key(name);
    return 1;
    }
    }
    }

    效果:

    j下一页,k上一页,数字+翻页(翻多页),gg回到第一页。均正常。
    注意:navy-apps/apps/nslider/src/main.cpp的const int N要等于pdf的页数,笔者一开始设置为1然后换多页pdf后忘记改了,怎么都实现不了翻页(下/上一页是自己)

    MENU (开机菜单)

    void SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, uint32_t color) {
    assert(dst);
    assert(dst->format);
    assert(dst->format->BitsPerPixel == 32);
    assert(dst->format->BytesPerPixel == 4);

    int x = 0, y = 0, w = dst->w, h = dst->h;
    if (dstrect) {
    x = dstrect->x;
    y = dstrect->y;
    w = dstrect->w;
    h = dstrect->h;
    }

    // 裁剪到 dst 范围内
    if (x < 0) {
    w += x;
    x = 0;
    }
    if (y < 0) {
    h += y;
    y = 0;
    }
    if (x + w > dst->w)
    w = dst->w x;
    if (y + h > dst->h)
    h = dst->h y;
    if (w <= 0 || h <= 0)
    return;

    // 按行填充
    uint8_t *rowp = (uint8_t *)dst->pixels + y * dst->pitch + x * 4;
    for (int row = 0; row < h; row++) {
    uint32_t *p = (uint32_t *)rowp;
    for (int col = 0; col < w; col++) {
    p[col] = color;
    }
    rowp += dst->pitch;
    }

    if (dstrect) {
    dstrect->x = x;
    dstrect->y = y;
    dstrect->w = w;
    dstrect->h = h;
    }
    }

    NTerm (NJU Terminal)

    SDL_GetTicks = 从 SDL_Init 开始算的相对时间(ms)

    // navy-apps/libs/libminiSDL/src/general.c
    uint32_t sdl_start_ticks;
    int SDL_Init(uint32_t flags) {
    int ret = NDL_Init(flags);
    sdl_start_ticks = NDL_GetTicks();
    return ret;
    }
    // navy-apps/libs/libminiSDL/src/timer.c
    extern uint32_t sdl_start_ticks;
    uint32_t SDL_GetTicks() {
    return NDL_GetTicks() sdl_start_ticks;
    }

    // 类似 SDL_WaitEvent,但非阻塞
    int SDL_PollEvent(SDL_Event *ev) {
    if (ev == NULL)
    return 0;

    char buf[64];
    if (!NDL_PollEvent(buf, sizeof(buf))) {
    return 0; // 没事件:立即返回
    }

    // "kd XXX\\n" or "ku XXX\\n"
    if (buf[0] == 'k' && (buf[1] == 'd' || buf[1] == 'u') && buf[2] == ' ') {
    int is_down = (buf[1] == 'd');

    char name[32];
    int n = 0;
    for (int i = 3; buf[i] && buf[i] != '\\n' && n < (int)sizeof(name) 1;
    i++) {
    name[n++] = buf[i];
    }
    name[n] = '\\0';

    ev->key.type = is_down ? SDL_KEYDOWN : SDL_KEYUP;
    ev->key.keysym.sym = lookup_key(name);
    return 1;
    }

    return 0;
    }

    Flappy Bird

    SDL_Surface* IMG_Load(const char *filename) {
    if (filename == NULL)
    return NULL;

    int fd = open(filename, O_RDONLY);
    if (fd < 0)
    return NULL;

    off_t size = lseek(fd, 0, SEEK_END);
    if (size <= 0) {
    close(fd);
    return NULL;
    }
    lseek(fd, 0, SEEK_SET);

    uint8_t *buf = (uint8_t *)SDL_malloc((size_t)size);
    if (buf == NULL) {
    close(fd);
    return NULL;
    }

    size_t off = 0;
    while (off < (size_t)size) {
    ssize_t n = read(fd, buf + off, (size_t)size off);
    if (n <= 0) { // 读失败或 EOF
    SDL_free(buf);
    close(fd);
    return NULL;
    }
    off += (size_t)n;
    }

    close(fd);

    SDL_Surface *surf = STBIMG_LoadFromMemory(buf, (int)size);

    SDL_free(buf);
    return surf;
    }

    PAL (仙剑奇侠传)

    data下载(网上找到的链接):https://box.nju.edu.cn/f/73c08ca0a5164a94aaba/?login=from_csdn

    好难,跳过。

    am-kernels

    通过Navy的运行时环境实现AM的API
    实际上就是外面是AM ioe的壳,里面模仿之前SDL调NDL的做法。
    一点思考
    Q:nanos-lite就是运行在AM上的,比如sys_exit就是用的halt,serial_write里面用的是putch。现在实现libam那函数名岂不是重复了?
    A:AM 应用(coremark/typing-game)是一个“用户态程序”,链接的是 Navy 的用户态库(libc、libndl、libminiSDL、libam…),它不会把 nanos-lite 或内核侧的 TRM/IOE 实现一起链接进来。
    两者的一些区别:内核那边(nanos-lite)使用的AM的 trm(putch/halt)还有ioe是与硬件强挂钩的,是store、ebreak、MMIO;而libam作为一个用户态库,libam与NDL的关系可以类似SDL与NDL,作用是为应用程序统一接口。

    #include <NDL.h>
    #include <am.h>
    #include <klib-macros.h>

    void __am_timer_init();
    void __am_gpu_init();
    void __am_audio_init();
    void __am_input_keybrd(AM_INPUT_KEYBRD_T *);
    void __am_timer_rtc(AM_TIMER_RTC_T *);
    void __am_timer_uptime(AM_TIMER_UPTIME_T *);
    void __am_gpu_config(AM_GPU_CONFIG_T *);
    void __am_gpu_status(AM_GPU_STATUS_T *);
    void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *);

    static void __am_timer_config(AM_TIMER_CONFIG_T *cfg) {
    cfg->present = true;
    cfg->has_rtc = true;
    }
    static void __am_input_config(AM_INPUT_CONFIG_T *cfg) { cfg->present = true; }
    static void __am_uart_config(AM_UART_CONFIG_T *cfg) { cfg->present = false; }
    static void __am_net_config(AM_NET_CONFIG_T *cfg) { cfg->present = false; }

    typedef void (*handler_t)(void *buf);
    static void *lut[128] = {
    [AM_TIMER_CONFIG] = __am_timer_config,
    [AM_TIMER_RTC] = __am_timer_rtc,
    [AM_TIMER_UPTIME] = __am_timer_uptime,
    [AM_INPUT_CONFIG] = __am_input_config,
    [AM_INPUT_KEYBRD] = __am_input_keybrd,
    [AM_GPU_CONFIG] = __am_gpu_config,
    [AM_GPU_FBDRAW] = __am_gpu_fbdraw,
    [AM_GPU_STATUS] = __am_gpu_status,
    [AM_UART_CONFIG] = __am_uart_config,
    [AM_NET_CONFIG] = __am_net_config,
    };

    static void fail(void *buf) { panic("labam access nonexist register"); }

    bool ioe_init() {
    for (int i = 0; i < LENGTH(lut); i++)
    if (!lut[i])
    lut[i] = fail;
    NDL_Init(0);
    __am_gpu_init();
    __am_timer_init();
    return true;
    }

    void ioe_read(int reg, void *buf) { ((handler_t)lut[reg])(buf); }
    void ioe_write(int reg, void *buf) { ((handler_t)lut[reg])(buf); }

    #include <am.h>
    #include "NDL.h"

    void __am_timer_init() {}

    void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
    // AM_TIMER_UPTIME_T: uint64_t us;
    uint32_t ms = NDL_GetTicks();
    uptime->us = (uint64_t)ms * 1000;
    return;
    }

    void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
    rtc->second = 0;
    rtc->minute = 0;
    rtc->hour = 0;
    rtc->day = 0;
    rtc->month = 0;
    rtc->year = 1900;
    }

    #include "amdev.h"
    #include <NDL.h>
    #include <am.h>
    #include <string.h>

    #define KEYNAME(k) #k,
    static const char *am_keyname[] = {
    "NONE",
    AM_KEYS(KEYNAME) // 依赖 am.h 里的 _KEYS
    };

    static uint32_t lookup_am_key(const char *name) {
    for (int i = 0; i < (int)(sizeof(am_keyname) / sizeof(am_keyname[0])); i++) {
    if (strcmp(am_keyname[i], name) == 0)
    return (uint32_t)i; // i == AM_KEY_*
    }
    return AM_KEY_NONE;
    }

    void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
    char buf[64];
    if (!NDL_PollEvent(buf, sizeof(buf))) {
    return; // 没事件:立即返回
    }

    // "kd XXX\\n" or "ku XXX\\n"
    if (buf[0] == 'k' && (buf[1] == 'd' || buf[1] == 'u') && buf[2] == ' ') {
    int is_down = (buf[1] == 'd');

    char name[32];
    int n = 0;
    for (int i = 3; buf[i] && buf[i] != '\\n' && n < (int)sizeof(name) 1;
    i++) {
    name[n++] = buf[i];
    }
    name[n] = '\\0';

    kbd->keydown = is_down;
    kbd->keycode = lookup_am_key(name);
    }
    return;
    }

    #include <NDL.h>
    #include <am.h>
    #include <stddef.h>
    #include <stdint.h>

    static int g_w = 0;
    static int g_h = 0;
    static int inited = 0;

    void __am_gpu_init() {
    if (inited)
    return;
    inited = 1;

    NDL_Init(0);

    // 传 0,0 表示全屏,让 NDL 用 /proc/dispinfo 的 screen_w/screen_h
    int w = 0, h = 0;
    NDL_OpenCanvas(&w, &h);
    g_w = w;
    g_h = h;
    }

    void __am_gpu_config(AM_GPU_CONFIG_T *cfg) {
    if (!inited)
    __am_gpu_init();

    *cfg = (AM_GPU_CONFIG_T){
    .present = true,
    .has_accel = false,
    .width = (uint32_t)g_w,
    .height = (uint32_t)g_h,
    .vmemsz = (uint32_t)g_w * (uint32_t)g_h * sizeof(uint32_t),
    };
    }

    void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
    if (!inited)
    __am_gpu_init();

    if (ctl == NULL)
    return;
    if (ctl->pixels == NULL || ctl->w <= 0 || ctl->h <= 0)
    return;

    NDL_DrawRect((uint32_t *)ctl->pixels, ctl->x, ctl->y, ctl->w, ctl->h);

    }

    void __am_gpu_status(AM_GPU_STATUS_T *status) { status->ready = true; }

    #include <am.h>
    #include <unistd.h>

    Area heap;

    void putch(char ch) { write(1, &ch, 1); }

    void halt(int code) { _exit(code); }

    am-coremark

    typing-game有bug:按键盘卡住,但Esc能正常退出,不管了

    此处跳过亿部分

    展示你的批处理系统

    通过开机菜单

    int _execve(const char *fname, char *const argv[], char *const envp[]) {
    return _syscall_(SYS_execve, (intptr_t)fname, (intptr_t)argv, (intptr_t)envp);
    }

    // void do_syscall(Context *c)
    case SYS_exit:
    naive_uload(NULL, "/bin/menu");
    panic("Should not reach here");

    case SYS_execve: {
    const char *fname = (const char *)(uintptr_t)a[1];
    // argv = (char * const *)a[2]; envp = (char * const *)a[3]; 先不管
    if (fname == NULL) {
    ret = 1;
    break;
    }
    naive_uload(NULL, fname);
    panic("Should not reach here");
    }

    通过NTerm

    在NTerm的內建Shell中实现命令解析:

    static void sh_handle_cmd(const char *cmd) {
    if (cmd == NULL)
    return;
    while (*cmd == ' ' || *cmd == '\\t')
    cmd++;
    if (*cmd == '\\0')
    return;

    char name[64];
    int n = 0;
    while (cmd[n] && cmd[n] != ' ' && cmd[n] != '\\t' && cmd[n] != '\\n' &&
    cmd[n] != '\\r' && n < (int)sizeof(name) 1) {
    name[n] = cmd[n];
    n++;
    }
    name[n] = '\\0';

    if (name[0] == '\\0')
    return;

    // 内建命令 help
    if (strcmp(name, "help") == 0) {
    sh_printf("Commands:\\n");
    sh_printf(" <prog> run /bin/<prog> (args ignored)\\n");
    sh_printf(" help show this help\\n");
    sh_printf(" clear clear screen\\n");
    return;
    }
    // 内建命令 clear
    if (strcmp(name, "clear") == 0) {
    term->clear();
    return;
    }

    execve(name, NULL, NULL);
    }

    为NTerm中的內建Shell添加环境变量的支持:

    setenv("PATH", "/bin", 0);
    execvp(name, argv);

    为bird添加了键入Esc退出游戏的判断:

    navyapps/apps/bird/repo/src/BirdGame.cpp/UpdateEvents()
    case SDL_KEYDOWN:
    if (evt.key.keysym.sym == SDLK_ESCAPE)
    {
    exit(0);
    }
    g_bMouseDown = true;
    break;

    效果:

    背景是因为运行过Flappy Bird和typing-game。bird没问题,但是跑typing-game还有bug(只有退出正常)


    写在最后:感觉燃尽了(T_T),下一站PA4

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » PA3之精彩纷呈的应用程序
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!