你不知道的Linux设备树--memory&chosen节点

根节点那一节我们说过,最简单的设备树也必须包含cpus节点和memory节点。memory节点用来描述硬件内存布局的。如果有多块内存,既可以通过多个memory节点表示,也可以通过一个memory节点的reg属性的多个元素支持。举一个例子,假如某个64位的系统有两块内存,分别是
• ram: 起始地址 0x0, 长度 0x80000000 (2gb)
• ram: 起始地址 0x100000000, 长度 0x100000000 (4gb)
对于64位的系统,根节点的#address-cells属性和#size-cells属性都设置成2。一个memory节点的形式如下(还记得前几节说过节点地址必须和reg属性第一个地址相同的事情吧):
memory@0 {
device_type = memory;
reg = ;
};
两个memory节点的形式如下:
memory@0 {
device_type = memory;
reg = ;
};
memory@100000000 {
device_type = memory;
reg = ;
};
chosen节点也位于根节点下,该节点用来给内核传递参数(不代表实际硬件)。对于linux内核,该节点下最有用的属性是bootargs,该属性的类型是字符串,用来向linux内核传递cmdline。规范中还定义了stdout-path和stdin-path两个可选的、字符串类型的属性,这两个属性的目的是用来指定标准输入输出设备的,在linux中,这两个属性基本不用。
memory和chosen节点在内核初始化的代码都位于start_kernel()->setup_arch()->setup_machine_fdt()->early_init_dt_scan_nodes()函数中(位于drivers/of/fdt.c),复制代码如下(本节所有代码都来自官方内核4.4-rc7版本):
1078 void __init early_init_dt_scan_nodes(void)
1079 {
1080 /* retrieve various information from the /chosen node */
1081 of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
1082
1083 /* initialize {size,address}-cells info */
1084 of_scan_flat_dt(early_init_dt_scan_root, null);
1085
1086 /* setup memory, calling early_init_dt_add_memory_arch */
1087 of_scan_flat_dt(early_init_dt_scan_memory, null);
1088 }
of_scan_flat_dt函数扫描整个设备树,实际的动作是在回调函数中完成的。第1081行是对chosen节点操作,该行代码的作用是将节点下的bootargs属性的字符串拷贝到boot_command_line指向的内存中。boot_command_line是内核的一个全局变量,在内核的多处都会用到。第1084行是根据根节点的#address-cells属性和#size-cells属性初始化全局变量dt_root_size_cells和dt_root_addr_cells,还记得前边说过如果没有设置属性的话就用默认值,这些都在early_init_dt_scan_root函数中实现。第1087行是对内存进行初始化,复制early_init_dt_scan_memory部分代码如下:
893 /**
894 * early_init_dt_scan_memory - look for an parse memory nodes
895 */
896 int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
897 int depth, void *data)
898 {
899 const char *type = of_get_flat_dt_prop(node, device_type, null);
900 const __be32 *reg, *endp;
901 int l;
902
903 /* we are scanning memory nodes only */
904 if (type == null) {
905 /*
906 * the longtrail doesn't have a device_type on the
907 * /memory node, so look for the node called /memory@0.
908 */
909 if (!is_enabled(config_ppc32) || depth != 1 || strcmp(uname, memory@0) != 0)
910 return 0;
911 } else if (strcmp(type, memory) != 0)
912 return 0;
913
914 reg = of_get_flat_dt_prop(node, linux,usable-memory, &l);
915 if (reg == null)
916 reg = of_get_flat_dt_prop(node, reg, &l);
917 if (reg == null)
918 return 0;
919
920 endp = reg + (l / sizeof(__be32));
921
922 pr_debug(memory scan node %s, reg size %d,, uname, l);
923
924 while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
925 u64 base, size;
926
927 base = dt_mem_next_cell(dt_root_addr_cells, ®);
928 size = dt_mem_next_cell(dt_root_size_cells, ®);
929
930 if (size == 0)
931 continue;
932 pr_debug( - %llx , %llx, (unsigned long long)base,
933 (unsigned long long)size);
934
935 early_init_dt_add_memory_arch(base, size);
936 }
937
938 return 0;
939 }
第914行可以看出linux内核不仅支持reg属性,也支持linux,usable-memory属性。对于dt_root_addr_cells和dt_root_size_cells的使用也能看出根节点的#address-cells属性和#size-cells属性都是用来描述内存地址和大小的。得到每块内存的起始地址和大小后,在第935行调用early_init_dt_add_memory_arch函数,复制代码如下:
983 void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
984 {
985 const u64 phys_offset = __pa(page_offset);
986
987 if (!page_aligned(base)) {
988 if (size max_memblock_addr) {
999 pr_warning(ignoring memory block 0x%llx - 0x%llx,
1000 base, base + size);
1001 return;
1002 }
1003
1004 if (base + size - 1 > max_memblock_addr) {
1005 pr_warning(ignoring memory range 0x%llx - 0x%llx,
1006 ((u64)max_memblock_addr) + 1, base + size);
1007 size = max_memblock_addr - base + 1;
1008 }
1009
1010 if (base + size < phys_offset) {
1011 pr_warning(ignoring memory block 0x%llx - 0x%llx,
1012 base, base + size);
1013 return;
1014 }
1015 if (base < phys_offset) {
1016 pr_warning(ignoring memory range 0x%llx - 0x%llx,
1015 if (base < phys_offset) {
1016 pr_warning(ignoring memory range 0x%llx - 0x%llx,
1017 base, phys_offset);
1018 size -= phys_offset - base;
1019 base = phys_offset;
1020 }
1021 memblock_add(base, size);
1022 }
从以上代码可以看出内核对地址和大小做了一系列判断后,最后调用memblock_add将内存块加入内核。

高效、紧凑的两相电源为英特尔移动式CPU提供2A电流
蔚来不是特斯拉,李斌不是马斯克
AI安防发展提速 逐步进入应用阶段
齐纳二极管原理及使用_齐纳二极管怎么使用_齐纳二极管使用电路图
N62400在燃料电池测试中的优势
你不知道的Linux设备树--memory&chosen节点
盘点新一代人工智能领域十大最具成长性技术
两款12v调光器电路图解析
关于半导体晶圆的介绍和分析
realme第三季度在菲律宾首个份额市场诞生
爱立信第三阶段2.6GHz频段基站设备测试正在顺利进行中
什么是RK3399开发板基础配置
粳稻地物光谱特征波段分析
尼康全画幅微单Z7相机采用了50mmf/1.2镜头对焦灵敏度极高
VR虚拟现实教堂解决宗教生活礼拜难题
人工智能疾病诊断系统诊断儿科疾病准确率为90%左右
LED照明如何依托借助“互联网+”发展战略,实现转型升级?
TI推出压摆率达2675V/us的JFET输入放大器
中国联通为5G新基建下保障了共享共建网络的数据安全
三合一食品安全检测仪的用途有哪些