10.3 DRM/TTM 灵魂拷问 100 问:drm_gpusvm_get_pages() 中的 goto map_pages
这是对drivers/gpu/drm/drm_gpusvm.c文件里drm_gpusvm_get_pages()函数中一行代码的解释文档gotomap_pages;该goto出现在映射/分配逻辑处用于在释放锁后重新进入“映射”分支。下面解释原因、正确性和可替代实现。1. 背景drm_gpusvm_get_pages()使用 HMMhmm_range_fault抓取 CPU PFN 信息并在得到 PFN 列表后做 DMA 映射dma_map_page或dpagemap-ops-device_map。关键点DMA 映射必须在持有gpusvm-notifier_locknotifier 锁的情况下进行以避免在映射期间页被释放或 unmapped而分配内存例如kvmalloc_array可能睡眠因此不能在持锁时执行。因此实现采用如下模式在map_pages:标签处统一处理“加锁 - 检查 - 映射”的逻辑如果检测到需要分配 dma 地址数组svm_pages-dma_addr NULL先解锁分配内存允许睡眠然后通过goto map_pages重新进入带锁的映射路径。2. 控制流先通过 HMMhmm_range_fault填充pfns并保存hmm_range.notifier_seq。跳到map_pages标签这里会获取gpusvm-notifier_lock并重新检查hmm_range.notifier_seq是否有效。若序号失效释放锁并重试 outerretry。这保证了分配/重试期间如果有 MMU 变化能够发现并安全重试。若svm_pages-dma_addr为 NULL解锁并进行kvmalloc_array分配可能睡眠然后goto map_pages回到步骤 2。在持锁情况下进行逐页映射device_map / dma_map_page 等写入svm_pages-dma_addr。这个流程保证了不在持锁时做会睡眠的分配且在重新进入映射路径后再次验证序号和标志避免竞态条件。3. 为什么使用goto优点简洁将“加锁 - 检查 - 映射”集中在一个位置避免重复写两套加锁/检查逻辑。易读在内核风格下内核代码里经常使用标签 goto 来实现“解锁-睡眠-重试”的常见模式相较于深层嵌套或状态机它更直观。语义清晰goto map_pages表示“完成分配后重新进入映射步骤”语义直接。4. 正确性与并发注意事项序号检测在map_pages处调用mmu_interval_read_retry(notifier, hmm_range.notifier_seq)若返回 true 则说明 HMM 的观察窗口已改变代码会释放锁并回到外层retry重新 fault再次尝试。这是防止分配/解锁间发生 CPU-side 映射变化导致的竞态。flags 检查在持锁后立即读取svm_pages-flags并处理flags.unmapped以应对映射被 unmap 的情况。锁的持有时长映射本身调用 dma/device_map在持锁时进行确保不会访问已释放的 page 结构或 dma 记录。总体来讲这个模式是内核中常见且被接受的做法先检查需要的资源、若需要做可能睡眠的操作就解锁、做操作、再重试进入临界区并继续。