资讯详情

AMD GPU 内核驱动分析(一)总览

📅 2026/11/2 9:37:04 | 华诺云谱 👁 阅读
AMD GPU 内核驱动分析(一)总览
和CPU相比GPU中包含了大量的并行计算单元适合处理像素矩阵坐标等大量同类型的数据因此很多LINUX上的应用程序为了能够利用GPU的加速功能都试图和GPU直接打交道因此系统中可能有多个组件或者程序同时使用GPU比如桌面系统中OpenGL的实现MESA。这样会带来一个问题就是多个进程并发访问GPU如果控制不好势必会造成系统工作不稳定为了解决这样的问题LINUX内核开发者在内核中设计了DRM模块所有访问GPU的操作都通过DRM统一管理由DRM来统一协调对GPU的访问所以在Linux系统中各类GPU驱动包括NVIDIAAMD等大厂的GPU驱动都是集成在DRM框架中的.以AMD GPU驱动为例AMD GPU的内核驱动结构上是一个复杂的软件堆栈负责管理和控制AMD图形硬件以便应用程序可以与GPU进行通信并利用其图形处理能力这些能力都是通过DRM提供的DRM屏蔽了不同显卡厂家的硬件差异以统一一致的接口向上层提供服务。以下是AMD GPU内核驱动的主要组成部分硬件抽象层HALHAL是位于底层的部分负责与GPU硬件之间的通信。它提供了一个抽象的接口使上层的驱动和应用程序可以访问GPU的功能和寄存器而不需要了解具体的硬件细节。图形命令处理器GCPGCP负责接收、解析和执行应用程序发送的图形命令。它将应用程序的图形请求翻译成GPU硬件可以理解的指令以执行绘图操作和渲染任务。AMDGPU内核驱动这是AMD GPU驱动的核心部分运行在操作系统的内核空间。它与硬件通信管理GPU资源如显存和显卡寄存器执行调度和任务分配以确保多个任务在GPU上正确运行。GPU用户空间驱动GPU用户空间驱动是运行在操作系统的用户空间的组件它与AMDGPU内核驱动协同工作提供对GPU的高级控制。它包括OpenGL和Vulkan驱动程序以及OpenCL运行时库允许应用程序与GPU进行图形和通用计算任务的交互。AMDGPU-PROAMDGPU-PRO是AMD的专业级GPU驱动主要用于支持专业应用程序和工作负载如CAD、3D建模和科学计算。它提供了更丰富的特性和支持包括对专业图形API和库的更好的兼容性。AMD ROCmAMD ROCmRadeon Open Compute是一个开源的GPU计算平台旨在支持GPU加速的深度学习和高性能计算工作负载。它包括ROCm内核驱动和ROCm用户空间工具为GPU计算提供了强大的支持。总之AMD GPU的内核驱动结构是一个多层次的系统由硬件抽象层、内核驱动、用户空间驱动和专业级组件组成以便应用程序可以有效地利用AMD GPU的图形和计算能力。这些组件共同协作确保GPU能够执行各种图形和计算任务。图中的箭头表示源模块会调用箭头指向的目标模块中的函数或者符号这种调用通常和LINUXN内核中的框架驱动注册相对应即由架构中低层次的模块向框架中高层次的模块注册操作句炳所以实际的架构层次应该是上下颠倒的也就是说从架构上看应该是DRM在上而AMDGPU在最底层drm.ko只有扇入口没有扇出口。PS:或者将drm.ko理解为支撑库来说更为合理drm.ko提供了和设备无关的DRM核心支撑函数同时也是用户态DRM设备节点的入口模块位于架构上层理所当然。虽然不清楚N卡的官方驱动的实现细节但作为对比从模块拓扑来看和AMD GPU应用架构很类似NVIDIA NOUVEAU开源驱动同样集成于DRM框架中模块之间的依赖比AMD更加简洁一些不过这可能意味着驱动的实现更加复杂。单纯从核显的角度作对照可能拿INTEL的核显作为对比最为合适毕竟两者面对的是同一个市场INTEL的核显I915驱动拓扑如下再看一个下图是VIRTUALBOX中基于虚拟GPU的DRM框架vmwgfx.ko是虚拟GPU的KMD.对比三张拓扑可以看到DRM和GPU之间的关系大同小异都是KMD将自身注册进入DRM驱动框架内部UMD通过DRM框架调用KMD的实现模块关联图架构上是从底层到上层的如果从调用层次上看应该把图上下颠倒同样作为虚拟机vmware是直接基于vmwgfx提供KMD的角色小结显卡是DRM设备的后端所以进一步抽象GPU和DRM框架的架构建立它们之间的联系似乎是这样的DRM以字符设备IOCTL命令的形式向用户态提供功能IOCTL的实现有两种一种是DRM通用的CMD,另一种是GPU specific实现GPU Specific的CMD范围在(0x40-0xA9).drm framwork通用IOCTL命令包括例如DRM_IOCTL_GEM_FLINK,DRM_IOCTL_PRIME_HANDLE_TO_FD,DRM_IOCTL_PRIME_FD_TO_HANDLE等。其中FLINK接口Frame Buffer Link是 Linux DRM 子系统中一个用于在不同进程间共享 GEM 缓冲区对象Buffer Object, BO的传统机制。它通过一个全局名称Global Name作为中介让多个进程能够访问同一块 GPU 内存。由于FLINK 的名称是一个可被猜测的 32 位整数且没有细粒度的访问控制。任何能够访问 DRM 设备并拥有相应权限的客户端都有可能通过遍历或猜测name来访问到本不属于它的缓冲区这构成了信息泄露的风险。现代 DRM 客户端不应再使用flink而应改用基于文件描述符fd的 PRIME / dma-buf 机制。dma-buf 利用文件描述符本身的安全模型提供了更精细的访问控制和更安全的进程间缓冲区共享能力。DRM_IOCTL_PRIME_FD_TO_HANDLE/DRM_IOCTL_PRIME_HANDLE_TO_FD两个IOCTL完成的就是GEM HANDLE和DMA BUF FD之间的创建和互转。尽管命令空间被划分但用户态的所有ioctl调用都只会到达一个统一的入口函数drm_ioctl()。内核并不为驱动和通用命令维护两个独立的文件操作入口。DRM的IOCTL机制通过统一的入口和严格的编号空间划分实现了核心通用代码与驱动私有代码的清晰解耦。0x40-0xA9这个范围恰好跨越了“完全由驱动自治”与“内核核心接管”的边界。以锐龙R5600G核显VEGA7为例其支持DX12,Vulkan,OpenCL,OpenGL等多种用户态编程接口这些接口在GPU软件栈中的作用基本上相当于Posix在CPU侧的作用是构成GPU生态系统(渲染计算多媒体的基石非常重要。AMDKFD驱动配置编译架构分析amdkfd目录最终会和amdgpu目录的源码一同编译生成amdgpu.koAMD GPU驱动主要是指amdgpu.ko.和AMDGPU相关的配置AMDGPU驱动初始化AMDGPU由多个硬件处理单元组成IP核block),任何一条GPU的渲染命令其最终都是发送到GPU的硬件单元上硬件单元可以处理一些通用的渲染命令也可能处理一些专有渲染命令因此一条GPU渲染命令需要指明它发往的硬件单元。实际上mesa驱动针对GPU的每个硬件单元都抽象了具体的结构当硬件单元驱动想给自己对应的硬件发送GPU命令时就需要设置硬件的类型。就是这里的ring_type。为什么叫ring_type呢因为每个硬件处理单元和CPU之间时通过ring buffer传递渲染命令的每个硬件单元上都有一个或者多个ring buffer所以ring_type就是GPU硬件单元的类型。从DMESG LOG信息可以看到AMDGPU初始化时初始化了compute, gfx, sdma, jpeg_dec, vcn_enc,vcn_dec,kiq等ring.注册的调用顺序为amdgpu_pci_probe-amdgpu_driver_load_kms-amdgpu_device_init-amdgpu_device_ip_early_init-amdgpu_discovery_set_ip_blocks-amdgpu_discovery_set_ih_ip_blocks-amdgpu_device_ip_block_add.最终由amdgpu_device_ip_block_add完成设备的注册。然后会在如下调用堆栈中完成RING调度线程的初始化。...-amdgpu_device_init-amdgpu_device_ip_init-amdgpu_ring_init-amdgpu_device_init_schedulers-drm_sched_init-kthread_run(drm_sched_main,...AMDGPU驱动会为每个RING创建一个内核线程专门负责对这个RING上的COMMAND PACKET进行调度。AMDGPU对应的DRM驱动结构体drm_driver为amdgpu_kms_driver对象定义在文件amdgpu_drv.c中在启用AMDGPU系统上这个结构体即是DRM框架的后端。2791 static const struct drm_driver amdgpu_kms_driver { 2792 .driver_features 2793 ¦ DRIVER_ATOMIC | 2794 ¦ DRIVER_GEM | 2795 ¦ DRIVER_RENDER | DRIVER_MODESET | DRIVER_SYNCOBJ | 2796 ¦ DRIVER_SYNCOBJ_TIMELINE, 2797 .open amdgpu_driver_open_kms, 2798 .postclose amdgpu_driver_postclose_kms, 2799 .lastclose amdgpu_driver_lastclose_kms, 2800 .ioctls amdgpu_ioctls_kms, 2801 .num_ioctls ARRAY_SIZE(amdgpu_ioctls_kms), 2802 .dumb_create amdgpu_mode_dumb_create, 2803 .dumb_map_offset amdgpu_mode_dumb_mmap, 2804 .fops amdgpu_driver_kms_fops, 2805 .release amdgpu_driver_release_kms, 2806 2807 .prime_handle_to_fd drm_gem_prime_handle_to_fd, 2808 .prime_fd_to_handle drm_gem_prime_fd_to_handle, 2809 .gem_prime_import amdgpu_gem_prime_import, 2810 .gem_prime_mmap drm_gem_prime_mmap, 2811 2812 .name DRIVER_NAME, 2813 .desc DRIVER_DESC, 2814 .date DRIVER_DATE, 2815 .major KMS_DRIVER_MAJOR, 2816 .minor KMS_DRIVER_MINOR, 2817 .patchlevel KMS_DRIVER_PATCHLEVEL, 2818 };DRM Driver initialization process are launched by AMD GPU PCIE Driver probe procedure.AMDKFD驱动AMD的amdkfd 可以理解为在 DRM 子系统中提供了 CPU 与 GPU 沟通的快速通道使得两者可以平等的访问内存资源而无需额外拷贝。AMDKFD驱动在内核中通过CONFIG_HSA_AMD配置项使能/关闭,关闭后整个KFD的注册全部为空函数系统自然也就不会创建/dev/kfd设备节点。KFD和GPU的渲染多媒体功能都隐藏在内核DRM框架后面两个是并行独立的模块KFD的存在目的是为了给HPC计算场景提供一个快速访问GPU的通道对于DRM渲染功能来说并不是必不可少的所以当关闭CONFIG_HSA_AMD后并不影响正常的DRM渲染功能。具体分析可以参考这篇对HSA架构的分析AMD HSA 异构计算架构和AMD-KFD内核驱动NVIDIA内核驱动_papaofdoudou的博客-CSDN博客AMDGPU固件amdgpu固件分布在/lib/firmware/amdgpu/lib/firmware/amd两个目录, GPU驱动加载时通过内核API request_firmware装载进GPU。AMDGPU驱动初始化流程GPU工作context1.pasid是 per file的不是per进程的.2.每个struct file下可以创建多个context,共享同一个PASID(drm设备open时分配的).3.pasid在不同的struct file是不同的便于细粒度控制不同的打开上下文。释放PASID堆栈发生在文件关闭的时候前面截图中线程分配了CTXID 1,2,3,4退出时只释放了1,2,3。 4实际上是在这里释放的下图显示某个进程可能或多次打开显卡设备得到多个PASID比如xorg进程还有一些进程只打开了一次获取1个PASID比如systemd-udevd,drawio。渲染pasid和计算pasid:基于AMDGPU的渲染框架DRM和计算框架KFD是完全不同的两种软件实现架构其PASID管理方式也有所差别在KFD中PASID是按进程分配而非按文件分配在KFD中每个进程绑定一个PASID.pasid分配通过amdgpu_pasid_alloc实现渲染和计算各自从不同的框架调用入口下来TODO一个进程在同一个设备上对应多个PASID的情况下如何实现UVA(unifiy virtual address)机制是个问题否则同一个进程内属于不同PASID的同一个虚拟地址映射到不同的物理内存岂不会对应用带来一些不便通过在关键路径上跟踪进程文件描述符PASID二元对的使用情况这个问题的答案似乎是虽然每个进程可能会绑定多个PASID不过貌似只会使用其中一个PASID(filep)来创建分配设备内存资源比如下图展示的gnome-shell, love, drawio,xorg.gpu_sched原理gpu 的调度策略如下图所示1.每个context定义一个三维的entity表作为某个指定IP Block的某个RING比如GFX支持四个RING而且VCN支持2个RING下某个JOB的容器。2.从AMDGPU数据结构的设计来看驱动初始化时为每个ring创建一个调度线程(amdgpu_ring和struct drm_gpu_scheduler)每个调度线程(gpu ring)管理四个优先级调度队列.3.entity是JOB的容器JOB中包含具体的由GPU执行的某个操作的渲染指令或者解码指令而entity代表了用户的操作ring buffer,它和某个RING上的某个优先级队列管理结构对应。4.不同上下文的entity如果分配在同一个IP BLOCK的同一个RING上的同优先级则通过优先级队列中struct drm_sched_rq-rb_tree_root红黑数管理管理值为最久等待时间oldest_job_waiting通过JOB提交时调用ktime_get获得.5.每个JOB代表一个doorbell间隔用户态不进行实际的trigger doorbell寄存器操作对用户态来说就是提交JOB真正的ring doorbell寄存器被KMS接管。6.amdgpu_ctx_alloc分配amdgpu_ctx对象后者是entity对象寄生的容器每个进程可以分配1个或者多个amdgpu_ctx对象由用户主动分配amdgpu_ctx里面包含数个entity,如果用户觉得不够用则可以继续分配新的amdgpu_ctx对象。7.描述JOB对象的结构体有两个struct amdgpu_job和struct drm_sched_job,后者是前者的基类。entity 是虚拟QUEUEJOB就是QUEUE中的元素而drm sched main调度线程的任务就是将虚拟QUEUE(entity)中的JOB提交到硬件队列上执行。8.每个GPU ring上调度线程上管理着4个调度队列entity挂在这些调度队列上JOB又挂在entity上每个entity对象持有当前所属的调度队列的指针以及当前所属调度对象的指针通过后者entity对象可以切换到其他三个不同的调度队列上。进程struct amdgpu_ctx和struct amdgpu_ctx_entity层级图调度策略entity是job的容器drm_sched_main 调度线程在选择可以运行的entity的时候涉及到一个调度策略问题在6.X内核之前从sched_rq中选择可以运行JOB的entity使用的是优先级roundrobin调度策略首先安按照sched_rq 3/2/1/0 的优先级顺序选择一个最高优先级的sched running queue,之后在当前选择的sched_rq里面根据round robin选择一个可以运行的entity投入运行。这种方式下sched_rq是通过list_head链表管理之下的所有entity的。下图表示和调度相关的几个关键数据结构之间的关系1.struct amdgpu_ring和struct drm_gpu_scheduler是一对一的关系每个GPU ring对应一个内核struct drm_gpu_scheduler调度对象对应一个内核线程。理论或者实践中应该也可以一个调度对象管理多个 gpu ring也就是一个调度线程管理多个GPU ring).2.每个RING调度对象包含四个struct drm_sched_rq对象后者实际上就是调度队列其中包含了entity对象。调度队列组织的方式可以是QUEUE或者红黑树。3.每个struct drm_sched_rq对象包含了多个entity对象每个entity对应用户空间某个命令队列。4.每个entity对象连接起多个structdrm_sched_job对象代表应用的某笔渲染命令包序列通常表示一个doorbell周期需要执行的任务包。5.entity对象会反向指向其所在的struct drm_sched_rq对象同时通过struct drm_gpu_scheduler **sched_list指针数组间接引用了其可以运行于其上的调度器对象意味着某个entity可以选择投入到多个相同性质的ring中的任意一个去执行比如有多个渲染ring,计算ring则相应队列可以选择任何一个ring执行为实施任务负载均衡算法提供了操作空间。而6.X内核之后调度策略增加了一种模式就是FIFO这种方式是通过红黑树对entity进行管理上图中绘制的就是这种方式。 为了兼容老的RR方式AMDGPU模块定义了drm_sched_policy变量进行控制在FIFO这种方式下红黑树使用JOB投入时间作为键值进树。直到调度器将时间窗口内的JOB全部取完后spcs queue未空下面的FIRST逻辑再次运行时彩才会切换entity在红黑数中的位置。简而言之调度器在选择entity的时候会判断drm_sched_policy的配置根据调度策略的不同选择使用FIFO还是round robin策略。AMDGPU对FIFO调度的支持是在6.0内核开始的,commit id为08fb97de03aa2205c6791301bd83a095abc1949c, comment里面详细解释了针对的问题和修改思路可以参考linux中对gpu_scheduler的应用情况linux kernel对gpu_scheduler的应用场景较为广泛5.15版本的内核中在AMD渲染卡商业GPU的开源逆向工程中应用较为广泛如下图表中所示清华linux-stable镜像仓库:https://mirrors.tuna.tsinghua.edu.cn/git/linux-stable.git参考资料https://www.x.org/docs/AMD/old/R5xx_Acceleration_v1.5.pdfhttps://blog.csdn.net/tugouxp/article/details/131619250?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22131619250%22%2C%22source%22%3A%22tugouxp%22%7Ddma-fence简析以及使用demo_papaofdoudou的博客-CSDN博客AMD GPU虚拟化_amd显卡虚拟化_shuaifeng.zhang的博客-CSDN博客结束
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑