分類: LINUX
在實際的工作中,由于產品型號的不同,經常需要調整linux所管理的內存的大小,而內核在啟動階段,會兩次去解析從uboot傳遞過來的關于內存的信息,具體如下:
一、解析從uboot傳遞過來的tag(在parse_tags中處理)
在uboot的do_bootm_linux()函數中,會創建一系列需要傳遞給內核的tag,所有的tag以鏈表形式鏈接到指定的物理內存中。setup_start_tag用來建立起始的tag,而起始的物理地址由bd->bi_boot_params指定,
static void setup_start_tag (bd_t *bd){ params =(struct tag*) bd->bi_boot_params; params->hdr.tag= ATAG_CORE; params->hdr.size= tag_size (tag_core); params->u.core.flags= 0; params->u.core.pagesize= 0; params->u.core.rootdev= 0; params = tag_next(params);}bi_boot_params是在board_init中初始化的,此地址是與內核協商一致的用來存放tag的基址。
intboard_init (void)
{
…………
gd->bd->bi_boot_params =CFG_BOOT_PARAMS;
…………
}
而內存的tag是在setup_memory_tags()函數中創建的,其hdr.tag指定了tag的類型為ATAG_MEM
static int __init parse_tag_mem32(conststruct tag *tag){ if (meminfo.nr_banks>= NR_BANKS){ PRintk(KERN_WARNING "Ignoring memory bank 0x%08x size %dKB/n", tag->u.mem.start, tag->u.mem.size/ 1024); return -EINVAL; } arm_add_memory(tag->u.mem.start, tag->u.mem.size); return 0;}__tagtable(ATAG_MEM, parse_tag_mem32);在內核中,會通過__tagtable 宏來建立起相關的struct tagtable的數據結構,并放入".taglist.init" 段中,#define__tag __used __attribute__((__section__(".taglist.init")))
#define__tagtable(tag, fn) /
static structtagtable __tagtable_##fn __tag = { tag, fn }
static int __init parse_tag_mem32(conststruct tag *tag){ return arm_add_memory(tag->u.mem.start, tag->u.mem.size);}__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。
MACHINE_START(hi3520v100,"hi3520v100") .phys_io = IO_SPACE_PHYS_START, .io_pg_offst =(IO_ADDRESS(IO_SPACE_PHYS_START)>> 18)& 0xfffc, .boot_params = PHYS_OFFSET+ 0x100, .map_io = hisilicon_map_io, .init_irq = hisilicon_init_irq, .timer =&hisilicon_timer, .init_machine = hisilicon_init_machine,MACHINE_ENDstruct tagtable{ __u32 tag; int (*parse)(conststruct tag *);};在parse_tags()中,會根據讀出來的tag的類型,即hdr.tag與從".taglist.init"段中的struct tagtable中的tag字段比較,如果相等,便執行struct tagtable中的parse()函數,對內存的tag來講,其類型是ATAG_MEM,解析函數是parse_tag_mem32();
static int __init parse_tag_mem32(conststruct tag *tag){ if (meminfo.nr_banks>= NR_BANKS){ printk(KERN_WARNING "Ignoring memory bank 0x%08x size %dKB/n", tag->u.mem.start, tag->u.mem.size/ 1024); return -EINVAL; } arm_add_memory(tag->u.mem.start, tag->u.mem.size); return 0;}__tagtable(ATAG_MEM, parse_tag_mem32);在內核中,物理內存的起始地址和大小存放在一個struct meminfo meminfo的全局變量中,
struct meminfo{ int nr_banks; struct membank bank[NR_BANKS];}; struct membank{ unsigned long start; unsigned long size; int node;};nr_banks表示內核總共管理了多少個bank。
structmembank記錄了內核中各個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的環境變量
static void fix_bootargs(char*cmdline){….…… /* fix "mem=" params */ p = strstr(cmdline,"mem="); if(!p){ sprintf(args," mem=%dM",gd->bd->bi_dram[0].size/0x200000); strcat(cmdline,args); }……………}在內核中是通過early_mem()來解析boot_command_line中有關內存大小的參數行的。
static void __init early_mem(char**p){ static int usermem __initdata= 0; unsigned long size, start; /* * If the user specifies memory size, we * blow away any automatically generated * size. */ if (usermem== 0){ usermem = 1; meminfo.nr_banks = 0; } start = PHYS_OFFSET; size = memparse(*p, p); if (**p== '@') start = memparse(*p+ 1, p); arm_add_memory(start,size);}__early_param("mem=", early_mem);該函數解析從uboot傳遞進來的boot_command_line命令行參數中以“mem=”開頭的命令行,如果boot_command_line中有以“mem=”開頭的命令行,就調用該函數解析“mem=”之后的關于內存的信息,
把內存的大小寫到對應的bank中去,內存的基地址在此處是一個默認值。如果有兩段不連續的物理內存,可以在boot_command_line中設置如下內容即可:
mem=72M@0xe2000000mem=128M@0xe8000000
在此處,定義了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所管理的內存大小,這樣可以做到不同產品間的兼容性,而且后續的產品升級等方面也比較簡單容易操作。
在海思平臺上實現了這種做法。
void__init hikio_fix_meminfo(char *cmdline,struct meminfo *mi)
{
……………….
strcpy(p,p+8);
strcat(cmdline," mem=72M");
mi->bank[0].size = 72*0x100000;
}
然后在解析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進行改動,所以是最方便快捷的方式。新聞熱點
疑難解答