当前位置:首页 > 嵌入式 > 嵌入式客栈
[导读]看了很多关于linux内存管理的文章还是云里雾里,听了很多关于linux内存管理的课程还是一头雾水。其实很多时候造成不懂的原因不是资料太少,恰恰是资料太多,而且各个内核版本的差异,32位64位的不同,文章的胡编乱造等都给读者带来疑惑。本着对内存深度剖析的

看了很多关于linux内存管理的文章还是云里雾里,听了很多关于linux内存管理的课程还是一头雾水。其实很多时候造成不懂的原因不是资料太少,恰恰是资料太多,而且各个内核版本的差异,32位64位的不同,文章的胡编乱造等都给读者带来疑惑。本着对内存深度剖析的态度,希望以版本kernel-4.14,架构AARCH64为专题做个内存管理的架构性整理。

这篇文章我们先来看下linux在启动过程中的初始化。


创建启动页表:

在汇编代码阶段的head.S文件中,负责创建映射关系的函数是create_page_tables。create_page_tables函数负责identity mapping和kernel image mapping。

  • identity map:是指把idmap_text区域的物理地址映射到相等的虚拟地址上,这种映射完成后,其虚拟地址等于物理地址。idmap_text区域都是一些打开MMU相关的代码。

  • kernel image map:将kernel运行需要的地址(kernel txt、rodata、data、bss等等)进行映射。

arch/arm64/kernel/head.S:ENTRY(stext) bl preserve_boot_args bl el2_setup // Drop to EL1, w0=cpu_boot_mode adrp x23, __PHYS_OFFSET and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0 bl set_cpu_boot_mode_flag bl __create_page_tables /* * The following calls CPU setup code, see arch/arm64/mm/proc.S for * details. * On return, the CPU will be ready for the MMU to be turned on and * the TCR will have been set. */ bl __cpu_setup // initialise processor b __primary_switchENDPROC(stext)

__create_page_tables主要执行的就是identity map和kernel image map:

 __create_page_tables:...... create_pgd_entry x0, x3, x5, x6 mov x5, x3 // __pa(__idmap_text_start) adr_l x6, __idmap_text_end // __pa(__idmap_text_end) create_block_map x0, x7, x3, x5, x6
/* * Map the kernel image (starting with PHYS_OFFSET). */ adrp x0, swapper_pg_dir mov_q x5, KIMAGE_VADDR + TEXT_OFFSET // compile time __va(_text) add x5, x5, x23 // add KASLR displacement create_pgd_entry x0, x5, x3, x6 adrp x6, _end // runtime __pa(_end) adrp x3, _text // runtime __pa(_text) sub x6, x6, x3 // _end - _text add x6, x6, x5 // runtime __va(_end) create_block_map x0, x7, x3, x5, x6 ......

其中调用create_pgd_entry进行PGD及所有中间level(PUD, PMD)页表的创建,调用create_block_map进行PTE页表的映射。关于四级页表的关系如下图所示,这里就不进一步解释了。


汇编结束后的内存映射关系如下图所示:


当执行完上面的map之后,MMU就已经打开了并且开始进入C代码运行阶段,那么下一步就要对dtb进行映射了。


fixmap区之dtb map:

在执行setup_arch中,会最先进行early_fixmap_init(),这个函数就是用来map dtb的,但是它只会建立dtb对应的这段物理地址中间level的页表entry,而最后一个level的页表映射则通过setup_machine_fdt函数里的fixmap_remap_fdt来创建。

void *__init fixmap_remap_fdt(phys_addr_t dt_phys){ void *dt_virt; int size;
dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO); if (!dt_virt) return NULL;
memblock_reserve(dt_phys, size); return dt_virt; }

fixmap_remap_fdt主要是为fdt建立地址映射,在该函数的最后,顺便就调用memblock_reserve保留了该段内存。

可以看出dtb的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里固定映射还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射(这个临时映射就是用来执行early ioremap使用的。),而dtb的映射属于永久映射。


fixmap区之early ioremap:

对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。

early_ioremap_init会调用early_ioremap_setup:

可见它的实现是依赖fixmap的,所以它必须要在early_fixmap_init之后才能运行。

注意:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。

至此我们已经知道dtb和early ioremap都是在fixmap区的,如下图:


系统内存的布局:

完成dtb的map之后,内核可以访问这一段的内存了,通过解析dtb中的内容,内核可以勾勒出整个内存布局的情况,为后续内存管理初始化奠定基础。这一步主要在setup_machine_fdt中完成。这里就不看代码了,其调用流程是:setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_nodes

就像注释中所示内核根据dtb的不同node勾勒出choosen node,root node,memory node相应内存区域。

除了这3个node,还有一个reserved-memory node,它是在上面讲到dtb map的时候fixmap_remap_fdt函数做的。下面我们看下这4个node的具体实现。

  • choosen node

该节点有一个bootargs属性,该属性定义了内核的启动参数,比如mem= xx,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中。

  • root node

与内存无关,暂时不详述,以后有机会讲到device tree系列再详述。

  • memory node

通过memblock_add加入到memblock.memory对应的memblock_type链表中进行管理。


接下来到arm64_memblock_init函数:

void __init arm64_memblock_init(void){...... memblock_reserve(__pa_symbol(_text), _end - _text); 1.kernel image保留区 #ifdef CONFIG_BLK_DEV_INITRD if (initrd_start) { memblock_reserve(initrd_start, initrd_end - initrd_start); 2.initrd保留区 /* the generic initrd code expects virtual addresses */ initrd_start = __phys_to_virt(initrd_start); initrd_end = __phys_to_virt(initrd_end); } #endif early_init_fdt_scan_reserved_mem(); 3.dts中配置为保留的区域......}
  1. reserve内核代码、数据区等(_text到_end那一段,具体的内容可以参考内核链接脚本)

  2. 保留initital ramdisk image区域(从initrd_start到initrd_end区域)

  3. reserved-memory node 如下所示:


完成:

通过上面的一系列操作,需要动态管理的内存已经被放到了memory type和reserved type这两个region中了,现在内存已经被memblock模块所管理了,这只是启动后的第一步,后续内存才会加入到伙伴系统去管理。

—END—

往期精彩推荐,点击即可阅读




▲Linux内核中I2C总线及设备长啥样?  [墙裂推荐]
▲学习AI之机器学习概念篇
▲手把手教系列之IIR数字滤波器设计实现

麻烦给个好看!

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

在系列底层技术文章中,我们先后深入探讨过系统调用实现原理、GDB调试底层逻辑、Linux最大并发数等核心系统主题,而内存交换机制是Linux虚拟内存体系中最容易被普通开发者忽略,却直接决定系统在高内存压力下稳定性的核心机...

关键字: Linux内存 内存

在C语言的标准库中,原生字符串始终以'\0'作为结束标记,这种以字符数组为基础的实现方式存在天生的局限性:开发者很难在运行时动态调整字符串的长度,一旦超出预先分配的固定数组大小,就会触发缓冲区溢出的安全风险。在实际开发中...

关键字: 字符串 内存

很多节点上线头几天一切正常,跑到一两个月后才开始变慢、重启或误报内存不足。边缘计算里的内存风险,常见的不是一次性爆掉,而是碎片和小泄漏长时间叠加后把系统推到边界。

关键字: 边缘计算 内存 碎片

2026年的PC市场,正经历着一场从硬件到软件的全面涨价潮。

关键字: PC 内存 微软 Windows

加利福尼亚州圣何塞2026年8月7日 /美通社/ -- 美国硅谷当地时间8月5日,FMS 2026期间,江波龙首席科学家陈健博士发表《Intelligence at the Edge: Powered by the St...

关键字: FOUNDRY FM AI 内存

“2031年之前中国别想翻盘!”韩国媒体近日借一份市场报告,向全球发出充满火药味的宣示

关键字: 内存 存储 存储芯片

横琴2026年7月31日 /美通社/ -- 澜起科技今日宣布,率先在业界试产CXL® 3.2内存扩展控制器(MXC)芯片。该产品面向人工智能、云计算、数据中心等场景的内存扩展与资源池化需求,旨在通过CXL技术助力...

关键字: 内存 控制器 MX DDR

全新 MRCD 与 MDB 设计提供 12800 MT/s 高速,注入强劲 AI 算力动能 深圳2026年7月30日 /美通社/ -- 全球工业存储及嵌入式外设领导品牌宜鼎国际(Innodi...

关键字: DDR 内存 带宽 AI

内存泄露是C/C++等手动内存管理语言开发中最常见也最头疼的问题之一:程序运行时间越长,占用内存越来越多,最终会把系统内存耗尽,导致程序崩溃或者系统卡死,甚至影响服务器上其他服务的正常运行。很多开发者遇到内存泄露问题,往...

关键字: 内存泄露 内存

虚拟内存是现代操作系统最核心的设计之一,它彻底改变了程序对物理内存的访问方式,让每个进程都拥有了独立、连续的超大地址空间,既实现了进程间的内存隔离,也解决了物理内存容量不足的问题。Linux作为目前应用最广泛的服务器操作...

关键字: 内存 虚拟内存
关闭