Linux内存管理 - 内存申请(一)

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。

  1. 申请一个vmap_area并将其插入vmap_area_root中。alloc_vmap_area在整个vmalloc空间中查找一块大小合适并且每人使用的空间,即hole。
  2. __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() 的共同特点是:

  1. 用于申请内核空间的内存;
  2. 内存以字节为单位进行分配;
  3. 所分配的内存虚拟地址上连续;

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个主要分配路径:

  1. 若设备使用device memory(dts声明"memory-region",且类型为"shared-dma-pool"),则使用__dma_alloc_from_coherent()从预留内存中分配连续buffer;
  2. 使用dma_alloc_contiguous()分配连续物理buffer;
  3. 使用dma_alloc_from_pool()从dma pool分配buffer,支持不允许blocking场景;
  4. 使用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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值