前面已經通過轉載的方式介紹了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:
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的地址。