物理内存的分配

物理内存的分配在内核分配内存时,必须记录页帧的已分配或空闲状态,以免两个进程使用同样的内存区域。由于内存分配和释放非常频繁,内核还必须保证相关操作尽快完成。内核可以只分配完整的页帧。将内存划分为更小的部分的工作,则委托给用户空间中的标准库。标准库将来源于内核的页帧拆分为小的区域,并为进程分配内存。
1. 伙伴系统
内核中很多时候要求分配连续页。为快速检测内存中的连续区域,内核采用了一种古老而历经检验的技术:伙伴系统。
系统中的空闲内存块总是两两分组,每组中的两个内存块称作伙伴。伙伴的分配可以是彼此独立的。但如果两个伙伴都是空闲的,内核会将其合并为一个更大的内存块,作为下一层次上某个内存块的伙伴。
内核对所有大小相同的伙伴(1、2、4、8、16或其他数目的页),都放置到同一个列表中管理。各有8页的一对伙伴也在相应的列表中。
如果系统现在需要8个页帧,则将16个页帧组成的块拆分为两个伙伴。其中一块用于满足应用程序的请求,而剩余的8个页帧则放置到对应8页大小内存块的列表中。
如果下一个请求只需要2个连续页帧,则由8页组成的块会分裂成2个伙伴,每个包含4个页帧。其中一块放置回伙伴列表中,而另一个再次分裂成2个伙伴,每个包含2页。其中一个回到伙伴系统,另一个则传递给应用程序。
在应用程序释放内存时,内核可以直接检查地址,来判断是否能够创建一组伙伴,并合并为一个更大的内存块放回到伙伴列表中,这刚好是内存块分裂的逆过程。这提高了较大内存块可用的可能性。
在系统长期运行时,服务器运行几个星期乃至几个月是很正常的,许多桌面系统也趋向于长期开机运行,那么会发生称为碎片的内存管理问题。频繁的分配和释放页帧可能导致一种情况:系统中有若干页帧是空闲的,但却散布在物理地址空间的各处。换句话说,系统中缺乏连续页帧组成的较大的内存块,而从性能上考虑,却又很需要使用较大的连续内存块。通过伙伴系统可以在某种程度上减少这种效应,但无法完全消除。如果在大块的连续内存中间刚好有一个页帧分配出去,很显然这两块空闲的内存是无法合并的。在内核版本2.6.24开发期间,增加了一些有效措施来防止内存碎片,我会在第3章更详细地讨论相关的底层实现机制。
2. slab缓存
内核本身经常需要比完整页帧小得多的内存块。由于内核无法使用标准库的函数,因而必须在伙伴系统基础上自行定义额外的内存管理层,将伙伴系统提供的页划分为更小的部分。该方法不仅可以分配内存,还为频繁使用的小对象实现了一个一般性的缓存--slab缓存。它可以用两种方法分配内存。
(1) 对频繁使用的对象,内核定义了只包含了所需类型对象实例的缓存。每次需要某种对象时,可以从对应的缓存快速分配(使用后释放到缓存)。slab缓存自动维护与伙伴系统的交互,在缓存用尽时会请求新的页帧。
(2) 对通常情况下小内存块的分配,内核针对不同大小的对象定义了一组slab缓存,可以像用户空间编程一样,用相同的函数访问这些缓存。不同之处是这些函数都增加了前缀k,表明是与内核相关联的:kmalloc和kfree。
虽然slab分配器在各种工作负荷下的性能都很好,但在真正规模庞大的超级计算机上使用时,出现了一些可伸缩性问题。另一方面,对真正微小的嵌入式系统来说,slab分配器的开销可能又太大了。内核提供了slab分配器的两种备选方案,可用于在相应的场景下替换slab分配器并提供更好的性能。对内核的其他部分而言,这3种方案的接口相同,因而不必关注内核中实际编译进来的底层分配器是哪一个。由于slab分配仍然是内核的标准方法,因此我不会详细讨论备选方案。图1-9综述了伙伴系统、slab分配器以及内核其他方面之间的关联。
3. 页面交换和页面回收
页面交换通过利用磁盘空间作为扩展内存,从而增大了可用的内存。在内核需要更多内存时,不经常使用的页可以写入硬盘。如果再需要访问相关数据,内核会将相应的页切换回内存。通过缺页异常机制,这种切换操作对应用程序是透明的。换出的页可以通过特别的页表项标识。在进程试图访问此类页帧时,CPU则启动一个可以被内核截取的缺页异常。此时内核可以将硬盘上的数据切换到内存中。接下来用户进程可以恢复运行。由于进程无法感知到缺页异常,所以页的换入和换出对进程是完全不可见的。
页面回收用于将内存映射被修改的内容与底层的块设备同步,为此有时也简称为数据回写。数据刷出后,内核即可将页帧用于其他用途(类似于页面交换)。内核的数据结构包含了与此相关的所有信息,当再次需要该数据时,可根据相关信息从硬盘找到相应的数据并加载。
<think>嗯,用户想知道在Linux系统中如何分配连续的物理内存用于DMA操作。我之前学过的相关知识应该能帮上忙。首先,DMA操作需要连续的物理内存,因为外设可能直接访问物理地址,而虚拟内存的碎片化可能导致物理地址不连续。所以,系统需要一些机制来确保分配的内存是物理连续的。 我记得Linux内核中有几种方法可以做到这一点。比如CMA(连续内存分配器),这是内核的一个机制,专门用来预留大块连续的物理内存,供DMA或类似需求使用。然后,还有kmalloc函数,当使用GFP_DMA标志时,可能会从DMA区域分配内存,但大小有限制,通常不超过几MB。另外,用户空间可能通过mmap映射/dev/mem设备,但这种方法需要root权限,而且不太安全,可能被禁用。 用户提供的引用里提到了方法1使用mmap和/dev/mem,还有引用3提到DMA操作在ARM架构的例子。所以可能需要结合这些信息。另外,启动阶段的内存在实模式是直接访问物理地址,但保护模式下分页开启,所以内核需要处理线性地址到物理地址的映射。 需要注意的是,用户空间直接分配连续物理内存几乎不可能,通常需要内核模块的帮助。比如通过CMA分配,然后导出到用户空间。或者使用dma_alloc_coherent函数,这个函数会返回物理地址连续的缓存,适用于DMA。 另外,用户提到ioremap,这个函数用于将物理地址映射到内核的虚拟地址空间,可能和DMA有关联,但主要是用于访问设备内存,而不是分配内存。 总结下来,可能的分配方法包括: 1. 使用dma_alloc_coherent函数 2. 使用CMA机制 3. 启动时预留内存 4. 内核模块中使用kmalloc或vmalloc(但vmalloc不保证物理连续) 5. 用户空间通过mmap映射/dev/mem(需要内核支持,且有安全隐患) 需要具体说明每种方法的机制和示例。比如dma_alloc_coherent的用法,以及CMA的配置步骤。同时,用户空间的例子可能需要内核模块配合,比如分配内存后通过ioctl或者mmap暴露给用户程序。 可能的问题点:用户是否需要用户空间的解决方案,还是内核驱动的?根据问题描述,用户可能是在开发驱动或需要在内核中分配,但问题提到“用于DMA操作”,通常驱动中处理。因此,重点在内核的分配方法。 另外,引用3中的例子是在ARM架构,可能需要注意平台差异,比如x86没有独立DMA控制器,所以可能无法直接应用相同的方法。 所以,回答应该涵盖内核中的分配方法,如dma_alloc_coherent,CMA,启动预留,以及可能的用户空间方法,但指出其限制。同时给出代码示例,比如模块中使用dma_alloc_coherent,并说明如何配置CMA。</think>### Linux DMA连续物理内存分配方法 在Linux系统中为DMA操作分配连续物理内存需要特殊处理机制。以下是五种主要方法及其实现原理和示例: #### 1. **DMA专用API(推荐方法)** 内核提供`dma_alloc_coherent()`接口直接分配物理连续内存: ```c // 驱动代码示例 void *dma_addr; dma_handle = dma_alloc_coherent(dev, size, &dma_addr, GFP_KERNEL); ``` - **特点**:自动处理缓存一致性 - **限制**:最大分配尺寸由平台DMA限制决定(通常32-128MB) - **物理连续性保证**:通过DMA区域预分配实现[^3] #### 2. **CMA连续内存分配器** 在设备树中预留CMA区域: ```dts reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; my_region: region@80000000 { compatible = "shared-dma-pool"; reg = <0x80000000 0x10000000>; no-map; }; }; ``` 驱动中通过DMA API自动使用CMA区域,需要内核配置`CONFIG_DMA_CMA` #### 3. **启动阶段内存预留** 在内核启动参数中添加: ```bash mem=1G@0x0 mem=1G@0x40000000 ``` - **原理**:通过`memmap=`参数保留未映射的物理内存区域 - **使用方式**:后续通过`ioremap`映射保留区域[^4] #### 4. **kmalloc特殊标志** ```c void *buf = kmalloc(size, GFP_DMA | GFP_KERNEL); ``` - **适用场景**:小内存分配(通常<4MB) - **实现机制**:从DMA Zone直接分配 #### 5. **用户空间映射(需root权限)** ```c // 通过/dev/mem映射 int fd = open("/dev/mem", O_RDWR); void *mem = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, phys_addr); ``` - **限制**:需要内核关闭`CONFIG_STRICT_DEVMEM`保护 - **风险**:直接物理内存访问可能引发系统崩溃[^1] #### 物理地址验证方法 ```shell # 通过sysfs查看物理地址 cat /sys/kernel/debug/dma_mapping/allocations # 内核调试接口 echo phys_addr > /proc/iomem ``` #### 架构差异注意 - x86平台依赖IOMMU进行地址转换 - ARM架构可直接使用物理地址(如RK3568开发板)[^3] - MIPS架构需要处理非一致性缓存
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值