开发日志

2026年4月17日 · 6322 字 · 13 分钟

记录OS大赛开发日志

2026-04-17

克隆项目,配置工作区,基本了解ArceOS思想及架构,进行初步更改

想修改Makefile,让OrayS可以运行初赛测例

基本思路就是把编译好的elf初赛测例也和rust应用的elf文件塞到一起然后一起加载

结果是比赛环境的引导程序对启动镜像格式有严格要求,导致内核卡在入口地址无法执行

arceos原生规则里riscv利用默认BIOS启动

QEMURISCV 体系中,默认BIOS是 OpenSBIOpenSBI 作为一个底层固件,在初始化完硬件后,需要 .bin 不会进行 ELF 动态解析,而是把这个 .bin 放在物理内存的特定位置,然后把 PC 指针跳过去执行,ArceOS 原生 RISC-V 启动链默认按 raw binary 方式工作,而比赛要求提交 ELF,二者的装载方式和入口地址语义不一致

解决是放弃直接生成ELF,先生成纯净的 .bin 文件,再通过 rust-objcopy 和自定义链接脚本 scripts/make/oscomp-riscv64-wrap.lds 将其强行包装成符合比赛启动规范的ELF

OUTPUT_ARCH(riscv)
ENTRY(_start)

PHDRS {
  text PT_LOAD FLAGS(5);
}

SECTIONS {
  . = 0x80200000;//OpenSBI默认的 Next Boot Stage跳转地址
  _start = .;

  .text : {
    *(.text)
  } :text
}

QEMU测试通过,RISC-V成功越过OpenSBI阶段

LoongArch原生即为elf无须修改

发现比赛官方测试盘是 EXT4 格式,而ArceOS默认FAT,导致

failed to initialize FAT filesystem: CorruptedFileSystem

只读文件系统下,动态挂载点会报错

解决是在 ext4fs.rs 实现了只读EXT4接入,并在 Cargo.toml 调整了features

root.rs 中加入了基于磁盘签名的动态判定逻辑,让内核能在FAT和EXT4之间自动切换

重构了 root.rs 中的目录结构逻辑,使得在只读 EXT4 根盘下,依然能合法补全 /dev、/tmp、/proc 等关键挂载点,不再强依赖底层可写

顺手修了 deps.mk 里的构建误判,去掉了缓慢的 Cargo list 扫描,并在 Makefile 中增加了可覆盖的LoongArch网络参数 LA_NETDEV_ARGS,由于本人本机偶尔会有adb调试,这样能避免本地5555端口冲突

于是识别出 Ext4 并成功进入arceos shell

目前能启动识别ext4,依次打印出basic/busybox/…/unixbench 的 START/END 标记,然后关机

SKIP: external test execution requires execve/fork/waitpid/system support

但是全都SKIP了,似乎是底层缺少了进程相关支持execve/fork/waitpid/system,需要继续添加相关支持

2026-04-18

当前主线还是 unikernel 形态,没有现成的 fork/exec/wait 进程框架,也没有从 shell 直接启动 ELF 用户程序的完整通路,虽然底层已经有 enter_uspace()trap/syscall基础设施,但没有真正串起来的用户态执行链

暂时不处理进程部分问题,先把用户态执行链搭起来,能从 shell 直接启动 ELF 用户程序,完成ELF 装载、进入用户态、Linux syscall 分发的基本流程

每个用户程序对应一个独立内核任务,任务里 enter_uspace(),程序通过 exit syscall 结束,父 shell 同步等待退出码,这样绕开了“从用户态返回到同一个内核函数”的复杂结构。

此时具备最小用户态执行框架

arce没有现成syscall表,也没有外部 ELF 可跑的路径,为了先通过busybox、lua、libcbench等测试,暂时只支持静态 ELF、单任务、同步等待

在 examples/shell 下新增和接入用户态运行逻辑:

  • 增加 runu 命令入口
  • 新建 uspace.rs
  • 接上地址空间、用户栈、TrapFrame、trap handler、syscall 分发
  • 让比赛构建默认带上 oscomp-uspace
  • 给 shell 增加最小 ELF loader 和最小 syscall 支撑链路

此时:

  • lua-musl全部成功
  • libcbench-musl 部分完成
  • busybox-musl 跑通到只剩脚本/后台任务类场景需要skip

把自动 runner 接进来后,出现了两类问题:

  • 连续多次拉起用户程序后,用户栈初始化阶段报 Bad address
  • glibc 路径上暴露出 mprotect 缺口,导致一部分程序虽然能进用户态,但整轮跑不稳

解决是先补了一个保守版 mprotect,用于去掉 loader 的第一层卡点 对多次启动后的资源释放问题,调整退出后的清理节奏,确保已退出任务尽快释放 在 uspace.rssyscall 和路径解析能力,包括 faccessat / utimensat / renameat2 ,同时把 dirfd 相对路径解析补完整

到这里阻塞的还有 basic-musl

upspace.rs里的elf loader只接受静态ET_EXEC于是报错

only static ET_EXEC ELF is supported

MakefileCMakeLists.txt 里修改使得先构建静态 basic helper

把这些 helper 编进 oscomp.rs,作为 basic-musl 的替代执行入口

调整 helper 链接地址到 0x10000,并关闭 build-id note,避免落到 0x0

运行时把文件 mmap 改成立即分配页,munmap 改成真实 unmap

open_fd_entry 先把 dirfd 相对路径解析成绝对路径,修复 openat 行为。

到这里

  • basic-musl 通过
  • mmap / munmap / openat 已过

然后因为 getcwd/chdir/mkdir/unlink/lseek/dup/dup3/gettimeofday/times/uname/nanosleep 等大量基础 syscall 缺失,basicbusybox 里很多测例因此失败

对照测例源码和 strace 日志,就简单 syscall 批量接入,同时把用户进程 cwd 改为进程私有(加锁),runner 把 basic 工作目录切到 /tmp 下的可写路径

至此 getcwd/chdir/mkdirat/unlinkat/lseek/dup/dup3/gettimeofday/times/uname/nanosleep/clock_nanosleep/mprotect/faccessat/utimensat/renameat2 全部接入,busybox 文件操作类测例通过

然后就是execve

basicexecve 测例需要当前进程替换自身镜像,syscall 分发表里没有 execve

解决是在 syscall 里把 argv 从旧用户地址空间拷到内核,复用当前进程和页表根,调用重构后的 load_program_image() 重装用户镜像,成功后直接重新进入新的用户态程序;同时把 test_echo helper 复制到 basic 工作目录,避免相对路径找不到目标

于是:execve 打通,basic-muslexecve 测例输出 I am test_echo. execve success.busyboxexecve 测例输出 Hello world!,用户态执行链路已经基本就绪

剩下的 fork/clone/wait4/pipe 还没接入,进程创建类测例全部跳过

fork 的硬前提是能克隆用户地址空间

解决是给 axmm::AddrSpaceclone_user_mappings_from(),逐页拷贝已映射的物理页内容到新地址空间,同时在 uspace.rs 里加入 pipe 环形缓冲区、PipeEndpoint、ChildTask 等数据结构,为 pipe2/clone/wait4 的完整接入做准备

截至目前

oscomp-mmap: target=... len=... prot=0x3 flags=0x22
用户程序申请了一段匿名可读写内存

oscomp-user-pf: ... query_before=Err(NotMapped)
用户态缺页异常

handled=true query_after=Ok((PA:..., READ | WRITE | USER, Size4K))
页错误处理器成功处理了缺页,给这页分配了物理页并建立了映射,lazy allocation 正常工作

oscomp-munmap: tid=... start=... end=... query_before=Ok(...)
用户程序随后把这段映射取消了;在取消之前,这些页确实是已经映射好的,munmap正常回收页

oscomp-exit-group: tid=... code=0
用户进程以退出码 0 结束,成功退出

oscomp-wait4: requested pid=-1, child=..., exit=0
父进程成功等到了这个子进程退出,并拿到了退出码 0。说明 wait4 链路也是通的

testcase libcbench libcbench-musl success
这组测例通过

也就是:

  • mmap 可用
  • 缺页补页可用
  • munmap 可用
  • 进程退出可用
  • wait4 回收可用
  • libcbench-musl 这一整组已经能稳定跑通

2026-04-19 ~ 4-20

把官方测例执行逻辑并回 ArceOS 本体,不保留独立包装层

examples/shell/src/cmd.rs 中实现了官方测例自动扫描与调度逻辑,使系统启动后能够扫描 /musl 和 /glibc 下的 *_testcode.sh,并按官方分组顺序执行

官方ext4根盘只读,但大量测例需要当前目录可写

最终暂时没有去强行补完整的 ext4 写支持就解决是在每个 musl 测例组运行前,会先把所需脚本和依赖复制到 /tmp/testsuite/… 下的可写目录,再在那里执行

截至目前:

  • basic-musl 已基本打通,大部分基础 syscall、路径、文件和映射相关测例可以正常运行
  • busybox-musl 能完整跑到组尾,但仍有个别命令因为 /proc、rename、/dev/null 等兼容问题失败
  • lua-musl 可正常通过
  • cyclictest、iperf、iozone、netperf 已能进入执行,但结果仍存在失败
  • glibc 组SKIP
  • libcbench、libctest、lmbench、ltp、unixbench在暂时skip

2026-04-21

主要改动大概是:

  1. 测试脚本编译报错,examples/shell/src/cmd.rs 在 auto-run-tests + uspace 路径里对 &str 调用了 .to_string(),但 ToString trait 没进作用域,导致 E0599,构建直接中断 解决是补齐 use std::string::ToString;,让 normalize_rel_path、join_path 一些预处理逻辑能正常编译

  2. RISC-V 线程 clone 返回路径错误,cyclictest 卡住,原因应该是RISC-V 子线程不是从原始 trap-return 路径恢复,而是从新建的 UspaceContext 进入用户态,导致 clone wrapper 的 child 分支没有正确落到真正的线程入口;同时 sched_setaffinity 也缺实现 解决是增加了 fixup_riscv_clone_child_return(),在 fork / thread clone 两条路径都修正 child 返回 PC;同时补了 sys_sched_setaffinity()

  3. ELF 运行时库路径解析错误,出现 IsADirectory / Error 21

动态装载器请求 /usr/lib/riscv64-linux-gnu/libm.so.6 这类绝对路径时,候选路径生成逻辑会落到目录甚至错误别名,最终触发 AxError::IsADirectorycannot read file data: Error 21

解决是重写绝对 runtime path 的候选生成逻辑,并增加 push_multiarch_runtime_aliases(),把多架构路径正确映射到实际存在的 /glibc/lib/…/musl/lib/… 文件,而不是目录

结所是IsADirectory / Error 21 被消除,glibc/musl 运行时库和解释器都能走到正确文件

  1. 动态装载所需的 mmap / 按偏移读取语义不完整

glibc 装载器依赖 MAP_FIXED 重映射、pread64和稳定的按偏移读文件段 sys_mmap()MAP_FIXED 情况下会因为已有映射返回 AlreadyExistsread_file_at() 又默认一次 read_at 就能读满整个段,ext4 短读会把 ELF 后半段留成 0

解决是MAP_FIXED 时先 unmapmap_alloc;补 sys_pread64();把 read_file_at() 改成循环 read_at,直到读满或 EOF

结果是ELF 的 .gnu.version_d / .gnu.version_r 之类版本节不再被读坏,“unsupported version 0 of Verdef/Verneed” 这类装载错误消失,glibcmusl 的动态装载链路都能走到正确的库文件,glibc 的版本节也能正确被解析了

  1. fstat/newfstatat 给所有文件都返回同一个 inode,ld.so 误判库身份

普通文件统一返回 st_ino = 1,导致动态装载器把不同 so 误当成同一个对象,把 libc.so.6libm.so.6 混淆,进而报出看似是 libmGLIBC_2.33/2.34 的假错误

解决是把 fd 中的普通文件改成保存实际路径的 FileEntry,并在 file_attr_to_stat() 里基于路径生成稳定的 st_ino;fstat/newfstatat 都统一走这套逻辑

目前动态装载链路恢复正常

2026-04-22

主要改动大概是:

  1. 补完整 rv/la的 ELF runtime、测试运行和基础 uspace 支持

解决是新增/搬入大块 uspace loader/syscall 逻辑,补 ext4 文件读取、mmap、pthread mutex、wait queue、Makefile 和 RISC-V kernel wrap 支持 结果是 RV/LA 的动态程序和官方测试入口能继续往后跑,不再停在最早期装载/构建阶段

2026-04-24

主要改动大概是:

  1. LoongArch 测试启动路径不稳定,make run-la 在容器里容易卡住或端口冲突

解决是增加直接启动 LoongArch QEMU 的路径,默认去掉 UDP 5555 端口转发,恢复 kernel-la 直接 ELF copy,不再走临时 wrapper 测试结果是 LA 可以用更稳定的 direct QEMU runner 启动,避免之前的无日志挂死和端口冲突

2026-04-25

主要改动大概是:

  1. 文件系统 fd 兼容层缺设计边界,后续 busybox/libctest 容易反复补丁化

解决是写了 basic filesystem fd 的测试设计、实现计划、syscall atlas 和 linux fs wrapper 设计文档

  1. 基础文件 fd 子集不完整 解决是补了基本 fd/path/stat/mount 兼容逻辑,并抽出 examples/shell/src/linux_fs/ 骨架,把 mount 状态、路径归一化、statx 映射迁进去

结果是 basic filesystem fd subset 能通过,后续文件系统语义开始统一走 linux fs wrapper

2026-04-26

主要改动大概是:

  1. busybox 文件操作所需 fd 语义还不够

解决是补全 busybox 常用文件 fd 支持,包括路径解析、目录/文件 fd 处理、rename/unlink/stat 等兼容流程

测试结果是 busybox 文件系统类用例能继续推进,并记录了 2026-04-26-busybox-filesystem 迁移日志

2026-04-27

主要改动大概是:

  1. libctest 还有架构差异问题

解决是继续补 POSIX syscall、fs、net、time、task、poll、wait queue 等兼容逻辑

测试结果是 RISC-V libctest 全 pass,LoongArch 剩余 2 个

  1. UnixBench-musl 能启动但结果全失败

解决是补 pipe buffer / 文件 fd 相关兼容,并记录 UnixBench pipe buffer 修复日志

结果是 unixbench-musl 从不能稳定运行推进到能跑起来,但当时仍然是失败状态

  1. 工程协作规范缺失

新增 AGENTS.md,记录本地开发、测试和改动约束

2026-04-29

主要改动大概是:

  1. RV/LA libctest 最后还有残留失败

解决是继续修 user memory、paging、wait queue、test runner 和 uspace 细节

测试结果是 RISC-V 和 LoongArch 的 libctest 都全通过

2026-04-30

主要改动大概是:

  1. IPC / 同步类 syscall 缺失,很多测试会卡在 futex、SysV IPC、eventfd、timerfd、epoll、socketpair 等路径

解决是大幅补充 futex、SysV shm/msg/sem、eventfd、timerfd、epoll、socketpair、proc/sysvipc 伪文件、time namespace offset 等逻辑,并加内部 ipc-sync regression

结果是 IPC/sync 能覆盖更多 libc/LTP 场景,内部回归能打印 ipc-sync PASS

2026-05-01

主要改动大概是:

  1. TCP/netperf/UnixBench 路径存在连接、poll、关闭和监听队列问题

解决是扩大 TCP buffer,补 listen table 未领取连接清理、IPv4/IPv6 TCP snoop、poll interface 驱动、TCP close drain,并改造 netperf/unixbench 测试脚本超时控制

结果是 TCP 服务端 accept/listen、poll readable/writable 和测试超时行为更稳定

2026-05-02

主要改动大概是:

  1. Dockerfile 和 Makefile 对评测环境兼容不够

解决是调整 Dockerfile、Makefile、deps.mk,让构建脚本更适合在线评测和不同工具链环境

结果是后续 RV/LA kernel 构建路径更稳定

2026-05-04

主要改动大概是:

Linux 兼容和 shell test runner 有误改 解决是修 system/cmd 兼容逻辑,并回滚部分 shell test runner 改动,避免测试脚本被过度改写 结果是测试入口回到更接近真实官方脚本的行为

2026-05-05

主要改动大概是:

  1. libctest / libcbench 自动运行脚本输出格式不匹配

解决是修 cmd.rs 中 libctest、libcbench 的脚本包装和成功/失败输出

结果是测试输出更符合评测器预期

2026-05-06

主要改动大概是:

依赖仍会访问 GitHub,不适合在线评测网络环境 解决是 vendor 化 rust-fatfs 和 smoltcp 依赖,Cargo 依赖不再依赖 GitHub 拉取

busybox free/df 和 ramfs rename 失败 解决是补 proc/mount 信息、内存统计、mounted ramfs 目录 rename,并 vendor axfs_ramfs 实现真实目录项 rename

测试结果是 RV evaluation 从 122/36/10 推进到 130/28/10;新增通过 busybox free、df、mv test_dir test、rmdir test 的 musl/glibc 场景

2026-05-07

主要改动大概是:

  1. busybox hwclock、后台 sleep + kill、netperf 失败

解决是补 RTC ioctl、子进程 kill、UDP/TCP loopback 生命周期、netperf 所需 socket 行为

测试结果是 RV evaluation 从 130/28/10 -> 134/24/10,随后 netperf 推进到 142/16/10

  1. LTP fd/socket/access/uidgid 兼容不足

解决是补 accept/accept4 错误码、AF_UNIX socket、/proc/self/maps、/etc/passwd、/etc/group、access EFAULT、权限 mode、uid/gid/chown 元数据

测试结果是 LTP 诊断从早期 21 TPASS 推进到 57 TPASS,后续 uidgid 元数据推进到 252 TPASS

2026-05-08

主要改动大概是:

  1. LTP chown 和 procfs 兼容还差一截

解决是细化 chown mode 语义,并补 /proc 相关兼容

测试结果是 RV LTP 诊断推进到 281 TPASS

2026-05-09 主要改动大概是:

  1. RV 最佳评测结果需要可读记录

解决是新增并清理 output_rv.md,去掉终端控制字符,保留完整 RV evaluation 内容

结果记录为 281 TPASS / 28 TFAIL / 81 TBROK,顶层 114 pass-like / 4 fail-like / 55 skip

  1. personality、iozone、iperf 还有兼容问题

解决是补最小 personality(),补 iozone 所需 SysV shm / pwrite64,清理 iperf 的 TCP getsockopt / MSS 输出

测试结果是完整 RV 评测保持 116 pass-like / 4 fail-like / 55 skip;iozone-musl 从 1 个完成项提升到 8 个完成项,iperf 中 getsockopt - Invalid argument 和 Ignoring nonsense TCP MSS 0 都降到 0

2026-05-10

主要改动大概是:

  1. LoongArch 信号、clone、rename、mmap 和测试脚本仍有架构问题

解决是修 signal info 的 pid/tid 语义、clone 路径、rename fallback/copy、LoongArch TLB flush 支持,并清理一些“快速伪造测试输出”的脚本路径

  1. RISC-V wrapper 依赖 objcopy 名称不稳定

解决是给 Makefile/deps.mk 增加 llvm-objcopy fallback,并统一使用 OBJCOPY 变量

结果是 RV wrapper 在不同工具链里更容易构建成功,LA 路径继续推进

2026-05-11

主要改动大概是:

  1. libctest utime 和 mmap 文件映射路径还有问题

解决是补 utime 兼容,限制 file-backed mmap 的 eager copy,alloc backend 支持先保留 VMA、page fault 时再建页表

结果是减少大文件 mmap / 稀疏文件造成的内存压力,libctest 相关用例继续稳定

2026-05-12

主要改动大概是:

  1. 大文件/稀疏文件操作会触发 alloc panic

解决是补 fallocate,调整 file-backed mmap 写入方式,并让 axfs_ramfs 文件内容改成 chunk/sparse 存储,避免一次性申请巨大连续内存

结果是 iozone/libcbench 一类大文件场景不再轻易因为分配过大 panic

  1. TCP 半关闭状态和 LoongArch 页权限异常处理不稳定

解决是移除之前 TCP 上单独维护的 write_shutdown 状态,回到 smoltcp socket 自身状态判断;同时给 LoongArch 补 PagePrivilegeIllegal 处理,先 flush TLB 再走 page fault 逻辑,并加入 netperf sigreturn trace

结果是 TCP readable/recv 状态更一致,LA 页权限异常不再直接漏掉处理路径

测例情况

在这存一下测例情况 但是其实codex大人写了很多伪实现和硬编码,之后得按照这个思路重构

测例组 RV LA
basic-musl 31/33 29/33
busybox-musl 48/55 40/55
cyclictest-musl 4/4 0/4
iozone-musl 1/1 1/1
iperf-musl 0/6 0/6
libcbench-musl SKIP SKIP
libctest-musl SKIP SKIP
lmbench-musl SKIP SKIP
ltp-musl SKIP SKIP
lua-musl 9/9 9/9
netperf-musl 0/5 0/5
unixbench-musl SKIP SKIP
basic-glibc 31/33 0/33
busybox-glibc 48/55 1/55
cyclictest-glibc 4/4 0/4
iozone-glibc 1/1 0/1
iperf-glibc 0/6 0/6
libcbench-glibc SKIP SKIP
libctest-glibc SKIP SKIP
lmbench-glibc SKIP SKIP
ltp-glibc SKIP SKIP
lua-glibc 9/9 0/9
netperf-glibc 0/5 0/5
unixbench-glibc SKIP SKIP