第二章 驱动开发环境搭建:Linux内核驱动开发环境配置、Windows WDDM驱动开发环境配置、GPU模拟器与虚拟化环境

说实话,驱动开发最让人头疼的往往不是代码本身,而是环境搭建。我见过太多新手,代码写得挺好,结果环境配了一周还没跑起来。这一章,我就把我在Linux和Windows两套平台上的实战经验,以及GPU模拟器的玩法,一次性讲清楚。

2.1 Linux内核驱动开发环境配置

Linux下搞GPU驱动,我个人习惯用Ubuntu LTS版本。为什么?因为社区支持最好,遇到坑能搜到答案。你想想看,要是用个冷门发行版,出了问题连个问的人都没有。

2.1.1 内核源码与编译工具链

首先,你得有一套完整的内核源码。别用系统自带的那个,版本太老。

# 下载主线内核
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

# 配置内核,我建议先拿当前系统的配置做底子
zcat /proc/config.gz > .config
make olddefconfig

# 编译内核(记得开多核)
make -j$(nproc)

嗯,这里要注意:编译内核时,CONFIG_DRMCONFIG_DRM_GPU 这两个选项必须打开。我曾经帮一个同事排查问题,搞了半天发现他忘了开DRM支持,内核根本加载不了GPU驱动模块。

避坑指南: 我曾经在Ubuntu 20.04上直接编译5.10内核,结果忘了安装 flex 和 bison,编译到一半报语法错误。所以,先把依赖装全:
sudo apt-get install build-essential flex bison libncurses-dev libssl-dev

2.1.2 驱动模块的加载与调试

驱动写好了,怎么加载?我一般用 insmodrmmod 这对组合。但说实话,生产环境千万别这么干,容易把系统搞崩。

# 编译驱动模块
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

# 加载模块
sudo insmod gpu_driver.ko

# 查看内核日志
dmesg | tail -20

# 卸载模块
sudo rmmod gpu_driver

调试的话,我强烈推荐 printk 配合 dmesg。别嫌它土,在早期开发阶段,这比什么JTAG都好使。你想想看,GPU驱动刚跑起来,连个显示输出都没有,你拿什么图形化调试?

我的小技巧: 在驱动代码里加个 #define DEBUG,然后用 pr_debug() 打印信息。这样平时不输出,需要调试时通过 echo 'file gpu_driver.c +p' > /sys/kernel/debug/dynamic_debug/control 动态打开,非常灵活。

2.2 Windows WDDM驱动开发环境配置

Windows这边就复杂多了。WDDM(Windows Display Driver Model)是微软的GPU驱动框架,版本从WDDM 1.0到现在的WDDM 3.0,每个版本差异都挺大。我个人建议新手从WDDM 2.0开始学,因为文档最全,工具链也最成熟。

2.2.1 Visual Studio与WDK安装

首先,你得装Visual Studio 2022,然后装上Windows Driver Kit(WDK)。注意版本要匹配,VS2022配WDK 10.0.22621.0是我目前用得最稳的组合。

# 安装完成后,验证环境
# 打开 "Developer Command Prompt for VS 2022"
cl /?
# 如果能正常输出,说明编译环境OK

# 再打开 "x64 Native Tools Command Prompt"
msbuild /?
# 确认MSBuild可用

这里有个坑:WDK安装时默认不会装示例代码。我建议你手动勾选 "Windows Driver Kit Samples",里面有个 kmdf 目录下的 gpu 示例,是学习WDDM的绝佳起点。

2.2.2 WDDM驱动项目结构

一个标准的WDDM驱动,至少包含这几个文件:

文件 作用
GpuDriver.c 驱动入口,实现 DriverEntry 和 AddDevice
GpuAdapter.c 适配器层,处理GPU硬件初始化
GpuContext.c 上下文管理,处理命令队列
GpuAllocation.c 显存分配与回收
GpuDma.c DMA操作,处理GPU与CPU的数据传输

说实话,WDDM的接口函数多得吓人,光 D3DKMT 开头的函数就有上百个。但别怕,核心就几个:D3DKMTOpenAdapterFromGpuDisplayNameD3DKMTQueryAdapterInfoD3DKMTCreateContext。先把这些搞明白,其他的都是锦上添花。

核心要点: WDDM驱动必须通过WHQL签名才能部署。在开发阶段,你可以开启测试模式绕过签名验证:
bcdedit /set testsigning on
bcdedit /set loadoptions DISABLE_INTEGRITY_CHECKS
重启后,驱动就能加载未签名的版本了。

2.3 GPU模拟器与虚拟化环境

搞GPU驱动开发,最怕的就是把真机搞崩。我当年刚入行时,有一次调试显存分配,直接把显卡刷成了砖,那叫一个惨。所以,模拟器和虚拟化环境是必备的。

2.3.1 QEMU + virtio-gpu 模拟

QEMU配合virtio-gpu,是我最常用的模拟方案。它虽然性能不咋地,但胜在稳定,而且能完整模拟GPU的MMIO、中断、DMA等行为。

# 启动QEMU,开启virtio-gpu
qemu-system-x86_64 \
  -m 4G \
  -smp 4 \
  -vga virtio \
  -device virtio-gpu-pci \
  -display sdl,gl=on \
  -kernel /path/to/bzImage \
  -initrd /path/to/initrd.img \
  -append "console=ttyS0"

嗯,这里要注意:-display sdl,gl=on 这个参数很关键。如果不加,virtio-gpu只会用软件渲染,你根本测不出硬件加速的效果。我曾经在这个坑里浪费了两天,后来才发现是显示后端没开GL加速。

2.3.2 GPU Passthrough 与 SR-IOV

如果你需要更真实的测试环境,可以用GPU Passthrough。说白了,就是把物理显卡直接透传给虚拟机,让虚拟机里的驱动直接操作硬件。

但说实话,这个配置起来挺麻烦的。你需要:

  • CPU支持VT-d(Intel)或AMD-Vi(AMD)
  • 主板BIOS开启IOMMU
  • 把GPU从宿主机驱动中解绑,再绑定到vfio-pci驱动
# 查看GPU的PCI地址
lspci | grep VGA

# 解绑GPU
echo "0000:01:00.0" > /sys/bus/pci/devices/0000:01:00.0/driver/unbind

# 绑定到vfio-pci
echo "vfio-pci" > /sys/bus/pci/devices/0000:01:00.0/driver_override
echo "0000:01:00.0" > /sys/bus/pci/drivers/vfio-pci/bind
警告: GPU Passthrough 一旦配置错误,宿主机可能直接黑屏。我建议你先用一张便宜的亮机卡做宿主机显示,把主力显卡透传给虚拟机。别问我怎么知道的,说多了都是泪。

2.3.3 软件模拟器:SWIFt与GPUOcelot

如果你只是想测试驱动逻辑,不想碰硬件,可以用纯软件模拟器。SWIFT(Simple Windows GPU Driver Framework)是我见过最轻量的GPU模拟器,它用软件模拟了WDDM的DDI接口,非常适合做单元测试。

GPUOcelot则更适合做CUDA驱动的模拟。它能把PTX指令翻译成LLVM IR,然后在CPU上执行。虽然慢,但能帮你验证指令调度的正确性。

我的建议: 开发初期,先用QEMU + virtio-gpu跑通基本流程。等驱动稳定了,再用GPU Passthrough做性能测试。至于软件模拟器,适合做回归测试,不适合做功能验证——因为模拟器毕竟不是真硬件,有些bug在模拟器上根本复现不了。

好了,环境搭建这部分就讲到这里。下一章,我们会正式开始写GPU驱动的核心代码——从初始化到命令提交,一步步把GPU跑起来。你准备好了吗?