问题思考
- 系统启动时,arm-linux内核怎么系统系统到底有多大的内存空间
- 在32bit的linux内核中,用户空间和内核空间的比例通常是3:1,可以修改成2:2么
- 物理内存页面如何添加到伙伴系统中,是一页一页添加,还是以2的几次幂来加入呢
内存简介
从硬件的角度来看内存。内存是什么,内存是ram(随机存储器,Random access memory),是可以直接与cpu交换数据的内部寄存器。现在大部分都使用ddr来作为内存,ddr包括DDR3L、DDR4L、LPDDR3/4。ddr的初始化一般是在BIOS或者boot loader中,BIOS或者boot loader吧ddr的大小传递给linux内核,因此从linux内核来看ddr其实就是一段物理内存空间。
内存管理结构
内存管理是一个很复杂的系统,设计的内容很多。如果用分层来描述,内存空间可以分成三个层次,分别是用户空间、内核空间、硬件层。如下图所示:
- 用户空间层:可以理解为linux内核内存管理为用户空间暴露的系统调用接口,例如brk、mmap等系统调用。通常libc库会封装成常见的c语言函数,如malloc和mmap等
- 内核空间层:内核空间的模块非常丰富,具体的内存实现原理都是在内核层面的。用户空间和内核空间的接口是系统调用,因此内核空间层首先需要处理这些内存管理相关的系统调用,比如sys_brk等。接下来包括vma管理、缺页中断管理、匿名页面、page cache、页面回收、反向映射、slab分配器、页表管理等模块了
- 硬件层:包括处理器的MMU、TLB和cache,以及板载的物理内存,比如ddr
内存大小
在ARM linux中,各种设备的相关属性描述都在用DTS方式来呈现。,比如arm vexpress平台,该文件中定义了内存的起始地址为0x60000000,大小为0x40000000,即1GB大小的内存空间。
比如arch\arm\boot\dts\vexpress-v2p-ca9.dts
memory@60000000 {
device_type = "memory";
reg = <0x60000000 0x40000000>;
};
内核在启动过程中会解析这个dts文件,然后得到内存的基地址和大小,然后把这些变量加到memblock中,使用函数early_init_dt_add_memory_arch-->memblock_add将内存块信息加到memblock中,memblock是系统启动阶段的内存分配器,系统启动之后有伙伴系统接管内存分配工作。
解析函数调用流程为:start_kernel-->setup_arch-->setup_machine_fdt-->early_init_dt_scan_nodes-->early_init_dt_scan_memory-->early_init_dt_add_memory_arch-->memblock_add
解析函数如下所示:
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
//device_type = "memory"
const __be32 *reg, *endp;
int l;
/* We are scanning "memory" nodes only */
if (type == NULL) {
/*
* The longtrail doesn't have a device_type on the
* /memory node, so look for the node called /memory@0.
*/
if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0)
return 0;
} else if (strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
//reg = <0x60000000 0x40000000>
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);//0x60000000
size = dt_mem_next_cell(dt_root_size_cells, ®);//0x40000000
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
early_init_dt_add_memory_arch(base, size);//base和size数据有效性校验
}
return 0;
}
物理内存映射
物理内存映射,其实就是简历内存页表的过程,因为内核中直接使用的都是内存,所以需要创建页表来映射物理内存。初始化内核页表主要在函数mam_lowmem()函数中。在影射页表之前,需要把页表的页表项清零,主要在prepare_page_table函数中实现。