本文按依赖顺序,逐行解析 AM 的 10 个核心文件。
代码路径:
~/ysyx-workbench/abstract-machine
目录
am/src/riscv/nemu/start.Sam/src/platform/nemu/trm.cam/src/platform/nemu/include/nemu.ham/src/riscv/riscv.ham/include/am.ham/include/amdev.ham/src/platform/nemu/ioe/ioe.cam/src/platform/nemu/ioe/timer.cam/src/platform/nemu/ioe/input.cam/src/platform/nemu/ioe/gpu.c
1. start.S
.section entry, "ax"
.globl _start
.type _start, @function
_start:
mv s0, zero
la sp, _stack_pointer
call _trm_init
.size _start, . - _start
| 行 | 解释 |
|---|---|
.section entry, "ax" |
放入可执行段 entry,链接脚本会把它放在 0x80000000 |
.globl _start |
声明全局符号 |
.type _start, @function |
标记为函数 |
_start: |
入口标签 |
mv s0, zero |
帧指针清零 |
la sp, _stack_pointer |
设置栈指针 |
call _trm_init |
调用 _trm_init,并把返回地址存入 ra |
.size ... |
记录符号大小 |
2. trm.c
#include <am.h>
#include <nemu.h>
extern char _heap_start;
int main(const char *args);
Area heap = RANGE(&_heap_start, PMEM_END);
static const char mainargs[MAINARGS_MAX_LEN] =
TOSTRING(MAINARGS_PLACEHOLDER);
static Context *event_handler(Event ev, Context *ctx) {
switch (ev.event) {
case EVENT_YIELD: break;
case EVENT_SYSCALL:
if (ctx->GPRx == 0) halt(ctx->GPR1);
break;
case EVENT_IRQ_TIMER: break;
default: panic("Unhandled event"); break;
}
return ctx;
}
void putch(char ch) { outb(SERIAL_PORT, ch); }
void halt(int code) { nemu_trap(code); while (1); }
void _trm_init() {
cte_init(event_handler);
int ret = main(mainargs);
halt(ret);
}
| 内容 | 解释 |
|---|---|
Area heap |
定义堆 [_heap_start, PMEM_END) |
mainargs |
保存传给 main 的参数 |
event_handler |
异常/中断处理函数 |
putch |
输出字符,写 0xa00003f8 |
halt |
通过 ebreak 停机 |
_trm_init |
注册 CTE,启动 main |
3. nemu.h
#if defined(__riscv)
# define nemu_trap(code) asm volatile("mv a0, %0; ebreak" : :"r"(code))
#endif
#define DEVICE_BASE 0xa0000000
#define SERIAL_PORT (DEVICE_BASE + 0x00003f8)
#define KBD_ADDR (DEVICE_BASE + 0x0000060)
#define RTC_ADDR (DEVICE_BASE + 0x0000048)
#define VGACTL_ADDR (DEVICE_BASE + 0x0000100)
#define AUDIO_ADDR (DEVICE_BASE + 0x0000200)
#define DISK_ADDR (DEVICE_BASE + 0x0000300)
#define FB_ADDR (MMIO_BASE + 0x1000000)
作用:
nemu_trap:定义停机指令- 各设备 MMIO 地址
PMEM_END:物理内存结束地址NEMU_PADDR_SPACE:合法地址空间
4. riscv.h
static inline uint8_t inb(uintptr_t addr) { return *(volatile uint8_t *)addr; }
static inline uint32_t inl(uintptr_t addr) { return *(volatile uint32_t *)addr; }
static inline void outb(uintptr_t addr, uint8_t data) { *(volatile uint8_t *)addr = data; }
static inline void outl(uintptr_t addr, uint32_t data) { *(volatile uint32_t *)addr = data; }
作用:
inb/inw/inl:读 1/2/4 字节outb/outw/outl:写 1/2/4 字节PTE_*:页表权限位MODE_U/S/M:特权模式MSTATUS_*:状态位
volatile 保证每次读写都真正执行,不被编译器优化。
5. am.h
核心接口:
typedef struct { void *start, *end; } Area;
typedef struct {
enum {
EVENT_NULL = 0,
EVENT_YIELD, EVENT_SYSCALL, EVENT_PAGEFAULT, EVENT_ERROR,
EVENT_IRQ_TIMER, EVENT_IRQ_IODEV,
} event;
uintptr_t cause, ref;
const char *msg;
} Event;
// TRM
void putch(char ch);
void halt(int code);
// IOE
void ioe_read(int reg, void *buf);
void ioe_write(int reg, void *buf);
// CTE
bool cte_init(...);
void yield(void);
Context *kcontext(...);
// VME / MPE
...
作用:
- 定义
Area、Event、AddrSpace - 声明 TRM/IOE/CTE/VME/MPE 五层接口
6. amdev.h
核心宏:
#define AM_DEVREG(id, reg, perm, ...) \
enum { AM_##reg = (id) }; \
typedef struct { __VA_ARGS__; } AM_##reg##_T;
AM_DEVREG(6, TIMER_UPTIME, RD, uint64_t us);
展开后:
enum { AM_TIMER_UPTIME = 6 };
typedef struct { uint64_t us; } AM_TIMER_UPTIME_T;
作用:
- 定义所有设备寄存器编号和结构体
- 供
io_read/io_write使用
7. ioe.c
static void *lut[128] = {
[AM_TIMER_CONFIG] = __am_timer_config,
[AM_TIMER_RTC ] = __am_timer_rtc,
[AM_TIMER_UPTIME] = __am_timer_uptime,
...
};
bool ioe_init() {
for (int i = 0; i < LENGTH(lut); i++)
if (!lut[i]) lut[i] = fail;
__am_gpu_init();
__am_timer_init();
__am_audio_init();
return true;
}
void ioe_read(int reg, void *buf) {
((handler_t)lut[reg])(buf);
}
作用:
lut:设备编号 → 处理函数ioe_init:填充空项、初始化设备ioe_read/ioe_write:查表分发
8. timer.c
void __am_timer_init() {
outl(RTC_ADDR, 0);
outl(RTC_ADDR + 4, 0);
}
void __am_timer_uptime(AM_TIMER_UPTIME_T *uptime) {
uptime->us = inl(RTC_ADDR + 4);
uptime->us <<= 32;
uptime->us += inl(RTC_ADDR);
}
void __am_timer_rtc(AM_TIMER_RTC_T *rtc) {
rtc->year = 1900; // 目前写死
}
作用:
- 初始化定时器
- 读取 64 位 uptime
- 读取 RTC 日历(当前写死)
9. input.c
#define KEYDOWN_MASK 0x8000
void __am_input_keybrd(AM_INPUT_KEYBRD_T *kbd) {
uint32_t kc = inl(KBD_ADDR);
kbd->keydown = kc & KEYDOWN_MASK ? true : false;
kbd->keycode = kc & ~KEYDOWN_MASK;
}
作用:
- 读
0xa0000060 - 拆出
keydown和keycode
10. gpu.c
void __am_gpu_init() {
uint32_t screen_wh = inl(VGACTL_ADDR);
screen_w = screen_wh >> 16;
screen_h = screen_wh & 0xffff;
}
void __am_gpu_fbdraw(AM_GPU_FBDRAW_T *ctl) {
uint32_t *fb = (uint32_t *)(uintptr_t)FB_ADDR;
for (int i = 0; i < h; i++) {
uint32_t *dst = fb + (y + i) * screen_w + x;
uint32_t *src = pixels + i * w;
for (int j = 0; j < w; j++) dst[j] = src[j];
}
if (ctl->sync) outl(SYNC_ADDR, 1);
}
作用:
- 读取屏幕宽高
- 把像素写入帧缓冲
- 刷新屏幕
总结
这 10 个文件组成了 AM 的最小可运行环境:
start.S -> 启动
trm.c -> TRM
nemu.h -> 硬件约定
riscv.h -> MMIO 读写
am.h -> 总接口
amdev.h -> 设备定义
ioe.c -> 设备分发
timer.c -> 定时器
input.c -> 键盘
gpu.c -> 显卡
这就是 AM 核心文件逐行解析的上半部分。