NVIDIA开源GPU内核模块:4大模块源码级拆解
2026/9/10 20:57:22 网站建设 项目流程

NVIDIA开源GPU内核模块:4大模块源码级拆解

【免费下载链接】open-gpu-kernel-modulesNVIDIA Linux open GPU kernel module source项目地址: https://gitcode.com/GitHub_Trending/op/open-gpu-kernel-modules

新内核落地,闭源包只能等

NVIDIA 开源 GPU 内核模块 open-gpu-kernel-modules 是 Linux 下 NVIDIA 驱动的内核态完整源码,包含 nvidia、nvidia-modeset、nvidia-drm、nvidia-uvm 四个 .ko 模块,覆盖 GPU 初始化、显示流水线、统一虚拟内存(UVM)等内核侧逻辑。工程上的动机很直接:新的 stable 内核发布后,闭源 .run 包里的内核模块是预编译二进制,要等适配版本,滞后常以周计;而这个仓库可以在目标内核上直接重编译。本文围绕该仓库,讲清 GPU 内核模块架构的分层逻辑、显示与 UVM 两条主调用链、4 个性能优化点的实现与验证路径,以及从源码到模块加载的完整步骤。

GPU 内核模块架构分层:OS 无关核心与内核接口层

open-gpu-kernel-modules/ ├── kernel-open/ # 内核接口层(随目标内核重编译) │ ├── common/ # 四模块共享头文件(ioctl 号、状态码等) │ ├── nvidia/ # nvidia.ko:字符设备、PCI、中断、电源管理 │ ├── nvidia-drm/ # nvidia-drm.ko:DRM connector/encoder/CRTC/fb │ ├── nvidia-modeset/ # nvidia-modeset.ko:KMS 与 DRM 之间的显示流水线 │ ├── nvidia-uvm/ # nvidia-uvm.ko:统一虚拟内存、缺页、迁移 │ └── nvidia-peermem/ # nvidia-peermem.ko:GPU peer memory 接入 ├── src/ # OS 无关核心逻辑 │ ├── nvidia/ # nvidia.ko 核心:HAL 层、RM 资源管理器 │ ├── nvidia-modeset/ # nvidia-modeset.ko 核心:模式设置逻辑 │ └── common/ # 公共代码:nvlink、nvswitch、display 等 └── nouveau/ # nouveau 固件提取工具

拆分逻辑是:src/下的高频核心逻辑(硬件抽象层 HAL、资源管理器 RM、NVLink 状态机)不感知 Linux 内核版本波动,构成驱动的"厚"部分;kernel-open/是每个 .ko 的薄接口层,只处理字符设备注册、PCI 操作、中断与电源管理等版本敏感代码。闭源包只能为前者提供预编译二进制、为后者现场编译;开源后整条链路都能在目标内核上从头编译,这也让支持 Turing 及后续各代 GPU 的芯片映射代码(src/nvidia/generated/g_chips2halspec.h等)全部可见。

注意边的方向:用户态请求只进 nvidia.ko 和 nvidia-uvm.ko 两个字符设备;nvidia-drm 与 nvidia-modeset 之间双向交互(函数表下行、回调上行);UVM 更新页表走 CE 通道,不经过 DRM。

🔍 显示与 UVM 两条主调用链

先握手:nvidia-drm 如何拿到 nvidia-modeset 的函数表

显示路径上 nvidia-drm.ko 与 nvidia-modeset.ko 是两个独立模块,前者不能直接调用后者符号,靠一张带版本号的函数表桥接。kernel-open/nvidia-drm/nvidia-drm.c 的nv_drm_init

int nv_drm_init(void) { #if defined(NV_DRM_AVAILABLE) if (!nvKmsKapiGetFunctionsTable(&nvKmsFuncsTable)) { NV_DRM_LOG_ERR( "Version mismatch: nvidia-modeset.ko(%s) nvidia-drm.ko(%s)", nvKmsFuncsTable.versionString, NV_VERSION_STRING); return -EINVAL; } nvKms->setCallbacks(&nv_drm_kapi_callbacks); return nv_drm_probe_devices(); #else return 0; #endif }

这段代码做了三件事:nvidia-drm 声明了带versionString的函数表nvKmsFuncsTable并把地址交给 nvidia-modeset-linux.c 中导出的nvKmsKapiGetFunctionsTable,版本不匹配立即返回-EINVAL,杜绝混插不同版本的两个 .ko;匹配后setCallbacks注册 probe/suspend/resume/remove 四个回调;随后才调用nv_drm_probe_devices开始设备发现。反向数据流也走这张表——modeset 发现 GPU 后回调 nvidia-drm 注册的 probe 创建drm_device,之后的模式设置请求才从 DRM 层流向 modeset,最终由 nvidia.ko 的 RM 落到显示引擎寄存器。

再主链:open /dev/nvidia-uvm → 初始化 → 首次缺页迁移

计算侧应用(CUDA 等)统一经/dev/nvidia-uvm访问 GPU 内存。open本身是个轻量操作,kernel-open/nvidia-uvm/uvm.c 中:

static int uvm_open(struct inode *inode, struct file *filp) { struct address_space *mapping; NV_STATUS status = uvm_global_get_status(); if (status != NV_OK) return -nv_status_to_errno(status); mapping = uvm_kvmalloc(sizeof(*mapping)); if (!mapping) return -ENOMEM; address_space_init_once(mapping); mapping->host = inode; mapping->a_ops = inode->i_mapping->a_ops; filp->private_data = NULL; filp->f_mapping = mapping; return NV_OK; }

注释里写明了动机:同 inode 的所有 file 默认共享一个address_space,一次unmap_mapping_range会波及所有进程;而 UVM 把 mapping offset 用作进程虚拟地址的标识,所以uvm_open为每次打开分配私有的address_space,保证失效操作只影响本进程。

随后进程下发UVM_INITIALIZEioctl,uvm_api_initialize调用uvm_va_space_create(filp->f_mapping, ...),以这块 mapping 为根创建进程级uvm_va_space_t;接着注册 GPU(UVM_REGISTER_GPU)、建立虚拟地址映射(UVM_MAP_*),地址空间就绪——此时数据还不在 GPU 显存里。

首次 GPU 侧访问触发 MMU 可重放缺页:缺页描述被写入 GPU 端 fault buffer,UVM 的工作线程调用uvm_parent_gpu_service_replayable_faults(uvm_gpu_replayable_faults.c)读出缺页,定位对应uvm_va_block,驱动该 block 的页面迁移——系统内存页经 CE 拷贝引擎搬入显存,uvm_page_table_update写新页表项,被暂停的 GPU 线程重放。迁移本体在 uvm_migrate.c 与uvm_migrate_pageable.c。这条链上 UVM 从不直接碰 RM:内存分配、资源查询全部经 nvidia.ko 暴露的内核接口(kernel-open/common/inc/rm-gpu-ops.huvm_rm_mem.c)完成——nvidia.ko 是四模块的地基,持有 GPU 对象生命周期、中断与电源状态。

4 个性能优化点:机制、动机与验证路径

2MB block 粒度的内存管理

  • 机制:UVM 用两级结构管理地址空间——uvm_va_range按映射区间记录属性(所属 GPU、访问权限),其内划分为uvm_va_block(默认 2MB),页表状态、迁移状态、缺页统计都挂在 block 级。
  • 为什么:2MB 与 GPU MMU 大页尺寸对齐,一条 TLB 项覆盖更大范围,减少页表遍历开销;迁移、预取、解除映射都以 block 为批量单位,CE 拷贝与页表更新在边界上最省。
  • 验证:block 结构体与状态机见 uvm_va_block.h,区间管理在uvm_va_range.c

UVM 页面迁移的触发链路与预取

  • 机制:上文主链是被动迁移;其上有预取层——block 发生缺页后,启发式模块根据历史缺页分布推断访问模式,启用 prefetch fault(uvm_parent_gpu_enable_prefetch_faults),提前把后续 block 拉入 GPU。
  • 为什么:被动迁移让每个 block 各付一次"缺页—中断—迁移"往返,而流式负载的访问顺序高度可预测,把部分被动开销换成主动预取是隐藏延迟成本最低的方式。
  • 验证:uvm_perf_heuristics.c 与uvm_perf_module.c同目录,预取开关在uvm_gpu_replayable_faults.c末尾。

NVLink 多 GPU 通信的源码入口

  • 机制src/common/nvlink/下的kernel/nvlink/实现 inband 链路训练状态机,interface/是协议定义;kernel-open/nvidia/nv-p2p.c 实现双 GPU 间 p2p 映射,UVM 侧另有uvm_va_range_device_p2p.c支撑虚拟地址上的跨卡映射。
  • 为什么:多卡负载(模型并行、集合通信)若走 PCIe 交换,带宽与时延都劣于卡内路径;NVLink 直连提供高带宽通道,而链路训练、错误恢复、内存映射必须落在内核态,源码全部在本仓库可见。
  • 验证:src/common/nvlink/ 与 nv-p2p.c 两条线对照读即可。

PCI 层动态电源管理

  • 机制:kernel-open/nvidia/nv-pci.c 末尾通过driver.pm = &nv_pm_ops注册标准dev_pm_ops(约 2969 行处),把 GPU 的 suspend/resume 挂进系统 PM 框架;nv-platform-pm.c处理平台上电/掉电顺序;RM 内部再按负载切换 P-state。
  • 为什么:单卡功耗可达数百瓦,系统休眠与空闲场景要求内核能安全下电 GPU 并在 resume 后恢复显示与内存状态;把散落的电源逻辑锚定在标准dev_pm_ops上,才能与整机休眠协议协同。
  • 验证nv-pci.cnv_pm_ops注册点与nv-platform-pm.c的电源时序函数。

实战:从克隆到模块加载

  1. 获取源码:
git clone https://gitcode.com/GitHub_Trending/op/open-gpu-kernel-modules cd open-gpu-kernel-modules
  1. 编译内核模块(需当前内核头文件):make modules -j$(nproc)
  2. 安装:sudo make modules_install
  3. 用户空间组件配合:安装对应版本 .run 并跳过预编译内核模块:sudo sh ./NVIDIA-Linux-x86_64-<version>.run --no-kernel-modules

open-gpu-kernel-modules 编译选项速查

选项作用适用场景
NV_VERBOSE=1输出详细编译命令与宏定位编译错误
DEBUG=1调试构建,含调试符号与额外日志驱动开发调试
ARCH=aarch64指定目标架构ARM 目标交叉编译
CROSS_COMPILE=aarch64-linux-gnu-交叉工具链前缀与 ARCH 配套

参与入口

贡献流程见仓库根目录CONTRIBUTING.md。一个低门槛方向:先选一个成熟芯片族把 HAL 调用链读通——芯片族映射在src/nvidia/arch/nvalloc/,显示时序在src/common/modeset/timing/;能完整走通一条链路后,为新 GPU 架构补齐 HAL 适配(寄存器访问、时钟、电源)的路径就很明确。UVM 侧uvm_unit_test.h自带的单元测试框架也支持在其上扩展用例。

【免费下载链接】open-gpu-kernel-modulesNVIDIA Linux open GPU kernel module source项目地址: https://gitcode.com/GitHub_Trending/op/open-gpu-kernel-modules

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询