Linux HMM 原理补充篇:实现机制详解

本文是Linux HMM(Heterogeneous Memory Management)原理与实现详解的补充篇。有些内容重复,就当复习了。

目录

  1. 引言
  2. HMM 核心概念
  3. 关键数据结构
  4. 核心机制实现
  5. 页表遍历与缺页处理
  6. 并发控制与同步
  7. DMA 映射扩展
  8. 性能优化策略
  9. 总结

引言

HMM (Heterogeneous Memory Management) 是 Linux 内核提供的一种机制,旨在实现 CPU 和异构设备(如 GPU、FPGA 等)之间的统一虚拟地址空间(Unified Virtual Addressing, UVA)。通过 HMM,设备可以直接访问进程的虚拟地址,无需在用户空间和设备内存之间显式拷贝数据,从而实现零拷贝和高效的异构计算。

主要优势:

  • 统一地址空间:CPU 和设备使用相同的虚拟地址
  • 按需迁移:页面可在 CPU 内存和设备内存间自动迁移
  • 透明性:对应用程序透明,无需修改现有代码
  • 高效同步:通过 MMU Notifier 机制实现内存一致性

HMM 核心概念

2.1 设计目标

HMM 的核心目标是允许设备透明地访问进程虚拟地址空间,主要解决以下问题:

  1. 地址空间统一:消除 CPU 和设备间的地址空间隔离
  2. 内存一致性:确保 CPU 和设备看到一致的内存视图
  3. 动态迁移:支持页面在不同内存域间迁移
  4. 细粒度控制:支持页级别的访问权限控制

2.2 工作模式

HMM 支持两种主要工作模式:

2.2.1 镜像模式(Mirroring)

设备直接访问系统内存中的页面,页表由 CPU MMU 管理。

2.2.2 设备私有内存模式(Device Private Memory)

页面迁移到设备私有内存,CPU 访问时触发迁移回系统内存。


关键数据结构

3.1 struct hmm_range

这是 HMM 操作的核心结构,定义在 include/linux/hmm.h

struct hmm_range {
	struct mmu_interval_notifier *notifier;  // MMU 失效通知器
	unsigned long		notifier_seq;    // 序列号,检测并发修改
	unsigned long		start;           // 虚拟地址范围起始
	unsigned long		end;             // 虚拟地址范围结束
	unsigned long		*hmm_pfns;       // PFN 数组(输出)
	unsigned long		default_flags;   // 默认标志(读/写权限)
	unsigned long		pfn_flags_mask;  // PFN 标志掩码
	void			*dev_private_owner; // 设备私有页所有者
};

字段说明:

  • notifier: 关联的 mmu_interval_notifier,用于追踪内存失效事件
  • notifier_seq: 序列号,通过 seqcount 机制检测并发修改
  • start/end: 要查询的虚拟地址范围 [start, end)
  • hmm_pfns: 输出数组,存储每个页面的 PFN 和标志位
  • default_flags: 默认请求标志(如 HMM_PFN_REQ_FAULTHMM_PFN_REQ_WRITE
  • pfn_flags_mask: 允许每页独立设置的标志掩码
  • dev_private_owner: 用于识别设备私有页的所有者指针

3.2 enum hmm_pfn_flags

HMM 使用巧妙的编码方式,将 PFN 和标志位编码在同一个 unsigned long 中:

enum hmm_pfn_flags {
	/* 输出标志 - 高位 */
	HMM_PFN_VALID = 1UL << (BITS_PER_LONG - 1),  // PFN 有效
	HMM_PFN_WRITE = 1UL << (BITS_PER_LONG - 2),  // 可写
	HMM_PFN_ERROR = 1UL << (BITS_PER_LONG - 3),  // 错误(无法访问)
	
	/* 粘性标志 - 从输入传递到输出 */
	HMM_PFN_DMA_MAPPED = 1UL << (BITS_PER_LONG - 4),  // 已 DMA 映射
	HMM_PFN_P2PDMA     = 1UL << (BITS_PER_LONG - 5),  // P2P DMA 页
	HMM_PFN_P2PDMA_BUS = 1UL << (BITS_PER_LONG - 6),  // 总线地址 P2P
	
	/* Order 编码位置 */
	HMM_PFN_ORDER_SHIFT = (BITS_PER_LONG - 11),
	
	/* 输入标志 */
	HMM_PFN_REQ_FAULT = HMM_PFN_VALID,  // 要求缺页
	HMM_PFN_REQ_WRITE = HMM_PFN_WRITE,  // 要求可写
	
	HMM_PFN_FLAGS = ~((1UL << HMM_PFN_ORDER_SHIFT) - 1),
};

编码格式(64位系统):

63 62 61 60 ... 11 10 9 8 ... 0
|  |  |  |      |  |  |        |
|  |  |  |      |  |  +--------+-- PFN (页帧号)
|  |  |  |      +--+-------------- Order (页面大小 2^order)
|  |  |  +------------------------ ERROR
|  |  +--------------------------- WRITE
|  +------------------------------ VALID
+--------------------------------- (保留)

3.3 struct hmm_vma_walk

页表遍历的私有上下文结构:

struct hmm_vma_walk {
	struct hmm_range	*range;   // 关联的 hmm_range
	unsigned long		last;     // 最后处理的地址(用于重试)
};

3.4 辅助函数

// 从 hmm_pfn 提取 struct page
static inline struct page *hmm_pfn_to_page(unsigned long hmm_pfn)
{
	return pfn_to_page(hmm_pfn & ~HMM_PFN_FLAGS);
}

// 从 hmm_pfn 提取物理地址
static inline phys_addr_t hmm_pfn_to_phys(unsigned long hmm_pfn)
{
	return __pfn_to_phys(hmm_pfn & ~HMM_PFN_FLAGS);
}

// 获取页面映射的 order(大小为 2^order 字节)
static inline unsigned int hmm_pfn_to_map_order(unsigned long hmm_pfn)
{
	return (hmm_pfn >> HMM_PFN_ORDER_SHIFT) & 0x1F;
}

核心机制实现

4.1 MMU Notifier 集成

HMM 依赖 mmu_interval_notifier 机制跟踪 CPU 页表的变化。

4.1.1 工作原理

当 CPU 页表发生变化时(如 unmap、写保护、页面迁移等),MMU Notifier 会触发回调通知设备驱动:

struct mmu_interval_notifier {
	struct interval_tree_node interval_tree;
	const struct mmu_interval_notifier_ops *ops;
	struct mm_struct *mm;
	struct hlist_node deferred_item;
	unsigned long seq;
};

struct mmu_interval_notifier_ops {
	bool (*invalidate)(struct mmu_interval_notifier *interval_sub,
			   const struct mmu_notifier_range *range,
			   unsigned long cur_seq);
};
4.1.2 序列号机制

使用 seqcount 实现无锁的并发检测:

// 开始读取序列号
unsigned long seq = mmu_interval_read_begin(notifier);

// 执行操作...

// 检查是否有并发修改
if (mmu_interval_check_retry(notifier, seq)) {
	// 发生并发修改,需要重试
	return -EBUSY;
}

4.2 hmm_range_fault() 核心流程

这是 HMM 的核心函数,负责查询和缺页处理:

int hmm_range_fault(struct hmm_range *range)
{
	struct hmm_vma_walk hmm_vma_walk = {
		.range = range,
		.last = range->start,
	};
	struct mm_struct *mm = range->notifier->mm;
	int ret;

	mmap_assert_locked(mm);

	do {
		/* 检查是否有并发修改 */
		if (mmu_interval_check_retry(range->notifier,
					     range->notifier_seq))
			return -EBUSY;
			
		/* 遍历页表 */
		ret = walk_page_range(mm, hmm_vma_walk.last, range->end,
				      &hmm_walk_ops, &hmm_vma_walk);
		/*
		 * 当返回 -EBUSY 时,循环重启,hmm_vma_walk.last 被设置为
		 * 尚未存储到 pfns 的地址。所有 < last 的条目已设置为输出值,
		 * >= last 的条目仍为输入值。
		 */
	} while (ret == -EBUSY);
	
	return ret;
}

流程说明:

  1. 初始化上下文:创建 hmm_vma_walk 结构
  2. 并发检测:检查 notifier_seq 是否变化
  3. 页表遍历:调用 walk_page_range() 遍历多级页表
  4. 重试机制:遇到 -EBUSY 时重试(如缺页、迁移等)
  5. 返回结果:填充 hmm_pfns 数组

4.3 缺页判断逻辑

static unsigned int hmm_pte_need_fault(
							const struct hmm_vma_walk *hmm_vma_walk,
				       unsigned long pfn_req_flags,
				       unsigned long cpu_flags)
{
	struct hmm_range *range = hmm_vma_walk->range;

	/* 合并每页请求和默认标志 */
	pfn_req_flags &= range->pfn_flags_mask;
	pfn_req_flags |= range->default_flags;

	/* 不需要任何操作 */
	if (!(pfn_req_flags & HMM_PFN_REQ_FAULT))
		return 0;

	/* 需要写权限?*/
	if ((pfn_req_flags & HMM_PFN_REQ_WRITE) &&
	    !(cpu_flags & HMM_PFN_WRITE))
		return HMM_NEED_FAULT | HMM_NEED_WRITE_FAULT;

	/* CPU 页表无效则需要缺页 */
	if (!(cpu_flags & HMM_PFN_VALID))
		return HMM_NEED_FAULT;
		
	return 0;
}

判断逻辑:

  • 不设置 REQ_FAULT:只查询当前状态,不触发缺页
  • REQ_FAULT + REQ_WRITE:要求可写,触发写时复制(COW)
  • REQ_FAULT:要求页面有效,触发换入/分配

页表遍历与缺页处理

5.1 页表遍历回调

HMM 定义了完整的页表遍历回调:

static const struct mm_walk_ops hmm_walk_ops = {
	.pud_entry	= hmm_vma_walk_pud,      // PUD 级别
	.pmd_entry	= hmm_vma_walk_pmd,      // PMD 级别
	.pte_hole	= hmm_vma_walk_hole,     // 页表空洞
	.hugetlb_entry	= hmm_vma_walk_hugetlb_entry,  // 大页
	.test_walk	= hmm_vma_walk_test,     // VMA 测试
	.walk_lock	= PGWALK_RDLOCK,         // 使用读锁
};

5.2 PMD 级别处理

static int hmm_vma_walk_pmd(
				  pmd_t *pmdp, unsigned long start,
			    unsigned long end, struct mm_walk *walk)
{
	struct hmm_vma_walk *hmm_vma_walk = walk->private;
	struct hmm_range *range = hmm_vma_walk->range;
	pmd_t pmd;

again:
	pmd = pmdp_get_lockless(pmdp);  // 无锁读取 PMD
	
	/* PMD 不存在 */
	if (pmd_none(pmd))
		return hmm_vma_walk_hole(start, end, -1, walk);

	/* PMD 迁移中 */
	if (thp_migration_supported() && is_pmd_migration_entry(pmd)) {
		if (hmm_range_need_fault(...)) {
			hmm_vma_walk->last = addr;
			pmd_migration_entry_wait(walk->mm, pmdp);
			return -EBUSY;  // 等待迁移完成
		}
		return hmm_pfns_fill(start, end, range, 0);
	}

	/* PMD 不存在(swap out 等)*/
	if (!pmd_present(pmd)) {
		if (hmm_range_need_fault(...))
			return -EFAULT;
		return hmm_pfns_fill(start, end, range, HMM_PFN_ERROR);
	}

	/* 透明大页 */
	if (pmd_trans_huge(pmd)) {
		pmd = pmdp_get_lockless(pmdp);
		if (!pmd_trans_huge(pmd))
			goto again;  // 并发拆分,重试
		return hmm_vma_handle_pmd(walk, addr, end, hmm_pfns, pmd);
	}

	/* 遍历 PTE */
	ptep = pte_offset_map(pmdp, addr);
	if (!ptep)
		goto again;
		
	for (; addr < end; addr += PAGE_SIZE, ptep++, hmm_pfns++) {
		int r = hmm_vma_handle_pte(walk, addr, end, pmdp, ptep, hmm_pfns);
		if (r) {
			/* hmm_vma_handle_pte() 已调用 pte_unmap() */
			return r;
		}
	}
	pte_unmap(ptep - 1);
	return 0;
}

5.3 PTE 级别处理

static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,
			      unsigned long end, pmd_t *pmdp, pte_t *ptep,
			      unsigned long *hmm_pfn)
{
	struct hmm_vma_walk *hmm_vma_walk = walk->private;
	struct hmm_range *range = hmm_vma_walk->range;
	pte_t pte = ptep_get(ptep);
	uint64_t pfn_req_flags = *hmm_pfn;
	uint64_t new_pfn_flags = 0;

	/* PTE 为空 */
	if (pte_none_mostly(pte)) {
		required_fault = hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, 0);
		if (required_fault)
			goto fault;
		goto out;
	}

	/* PTE 不在内存中 */
	if (!pte_present(pte)) {
		swp_entry_t entry = pte_to_swp_entry(pte);

		/* 设备私有页 - 由调用者拥有 */
		if (is_device_private_entry(entry) &&
		    page_pgmap(pfn_swap_entry_to_page(entry))->owner ==
		    range->dev_private_owner) {
			cpu_flags = HMM_PFN_VALID;
			if (is_writable_device_private_entry(entry))
				cpu_flags |= HMM_PFN_WRITE;
			new_pfn_flags = swp_offset_pfn(entry) | cpu_flags;
			goto out;  // 直接返回设备私有页的 PFN
		}

		/* 普通 swap 页 */
		if (!non_swap_entry(entry))
			goto fault;  // 触发换入

		/* 设备独占页 */
		if (is_device_exclusive_entry(entry))
			goto fault;

		/* 迁移中 */
		if (is_migration_entry(entry)) {
			pte_unmap(ptep);
			hmm_vma_walk->last = addr;
			migration_entry_wait(walk->mm, pmdp, addr);
			return -EBUSY;  // 等待迁移完成
		}

		/* 其他错误 */
		pte_unmap(ptep);
		return -EFAULT;
	}

	/* 正常页面 */
	cpu_flags = pte_to_hmm_pfn_flags(range, pte);
	required_fault = hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, cpu_flags);
	if (required_fault)
		goto fault;

	/* 特殊页面(非正常映射)*/
	if (!vm_normal_page(walk->vma, addr, pte) &&
	    !is_zero_pfn(pte_pfn(pte))) {
		if (hmm_pte_need_fault(hmm_vma_walk, pfn_req_flags, 0)) {
			pte_unmap(ptep);
			return -EFAULT;
		}
		new_pfn_flags = HMM_PFN_ERROR;
		goto out;
	}

	new_pfn_flags = pte_pfn(pte) | cpu_flags;
out:
	*hmm_pfn = (*hmm_pfn & HMM_PFN_INOUT_FLAGS) | new_pfn_flags;
	return 0;

fault:
	pte_unmap(ptep);
	/* 触发缺页 */
	return hmm_vma_fault(addr, end, required_fault, walk);
}

5.4 缺页处理

static int hmm_vma_fault(unsigned long addr, unsigned long end,
			 unsigned int required_fault, struct mm_walk *walk)
{
	struct hmm_vma_walk *hmm_vma_walk = walk->private;
	struct vm_area_struct *vma = walk->vma;
	unsigned int fault_flags = FAULT_FLAG_REMOTE;

	WARN_ON_ONCE(!required_fault);
	hmm_vma_walk->last = addr;

	/* 需要写权限?*/
	if (required_fault & HMM_NEED_WRITE_FAULT) {
		if (!(vma->vm_flags & VM_WRITE))
			return -EPERM;  // VMA 不允许写
		fault_flags |= FAULT_FLAG_WRITE;
	}

	/* 逐页触发缺页 */
	for (; addr < end; addr += PAGE_SIZE)
		if (handle_mm_fault(vma, addr, fault_flags, NULL) &
		    VM_FAULT_ERROR)
			return -EFAULT;
			
	return -EBUSY;  // 返回 EBUSY,触发重试
}

5.5 透明大页处理

static int hmm_vma_handle_pmd(struct mm_walk *walk, unsigned long addr,
			      unsigned long end, unsigned long hmm_pfns[],
			      pmd_t pmd)
{
	struct hmm_vma_walk *hmm_vma_walk = walk->private;
	struct hmm_range *range = hmm_vma_walk->range;
	unsigned long pfn, npages, i;
	unsigned int required_fault;
	unsigned long cpu_flags;

	npages = (end - addr) >> PAGE_SHIFT;
	cpu_flags = pmd_to_hmm_pfn_flags(range, pmd);
	
	/* 检查是否需要缺页 */
	required_fault = hmm_range_need_fault(hmm_vma_walk, hmm_pfns, 
					      npages, cpu_flags);
	if (required_fault)
		return hmm_vma_fault(addr, end, required_fault, walk);

	/* 填充所有页面的 PFN */
	pfn = pmd_pfn(pmd) + ((addr & ~PMD_MASK) >> PAGE_SHIFT);
	for (i = 0; addr < end; addr += PAGE_SIZE, i++, pfn++) {
		hmm_pfns[i] &= HMM_PFN_INOUT_FLAGS;  // 保留粘性标志
		hmm_pfns[i] |= pfn | cpu_flags;      // 设置 PFN 和标志
	}
	return 0;
}

并发控制与同步

6.1 锁机制

HMM 使用读写锁保护页表遍历:

static const struct mm_walk_ops hmm_walk_ops = {
	...
	.walk_lock	= PGWALK_RDLOCK,  // 使用 mmap_lock 读锁
};

特点:

  • 允许多个读者并发遍历
  • 不阻塞页表更新(使用无锁读取)
  • 通过序列号检测并发修改

6.2 无锁读取

pmd = pmdp_get_lockless(pmdp);  // 无锁读取 PMD

6.3 序列号检测

do {
	/* 检查序列号 */
	if (mmu_interval_check_retry(range->notifier, range->notifier_seq))
		return -EBUSY;
		
	ret = walk_page_range(...);
} while (ret == -EBUSY);

6.4 内存屏障

/* 确保读取顺序 */
rmb();  // Read Memory Barrier

DMA 映射扩展

7.1 HMM-DMA 架构

HMM 提供了 DMA 映射的扩展接口,支持将 HMM PFN 映射到 DMA 地址:

struct hmm_dma_map {
	size_t dma_entry_size;           // 每个 DMA 条目的大小
	unsigned long *pfn_list;         // PFN 列表
	dma_addr_t *dma_list;            // DMA 地址列表
	struct dma_iova_state state;     // IOVA 状态
};

7.2 分配 DMA 映射

int hmm_dma_map_alloc(struct device *dev, struct hmm_dma_map *map,
		      size_t nr_entries, size_t dma_entry_size)
{
	bool dma_need_sync = false;
	bool use_iova;

	/* 检查 DMA 限制 */
#ifdef CONFIG_DMA_NEED_SYNC
	dma_need_sync = !dev->dma_skip_sync;
#endif
	if (dma_need_sync || dma_addressing_limited(dev))
		return -EOPNOTSUPP;

	map->dma_entry_size = dma_entry_size;
	
	/* 分配 PFN 列表 */
	map->pfn_list = kvcalloc(nr_entries, sizeof(*map->pfn_list),
				 GFP_KERNEL | __GFP_NOWARN);
	if (!map->pfn_list)
		return -ENOMEM;

	/* 尝试分配 IOVA */
	use_iova = dma_iova_try_alloc(dev, &map->state, 0,
				      nr_entries * PAGE_SIZE);
				      
	/* 如果不使用 IOVA 且需要 unmap,分配 DMA 地址列表 */
	if (!use_iova && dma_need_unmap(dev)) {
		map->dma_list = kvcalloc(nr_entries, sizeof(*map->dma_list),
					 GFP_KERNEL | __GFP_NOWARN);
		if (!map->dma_list)
			goto err_dma;
	}
	return 0;

err_dma:
	kvfree(map->pfn_list);
	return -ENOMEM;
}

7.3 映射单个 PFN

dma_addr_t hmm_dma_map_pfn(struct device *dev, struct hmm_dma_map *map,
			   size_t idx, struct pci_p2pdma_map_state *p2pdma_state)
{
	struct dma_iova_state *state = &map->state;
	unsigned long *pfns = map->pfn_list;
	struct page *page = hmm_pfn_to_page(pfns[idx]);
	phys_addr_t paddr = hmm_pfn_to_phys(pfns[idx]);
	size_t offset = idx * map->dma_entry_size;
	dma_addr_t dma_addr;

	/* 已映射?*/
	if ((pfns[idx] & HMM_PFN_DMA_MAPPED) &&
	    !(pfns[idx] & HMM_PFN_P2PDMA_BUS)) {
		if (dma_use_iova(state))
			return state->addr + offset;
		if (dma_need_unmap(dev))
			return map->dma_list[idx];
	}

	/* 检查 P2P DMA */
	switch (pci_p2pdma_state(p2pdma_state, dev, page)) {
	case PCI_P2PDMA_MAP_THRU_HOST_BRIDGE:
		pfns[idx] |= HMM_PFN_P2PDMA;
		break;
	case PCI_P2PDMA_MAP_BUS_ADDR:
		pfns[idx] |= HMM_PFN_P2PDMA_BUS | HMM_PFN_DMA_MAPPED;
		return pci_p2pdma_bus_addr_map(p2pdma_state, paddr);
	default:
		break;
	}

	/* 使用 IOVA */
	if (dma_use_iova(state)) {
		ret = dma_iova_link(dev, state, paddr, offset,
				    map->dma_entry_size, DMA_BIDIRECTIONAL, 0);
		if (ret)
			goto error;
		dma_addr = state->addr + offset;
	} else {
		/* 普通 DMA 映射 */
		dma_addr = dma_map_page(dev, page, 0, map->dma_entry_size,
					DMA_BIDIRECTIONAL);
		if (dma_mapping_error(dev, dma_addr))
			goto error;
		if (dma_need_unmap(dev))
			map->dma_list[idx] = dma_addr;
	}
	
	pfns[idx] |= HMM_PFN_DMA_MAPPED;
	return dma_addr;

error:
	pfns[idx] &= ~HMM_PFN_P2PDMA;
	return DMA_MAPPING_ERROR;
}

7.4 解除映射

bool hmm_dma_unmap_pfn(struct device *dev, struct hmm_dma_map *map, size_t idx)
{
	const unsigned long valid_dma = HMM_PFN_VALID | HMM_PFN_DMA_MAPPED;
	struct dma_iova_state *state = &map->state;
	unsigned long *pfns = map->pfn_list;

	if ((pfns[idx] & valid_dma) != valid_dma)
		return false;

	/* P2P 总线地址无需 unmap */
	if (pfns[idx] & HMM_PFN_P2PDMA_BUS)
		;
	/* IOVA */
	else if (dma_use_iova(state)) {
		dma_iova_unlink(dev, state, idx * map->dma_entry_size,
				map->dma_entry_size, DMA_BIDIRECTIONAL, 0);
	}
	/* 普通 DMA */
	else if (dma_need_unmap(dev))
		dma_unmap_page(dev, map->dma_list[idx], map->dma_entry_size,
			       DMA_BIDIRECTIONAL);

	pfns[idx] &= ~(HMM_PFN_DMA_MAPPED | HMM_PFN_P2PDMA | HMM_PFN_P2PDMA_BUS);
	return true;
}

性能优化策略

8.1 大页支持

HMM 通过 HMM_PFN_ORDER_SHIFT 编码页面大小:

static inline unsigned long hmm_pfn_flags_order(unsigned long order)
{
	return order << HMM_PFN_ORDER_SHIFT;
}

// PMD 大页:order = PMD_SHIFT - PAGE_SHIFT (通常为 9,即 2MB)
// PUD 大页:order = PUD_SHIFT - PAGE_SHIFT (通常为 18,即 1GB)

优势:

  • 减少页表遍历次数
  • 减少 TLB miss
  • 提高 DMA 效率

8.2 批量操作

static int hmm_pfns_fill(unsigned long addr, unsigned long end,
			 struct hmm_range *range, unsigned long cpu_flags)
{
	unsigned long i = (addr - range->start) >> PAGE_SHIFT;

	for (; addr < end; addr += PAGE_SIZE, i++) {
		range->hmm_pfns[i] &= HMM_PFN_INOUT_FLAGS;
		range->hmm_pfns[i] |= cpu_flags;
	}
	return 0;
}

8.3 无锁读取

pmd = pmdp_get_lockless(pmdp);  // 避免锁竞争

8.4 IOVA 重用

对于支持 IOVA 的设备,可以重用 IOVA 空间,避免频繁的 TLB 刷新:

if (dma_use_iova(state))
	return state->addr + offset;  // 重用 IOVA

8.5 分段处理

为避免长时间持锁,HMM 支持分段处理大范围:

#define MAX_WALK_BYTE	(2UL << 30)  // 2GB

do {
	hmm_range->end = min(hmm_range->start + MAX_WALK_BYTE, end);
	// 处理当前段...
	hmm_range->start = hmm_range->end;
} while (hmm_range->end < end);

总结

9.1 核心特性

  1. 统一地址空间:CPU 和设备共享虚拟地址
  2. 按需缺页:支持透明的页面换入/迁移
  3. 细粒度控制:页级别的权限和属性控制
  4. 高效同步:基于 MMU Notifier 的无锁同步
  5. 大页优化:支持 THP、Hugetlb 等大页
  6. DMA 集成:提供 DMA 映射扩展接口

9.2 适用场景

  • GPU 计算:CUDA Unified Memory、OpenCL SVM
  • 机器学习:大规模张量运算
  • 数据库加速:FPGA/GPU 加速查询
  • 视频处理:GPU 加速编解码
  • 科学计算:异构并行计算

9.3 限制与约束

  1. 不支持 VM_IO/VM_PFNMAP:I/O 映射和纯物理映射不支持
  2. 需要 MMU Notifier:依赖 CONFIG_MMU_NOTIFIER
  3. 性能开销:页表遍历和同步有一定开销
  4. 内存碎片:频繁迁移可能导致碎片
  5. 调试复杂:并发问题难以调试

9.4 未来发展

  • 更细粒度的迁移策略:基于访问模式的自适应迁移
  • 更好的大页支持:自动大页合并和拆分
  • 跨 NUMA 节点优化:优化跨节点访问性能

参考文档:

  • Documentation/mm/hmm.rst
  • include/linux/hmm.h
  • mm/hmm.c
  • LWN Articles on HMM
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值