Linux深入理解内存管理14(基于Linux6.6)---dma_contiguous_remap介绍
一、概述
1. 背景
DMA(Direct Memory Access)是一种允许外部设备直接访问系统内存的机制,绕过了 CPU 的参与,进而提高了数据传输效率。在进行 DMA 操作时,设备需要访问内存中的某一块连续区域。由于虚拟内存的存在,物理内存是通过页表映射到虚拟地址空间的,因此设备需要将物理内存映射到虚拟内存中,以便通过 DMA 进行直接访问。
通常,操作系统为了提高内存管理的灵活性,会尽量让内存分散在物理内存的不同位置。然而,某些硬件设备(如网络接口卡、图形卡等)通常需要访问一块连续的物理内存区域。Linux 为了满足这些硬件设备的需求,提供了诸如 dma_alloc_coherent
和 dma_contiguous_remap
等接口来分配并映射连续的物理内存区域。
2. dma_contiguous_remap
的作用
dma_contiguous_remap
函数的主要作用是将物理内存中连续的一段区域映射到内核的虚拟地址空间,从而使设备能够通过 DMA 操作访问这些内存。它确保设备能够高效、安全地进行内存操作,尤其是在内存区域是连续的情况下。
3. 函数原型
void *dma_contiguous_remap(struct device *dev, phys_addr_t phys_addr, size_t size);
- dev: 需要进行 DMA 操作的设备结构体。
- phys_addr: 需要映射的物理内存的起始地址。
- size: 需要映射的内存区域的大小。
4. 功能概述
dma_contiguous_remap
函数首先检查物理内存区域是否满足映射要求(是否是连续的)。如果满足要求,它会返回一个虚拟地址,该地址映射到指定的物理地址区域。这样,设备就可以使用这个虚拟地址进行 DMA 访问。
5. 应用场景
- 设备驱动程序开发:当设备需要通过 DMA 访问一个大块的连续内存时,
dma_contiguous_remap
会帮助设备驱动分配并映射这块内存区域。 - 高效内存访问:DMA 操作通常要求内存是连续的,因为一些硬件设备(如网卡、GPU)对内存的访问速度依赖于内存区域的连续性。使用该函数可以确保内存区域连续且映射到虚拟地址空间。
二、实现过程
- 当设备驱动不适用时,内存管理系统将该区域用于分配和管理可移动类型页面。
- 当驱动程序使用时,用于连续内存分配,此时已经分配的页面进行迁移。
这样既可以在内核中分配连续的大内存,同时又不浪费内存空间,因为当内存不足的时候,可以通过内存迁移实现内存重新分配的方式。通过之前的分析,当启动的时候通过arm_memblock_init->dma_contiguous_reserve->dma_contiguous_reserve_area->dma_contiguous_early_fixup,将CMA的起始地址和空间长度存储在dma_mmu_remap。
arch/arm/mm/dma-mapping.c
void __init dma_contiguous_early_fixup(phys_addr_t base, unsigned long size)
{
dma_mmu_remap[dma_mmu_remap_num].base = base;
dma_mmu_remap[dma_mmu_remap_num].size = size;
dma_mmu_remap_num++;
}
在paging_init做完低端内存映射后,就调用到dma_contiguous_remap进行相关设置,看看这个函数做了些什么呢?
arch/arm/mm/dma-mapping.c
void __init dma_contiguous_remap(void)
{
int i;
for (i = 0; i < dma_mmu_remap_num; i++) {
phys_addr_t start = dma_mmu_remap[i].base;---解析1
phys_addr_t end = start + dma_mmu_remap[i].size;
struct map_desc map;
unsigned long addr;
if (end > arm_lowmem_limit)
end = arm_lowmem_limit;
if (start >= end)
continue;
map.pfn = __phys_to_pfn(start);---解析2
map.virtual = __phys_to_virt(start);
map.length = end - start;
map.type = MT_MEMORY_DMA_READY;
/*
* Clear previous low-memory mapping to ensure that the
* TLB does not see any conflicting entries, then flush
* the TLB of the old entries before creating new mappings.
*
* This ensures that any speculatively loaded TLB entries
* (even though they may be rare) can not cause any problems,
* and ensures that this code is architecturally compliant.
*/
for (addr = __phys_to_virt(start); addr < __phys_to_virt(end);---解析3
addr += PMD_SIZE)
pmd_clear(pmd_off_k(addr));
flush_tlb_kernel_range(__phys_to_virt(start),
__phys_to_virt(end));---解析4
iotable_init(&map, 1);---解析5
}
}
- 这个区域是之前通过dma_contiguous_reserve来设置的保留区域,其start = 0x8c00 0000,length = 0x1400 0000,这个空间大小与dts中配置的"shared-dma-pool"大小一致。
- 配置该区间的Map_desc,为后边的映射create_mapping做准备。
- 清0x8c00 0000 - 0xA000 0000空间的pmd表。
- 刷新该的区域对应的tlb。
- iotable_init函数来建立页映射关系,主要是CAM的映射。
iotable_init函数就是最终实现建立虚拟地址映射的函数,其处理流程如下:
arch/arm/mm/mmu.c
/*
* Create the architecture specific mappings
*/
void __init iotable_init(struct map_desc *io_desc, int nr)
{
struct map_desc *md;
struct vm_struct *vm;
struct static_vm *svm;
if (!nr)
return;
svm = memblock_alloc(sizeof(*svm) * nr, __alignof__(*svm));
if (!svm)
panic("%s: Failed to allocate %zu bytes align=0x%zx\n",
__func__, sizeof(*svm) * nr, __alignof__(*svm));
for (md = io_desc; nr; md++, nr--) {
create_mapping(md);
vm = &svm->vm;
vm->addr = (void *)(md->virtual & PAGE_MASK);
vm->size = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));
vm->phys_addr = __pfn_to_phys(md->pfn);
vm->flags = VM_IOREMAP | VM_ARM_STATIC_MAPPING;
vm->flags |= VM_ARM_MTYPE(md->type);
vm->caller = iotable_init;
add_static_vm_early(svm++);
}
}
也是调用create_mapping依次对传入的io端口地址进行映射,而且利用add_static_vm_early函数添加到了vmlist和static_vmlist链表,这两个全局链表应该是用来管理虚拟内存的,标记io映射使用的虚拟内存。具体的后面再分析CMA与伙伴系统的关系的时候,再深入分析之间的联系。
总结下,主要是分析了dma_contiguous_remap的作用,主要是针对CMA的区域进行了重新的映射,主要是0x8c00 000 ~ 0xe000 0000这个区域清了之前低端内存的映射关系,又重新作了二级映射。
三、总结
dma_contiguous_remap
是 Linux 内存管理中与 DMA 相关的一个重要工具。它提供了一个简单的接口来映射连续的物理内存区域到虚拟地址空间,确保设备能够通过 DMA 高效地访问内存。理解该函数的工作原理,对于开发需要直接与硬件交互的设备驱动程序至关重要,同时也有助于深入理解 Linux 内存管理与设备驱动的工作方式。
1. 实现原理
dma_contiguous_remap
会根据传入的物理地址和大小参数,检查指定的内存区域是否是连续的。为了能够进行有效的 DMA 访问,内存区域必须满足连续性要求。- 如果满足条件,函数会通过页表机制将这块连续的物理内存区域映射到内核的虚拟地址空间。
- 设备驱动程序可以通过虚拟地址访问该内存区域,从而进行高效的 DMA 操作。
2. 与其他相关函数的关系
dma_alloc_coherent
:用于分配连续物理内存并进行映射。它通常用于没有缓存的内存区域,而dma_contiguous_remap
更多用于已经存在的物理内存区域的映射。dma_map_single
/dma_map_page
:这些函数用于将内存区域映射到设备的 DMA 地址空间。不同的是,它们不要求内存是连续的,而dma_contiguous_remap
明确要求内存是连续的。dma_free_coherent
:用于释放由dma_alloc_coherent
分配的内存,而与dma_contiguous_remap
结合使用时,映射解除则通常通过其他接口完成。
3. 注意事项
- 内存连续性:设备的 DMA 引擎要求内存块是连续的,因此在使用
dma_contiguous_remap
时,必须确保传入的物理内存区域满足这一要求。如果内存区域不连续,函数可能会返回错误。 - 虚拟内存映射:通过
dma_contiguous_remap
映射的物理内存区域可以被设备通过 DMA 操作,但必须在操作完成后及时释放资源,以防止内存泄漏或其他错误。 - 性能考量:虽然
dma_contiguous_remap
可以提供连续内存映射,但在大规模内存分配时,需要特别考虑性能,确保映射过程不会引起不必要的开销。