linux内存管理——内存大小起始地址的解析与修改
Posted 正在起飞的蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内存管理——内存大小起始地址的解析与修改相关的知识,希望对你有一定的参考价值。
1、前言
(1)本文是以hi3516dv300芯片的uboot和内核源码进行讲解,uboot版本是2016.11,内核版本是4.9.37;
(2)uboot没有采用设备树技术,还是传统的tag传参;内核采用了设备树技术,镜像是zImage-dtb格式;
(3)下面的源码都是摘抄自dv300芯片的uboot和内核;
2、linux内核获取内存信息的来源
(1)设备树中可以通过"/memory"节点来指定内存的起始地址、大小等信息;
(2)uboot启动内核时,可以通过ATAG_MEM类型的struct tag结构体向内核传递内存起始地址、大小等信息;
(3)uboot启动内核时,可以通过ATAG_CMDLINE类型的struct tag结构体向内核传递内存起始地址、大小等信息;
内存信息生效的优先级:ATAG_CMDLINE类型的tag > ATAG_MEM类型的tag > 设备树"/memory"节点
3、hi3516dv300芯片的内存地址范围
(1)通过查阅数据手册可知《Hi3516DV300 专业型 Smart IP Camera SoC 用户指南》,dv300芯片的内存地址范围是0x8000_0000-0xFFFF_FFFF,最大能支持2G内存;
(2)海思芯片把内存分为mmz内存和os内存,最终linux系统能管理的内存是小于实际接的内存大小,因为要分一部分内存作为mmz;
4、设备树中指定内存大小
/
······
memory
device_type = "memory";
reg = <0x82000000 0x20000000>;
;
······
;
(1)memory节点是设备树中专门用来指定内存起始地址、大小等信息,在内核启动时会被解析;
(2)上面的含义:内核的内存起始地址是0x82000000(2G),内存大小是0x20000000(512M);
5、uboot启动内核的BOOTM_ENABLE_MEMORY_TAGS传参
static void setup_memory_tags(bd_t *bd)
int i;
//内存地址可能不连续,分为好几块内存,这里用循环来设置每块内存的信息
//可能存在多个ATAG_MEM类型的tag
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++)
params->hdr.tag = ATAG_MEM; //标明这是内存信息的tag
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start; //内存的起始地址
params->u.mem.size = bd->bi_dram[i].size; //内存的大小
params = tag_next (params);
当uboot中定义了BOOTM_ENABLE_MEMORY_TAGS宏时,会传递给内核ATAG_MEM类型的tag参数,内存具体的start和size分析uboot的源码可知;
6、uboot启动内核的BOOTM_ENABLE_CMDLINE_TAG传参
static void setup_commandline_tag(bd_t *bd, char *commandline)
char *p;
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\\0')
return;
//构建ATAG_CMDLINE类型的tag
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
当uboot中定义了BOOTM_ENABLE_CMDLINE_TAG宏时,会传递给内核ATAG_CMDLINE类型的tag参数,具体bootarg分析uboot源码可知;
7、内核启动时设置内存信息的流程解析
7.1、内存信息的设置信息
//设备树dts文件
/
······
memory
device_type = "memory";
reg = <0x82000000 0x20000000>;
;
······
;
//内核启动参数
~ # cat /proc/cmdline
mem=1408M console=ttyS0,115200 root=/dev/mmcblk0p7 rootfstype=squashfs rootwait
//uboot的ATAG_MEM类型的struct tag
这里直接告诉结论:内存起始地址是0x8000_0000,内存大小是0x20000000;
(1)如果设备树memory节点生效:内存起始地址是0x82000000,大小是0x20000000(512M);
(2)如果启动参数生效:内存起始地址是0x80000000,大小是1408M;(内存起始地址0x80000000可以分析内核源码)
(3)如果ATAG_MEM类型的tag生效:内存起始地址是0x80000000,大小是0x20000000(512M);
7.2、内核解析内存信息的流程
(1)将设备树dts文件编译成dtb格式文件,接续到zImage镜像后面构成zImage-dtb;
(2)uboot构建好构建ATAG_CMDLINE类型和ATAG_MEM类型的tag,启动内核,将tag的内存地址告诉内核;
(3)内核在解压缩阶段会解析tag传参,其中会用ATAG_MEM类型的tag替换dtb的"/memory"节点的reg属性值,用ATAG_CMDLINE类型的tag替换dtb的"/chosen"节点的bootargs属性值;
(4)解析dtb的"/memory"节点,向内核注册内存信息;
(5)解析dtb的"/chosen"节点的bootargs属性值,保存到boot_command_line变量;
(6)解析boot_command_line变量中的内存信息,向内核注册内存信息;这里注册时会覆盖掉第四步解析"/memory"节点时设置的内存信息;
7.3、内核解析内存信息的相关函数调用
start_kernel()
setup_arch()
setup_machine_fdt()
early_init_dt_scan_nodes()
of_scan_flat_dt() //遍历dtb数据中的节点,找到memory节点
early_init_dt_scan_chosen() //解析"/chosen"节点的bootargs属性值,保存到boot_command_line变量
early_init_dt_scan_memoryc() //解析memory节点
of_get_flat_dt_prop() //获取memroy节点的reg属性值,也就是内存的起始地址、范围
early_init_dt_add_memory_arch()
memblock_add(base, size)
memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0) //向内核注册内存起始地址、范围
parse_early_param() //解析boot_command_line变量
parse_early_options()
parse_args()
parse_one() //将bootargs中的每项设置都单独解析出来
do_early_param()
early_mem() //解析"mem=1408M"
arm_add_memory()
memblock_add()
memblock_add_range() //向内核注册内存起始地址、范围
(1)设备树的节点解析参考博客:《linux内核启动阶段对设备树的解析》;
(2)boot_command_line变量的解析参考博客:《内核启动参数cmdline详解》;
(3)uboot给内核的tag传参,参考博客:《uboot以tag方式给内核传参》;
(4)不采用设备树时内核对tag的解析参考博客:《内核中对uboot传参tags的校验》;
7.4、memblock_add()函数
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
printk("memblock_add: [%#016llx-%#016llx], flags %#02lx, %pF @@@@@@@\\n",
(unsigned long long)base,
(unsigned long long)base + size - 1,
0UL, (void *)_RET_IP_);
return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
(1)memblock_add()函数是内存提供的注册内存信息的接口,传入内存的起始地址(物理地址)和大小;
(2)memblock_add()函数内部有打印,默认是不打印的,放开打印帮助调试,我这里是直接改成printk;
7.5、内核启动打印分析
······
- 80000000 , 20000000
memblock_add: [0x00000080000000-0x0000009fffffff], flags 0x0, early_init_dt_scan_memory+0x16c/0x190
······
memblock_add: [0x00000080000000-0x000000d7ffffff], flags 0x0, arm_add_memory+0x174/0x19c
······
(1)从内核打印可以看出内核总共设置两次内存信息,根据上面流程分析,第一次是解析"/memory"节点时设置的,第二次是解析bootargs的"mem=1408M"时设置的;
(2)第一次设置:内存启动地址是0x80000000,内存大小是0x20000000,刚好符合uboot的ATAG_MEM类型的tag参数;
(3)第二次设置:内存起始地址是0x80000000,内存大小是0xd8000000 - 0x80000000 = 1408M,刚好符合bootargs的"mem=1408M";
7.6、内核实际内存信息
~ # free
total used free shared buffers
Mem: 1419980 132504 1287476 448 23700
-/+ buffers: 108804 1311176
Swap: 0 0 0
通过free命令可知,内核实际识别到的内存大概是1408M(识别到1408M,但是内核可能保留一部分内存不对外显示),证实我们之前的结论是bootargs的"mem=1408M"生效;
以上是关于linux内存管理——内存大小起始地址的解析与修改的主要内容,如果未能解决你的问题,请参考以下文章
清晰讲解Linux内核,连续内存分配与非连续内存分配(图例解析)