如何修改linux下的内存大小、起始地址

时间:2024-04-14 19:20:56

在实际的工作中,由于产品型号的不同,经常需要调整linux所管理的内存的大小,而内核在启动阶段,会两次去解析从uboot传递过来的关于内存的信息,具体如下:

如何修改linux下的内存大小、起始地址

一、解析从uboot传递过来的tag(在parse_tags中处理)

在uboot的do_bootm_linux()函数中,会创建一系列需要传递给内核的tag,所有的tag以链表形式链接到指定的物理内存中。setup_start_tag用来建立起始的tag,而起始的物理地址由bd->bi_boot_params指定:

 

 
  1. static void setup_start_tag (bd_t *bd)

  2. {

  3. params = (struct tag *) bd->bi_boot_params;

  4.  
  5. params->hdr.tag = ATAG_CORE;

  6. params->hdr.size = tag_size (tag_core);

  7.  
  8. params->u.core.flags = 0;

  9. params->u.core.pagesize = 0;

  10. params->u.core.rootdev = 0;

  11.  
  12. params = tag_next (params);

  13. }

 

bi_boot_params是在board_init中初始化的,此地址是与内核协商一致的用来存放tag的基址。

int board_init (void)

{

…………

    gd->bd->bi_boot_params = CFG_BOOT_PARAMS;

…………

}

而内存的tag是在setup_memory_tags()函数中创建的,其hdr.tag指定了tag的类型为ATAG_MEM

 

 
  1. static int __init parse_tag_mem32(const struct tag *tag)

  2. {

  3. if (meminfo.nr_banks >= NR_BANKS) {

  4. printk(KERN_WARNING

  5. "Ignoring memory bank 0x%08x size %dKB\n",

  6. tag->u.mem.start, tag->u.mem.size / 1024);

  7. return -EINVAL;

  8. }

  9. arm_add_memory(tag->u.mem.start, tag->u.mem.size);

  10. return 0;

  11. }

  12.  
  13. __tagtable(ATAG_MEM, parse_tag_mem32);


在内核中,会通过 __tagtable  宏来建立起相关的 struct tagtable  的数据结构,并放入 ".taglist.init"  段中,

 

#define __tag __used __attribute__((__section__(".taglist.init")))

#define __tagtable(tag, fn) \

static struct tagtable __tagtable_##fn __tag = { tag, fn }

 
  1. static int __init parse_tag_mem32(const struct tag *tag)

  2. {

  3. return arm_add_memory(tag->u.mem.start, tag->u.mem.size);

  4. }

  5.  
  6. __tagtable(ATAG_MEM, parse_tag_mem32);

 

而在start_kernel()->setup_arch()->parse_tags()函数中会根据从指定的物理内存中解析出来的tag的类型(即在uboot中写入的hdr.tag)去解析不同的tag。

在内核中此物理内存地址是在MACHINE_START中定义的,其中的boot_params与uboot中的bi_boot_params数据段指向相同的物理内存地址。因此是在uboot中写入tag,在内核中此地址解析tag。

 
  1. MACHINE_START(hi3520v100, "hi3520v100")

  2. .phys_io = IO_SPACE_PHYS_START,

  3. .io_pg_offst = (IO_ADDRESS(IO_SPACE_PHYS_START) >> 18) & 0xfffc,

  4. .boot_params = PHYS_OFFSET + 0x100,

  5. .map_io = hisilicon_map_io,

  6. .init_irq = hisilicon_init_irq,

  7. .timer = &hisilicon_timer,

  8. .init_machine = hisilicon_init_machine,

  9. MACHINE_END

 

 
  1. struct tagtable {

  2. __u32 tag;

  3. int (*parse)(const struct tag *);

  4. };


在 parse_tags() 中,会根据读出来的 tag 的类型,即 hdr.tag 与从 ".taglist.init" 段中的 struct tagtable 中的 tag 字段比较,如果相等,便执行 struct tagtable 中的 parse() 函数,对内存的 tag 来讲,其类型是 ATAG_MEM ,解析函数是 parse_tag_mem32();

 

 
  1. static int __init parse_tag_mem32(const struct tag *tag)

  2. {

  3. if (meminfo.nr_banks >= NR_BANKS) {

  4. printk(KERN_WARNING

  5. "Ignoring memory bank 0x%08x size %dKB\n",

  6. tag->u.mem.start, tag->u.mem.size / 1024);

  7. return -EINVAL;

  8. }

  9. arm_add_memory(tag->u.mem.start, tag->u.mem.size);

  10. return 0;

  11. }

  12.  
  13. __tagtable(ATAG_MEM, parse_tag_mem32);

在内核中,物理内存的起始地址和大小存放在一个 struct meminfo meminfo 的全局变量中:

 

 

 
  1. struct meminfo {

  2. int nr_banks;

  3. struct membank bank[NR_BANKS];

  4. };

 
  1. struct membank {

  2. unsigned long start;

  3. unsigned long size;

  4. int node;

  5. };

 

nr_banks表示内核总共管理了多少个bank。

struct membank记录了内核中各个bank的信息,start表示起始地址,size表示此bank的大小,node表示此bank属于哪个内存结点。

Linux内核可以管理多个不连续的物理内存,每段连续的物理内存的大小和起始地址存在一个struct membank结构体中,有多少段物理内存,就有多少个bank。

parse_tag_mem32解析在uboot中建立的关于内存的tag,把其中的物理内存地址和大小填充到bank中。

 

二、解析从uboot传递过来的boot_command_line(在parse_cmdline函数中解析)。

boot_command_line命令行是在uboot的fix_bootargs()函数里建立的。即在uboot中看到的bootargs的环境变量

 
  1. static void fix_bootargs(char *cmdline)

  2. {

  3. ….……

  4. /* fix "mem=" params */

  5. p = strstr(cmdline,"mem=");

  6. if(!p) {

  7. sprintf(args," mem=%dM",gd->bd->bi_dram[0].size/0x200000);

  8. strcat(cmdline,args);

  9. }

  10. ……………

  11. }


在内核中是通过 early_mem() 来解析 boot_command_line 中有关内存大小的参数行的。

 

 
  1. static void __init early_mem(char **p)

  2. {

  3. static int usermem __initdata = 0;

  4. unsigned long size, start;

  5.  
  6. /*

  7. * If the user specifies memory size, we

  8. * blow away any automatically generated

  9. * size.

  10. */

  11. if (usermem == 0) {

  12. usermem = 1;

  13. meminfo.nr_banks = 0;

  14. }

  15.  
  16. start = PHYS_OFFSET;

  17. size = memparse(*p, p);

  18. if (**p == '@')

  19. start = memparse(*p + 1, p);

  20.  
  21. arm_add_memory(start, size);

  22. }

  23. __early_param("mem=", early_mem);

 

 

该函数解析从uboot传递进来的boot_command_line命令行参数中以“mem=”开头的命令行,如果boot_command_line中有以“mem=”开头的命令行,就调用该函数解析“mem=”之后的关于内存的信息,

把内存的大小写到对应的bank中去,内存的基地址在此处是一个默认值。如果有两段不连续的物理内存,可以在boot_command_line中设置如下内容即可:

[email protected] [email protected]

在此处,定义了static int usermem __initdata = 0,从而设置meminfo.nr_banks = 0,这样把前面解析uboot的tag所赋值的bank内容又重写了,所以相当于前面解析tag的操作没有生效,起作用的还是此处的解析boot_command_line的操作。

 

 

三、以上是内核启动过程中所做的两次解析内存参数的操作,在实际应用中需要修改linux内存大小时可以采取相应的方法:

1、 修改uboot中内存相关的tags或者bootargs的命令行参数。这种做法虽然可以修改linux管理的内存的大小,但是由于要修改uboot,这样会对产品生产中增加困难,而且bootloader在原则上是要尽量少做改动,防止由于修改bootloader造成板子无法启动等问题,所以此方法不推荐使用。

2、 通过在解析boot_command_line之前修改其中的”mem=”之后的相关内容来修改linux所管理的内存大小,这样可以做到不同产品间的兼容性,而且后续的产品升级等方面也比较简单容易操作。

 

在海思平台上实现了这种做法。

 
  1. void __init hikio_fix_meminfo(char *cmdline,struct meminfo *mi)

  2. {

  3. ……………….

  4. strcpy(p,p+8);

  5. strcat(cmdline," mem=72M");

  6. mi->bank[0].size = 72*0x100000;

  7. }

然后在解析cmdline之前执行此函数。
hikio_fix_meminfo(from,&meminfo);/* wangqian fix for cost-down boards*/

memcpy(boot_command_line, from, COMMAND_LINE_SIZE);

boot_command_line[COMMAND_LINE_SIZE-1] = '\0';

parse_cmdline(cmdline_p, from);

这种方法可以很方便的根据不同的产品型号修改内存的大小,而且只需要修改内核部分,不用去对uboot进行改动,所以是最方便快捷的方式。