Linux内核:内存管理——DMA
Linux内核中的内存管理,特别是存管DMA(Direct Memory Access)技术,对于高效的理源数据传输至关重要。DMA允许硬件直接与内存进行通信,管理无需CPU干预,源码提高了性能。剖析dll源码获取方法主要分为两种映射类型:
1. 一致性DMA映射(Consistent DMA mappings)适用于需要长时间使用的内内存内存区域,它能避免CPU和DMA控制器因缓存问题产生干扰。存管尽管称为"consistent",理源但仍然需要内存屏障来确保内存顺序。管理
2. 流式DMA映射(Streaming DMA mapping)则适用于一次性传输,源码传输完成后即释放资源,剖析适合于对内存空间要求不高的内内存场景。
在使用DMA时,存管开发者需要指定DMA设备的理源寻址范围,这在include/linux/dma-mapping.h文件中定义。DMA映射接口提供了两个选项:
3.1 一致性DMA接口支持分配大块和小块DMA缓冲区,其中小块可通过dma poll机制申请。
3.2 流式DMA接口涉及page映射、错误处理、scatterlist映射以及sync操作等。
想深入了解Linux内核内存管理的DMA技术,可以参考反光博主的文章,原文链接:[博客园](blogs.com/fan-guang/p/linux_DMA_interface.html)。群组内有丰富的学习资源,包括技术交流、书籍、视频教程和代码资料,感兴趣的读者可以通过私信群管理获取内核资料包。
此外,还有内核技术学习的综合资源直达:Linux内核源码技术、内存调优、文件系统、进程管理、设备驱动和网络协议栈等内容。通过这些资源,您可以更深入地探索Linux内核的DMA管理机制。
值得一看的LINUX内核内存管理kmalloc,vmalloc
在设备驱动程序或内核模块中进行动态内存分配时,通常使用 kmalloc 和 vmalloc 函数而非 malloc。kmalloc 和 vmalloc 分配的内存类型和使用方式存在显著差异。
kmalloc 用于从物理上连续的低端内存区域分配小块(一般不超过 k)内存,分配的内存地址为物理连续的线性地址,适合于需要连续内存以进行直接内存访问(DMA)操作的设备。释放 kmalloc 分配的内存时使用 kfree 函数。
相比之下,vmalloc 用于从虚拟上连续但物理上可能不连续的app注册任务源码高端内存区域分配较大块(一般为大块内存)内存,适合在内存资源紧张时使用。vmalloc 分配的内存仅在逻辑上连续,物理地址无需连续,因此不能直接用于 DMA 操作。释放 vmalloc 分配的内存时使用 vfree 函数。
总结两者区别:
1. kmalloc 分配的是低端内存,而 vmalloc 分配的是高端内存,当内存资源紧张时才会使用低端内存。
2. kmalloc 分配的物理地址是连续的,而 vmalloc 分配的物理地址可能不连续。
3. kmalloc 分配的内存适用于小块内存需求,而 vmalloc 分配的内存适用于大块内存需求。
在 DMA 工作中,为了减轻 CPU 负载,数据传输由 DMA 控制器在快速设备与主存储器之间直接控制完成。在 DMA 模式下,CPU 只需下达指令给 DMA 控制器,由其处理数据的传输,并在传输完成后反馈信息给 CPU,从而显著提高数据传输效率。
在测试代码中,通过将数据放置在 地址,并在 d 物理地址读取数据,可以观察到 kmalloc 分配的物理地址位于低端内存区域,而 vmalloc 分配的物理地址位于高端内存区域。通过 DMA 传输的数据通常位于低端内存区域。
kmalloc 函数的原型为 static __always_inline void *kmalloc(size_t size, gfp_t flags),其中 flags 参数主要决定内存的分配类型,如 GFP_KERNEL、GFP_USER 和 GFP_ATOMIC 等,分别用于内核、用户空间和不允许睡眠的原子分配。
内核编程时,应遵守中断上下文不可睡眠的原则,使用 is_in_interrupt_context() 函数检查是否处于中断处理程序中,以避免睡眠。
一文搞懂Linux内核内存管理中的KASAN实现原理
深入探索Linux内核的KASAN内存保护机制:越界检测与实现原理
Kernel Address Sanitizer (KASAN),作为内存错误检测的守护者,专为x_和arm架构的Linux 4.4及更高版本设计。它通过GCC 4.9.2及以上版本的强大支持,利用shadow memory这一内存监控机制,确保了对内存越界访问的精准捕捉。启动时,只需在编译选项中设置CONFIG_SLUB_DEBUG=y和CONFIG_KASAN=y,SLUB_DEBUG将提供额外的java源码做app调试信息,可能会增加boot.img的大小。
KASAN的核心策略是利用系统1/8的内存空间作为shadow memory,通过编译时插入的load/store检查,实时监控每个内存操作。当内存访问尝试超出定义范围时,__asan_load#size()和__asan_store#size()这些内置函数会触发异常,发出警告。比如,8字节内存访问要求shadow memory值为0,任何偏离这一规则的操作都会触发错误检查。
在ARM架构下,KASAN区域位于kernel空间的VMALLOC区域,支持KASLR。编译器会在每个内存访问前后自动插入检查,通过__asan_load#size()和__asan_store#size()来验证内存地址的有效性。例如,全局变量的构造函数由编译器生成,如char a[4]变为struct { char original[4]; char redzone[]; },redzone的填充规则由实际占用内存与字节的余数决定。
关于shadow memory的物理映射,它与kernel地址之间的关系是:shadow_addr = (kaddr 3) + KASAN_SHADOW_OFFSE。KASAN区域作为虚拟地址,必须通过映射将其转换为物理地址才能操作。初始化过程在kasan_early_init()和kasan_init()函数中进行,确保内存映射的正确性。当分配内存时,如kmalloc(),KASAN会标记多余的内存为不可访问,以防止意外的越界使用。
更具体地,KASAN通过struct kasan_global结构体来管理全局变量,如smc_num1、smc_num2和smc_num3。反编译System.map和vmlinux.txt,我们发现每个全局变量都有对应的构造函数,如smc_num1的构造函数地址为ffffdf0,初始化过程由__asan_register_globals()执行。
总结来说,KASAN通过编译器的智能处理,确保了对内存访问的严格监控。它在栈分配变量和全局变量上都有相应的边界检查和shadow memory初始化。错误日志提供了详尽的bug信息,如地址、任务、kmalloc_oob_right错误描述等,如何看懂php源码帮助开发者快速定位问题。
在实践中,KASAN在Linux内核4.版本中表现出色,通过检测slab-out-of-bounds错误,为内存管理提供了强大的安全保障。深入理解KASAN的工作原理,对保证系统稳定性和代码质量至关重要。
Linux内核:内存管理——bootmem分配器
为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好,此时就引入了一种内存管理器bootmem分配器在系统初始化的时候进行内存管理与分配,当buddy系统和slab分配器初始化好后,在mem_init()中对bootmem分配器进行释放,内存管理与分配由buddy系统,slab分配器等进行接管。
bootmem分配器使用一个bitmap来标记物理页是否被占用,分配的时候按照第一适应的原则,从bitmap中进行查找,如果这位为1,表示已经被占用,否则表示未被占用。为什么系统运行的时候不使用bootmem分配器呢?bootmem分配器每次在bitmap中进行线性搜索,效率非常低,而且在内存的起始端留下许多小的空闲碎片,在需要非常大的内存块的时候,检查位图这一过程就显得代价很高。bootmem分配器是用于在启动阶段分配内存的,对该分配器的需求集中于简单性方面,而不是性能和通用性。
一、bootmem分配器核心数据结构
bootmem核心数据结构bootmem_data:
系统内存的中每一个结点(如果是内存是UMA,就只有一个这样的结构)都有一个bootmem_data_t结构,它含有bootmem分配器给结点分配内存时所需的信息。1、node_boot_start是这个结点内存的起始地址(如果内存是UMA的话,为0);2、node_low_pfn是低端内存最后一个page的页帧号;3、node_bootmem_map指向内存中bitmap所在的位置;4、last_offset是分配的最后一个页内的偏移,如果该页完全使用,则offset为0;5、last_pos是分配最后一个页帧号;6、last_success是平板查看java源码最后一次成功分配的位置。
二、bootmem分配器初始化
是在init_bootmem_core函数对bootmem分配器进行初始化的。linux系统初始化过程中通过init_bootmem_core(NODE_DATA(0), min_low_pfn, 0, max_low_pfn)来调用这个函数初始化bootmem分配器。其中min_low_pfn是临时页表后的第一个可用页框,max_low_pfn是低端内存结束的页框。
三、bootmem分配器可使用内存注册
在bootmem分配器初始化中把所有的页都标记为保留,即不可分配的,所以必须要给bootmem分配器注册可使用的内存。Linux在初始化中通过调用函数register_bootmem_low_pages(max_low_pfn)来向bootmem分配器注册低端内存中可使用的内存,该函数根据e检测到的内存信息(e.map[]),将类型为E_RAM的page页框对应的位图中的位置为0,表示对应的page页框为空闲状态。然后调用reserve_bootmem(*, (PFN_PHYS(start_pfn) + bootmap_size + PAGE_SIZE-1) – *)来保留bootmem分配器中内存从1MB到bitmap所用的内存页框,这些保留就包括了kernel内核,临时页表,bitmap。
四、bootmem分配器的使用
在bootmem分配器中,通过find_next_zero_bit函数来查找空的页框进行分配。这个函数从第i个页框开始搜索空的页框,并返回空的页框i,如果后面有连续areasize个空的页,则找到了并记下起始位置start,如果没有连续的areasize个页框,就从新查找连续areasize个空的页。找到连续areasize个空的页后,重新设置last_success。如果align小于PAGE_SIZE并且上一次分配的页框就是这次分配页框的上一个页框并且上一次分配的页框还没用完,则合并前面分配的未用的页框。
五、bootmem分配器释放内存
释放bootmem分配器分配的内存,很简单,直接把对应的bitmap位清0,并更新last_success。
六、bootmem分配器的销毁
在buddy系统和slab分配器初始化之后,就可以把bootmem分配器销毁,把内存的管理交给buddy系统和slab分配器。bootmem分配器销毁是在mem_init函数中调用free_all_bootmem_core函数实现。如果起始物理地址页框号是对齐的,设置gofast为1。如果gofast等于1,并且从page开始的个页都未使用,则将page开始的个页框回收到buddy系统中,这个通过调用函数__free_pages(page, order),一次回收个页框。如果从page开始的个页框中存在未使用的,查找这个页框中空的页框,并回收到buddy系统中,一次回收一个页框。如果个页框都使用了,则什么都不做。然后查找下一组个页框回收,直到查找完bootmem分配器管理的所有页框。
既然bootmem分配器要释放了,那么bootmem分配器所使用的bitmap所在的页框也应该回收到buddy系统中去。((bdata->node_low_pfn-(bdata->node_boot_start >> PAGE_SHIFT))/8 + PAGE_SIZE-1)/PAGE_SIZE计算bitmap所占的页框数,然后从bitmap所在的起始页框开始回收bitmap占用的所有页框到buddy系统中,一次回收一个页框。
linux内存管理子系统架构图
内存管理体系
内存管理是Linux内核中复杂且关键的部分,它涉及三个主要层次:用户空间、内核空间和硬件空间。
用户空间层,主要由Linux内核为用户提供的一系列系统调用接口组成,如brk、mmap等,这些接口被封装在libc库中,从而在C语言中以标准函数的形式呈现,例如mmap()和malloc。
内核空间层包含多种模块,主要负责处理用户空间请求并管理内存资源,如系统调用处理、VMA管理、缺页中断管理、匿名页面、page cache、页面回收、反映映射、slab分配器、页表管理等。
硬件层由处理器的MMU、TLB和cache部件,以及物理内存DDR组成,是内存管理的基础。
以下为Linux内存管理框图、内存管理体系简图和内存管理全局图,图中以task_struct的mm作为起点进行展示。
参考资料:
Linux内核分析(三)----初识linux内存管理子系统 - wrjvszq - 博客园
Linux内核MMU机制实现讲解_mmu工作机制图示-CSDN博客
Linux 内存管理机制解析 - DBKernel - 博客园
五万字 | 深入理解Linux内存管理-腾讯云开发者社区-腾讯云
极致Linux内核:纯干货,linux内存管理——内存管理架构(建议收藏)
linux内核源码:内存管理——内存分配和释放关键函数分析&ZGC垃圾回收
本文深入剖析了Linux内核源码中的内存管理机制,重点关注内存分配与释放的关键函数,通过分析4.9版本的源码,详细介绍了slab算法及其核心代码实现。在内存管理中,slab算法通过kmem_cache结构体进行管理,利用数组的形式统一处理所有的kmem_cache实例,通过size_index数组实现对象大小与kmem_cache结构体之间的映射,从而实现高效内存分配。其中,关键的计算方法是通过查找输入参数的最高有效位序号,这与常规的0起始序号不同,从1开始计数。
在找到合适的kmem_cache实例后,下一步是通过数组缓存(array_cache)获取或填充slab对象。若缓存中有可用对象,则直接从缓存分配;若缓存已空,会调用cache_alloc_refill函数从三个slabs(free/partial/full)中查找并填充可用对象至缓存。在对象分配过程中,array_cache结构体发挥了关键作用,它不仅简化了内存管理,还优化了内存使用效率。
对象释放流程与分配流程类似,涉及数组缓存的管理和slab对象的回收。在cache_alloc_refill函数中,关键操作是检查slab_partial和slab_free队列,寻找空闲的对象以供释放。整个过程确保了内存资源的高效利用,避免了资源浪费。
总结内存操作函数概览,栈与堆的区别是显而易见的。栈主要存储函数调用参数、局部变量等,而堆用于存放new出来的对象实例、全局变量、静态变量等。由于堆的动态分配特性,它无法像栈一样精准预测内存使用情况,导致内存碎片问题。为了应对这一挑战,Linux内核引入了buddy和slab等内存管理算法,以提高内存分配效率和减少碎片。
然而,即便使用了高效的内存管理算法,内存碎片问题仍难以彻底解决。在C/C++中,没有像Java那样的自动垃圾回收机制,导致程序员需要手动管理内存分配与释放。如果忘记释放内存,将导致资源泄漏,影响系统性能。为此,业界开发了如ZGC和Shenandoah等垃圾回收算法,以提高内存管理效率和减少内存碎片。
ZGC算法通过分页策略对内存进行管理,并利用“初始标记”阶段识别GC根节点(如线程栈变量、静态变量等),并查找这些节点引用的直接对象。此阶段采用“stop the world”(STW)策略暂停所有线程,确保标记过程的准确性。接着,通过“并发标记”阶段识别间接引用的对象,并利用多个GC线程与业务线程协作提高效率。在这一过程中,ZGC采用“三色标记”法和“remember set”机制来避免误回收正常引用的对象,确保内存管理的精准性。
接下来,ZGC通过“复制算法”实现内存回收,将正常引用的对象复制到新页面,将旧页面的数据擦除,从而实现内存的高效管理。此外,通过“初始转移”和“并发转移”阶段进一步优化内存管理过程。最后,在“对象重定位”阶段,完成引用关系的更新,确保内存管理过程的完整性和一致性。
通过实测,ZGC算法在各个阶段展现出高效的内存管理能力,尤其是标记阶段的效率,使得系统能够在保证性能的同时,有效地管理内存资源。总之,内存管理是系统性能的关键因素,Linux内核通过先进的算法和策略,实现了高效、灵活的内存管理,为现代操作系统提供稳定、可靠的服务。
Linux内核:内存管理——NUMA架构
在早期单CPU时代,CPU与内存的交互依赖于北桥芯片和前端总线。随着技术发展,CPU频率提升并扩展至多核,共享北桥的UMA架构逐渐暴露了性能瓶颈。为了优化,内存控制器被移至CPU内,形成NUMA架构,每个CPU有自己的本地内存,通过QPI总线访问远程内存,这导致了访问速度的差异,即非均匀内存访问模型。
UMA架构的局限在于资源共享,扩展性受限。而NUMA通过多个内存控制器提升带宽,通过numactl命令可以查看和调整CPU和内存策略。然而,不正确的策略可能导致内存分配不均,比如数据库应用中可能因内存回收策略不当引发频繁的内存交换,影响服务性能。
解决这些问题,可以调整内存分配策略,如使用interleave=all,确保内存均匀分布。对于内存回收,数据库通常选择vm.zone_reclaim_mode=0,限制内存区域内部回收,减少页面频繁交换。对于深入了解,可以参考相关文档和教程,如腾讯云、GitHub和知乎的文章。
Linux内存管理(三)--内存分配之malloc
本文将探讨 Linux 中动态内存分配的核心机制,特别是 malloc 函数的运作原理。开源社区提供了丰富的内存分配器,其中 glibc 中的 ptmalloc2 就是基于 dlmalloc 并引入多线程支持的实例。malloc 的源码位于 glibc-2.\malloc\malloc.c 文件中,它实际上是指向内部实现的别名 __libc_malloc。
动态内存分配主要通过两个系统调用完成:mmap 和 brk。当所需内存大小超过预设阈值(默认KB)时,使用mmap分配;否则,采用brk分配。这一策略旨在平衡系统调用的频繁程度与内存分配的效率。
为了提升效率,malloc 实际上利用了池化思想,预先分配较大的内存块,以便在后续请求时直接使用,避免频繁调用系统调用。这一过程涉及多个核心数据结构的使用,包括 arena、malloc_state、heap_info、chunk 等。
arena 被用来表示连续的堆区域,分为 main arena 和 thread arena。main arena 作为全局变量存在于 libc.so 的数据段中,不需维护多个堆,且可通过 sbrk 扩展堆段。在内存耗尽时,main arena 可以通过 sbrk 或 mmap 扩展堆段至遇到内存映射段。另一方面,thread arena 的数量有限,以减少开销,当线程数量超过 arena 数量时,arena 开始共享。
heap_info 用于存储堆的元数据,当一个 thread arena 的堆空间耗尽时,新的堆会映射到该 thread arena 中。chunk 则是描述内存分配的基本单位,包含 chunk 的大小、上一个 chunk 的状态信息以及对齐需求。
在内存组织方面,存在多种类型的 chunk,包括已分配 chunk、空闲 chunk、top chunk 和 last remainder chunk。top chunk 位于 arena 的最顶部,用于处理所有 bin 中未找到合适空闲内存的情况。当 top chunk 大小不合适时,它会被分割或通过系统调用扩容。
关于 free chunk 的管理、brk 与 mmap 的详细解释将在后续文章中深入探讨。更多关于内存管理的内容可参考《嵌入式 Linux 笔记》专栏。请在引用时注明出处。
2024-11-13 10:56
2024-11-13 10:52
2024-11-13 09:54
2024-11-13 09:38
2024-11-13 09:12