Linux memory的初始化(二)

今天接着講memory的初始化函數arm_memblock_init,在上面完成了memory size的初始化,將整個memory添加以memory type的方式添加到了memory block中,下面是memory reserve部分的初始化,其初始化流程如下:

setup_arch->arm_memblock_init->early_init_fdt_scan_reserved_mem->of_scan_flat_dt->__fdt_scan_reserved_mem->__reserved_mem_reserve_reg->early_init_dt_reserve_memory_arch->memblock_reserve->memblock_reserve_region->memblock_add_range.

首先看arm_memblock_init, 此函數主要在上面已經初始化完成的memory size上完成kernel以及外設的memory reserve工作:

void __init arm_memblock_init(const struct machine_desc *mdesc)
{
	/* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
	memblock_reserve(__pa(_sdata), _end - _sdata);//調用memblock_reserve reserve從_sdata的物理地址到_end的物理地址的一段內存
#else
	memblock_reserve(__pa(_stext), _end - _stext);//調用memblock_reserve reserve從_stext的物理地址到_end的物理地址的一段內存,此處宏一般未定義,所以走下面
#endif
#ifdef CONFIG_BLK_DEV_INITRD
	/* FDT scan will populate initrd_start */
	if (initrd_start && !phys_initrd_size) {//關於initrd的一些判斷
		phys_initrd_start = __virt_to_phys(initrd_start);
		phys_initrd_size = initrd_end - initrd_start;
	}
	initrd_start = initrd_end = 0;
	if (phys_initrd_size &&
	    !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
		pr_err("INITRD: 0x%08llx+0x%08lx is not a memory region - disabling initrd\n",
		       (u64)phys_initrd_start, phys_initrd_size);
		phys_initrd_start = phys_initrd_size = 0;
	}
	if (phys_initrd_size &&
	    memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
		pr_err("INITRD: 0x%08llx+0x%08lx overlaps in-use memory region - disabling initrd\n",
		       (u64)phys_initrd_start, phys_initrd_size);
		phys_initrd_start = phys_initrd_size = 0;
	}
	if (phys_initrd_size) {//如果initrd段沒有被reserve,則調用memblock_reserve函數reserve initial ramdisk image區域
		memblock_reserve(phys_initrd_start, phys_initrd_size);

		/* Now convert initrd to virtual addresses */
		initrd_start = __phys_to_virt(phys_initrd_start);
		initrd_end = initrd_start + phys_initrd_size;
	}
#endif

	arm_mm_memblock_reserve();//reserve一段地址用來存放用於進行地址映射的pgd table.

	/* reserve any platform specific memblock areas */
	if (mdesc->reserve)
		mdesc->reserve();

	early_init_fdt_scan_reserved_mem();//解析dts的node name爲reserved-memory的所有部分進行reserve

	/* reserve memory for DMA contiguous allocations */
	dma_contiguous_reserve(arm_dma_limit);

	/* reserve memory for MT-RAMDUMP */
	mrdump_rsvmem();
	//memblock_reserve(0x78000000, 0x8000000);
	//memblock_reserve(0x72100000, 0x1600000);
	//memblock_reserve(0x44640000, 0xE00000);
	
	arm_memblock_steal_permitted = false;
	memblock_dump_all();
}

early_init_fdt_scan_reserved_mem:

void __init early_init_fdt_scan_reserved_mem(void)
{
	int n;
	u64 base, size;

	if (!initial_boot_params)//initial_boot_params實際上對應得是fdt的虛擬地址,如果此處爲0,則表示沒有fdt需要scan,return
		return;

	/* Reserve the dtb region */
	early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
					  fdt_totalsize(initial_boot_params),
					  0);//reserve一段設備樹文件大小內存用來存放設備樹文件
	/* Process header /memreserve/ fields */
	for (n = 0; ; n++) {
		fdt_get_mem_rsv(initial_boot_params, n, &base, &size);//對header /memreserve/ fields進行內存保留,在fdt header中有一組memory reserve參數,具體位置是fdt base address+off_mem_rsvmap,其中off_mem_rsvmap是fdt_header結構體成員
		if (!size)
			break;
		early_init_dt_reserve_memory_arch(base, size, 0);//保留每一個/memreserve/fields定義的memory region
	}

	of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);//核心執行函數,of_scan_flat_dt將整個dts解析一遍,找到node name爲reserved-memory的,然後解析其reg屬性獲取node的base以及size
	fdt_init_reserved_mem();
}

of_scan_flat_dt:

int __init of_scan_flat_dt(int (*it)(unsigned long node,
				     const char *uname, int depth,
				     void *data),
			   void *data)
{
	const void *blob = initial_boot_params;
	const char *pathp;
	int offset, rc = 0, depth = -1;

        for (offset = fdt_next_node(blob, -1, &depth);
             offset >= 0 && depth >= 0 && !rc;//這裏的rc表示回調函數的返回值對於for循環是否繼續循環至關重要
             offset = fdt_next_node(blob, offset, &depth)) {循環讀取所有的node,並調用回調函數進行判斷,符合條件則reserve

		pathp = fdt_get_name(blob, offset, NULL);//解析出每個node的名字
		if (*pathp == '/')
			pathp = kbasename(pathp);
		rc = it(offset, pathp, depth, data);//調用回調函數,將解析到的node爲reserved-memory的地址進行reserve
	}
	return rc;
}

__fdt_scan_reserved_mem:

static int __init __fdt_scan_reserved_mem(unsigned long node, const char *uname,
					  int depth, void *data)
{
	static int found;
	const char *status;
	int err;

	if (!found && depth == 1 && strcmp(uname, "reserved-memory") == 0) {//過濾操作,如果解析到的node名字爲reserved-memory,且此時是root node的子節點,depth爲1
		if (__reserved_mem_check_root(node) != 0) {
			pr_err("Reserved memory: unsupported node format, ignoring\n");
			/* break scan */
			return 1;
		}
		found = 1;//找到了reserved-memory節點,found置1
		/* scan next node */
		return 0;//return 0表示of_scan_flat_dt的for循環會繼續循環
	} else if (!found) {//沒找到reserved-memory節點,則繼續遍歷下一個節點
		/* scan next node */
		return 0;
	} else if (found && depth < 2) {//如果找到了reserved-memory節點,並完成了對其所有的subnode的scan,則結束scan過程
		/* scanning of /reserved-memory has been finished */
		return 1;//return 1表示結束of_scan_flat_dt函數的for循環,即退出scan dts操作
	}

	status = of_get_flat_dt_prop(node, "status", NULL);//如果定義了status屬性,則要求其爲okey或者ok,一般情況下爲null
	if (status && strcmp(status, "okay") != 0 && strcmp(status, "ok") != 0)
		return 0;

	err = __reserved_mem_reserve_reg(node, uname);//定義reserved memory有兩種方法,一種是靜態定義,也就是定義了reg屬性,這時候,可以通過調用__reserved_mem_reserve_reg函數解析reg的(address,size)的二元數組,逐一對每一個定義的memory region進行預留。實際的預留內存動作可以調用memblock_reserve或者memblock_remove,具體調用哪一個是和該節點是否定義no-map屬性相關,如果定義了no-map屬性,那麼說明這段內存操作系統根本不需要進行地址映射,也就是說這塊內存是不歸操作系統內存管理模塊來管理的,而是歸於具體的驅動使用(在device tree中,設備節點可以定義memory-region節點來引用在memory node中定義的保留內存,具體可以參考reserved-memory.txt文件)。
	if (err == -ENOENT && of_get_flat_dt_prop(node, "size", NULL))//靜態定義無法reserve,則採用動態分配,也就是說定義了該內存區域的size(也可以定義alignment或者alloc-range進一步約定動態分配的reserved memory屬性,不過這些屬性都是option的),但是不指定具體的基地址,讓操作系統自己來分配這段memory。
		fdt_reserved_mem_save_node(node, uname, 0, 0);//對於沒有指定初始地址的,首先判斷是否有指定size,如果有則先初始化reserved_mem結構體數組,以及初始化reserve memory region個數,保留reserve信息,後續再通過動態分配的方式來分配reserve的memory

	/* scan next node */
	return 0;
}

fdt_reserved_mem_save_node:

void __init fdt_reserved_mem_save_node(unsigned long node, const char *uname,
				      phys_addr_t base, phys_addr_t size)//沒添加一個reserve region,次函數會執行一次,全局變量reserved_mem_count加1,此變量保存reserve memory region的個數,
{
	struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];

	if (reserved_mem_count == ARRAY_SIZE(reserved_mem)) {//超過最大限制MAX_RESERVED_REGIONS=30
		pr_err("Reserved memory: not enough space all defined regions.\n");
		return;
	}

	rmem->fdt_node = node;//初始化信息
	rmem->name = uname;
	rmem->base = base;
	rmem->size = size;

	reserved_mem_count++;
	return;
}

__reserved_mem_reserve_reg:

static int __init __reserved_mem_reserve_reg(unsigned long node,
					     const char *uname)
{
	int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32);
	phys_addr_t base, size;
	int len;
	const __be32 *prop;
	int nomap, first = 1;

	prop = of_get_flat_dt_prop(node, "reg", &len);//獲取節點的reg屬性值
	if (!prop)
		return -ENOENT;

	if (len && len % t_len != 0) {
		pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n",
		       uname);
		return -EINVAL;
	}

	nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;

	while (len >= t_len) {//循環獲取當前節點的需要reserve的起始地址以及大小,每個節點有可能會reserve多段內存,這也是len會大於t_len的原因
		base = dt_mem_next_cell(dt_root_addr_cells, &prop);//獲取當前節點的base和size
		size = dt_mem_next_cell(dt_root_size_cells, &prop);

		if (size &&
		    early_init_dt_reserve_memory_arch(base, size, nomap) == 0)//核心函數,進行內存的reserve操作
			pr_info("Reserved memory: reserved region for node '%s': base %pa, size %ld MiB\n",
				uname, &base, (unsigned long)size / SZ_1M);
		else
			pr_info("Reserved memory: failed to reserve memory for node '%s': base %pa, size %ld MiB\n",
				uname, &base, (unsigned long)size / SZ_1M);

		len -= t_len;
		if (first) {
			fdt_reserved_mem_save_node(node, uname, base, size);
			first = 0;
		}
	}
	return 0;
}

early_init_dt_reserve_memory_arch:

int __init __weak early_init_dt_reserve_memory_arch(phys_addr_t base,
					phys_addr_t size, bool nomap)
{
	if (nomap)//如果定義了no-map屬性,則說明此memory region不需要映射,直接memblock_remove掉這一段region
		return memblock_remove(base, size);//此處爲memory type region隨着reserve memory region增加的關鍵所在
	return memblock_reserve(base, size);//調用memblock_reserve->memblock_reserve_region->memblock_add_range,memblock_add_range操作見前面文章,完成reserve操作
}

device tree中的reserved-memory節點及其子節點靜態或者動態定義了若干的reserved memory region,靜態定義的memory region起始地址和size都是確定的,因此可以立刻調用memblock的模塊進行內存區域的預留,但是對於動態定義的memory region,__fdt_scan_reserved_mem只是將信息保存在了reserved_mem全局變量中,並沒有進行實際的內存預留動作,具體的操作在fdt_init_reserved_mem函數中,代碼如下:

void __init fdt_init_reserved_mem(void)
{
	int i;

	/* check for overlapping reserved regions */
	__rmem_check_for_overlap();//檢測reserve memory region是否有重疊,如果有,打印錯誤信息

	for (i = 0; i < reserved_mem_count; i++) {//遍歷每一個reserved memory region
		struct reserved_mem *rmem = &reserved_mem[i];
		unsigned long node = rmem->fdt_node;
		int len;
		const __be32 *prop;
		int err = 0;

		prop = of_get_flat_dt_prop(node, "phandle", &len);//每一個需要被其他node引用的node都需要定義"phandle", 或者"linux,phandle"。雖然在實際的device tree source中看不到這個屬性,實際上dtc會完美的處理這一切的。
		if (!prop)
			prop = of_get_flat_dt_prop(node, "linux,phandle", &len);
		if (prop)
			rmem->phandle = of_read_number(prop, len/4);

		if (rmem->size == 0)//size等於0的memory region表示這是一個動態分配region,base address尚未定義,因此我們需要通過__reserved_mem_alloc_size函數對節點進行分析(size、alignment等屬性),然後調用memblock的alloc接口函數進行memory block的分配,最終的結果是確定base address和size,並將這段memory region從memory type的數組中移到reserved type的數組中。當然,如果定義了no-map屬性,那麼這段memory會從系統中之間刪除(memory type和reserved type數組中都沒有這段memory的定義)。
			err = __reserved_mem_alloc_size(node, rmem->name,
						 &rmem->base, &rmem->size);
		if (err == 0)
			__reserved_mem_init_node(rmem);//保留內存有兩種使用場景,一種是被特定的驅動使用,這時候在特定驅動的初始化函數(probe函數)中自然會進行處理。還有一種場景就是被所有驅動或者內核模塊使用,例如CMA,per-device Coherent DMA的分配等,這時候,我們需要借用device tree的匹配機制進行這段保留內存的初始化動作。有興趣的話可以看看RESERVEDMEM_OF_DECLARE的定義,這裏就不再描述了。
	}
}
#define RESERVEDMEM_OF_DECLARE(name, compat, init)			\
	_OF_DECLARE(reservedmem, name, compat, init, reservedmem_of_init_fn)
#define _OF_DECLARE(table, name, compat, fn, fn_type)			\
	static const struct of_device_id __of_table_##name		\
		__used __section(__##table##_of_table)			\
		 = { .compatible = compat,				\
		     .data = (fn == (fn_type)NULL) ? fn : fn  }

__reserved_mem_init_node:

static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
{
	extern const struct of_device_id __reservedmem_of_table[];
	const struct of_device_id *i;

	for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {//遍歷所有初始化了of_device_id結構體的驅動
		reservedmem_of_init_fn initfn = i->data;
		const char *compat = i->compatible;

		if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))//通過compatible name進行匹配,如果匹配到了則說明有專用的驅動來使用此reserve memory region,如果沒有匹配到,這說明是通用reserve memory region,接觸次輪循環,繼續scan下一個驅動
			continue;

		if (initfn(rmem) == 0) {//調用驅動註冊的回調函數進行初始化以及驅動在此需要做的事情
			pr_info("Reserved memory: initialized node %s, compatible id %s\n",
				rmem->name, compat);
			return 0;
		}
	}
	return -ENOENT;
}

最後來看一下device tree的實例:

 217         reserved_memory: reserved-memory {
 218                 #address-cells = <2>;
 219                 #size-cells = <2>;
 220                 ranges;
 221                 /*TODO: add reserved memory node here*/
 222                 pstore-reserved-memory@44410000 {
 223                         compatible = "mediatek,pstore";
 224                         reg = <0 0x44410000 0 0xe0000>;
 225                 };
 226                 ram_console-reserved-memory@44400000 {
 227                         compatible = "mediatek,ram_console";
 228                         reg = <0 0x44400000 0 0x10000>;
 229                 };
 230                 minirdump-reserved-memory@444f0000{
 231                         compatible = "mediatek,minirdump";
 232                         reg = <0 0x444f0000 0 0x10000>;
 233                 };
 234                 consys-reserve-memory {
 235                         compatible = "mediatek,consys-reserve-memory";
 236                         #address-cells = <2>;
 237                         #size-cells = <2>;
 238                         no-map;
 239                         size = <0 0x200000>;
 240                         alignment = <0 0x200000>;
 241                         alloc-ranges = <0 0x40000000 0 0x38000000>;
 242                 };
 243                 //add XWWYWHSJB-1791 by wurui.zhang  2018-05-29 start
 244                 soter-shared-mem {
 245                         compatible = "microtrust,shared_mem";
 246                         no-map;
 247                         alloc-ranges = <0 0x40000000 0 0x38000000>;
 248                         size = <0 0x500000>;
 249                 };
 250                 //add XWWYWHSJB-1791 by wurui.zhang  2018-05-29 end
 251         };

總結:

物理內存佈局是歸於memblock模塊進行管理的,該模塊定義了struct memblock memblock這樣的一個全局變量保存了memory type和reserved type的memory region list。而通過這兩個memory region的數組,我們就知道了操作系統需要管理的所有系統內存的佈局情況。

reserve實際上就是在總的memory size上面挖坑。

1. 往memblock裏面添加type爲reserve類型的memory操作使用memblock_reserve()函數.

2. 往memblock裏面添加type爲memory類型的memory時候使用memblock_add().

3. 從memblock裏面移除一塊type爲memory的memory使用memblock_remove().

4. 從memblock裏面移除一塊type爲reserve的memory使用memblock_free().

注本文有部分參考了wowotech的內存初始化代碼分析(二):內存佈局
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章