Linux内核使用伙伴系统管理内存。在使用伙伴系统之前,内核会使用memblock管理内存(里面的都是物理地址)。在系统启动阶段使用memblock记录物理内存的使用情况。
内核启动以后,内存会被分开很多块:1 静态内存,eg代码段,数据段等,属于系统内存的一部分,不会参与内存分配。2 预留的保留内存。3 其余的为内核需要管理的内存(后面伙伴系统管理的内存??),动态内存
memblock子系统就是将以上内存按照功能划分为若干内存区。相应的memblock也会包含保留内存以及动态内存
struct memblock {
bool bottom_up; /* is bottom up direction? 表示分配器的分配方式,true表示从低地址向高地址分配,false则相反*/
phys_addr_t current_limit;//来表示用来限制alloc的内存申请
struct memblock_type memory;//对应动态内存
struct memblock_type reserved;//对应保留内存
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
struct memblock_type physmem;
#endif
};
struct memblock_type {
unsigned long cnt; /* number of regions表示当前状态(memory/reserved)的内存块可用数 */
unsigned long max; /* size of the allocated array */
phys_addr_t total_size; /* size of all regions 当前状态(memory/reserved)的空间大小,也就是内存总大小空间*/
struct memblock_region *regions;/* 用于保存内存块信息的结构(包括基址、大小和标记等)*/
};
struct memblock_region {
phys_addr_t base;/* 内存块对应的物理基地址 */
phys_addr_t size;/* 内存块大小 */
unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
int nid;
#endif
};
内核中memblock被初始化为了一个全局变量
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endif
struct memblock memblock __initdata_memblock = {
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_REGIONS,
.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_REGIONS,
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
.physmem.regions = memblock_physmem_init_regions,
.physmem.cnt = 1, /* empty dummy entry */
.physmem.max = INIT_PHYSMEM_REGIONS,
#endif
.bottom_up = false,
.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};
memblock添加
early_init_dt_scan_nodes(会去解析choosen, root, memory三个节点)->early_init_dt_scan_memory(解析出memory节点的起始地址以及daxiao,物理地址,对应dts里面填写的起始地址和大小)->early_init_dt_add_memory_arch-->memblock_add->memblock_add_range
phys_offset是代表物理地址的起始地址。early_init_dt_add_memory_arch主要是对物理地址是否合法进行判断以及裁剪
void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
const u64 phys_offset = __pa(PAGE_OFFSET);
base &= PAGE_MASK;
size &= PAGE_MASK;
printk(KERN_EMERG "\r\nxxxxxxx phys_offset %llx\n", phys_offset);
if (sizeof(phys_addr_t) < sizeof(u64)) {
if (base > ULONG_MAX) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base + size > ULONG_MAX) {
pr_warning("Ignoring memory range 0x%lx - 0x%llx\n",
ULONG_MAX, base + size);
size = ULONG_MAX - base;
}
}
if (base + size < phys_offset) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base < phys_offset) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
base, phys_offset);
size -= phys_offset - base;
base = phys_offset;
}
memblock_add(base, size);
}
打印了一下phys_offset:0x60000000,确实也和dts里面是一致的。看来确实是物理内存的地址不一定都是从0开始的 。
memblock.memory:这里是将memory节点的内存加入到memblock中的动态内存中
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
return memblock_add_range(&memblock.memory, base, size,
MAX_NUMNODES, 0);
}
上面示意图就是新增region的情况:假设region[i]的范围是[rbase, rend].新加入的为[base, end]
如果大小按照上图所示,则会新增两个region[base, rbase]和[rend, end]
memblock_add_range:会执行两次(通过insert控制),第一次主要是查看需要新增几个元素或者说region(nr_new表示)。根据新加入的物理地址范围,判断是否新增region的数量。如果当前元素个数+新增数量(type->cnt + nr_new > type->max)超过了数组长度,则需要进行扩容。
当确定扩容之后或者数组长度足够后,就可以将新加入的地址范围[base, size]加入memblock
在循环第二次的时候,base会被重写赋值。又重新开始,区别是此时会执行插入操作memblock_insert_region(该函数只是简单的将第i到最后一个元素,统统往后移动一个位置)
int __init_memblock memblock_add_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size,
int nid, unsigned long flags)
{
bool insert = false;
phys_addr_t obase = base;//old base??
phys_addr_t end = base + memblock_cap_size(base, &size);
int i, nr_new;
if (&memblock.memory == type)
{
printk(KERN_EMERG "\r\nmemory add, base: %lx, size: %lx\r\n", base, size);
}
else if (&memblock.reserved == type)
{
printk(KERN_EMERG "\r\nreserved add, base: %lx, size: %lx\r\n", base, size);
}
if (!size)
return 0;
/* special case for empty array */
if (type->regions[0].size == 0) {
WARN_ON(type->cnt != 1 || type->total_size);
type->regions[0].base = base;
type->regions[0].size = size;
type->regions[0].flags = flags;
memblock_set_region_node(&type->regions[0], nid);
type->total_size = size;
return 0;
}
repeat:
/*
* The following is executed twice. Once with %false @insert and
* then with %true. The first counts the number of regions needed
* to accomodate the new area. The second actually inserts them.
*/
base = obase;
nr_new = 0;
for (i = 0; i < type->cnt; i++) {
struct memblock_region *rgn = &type->regions[i];
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
/* 我感觉这里是想找一个起始地址是小于base的region */
if (rbase >= end)
break;
if (rend <= base)
continue;
/*
* @rgn overlaps. If it separates the lower part of new
* area, insert that portion.
*/
/*
区域有重叠,即新插入的区域[base, end]必定和当前region[rbase, rend]有交集
但是看代码感觉只有这种情形出现(不知道为什么)
base <= rbase
end和rend大小关系不定
*/
if (rbase > base) {
nr_new++;
if (insert)//首次执行insert=fasle,不会执行这里
memblock_insert_region(type, i++, base,
rbase - base/* 从这里看感觉rbase是大于base的 */, nid,
flags);
}
/* area below @rend is dealt with, forget about it */
/*
由于区域有重叠,目前存在两种情况:
1 当前region已经完全包含新加的区域 base <= rbase <= end <= rend
2 部分重叠 base <= rbase <= rend <= end
因此更新,新的起始地址为rend和end的较小值
新插入的区域为[min(rend, end), end]
如果是情况1,那么该范围则不成立,不过后面会检查该情况
*/
base = min(rend, end);
}
/* insert the remaining portion */
if (base < end) {/* */
nr_new++;
if (insert)
memblock_insert_region(type, i, base, end - base,
nid, flags);
}
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
if (!insert) {
while (type->cnt + nr_new > type->max)/* 如果超过了数组的长度,重新扩容 */
if (memblock_double_array(type, obase, size) < 0)
return -ENOMEM;
insert = true;
goto repeat;
} else {
memblock_merge_regions(type);
return 0;
}
}
从打印看,设备树里面的memory节点里面定义的物理内存全部都被加入到了动态内存部分
而 。保留内存同样也是从memory节点里面划分出来了一部分,当做保留内存
static void __init_memblock memblock_insert_region(struct memblock_type *type,
int idx, phys_addr_t base,
phys_addr_t size,
int nid, unsigned long flags)
{
struct memblock_region *rgn = &type->regions[idx];
BUG_ON(type->cnt >= type->max);
/* 将rgn复制到rgn+1,即将第i + 1到type->cnt的region往后挪一个位置 */
memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
/*
原来region[i]的范围是[rbase, rend]
现在变为了region[i + 1]的范围是[rbase, rend]
而region[i]的范围是[base, rbase]
*/
rgn->base = base;
rgn->size = size;
rgn->flags = flags;
memblock_set_region_node(rgn, nid);
type->cnt++;
type->total_size += size;
}
reserved-memory节点保留内存,以下为某dts中保留内存节点的一段。由dts可见它可以定义多个保留内存块属性,每个属性包含一块要保留的内存,在初始化reserved region时,可以通过解析dtb中该节点的各个属性,然后填充reserved region。在这个例子中,保留内存属性还包含了no-map标志,它表示这段内存不要放入线性映射区,因此需要从memory region中移除它们,例如这种方式
no-map”属性决定memory region移除内存区。内核不会给”no-map”属性的内存区建立内存映射,即该内存区不在动态内存管理范围
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
nss@40000000 {
reg = <0x40000000 0x1000000>;
no-map;
};
smem@41000000 {
reg = <0x41000000 0x200000>;
no-map;
};
};
需要被管理的内存被加入到了memblock子系统之后,就开始真正的内存管理,页表映射、zone初始化等