今天接着講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的內存初始化代碼分析(二):內存佈局