前面已经通过转载的方式介绍了Linux的设备树,现在来讲讲Linux memory size的初始化:
kernel memory size的初始化函数调用顺序如下:
start_kernel->setup_arch->setup_machine_fdt->early_init_dt_scan_nodes->early_init_dt_scan_chosen->early_init_dt_scan_root->early_init_dt_scan_memory->early_init_dt_add_memory_arch->memblock_add->memblock_add_range.
从setup_machine_fdt开始说起:
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)//做两件事:通过dts的compitable找到合适的machine_dsec结构体,解析DTS的内容
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#ifdef CONFIG_ARCH_MULTIPLATFORM
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
return NULL;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) {
const char *prop;
int size;
unsigned long dt_root;
early_print("\nError: unrecognized/unsupported "
"device tree compatible list:\n[ ");
dt_root = of_get_flat_dt_root();
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("]\n\n");
dump_machine_table(); /* does not return */
}
/* We really don't want to do this, but sometimes firmware provides buggy data */
if (mdesc->dt_fixup)
mdesc->dt_fixup();
early_init_dt_scan_nodes();//解析dts中的内容
/* Change machine number to match the mdesc we're using */
__machine_arch_type = mdesc->nr;
return mdesc;
}
early_init_dt_scan_nodes:
void __init early_init_dt_scan_nodes(void)
{
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);//of_scan_flat_dt负责扫描整个设备树,起作用的是回调函数,early_init_dt_scan_chosen负责将chosen节点中的bootargs内容copy到boot_command_line中
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);//early_init_dt_scan_root根据节点的address-cells和size-cells初始化dt_root_addr_cells和dt_root_size_cells,#address-cells表示reg里面address用几个u32来表示,#size-cells表示reg里面size用几个u32来表示
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);//扫描device_type为memory的节点,一般就是总的memory size为一个memory节点,并通过early_init_dt_add_memory_arch将这些节点的信息添加到memory block中
}
early_init_dt_scan_chosen:
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
int depth, void *data)
{
int l = 0;
const char *p = NULL;
char *cmdline = data;
pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);
if (depth != 1 || !cmdline ||
(strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))//depth表示遍历dts的层数,根节点为第0层,chosen为第一层,以此类推
return 0;//此处chosen必须是第一层,其名字必须为chosen,则继续往下执行,否则没找到chosen节点退出,节省执行效率
early_init_dt_check_for_initrd(node);//解析chosen节点中的initrd的信息
/* Put CONFIG_CMDLINE in if forced or if data had nothing in it to start */
if (overwrite_incoming_cmdline || !cmdline[0])
strlcpy(cmdline, config_cmdline, COMMAND_LINE_SIZE);//解析choen中的bootargs信息,并将其copy到boot_command_line
/* Retrieve command line unless forcing */
if (read_dt_cmdline)
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0) {
if (concat_cmdline) {
int cmdline_len;
int copy_len;
strlcat(cmdline, " ", COMMAND_LINE_SIZE);
cmdline_len = strlen(cmdline);
copy_len = COMMAND_LINE_SIZE - cmdline_len - 1;
copy_len = min((int)l, copy_len);
strncpy(cmdline + cmdline_len, p, copy_len);
cmdline[cmdline_len + copy_len] = '\0';
} else {
strlcpy(cmdline, p, min((int)l, COMMAND_LINE_SIZE));//一般系统会定义默认的cmdline,如果BootLoader没有传递cmdline,可以使用此default值,如果BootLoader传递了cmdline,则使用BootLoader传递的cmdline
}
}
pr_debug("Command line is: %s\n", (char*)data);
/* break now */
return 1;
}
early_init_dt_scan_memory:
扫描二维码关注公众号,回复:
1841503 查看本文章
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);
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)//如果不是PPC32架构,如果depth不等于1,说明memory节点不是root节点的子节点,如果node name不等于memory,说明不是我们关注的memory节点返回
return 0;
} else if (strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);//判断当前memory信息是否保存在"linux,usable-memory”中
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);//如果不在“linux,usable-memory”,说明信息保存在reg中
if (reg == NULL)//如果还没找到,说明找不到memorysize信息,return。
return 0;
endp = reg + (l / sizeof(__be32));//reg中cell的个数,reg表示第一个cell,endp表示最后一个cell
pr_err("memory scan node %s, reg size %d,\n", uname, l);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {//memory node的reg属性值其实就是一个数组,数组中的每一个entry都是base address和size的二元组。解析reg属性需要两个参数,dt_root_addr_cells和dt_root_size_cells,这两个参数分别定义了root节点的子节点(比如说memory node)reg属性中base address和size的cell数目,如果等于1,基地址(或者size)用一个32-bit的cell表示。对于ARMv8,一般dt_root_addr_cells和dt_root_size_cells等于2,表示基地址(或者size)用两个32-bit的cell表示。u64 base, size;//dt_root_addr_cells和dt_root_size_cells已经在early_init_dt_scan_root中初始化完成
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
pr_err("%s: memblock base = 0x%llx, size = 0x%llx\n", __func__, base, size);
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);//针对该memory mode中的每一个memory region,调用early_init_dt_add_memory_arch向系统注册memory type的内存区域(实际上是通过memblock_add完成的)。
}
return 0;
}
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);
if (!PAGE_ALIGNED(base)) {//一些条件判断
size -= PAGE_SIZE - (base & ~PAGE_MASK);
base = PAGE_ALIGN(base);
}
size &= PAGE_MASK;
if (base > MAX_PHYS_ADDR) {
pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
base, base + size);
return;
}
if (base + size - 1 > MAX_PHYS_ADDR) {
pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
((u64)MAX_PHYS_ADDR) + 1, base + size);
size = MAX_PHYS_ADDR - base + 1;
}
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);//调用memblock_add将当前memory节点信息添加到memblock中,base为memory节点的七点,size为memory节点的大小,从这里可以看出此处device_type为memory的一个节点就是memblock type为memory的一个region,memblock_add最后调用memblock_add_range函数完成memory节点的添加工作,见文章https://blog.csdn.net/zsj100213/article/details/78366750
}
文章最后有个问题需要说明:我们在arch/arm/boot/dts目录下面看到的dts文件关于memory的size的描述可能和实际的memory的size大小不一致,此问题主要是在bootloader中也会对memory信息进行获取,然后修改dts,并把dts load到内存制定的位置,然后把这个值传递给kernel,在setup_arch里面调用setup_machine_fdt时所传递的参数__atags_pointer就是boot loader传递过来存放dts的地址。