从 C 到 Rust:GDEV GPU 运行时移植纪实

在 QEMU 环境中进行测试

背景

前四阶段的翻译工作已全部完成(31 文件,10,300 行 Rust,196 个纯软件测试通过),并在开发机上成功编译出 libgdev_layer4.so。但开发机(glibc 2.33+)与目标 QEMU 环境(CentOS 7,glibc 2.17)的 ABI 差异在第一次部署时暴露了一系列链接与运行时错误,在此期间由于测试环境不方便,我将模拟器放到了本机上,整理方便agent能理解的仅用于编译测试的环境用的时间比较多,目前是nfs挂载源码到qemu上实现同步改动,然后qemu上进行编译环境和测试题并测试,最后让agent理解该测试流程之后开始正式调试launch。


glibc 版本

将开发机上编译的 .so 替换进 QEMU 后,编译 C 测试程序时立即报错:

undefined reference to `fstat64@GLIBC_2.33'  
undefined reference to `pthread_key_create@GLIBC_2.34'  

原因是 Rust 标准库在编译时锁定了宿主 glibc 的高版本符号。尝试用 .symver 汇编技巧创建兼容库 libglibc_compat.so 虽然在链接阶段通过了,但运行时动态链接器 ld.so 直接拒绝加载—— ELF 头部 DT_VERNEED 字段明确要求 GLIBC_2.34,版本检查失败,进程直接终止。

最终解:在 QEMU 虚拟机(CentOS 7)内安装 Rust 1.72,将整个 gdev-rust 工程上传,本地 cargo build --release。产出的 .so 天然链接 glibc 2.17,彻底消除了所有版本兼容问题。


设备计数

编译通过后运行 ./user_test 立刻段错误:

[root@qemu-ai-ep launch2]# ./user_test 
size = 0x10000, dev_num = 0
fil[ 1013.290821] user_test[1741]: segfault at 7ffdf92ff000 ip 00007f15e0165c9b sp 00007ffdf92fe578 error 4e size =211762
 in libc-2.17.so[7f15e00d1000+1c3000]
 dev count 0
---
-----cubin = 0x7f15e110a010-------
Segmentation fault

QEMU 里明明有四个 aicard 设备节点(/dev/aicard0 ~ aicard3),但 sdInit() 内部调用 gdev_count() 始终返回 0。

根因:原翻译的 gdev_count() 只统计了“已调用 gopen() 的槽位”,而系统启动时还没有任何进程打开设备,槽位全空,自然返回 0。

修复:将 gdev_count() 的实现改为用 stat() 系统调用直接探测 /dev/aicard0aicard1 等设备文件是否存在。与原始 C 代码中 gdev_getinfo_device_count() 的行为保持一致。改动仅一行逻辑,dev count 立刻正确输出 4。

[root@qemu-ai-ep launch2]# ./user_test -s 0x10000 -d 0
size = 0x10000, dev_num = 0
file size =211762
dev count 4
--------cubin = 0x7f915e5c2010-------

缺少设备句柄导致内存分配失败

设备计数正确后,接着报错:

Sdaa runtime error in line 104 of file launch.c     : sdaaErrorOutOfMemory 

错误链:sdaaMallocsd_mem_allocsd_ctx_get_current() 找不到上下文 → 句柄为 0 → gmalloc(0, size) 必然失败。

根因enumerate_devices() 只做了设备计数(stat),但从没有调用 gopen() 真正打开设备并获取内核句柄。没有句柄,任何内存分配都无法和内核通信。

短期方案:为加快调试节奏,暂时采用纯软件模拟——实现一个软件后备路径,将设备内存操作全部重定向到 Rust 的 HashMap<u64, Vec<u8>> 中。

这样一来,所有 memcpy HtoD/DtoH/DtoD 都在软件层完成,launch 也直接在 CPU 上解析参数块并执行向量加法。

测试结果:

[root@qemu-ai-ep launch2]# ./user_test 
size = 0x10000, dev_num = 0
file size =211762
[DEBUG] enumerate_devices: entering
[DEBUG] enumerate_devices: calling sd_init(0)
[DEBUG] enumerate_devices: raw count=4
[DEBUG] enumerate_devices: minor=0 gopen→handle=0x1
[DEBUG] enumerate_devices: minor=1 gopen→handle=0x2
[DEBUG] enumerate_devices: minor=2 gopen→handle=0x3
[DEBUG] enumerate_devices: minor=3 gopen→handle=0x4
[DEBUG] enumerate_devices: opened 4 handles
[DEBUG] enumerate_devices: sd_ctx_create(0,0)11
[DEBUG] enumerate_devices: done, count=4
dev count 4
--------cubin = 0x7f2934e2c010-------
[DEBUG] register_fat_binary: ptr=0x7ffd06c389e0 name=unnamed
[DEBUG] register_fat_binary: about to lock_runtime
[DEBUG] register_fat_binary: locked
[DEBUG] register_fat_binary: handle assigned=0
[DEBUG] register_fat_binary: about to read 4096 bytes from ptr
[DEBUG] register_fat_binary: cubin_data.len=4096
[DEBUG] register_fat_binary: handle=0x0
[DEBUG] register_function: handle=0x0 name=add_test
[DEBUG] register_function: registered, kernels.len=1
[DEBUG] malloc: size=0x10000, device_count=4, handles.len=4
[DEBUG] malloc: using handle=0x1
[DEBUG] malloc: gmalloc→addr=0x61000100c000
[DEBUG] malloc: size=0x10000, device_count=4, handles.len=4
[DEBUG] malloc: using handle=0x1
[DEBUG] malloc: gmalloc→addr=0x61000101c000
[DEBUG] malloc: size=0x10000, device_count=4, handles.len=4
[DEBUG] malloc: using handle=0x1
[DEBUG] malloc: gmalloc→addr=0x61000102c000
[DEBUG] memcpy: dst=0x61000100c000 src=0x2517010 count=65536 kind=HostToDevice
[DEBUG] memcpy: using handle=0x1
[DEBUG] memcpy: ok=true
[DEBUG] memcpy: dst=0x61000101c000 src=0x2527020 count=65536 kind=HostToDevice
[DEBUG] memcpy: using handle=0x1
[DEBUG] memcpy: ok=true
[DEBUG] launch: entry=add_test
[DEBUG] launch: kernel_registered=true
[DEBUG] launch: handle=0x1 param_size=32
[DEBUG] launch: glaunch→ok=true kid=0
[DEBUG] launch SW: A=0x61000100c000 B=0x61000101c000 C=0x61000102c000 n=16384
[DEBUG] launch SW: wrote 65536 bytes to C=0x61000102c000
launch_sync time: 0.292000
[DEBUG] memcpy: dst=0x2537030 src=0x61000102c000 count=65536 kind=DeviceToHost
[DEBUG] memcpy: using handle=0x1
[DEBUG] memcpy: ok=true
[DEBUG] free: ptr=0x61000102c000
[DEBUG] free: gfree→Ok(65536)
[DEBUG] free: ptr=0x61000101c000
[DEBUG] free: gfree→Ok(65536)
[DEBUG] free: ptr=0x61000100c000
[DEBUG] free: gfree→Ok(65536)
Test passed: size = 0x10000, dev_num = 0

全链路在无硬件的纯软件模拟下跑通——证明四层翻译的调度、内存管理、参数打包、错误传播逻辑都是正确的。


未完成的工作

软件测试虽然通过,但数据根本没有进入 aicard 硬件。真正驱动 GPU 需要将 C 源码 gdev_aidev.c(297 行)中的 19 个硬件后端函数翻译为 Rust 并注册到 GdevCompute 的函数指针表里。

同时,部分对外 API 在翻译中为了快速闭环被临时设为 stub(返回“未实现”)。现将未完成部分统计如下:

1. 公开 API 层的 stub(16 个)

这些函数在外部调用时会得到 SDAA_ERROR_NOT_YET_IMPLEMENTED,不影响核心 launch/memcpy 流程:

#所属阶段函数原因
1-3Layer2gipc_get_mem_handle, gipc_open_mem_handle, gipc_close_mem_handle跨进程共享内存
4Layer3sd_func_get_attributeC 代码原就是 weak stub
5-7Layer4device_synchronize, thread_synchronize, set_device_limit全局同步 / 平台特定
8-9Layer4host_register, host_unregistermmap 固定页
10-12Layer4ipc_get/open/close_mem_handle跨进程句柄
13Layer4print_info设备 print buffer
14-16Layer4trans_encode, trans_decode, transformerTransformer 推理

占比:365 个总 API 函数中,16 个 stub(4.4%),全部位于非核心路径。

2. GdevCompute 函数指针(11 个未实现)

这 19 个函数指针是“硬件会话”的真正执行者。每次 glaunchgmemcpy 最终都要通过它们操作 ring buffer 和 ioctl。目前状态:

状态函数说明
✅ 已实现 (8)launch, fence_read, fence_write, fence_reset, memcpy, memcpy_async, init, load核心启动、DMA、fence
❌ 未实现 (11)event_read, event_read_timestamp, event_write, event_reset, swait_event, printbuffer, transencode, transdecode, transformer, membar, notify_intr, memset事件、调试、Transformer 等

已实现的 8 个覆盖了最小可用集(launch + memcpy + fence 同步),但要完整运行 QEMU 中的 C 测试样例还需要补全事件(event)相关的 5 个函数,目前这8个还有待考证,需要在qemu环境下进一步测试。


更多推荐