文章目录
1. 常用内存申请函数
1.1 kmalloc
特点:分配内存物理连续、响应速度快、性能好,适用频繁分配和释放的小内存场景
实现:基于slab分配器,底层来源伙伴系统
在Linux内核中申请内存通常使用kmalloc,它用于申请小块内存(通常小于页表大小),适合于内核中的小型数据结构和缓存。函数实现如下:
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
// GCC编译器内置宏,判断参数size是否为常量,返回true,即编译阶段能确定size值,不依赖计算和外部因素。
if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
unsigned int index;
#endif
if (size > KMALLOC_MAX_CACHE_SIZE)
return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
index = kmalloc_index(size);
if (!index)
return ZERO_SIZE_PTR;
return kmem_cache_alloc_trace(
kmalloc_caches[kmalloc_type(flags)][index],
flags, size);
#endif
}
return __kmalloc(size, flags);
}
kmalloc会根据传入size是否在编译阶段决定,走到不同函数调用,不过所执行得函数kmem_cache_alloc_trace()
和__kmalloc()
最终都会调用到slab_alloc()
申请内存。
kmalloc() 分配的内存物理地址上连续,虚拟地址上自然连续。它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。
kmalloc分配的内存,它的物理地址与虚拟地址只有一个PAGE_OFFSET偏移,不需要为地址段修改页表,而vmalloc分配内存时需要修改主内核页表.
在32位linux系统中,kmalloc和vmalloc 这两个函数所分配的内存都处于内核空间,即从3GB~4GB;但位置不同,kmalloc()分配的内存处于3GB~high_memory之间,而vmalloc()分配的内存在VMALLOC_START~4GB之间,也就是非连续内存区。
1.2 vmalloc
特点:分配内存物理不连续,适用大段内存分配场景,支持高端内存映射
实现:将分散page动态映射到预留虚拟地址段(vmalloc区)
vmalloc每次调用都会从伙伴系统分配内存,并且分配是以page为单位的。分配几个字节的小内存也会导致一整个page被分配,多余的内存也不能被访问,会出现大量的浪费。因此需要搞清楚vmalloc的使用场景,分配小内存时应该使用kmalloc。
kernel 5.10
函数实现如下:
vmalloc的核心是在vmalloc区域中找到合适的hole,hole是虚拟地址连续的;然后逐页分配内存来从物理上填充hole。
- 申请一个vmap_area并将其插入vmap_area_root中。
alloc_vmap_area
在整个vmalloc空间中查找一块大小合适并且每人使用的空间,即hole。 __vmalloc_area_node
申请具体的物理页,并把这些页和对应的虚拟地址填入页表。
vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。
1.2.1 示例:aarch64 malloc区域范围(扩展阅读)
上述过程可见,64位linux操作系统,vmalloc区域很大,范围:(0xFFFF_8000_0800_0000, 0xFFF_FBFF_F000_0000)
1.3 kzalloc
特点和实现:kzalloc()
在kmalloc()
基础上做了个内存初始化清零
kzalloc()
函数与 kmalloc()
非常相似,参数及返回值是一样的,可以说是前者是后者的一个变种,因为 kzalloc()
实际上只是额外附加了 __GFP_ZERO
标志。所以它除了申请内核内存外,还会对申请到的内存内容清零。kzalloc()
对应的内存释放函数也是 kfree()
。
/**
* kzalloc - allocate memory. The memory is set to zero.
* @size: how many bytes of memory are required.
* @flags: the type of memory to allocate (see kmalloc).
*/
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}
kmalloc()、kzalloc()、vmalloc() 的共同特点是:
- 用于申请内核空间的内存;
- 内存以字节为单位进行分配;
- 所分配的内存虚拟地址上连续;
kmalloc()、kzalloc()、vmalloc() 的区别是:(以下描述不区分 kmalloc 和 kzalloc)
- 是否限制分配内存大小:kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
- 物理地址是否连续:kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
- 是否保证原子过程:kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞并中断,无法保证原子性;
- 内存分配速度:kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 速度快;
1.4 dma_alloc_wc
特点:对映射的写入可能会被缓冲以提高性能,用于非一致性buffer
实现:在dma_alloc_attrs
基础上,开启DMA_ATTR_WRITE_COMBINE
属性
由dma_alloc_wc
分配的buffer,CPU在读操作时直接反映到总线上,不经过cache。而在写操作时,CPU将数据写入到写缓冲区后继续运行(以提高CPU写入效率),由写缓冲区进行写回操作。
多用于音视频领域,不过在arm64系统上,dma_alloc_wc并没有真正使用起来:
/*
* DMA allocations for non-coherent devices use what the Arm architecture calls
* "Normal non-cacheable" memory, which permits speculation, unaligned accesses
* and merging of writes. This is different from "Device-nGnR[nE]" memory which
* is intended for MMIO and thus forbids speculation, preserves access size,
* requires strict alignment and can also force write responses to come from the
* endpoint.
*/
#define pgprot_writecombine(prot) \
__pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)
#define pgprot_dmacoherent(prot) \
__pgprot_modify(prot, PTE_ATTRINDX_MASK, \
PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)
可以看到,writecombine和dmacoherent定义没有差别。
1.5 dma_alloc_coherent
特点和实现:一致性buffer,读写都不经过cache,而是直接用到外部总线
调用dma_alloc_attr
,不额外配置attr传参。分配buffer时,会根据dts是否包含"dma-coherent;"
字段来决定是否真正申请一致性buffer。
若不使用iommu分配,申请并映射连续物理page,不启用CPU cache。
若使用iommu分配,申请并映射物理离散page,不启用CPU cache。
1.6 dma_alloc_attrs
特点和实现:dma buffer基本实现函数,支持调用时传入不同attr。
DMA_ATTR_FORCE_CONTIGUOUS
,仅用于开启iommu情况,若开启则申请物理连续page,否则使用物理上离散的page。
完整关系:
可以看到dma_alloc_attr共涉及4个主要分配路径:
- 若设备使用device memory(dts声明"memory-region",且类型为"shared-dma-pool"),则使用
__dma_alloc_from_coherent()
从预留内存中分配连续buffer; - 使用
dma_alloc_contiguous()
分配连续物理buffer; - 使用
dma_alloc_from_pool()
从dma pool分配buffer,支持不允许blocking场景; - 使用
iommu_dma_alloc_remap()
分配物理不连续buffer,支持iommu开启的常规场景;
2. 内存地址查询函数
2.1 virt_to_page
是Linux内核中用于将虚拟地址转换为对应的页描述符(page descriptor)的函数。
2.2 virt_to_phys
将虚拟地址转换为物理地址。
2.3 virt_to_pfn
获取虚拟地址所在page的序号(页帧)
2.4 dma_to_phys
dma_addr_t(总线地址)转phys_addr_t,绝大部分体系结构中(且memory未加密),这两个地址是相等的。
2.5 page_to_phys
得到page描述符对应物理地址
2.6 iommu_iova_to_phys
将iova地址转物理地址。这里注意device iommu申请的地址是iova,cpu mmu申请的地址称为va。