Linux memory的初始化(一)

前面已經通過轉載的方式介紹了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, &reg);
		size = dt_mem_next_cell(dt_root_size_cells, &reg);
		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的地址。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章