1. 程式人生 > >linux核心啟動2_setup_arch函式

linux核心啟動2_setup_arch函式

執行setup_arch()函式
回到start_kernel當中,569行,呼叫setup_arch函式,傳給他的引數是那個未被初始化的內部變數command_line。這個setup_arch()函式是start_kernel階段最重要的一個函式,每個體系都有自己的setup_arch()函式,是體系結構相關的,具體編譯哪個體系的setup_arch()函式,由頂層Makefile中的ARCH變數決定:
它首先通過檢測出來的處理器型別進行處理器核心的初始化,然後通過 bootmem_init()函式根據系統定義的 meminfo 結構進行記憶體結構的初始化,最後呼叫paging_init()開啟 MMU,建立核心頁表,對映所有的實體記憶體和 IO空間。
start_kernel ()
–> setup_arch ()
–> paging_init ()
–> bootmem_init ()
–> alloc_bootmem_low_pages ()
第569行setup_arch(&command_line)在arch/arm/kernel/setup.c定義如下:
767 void __init setup_arch(char **cmdline_p)
768 {
769 struct tag tags = (struct tag

)&init_tags;
770 struct machine_desc *mdesc;
771 char *from = default_command_line;
772
773 unwind_init();
774
775 setup_processor();
776 mdesc = setup_machine(machine_arch_type);
777 machine_name = mdesc->name; //machine_name在126行定義static const char *machine_name;
778
779 if (mdesc->soft_reboot) //這個變數初始值為”h”,如果這裡設定成softboot,它會將這個初始值變為”s”
780 reboot_setup(“s”);
781
782 if (__atags_pointer) //檢查BootLoader是否傳入引數
783 tags = phys_to_virt(__atags_pointer);
784 else if (mdesc->boot_params)//machine descriptor中設定的啟動引數地址(arch/arm/mach-s3c2410/mach-smdk2410.c)
785 tags = phys_to_virt(mdesc->boot_params);
786
787 #if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
788 /*
789 * If we have the old style parameters, convert them to
790 * a tag list.
791 */
792 if (tags->hdr.tag != ATAG_CORE)//核心引數列表第一項必須是ATAG_CORE型別,如果不是,則需要轉換成新的核心引數型別,新的核心引數型別用下面 struct tag結構表示,由bootloader[u-boot-1.1.5]傳遞到實體地址0x30000100處的引數型別是tag list結構,在u-boot-1.1.6後是採用了新的核心引數型別struct tag結構
793 convert_to_tag_list(tags);//此函式完成新舊引數結構轉換,將引數結構轉換為tag list結構
794 #endif
795 if (tags->hdr.tag != ATAG_CORE)//如果沒有核心引數
796 tags = (struct tag *)&init_tags;//則選用預設的核心引數,init_tags檔案中有定義。
797
798 if (mdesc->fixup) //用核心引數列表填充meminfo,fixup函數出現在註冊machine_desc中,即MACHINE_START、MACHINE_END定義中,這個函式,有些板子有,但在2410中沒有定義這個函式。
799 mdesc->fixup(mdesc, tags, &from, &meminfo);
800
801 if (tags->hdr.tag == ATAG_CORE) {
802 if (meminfo.nr_banks != 0) //說明記憶體被初始化過
803 squash_mem_tags(tags);//如果是tag list,那麼如果系統已經建立了預設的meminfo.nr_banks,清除tags中關於MEM的引數,以免再次被初始化
804 save_atags(tags);
805 parse_tags(tags);//做出一些針對各個tags的處理
806 }
807 //下面是記錄核心程式碼的起始,結束虛擬地址
808 init_mm.start_code = (unsigned long) _text;
809 init_mm.end_code = (unsigned long) _etext;
810 init_mm.end_data = (unsigned long) _edata;
811 init_mm.brk = (unsigned long) _end;
812
813 /* parse_early_param needs a boot_command_line */
814 strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
815
816 /* populate cmd_line too for later use, preserving boot_command_line */
817 strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
818 *cmdline_p = cmd_line;
819
820 parse_early_param(); //解釋命令列引數,見後
821
822 arm_memblock_init(&meminfo, mdesc);
823
824 paging_init(mdesc);
825 request_standard_resources(&meminfo, mdesc);//將裝置實體登記註冊到匯流排空間連結串列
826
827 #ifdef CONFIG_SMP
828 smp_init_cpus();
829 #endif
830 reserve_crashkernel(); //要配置CONFIG_KEXEC,否則為空函式,2410中沒有配置
831
832 cpu_init(); //對用到的IRQ, ABT, UND模式的SP都作了初始化
833 tcm_init(); //在ARM中是個空函式
834
835 /* 根據arch/arm/mach-s3c2410/mach-smdk2410.c中machine_desc結構,
設定各種特定於體系結構的指標
836 * Set up various architecture-specific pointers
837 */
838 arch_nr_irqs = mdesc->nr_irqs; //在arch/arm/kernel/irq.c第171行,由nr_irqs = arch_nr_irqs ? arch_nr_irqs : NR_IRQS;引用,而nr_irqs在init_IRQ、asm_do_IRQ、set_irq_flags中都有引用

839 init_arch_irq = mdesc->init_irq; //在init_IRQ函式第165行呼叫
840 system_timer = mdesc->timer;
841 init_machine = mdesc->init_machine;
842
843 #ifdef CONFIG_VT
844 #if defined(CONFIG_VGA_CONSOLE)
845 conswitchp = &vga_con;
846 #elif defined(CONFIG_DUMMY_CONSOLE)
847 conswitchp = &dummy_con;
848 #endif
849 #endif
850 early_trap_init(); //見後
851 }
852

769行tag資料結構在arch/arm/include/asm/setup.h中定義如下:
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;

    /*
     * Acorn specific
     */
    struct tag_acorn    acorn;

    /*
     * DC21285 specific
     */
    struct tag_memclk    memclk;
} u;

};
其中init_tags在arch/arm/kernel/setup.c檔案下定義如下
662 static struct init_tags {
663 struct tag_header hdr1;
664 struct tag_core core;
665 struct tag_header hdr2;
666 struct tag_mem32 mem;
667 struct tag_header hdr3;
668 } init_tags __initdata = {
669 { tag_size(tag_core), ATAG_CORE },
670 { 1, PAGE_SIZE, 0xff },
671 { tag_size(tag_mem32), ATAG_MEM },
672 { MEM_SIZE, PHYS_OFFSET },
673 { 0, ATAG_NONE }
674 };
675
676 static void (*init_machine)(void) __initdata;
770行完整的machine_desc結構描述如下:arch/arm/include/asm/mach/arch.h
13 struct tag;
14 struct meminfo;
15 struct sys_timer;
16
17 struct machine_desc {
18 /*
19 * Note! The first four elements are used
20 * by assembler code in head.S, head-common.S
21 */
22 unsigned int nr; /* 開發板的機器型別ID */
23 unsigned int nr_irqs; /* number of IRQs */
24 unsigned int phys_io; /* 起始IO實體地址 */
25 unsigned int io_pg_offst; /* byte offset for io
26 * page tabe entry */
27
28 const char name; / 開發板名稱 */
29 unsigned long boot_params; /* tagged list核心啟動引數的地址*/
30
31 unsigned int video_start; /* start of video RAM */
32 unsigned int video_end; /* end of video RAM */
33
34 unsigned int reserve_lp0 :1; /* never has lp0 */
35 unsigned int reserve_lp1 :1; /* never has lp1 */
36 unsigned int reserve_lp2 :1; /* never has lp2 */
37 unsigned int soft_reboot :1; /* soft reboot */
38 void (fixup)(struct machine_desc ,
39 struct tag , char *,
40 struct meminfo *);
41 void (reserve)(void);/ reserve mem blocks */
42 void (map_io)(void);/*IO對映函式(在這裡修改時鐘頻率)/
43 void (init_irq)(void); /中斷初始化函式*/
44 struct sys_timer timer; / system tick timer */
45 void (*init_machine)(void);
46 };

771行default_command_line在setup.c檔案129行中定義如下:
static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
其中CONFIG_CMDLINE在“.config”配置檔案中定義的。
773行arch/arm/kernel/unwind.c
439 int __init unwind_init(void)
440 {
441 struct unwind_idx *idx;
442
443 /* Convert the symbol addresses to absolute values */
444 for (idx = __start_unwind_idx; idx < __stop_unwind_idx; idx++)
445 idx->addr = prel31_to_addr(&idx->addr);
446
447 pr_debug(“unwind: ARM stack unwinding initialised\n”);
448
449 return 0;
450 }
struct unwind_idx {
unsigned long addr;
unsigned long insn;
};
776行,machine_arch_type在arch/arm/tools/gen-mach-types中第69行定義,
printf(“#define machine_arch_type\t__machine_arch_type\n”)
它最後會被轉換成一個頭檔案include/generated/mach-types.h,包含在arch/arm/include/asm/mach- types.h檔案中,__machine_arch_type在arch/arm/kernel/head-common.S定義
27 .long __machine_arch_type @ r5
並通過arch/arm/kernel/head-common.S第60行命令
60 str r1, [r5] @ Save machine type
將bootloader通過r1傳遞過來的機器碼儲存到__machine_arch_type。
下面我們來看setup_machine函式,在在arch/arm/kernel/setup.c檔案下定義如下
391 static struct machine_desc * __init setup_machine(unsigned int nr)
392 {
393 struct machine_desc *list;
394
395 /*
396 * locate machine in the list of supported machines.
397 */
398 list = lookup_machine_type(nr);
399 if (!list) {
400 printk(“Machine configuration botched (nr %d), unable ”
401 “to continue.\n”, nr);
402 while (1);
403 }
404
405 printk(“Machine: %s\n”, list->name);
406
407 return list;
408 }
在這個函式中就是查詢你是什麼版本的處理器架構,最後就是呼叫了lookup_processor_type這個函式,它在彙編部分也提到過,在arch/arm/kernel/head-common.S定義
230 ENTRY(lookup_machine_type)
231 stmfd sp!, {r4 - r6, lr}
232 mov r1, r0
233 bl __lookup_machine_type
234 mov r0, r5
235 ldmfd sp!, {r4 - r6, pc}
236 ENDPROC(lookup_machine_type)
可見最後呼叫的是__lookup_machine_type,這個函式在彙編中我們已經分析過了。這裡再來分析一下:
核心中對於每種支援的開發板都會使用巨集MACHINE_START、MACHINE_END來定義一個machine_desc結構,它定義開發板相關的一些屬性及函式,比如機器型別ID、起始I/O實體地址、Bootloader傳入的引數的地址、中斷初始化函式、I/O對映函式等,比如對於 SMDK2410開發板,在arch/arm/mach-s3c2410/mach-smdk2410.c中定義如下:
MACHINE_START(SMDK2410, “SMDK2410”) /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
而巨集MACHINE_START、MACHINE_END在arch/arm/include/asm/mach/arch.h中定義如下:
52 #define MACHINE_START(_type,_name) \
53 static const struct machine_desc _mach_desc##_type \
54 __used \
55 attribute((section(“.arch.info.init”))) = { \
56 .nr = MACH_TYPE_##_type, \
57 .name = _name,
58
59 #define MACHINE_END \
60 };
所以以下展開後如下:
static const struct machine_desc __mach_desc_SMDK2410
__used
attribute((section(“.arch.info.init”))) = {
.nr = MACH_TYPE_SMDK2410,
.name = SMDK2410,
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
};
對照前面完整的machine_desc結構描述就能找到系統定義開發板相關的一些屬性及函式
所有machine_desc結構體初始化後被編譯連線到“.arch.info.init”段中,在連線核心時,它們被組織在一起,開始地址為 __arch_info_begin,結束地址為__arch_info_end,從連線指令碼“arch/arm/kernel /vmlinux.lds.S”中可以看出:
37 __arch_info_begin = .; //machine_desc結構體的開始地址
38 *(.arch.info.init) //所有machine_desc都在這裡面
39 __arch_info_end = .; //machine_desc結構體的開始地址
不同的machine_desc結構用於不同的開發板,U-BOOT呼叫核心時,會在r1暫存器中給出開發板的標記(機器型別ID);對於S3C2410、 S3C2440開發板,U-Boot傳入的機器型別ID為MACH_TYPE_SMDK2410、MACH_TYPE_S3C2440,它們對應的 machine_desc結構分別在arch/arm/mach-s3c2410/mach-smdk2410.c和
arch/arm/mach-s3c2440/mach-smdk2440.c中定義,現在再來看看__lookup_machine_type函式。它在arch/arm/kernel/head-common.S中定義如下:
196 4: .long . //當前行編譯連結後的虛擬地址
197 .long __arch_info_begin //machine_desc結構體的開始地址(虛擬地址)
198 .long __arch_info_end //machine_desc結構體的結束地址(虛擬地址)

211 __lookup_machine_type:
212 adr r3, 4b //r3 = 196行的實體地址
213 ldmia r3, {r4, r5, r6} /*[r3]->r4,[r3+4]->r5,[r3+8]->r6,
r4 = 196行的虛擬地址,
r5 = __arch_info_begin (VA)
r6 = __arch_info_end (VA)*/
214 sub r3, r3, r4 @r3=(196行的實體地址)-(196行的虛擬地址),即實體地址與虛擬地址的差值
215 add r5, r5, r3 @ r5= __arch_info_begin(實體地址),虛擬地址轉實體地址
216 add r6, r6, r3 @ r6= __arch_info_end(實體地址)
217 1: ldr r3, [r5, #MACHINFO_TYPE] @ r5是machine_desc結構體地址
218 teq r3, r1 @ matches loader number? r1是Bootloader呼叫核心時傳入的機器型別ID
219 beq 2f @ found 相等則跳轉到224行
220 add r5, r5, #SIZEOF_MACHINE_DESC @ 否則跳轉到下一個machine_desc結構體
其中MACHINFO_TYPE,SIZEOF_MACHINE_DESC 在arch/arm/kernel/asm-offsets.c中定義:
DEFINE(MACHINFO_TYPE, offsetof(struct machine_desc, nr));
DEFINE(SIZEOF_MACHINE_DESC, sizeof(struct machine_desc));
offsetof在include/linux/stddef.h中定義:

define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

可以看出nr是函式setup_machine傳遞過來的引數,這裡得到的是machine_desc結構體地址。
221 cmp r5, r6 是否已經比較完所有的machine_desc結構體
222 blo 1b 沒有則繼續比較(lo:無符號小於)
223 mov r5, #0 @ 比較完畢,但沒有匹配的machine_desc結構體,r5=0
224 2: mov pc, lr 返回
225 ENDPROC(__lookup_machine_type)
上面程式碼功能說明:
__lookup_machine_type 函式將這個r1暫存器中的機器型別ID與“.arch.info.init”段中machine_desc結構中的nr成員比較,如果相等則表示找到了匹配的machine_desc結構,於是返回它的地址(存於r5中),如果__arch_machine_begin和 __arch_machine_end間所有machine_desc結構的nr成員都不等於r1暫存器中的值,則返回0(r5等於0)

在配置選單時,選中兩個開發板即可:
System Type—>
S3C2410 Machines—>
[*] SMDK2410/A9M2410
S3C2440 Machines—>
[*] SMDK2440
第780行reboot_setup函式在arch/arm/kernel/process.c定義如下:
198 static char reboot_mode = ‘h’;
199
200 int __init reboot_setup(char *str)
201 {
202 reboot_mode = str[0];
203 return 1;
204 }
205
206 __setup(“reboot=”, reboot_setup);
reboot這個引數在引導引數中設定。
第782行__atags_pointer是bootloader傳遞引數的實體地址,見第一階段啟動原始碼arch/arm/kernel/head-common.S第61行(str r2, [r6]),此處r6就是__atags_pointer的地址,__atags_pointer在28處有定義,它將bootloader通過r2傳遞過來的地址引數存放到 __atags_pointer。
第783、784行,由於MMU單元已開啟,此處__atags_pointer是實體地址,需要轉換成虛擬地址才能訪問,因為此時CPU訪問的都是虛擬地址,在這裡如果bootloader沒有傳遞地址引數則就使用arch/arm/mach-s3c2410 /mach-smdk2410.c檔案中MACHINE_START、MACHINE_END定義的boot_params引數,對2410定義如下:
.boot_params = S3C2410_SDRAM_PA + 0x100,
S3C2410_SDRAM_PA在arch/arm/mach-s3c2410/include/mach/map.h中定義如下

define S3C2410_CS6 (0x30000000)

define S3C2410_CS7 (0x38000000)

define S3C2410_SDRAM_PA (S3C2410_CS6)

所以.boot_params引數地址是0x30000100位置
801 if (tags->hdr.tag == ATAG_CORE) {
802 if (meminfo.nr_banks != 0) //說明記憶體被初始化過
803 squash_mem_tags(tags);//如果是tag list,那麼如果系統已經建立了預設的meminfo.nr_banks,清除tags中關於MEM的引數,以免再次被初始化
804 save_atags(tags);
805 parse_tags(tags);//做出一些針對各個tags的處理
806 }
803行squash_mem_tags定義如下(arch/arm/kernel/setup.c)
static void __init squash_mem_tags(struct tag *tag)
{
for (; tag->hdr.size; tag = tag_next(tag))
if (tag->hdr.tag == ATAG_MEM)
tag->hdr.tag = ATAG_NONE;
}
這個函式功能是清除tags中關於MEM的引數,以免再次被初始化
804行save_atags定義如下(arch/arm/kernel/atags.c)

define BOOT_PARAMS_SIZE 1536

static char __initdata atags_copy[BOOT_PARAMS_SIZE];

void __init save_atags(const struct tag *tags)
{
memcpy(atags_copy, tags, sizeof(atags_copy));
}
805行parse_tags定義如下(arch/arm/kernel/setup.c)
static void __init parse_tags(const struct tag *t)
{
for (; t->hdr.size; t = tag_next(t))
if (!parse_tag(t)) //針對每個tag 呼叫parse_tag 函式
printk(KERN_WARNING
“Ignoring unrecognised tag 0x%08x\n”,
t->hdr.tag);
}
這個函式解析核心引數列表,然後呼叫核心引數列表的處理函式對這些引數進行處理。比如,如果列表為命令列,則最終會用parse_tag_cmdlin函式進行解析,這個函式用_tagtable編譯連線到了核心裡,其中如果U-boot傳入ATAG_CMDLINE,則使用U-boot傳入的 bootargs覆蓋default_command_line
static int __init parse_tag(const struct tag *tag)
{
extern struct tagtable __tagtable_begin, __tagtable_end;
struct tagtable *t;

for (t = &__tagtable_begin; t < &__tagtable_end; t++) //遍歷tagtable列表,並呼叫處理函式,
    if (tag->hdr.tag == t->tag) {
        t->parse(tag);
        break;
    }

return t < &__tagtable_end;

}
arch/arm/include/asm/setup.h
struct tagtable {
__u32 tag;
int (parse)(const struct tag );
};

define tag_next(t) ((struct tag )((__u32 )(t) + (t)->hdr.size))

define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

define for_each_tag(t,base) \

for (t = base; t->hdr.size; t = tag_next(t))

ifdef KERNEL

define tag __used __attribute((section(“.taglist.init”)))

define __tagtable(tag, fn) \

static struct tagtable _tagtable##fn __tag = { tag, fn }
這個tagtable 列表 是怎麼形成的?
如arch/arm/kernel/setup.c
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558 return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560
561 __tagtable(ATAG_MEM, parse_tag_mem32);

607 __tagtable(ATAG_SERIAL, parse_tag_serialnr);
608
609 static int __init parse_tag_revision(const struct tag *tag)
610 {
611 system_rev = tag->u.revision.rev;
612 return 0;
613 }
614
615 __tagtable(ATAG_REVISION, parse_tag_revision);

618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620 strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621 return 0;
622 }
623
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
根據前面相關巨集定義,__tagtable(ATAG_CMDLINE, parse_tag_cmdline)展開後為
static struct tagtable tagtable_parse_tag_cmdline __used __attribute((section(“.taglist.init”))) = { ATAG_CMDLINE, parse_tag_cmdline }
再參看arch/arm/kernel/vmlinux.lds.S檔案
34 __proc_info_begin = .;
35 *(.proc.info.init)
36 __proc_info_end = .;
37 __arch_info_begin = .;
38 *(.arch.info.init)
39 __arch_info_end = .;
40 __tagtable_begin = .;
41 *(.taglist.init)
42 __tagtable_end = .;
tagtable 列表編譯連線後被存放在.taglist.init中。

從808行到811行是init_mm的初始化,記錄核心程式碼的起始,結束虛擬地址
808 init_mm.start_code = (unsigned long) _text; 核心程式碼段開始
809 init_mm.end_code = (unsigned long) _etext; 核心程式碼段結束
810 init_mm.end_data = (unsigned long) _edata; 核心資料段開始
811 init_mm.brk = (unsigned long) _end; 核心資料段結束
_text,_etext,_edata,_end參見arch/arm/kernel/vmlinux.lds.S 連結指令碼,init_mm定義在mm/init-mm.c
12 #ifndef INIT_MM_CONTEXT
13 #define INIT_MM_CONTEXT(name)
14 #endif
15
16 struct mm_struct init_mm = {
17 .mm_rb = RB_ROOT,
18 .pgd = swapper_pg_dir,
19 .mm_users = ATOMIC_INIT(2),
20 .mm_count = ATOMIC_INIT(1),
21 .mmap_sem = __RWSEM_INITIALIZER(init_mm.mmap_sem),
22 .page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),
23 .mmlist = LIST_HEAD_INIT(init_mm.mmlist),
24 .cpu_vm_mask = CPU_MASK_ALL,
25 INIT_MM_CONTEXT(init_mm)
26 };

每一個任務都有一個mm_struct結構管理任務記憶體空間,init_mm是核心的mm_struct,設定* pgd=swapper_pg_dir,swapper_pg_dir是核心的頁目錄,在arm體系結構有16k,所以init_mm定義了整個 kernel的記憶體空間,下面我們會碰到核心執行緒,所有的核心執行緒都使用核心空間,擁有和核心同樣的訪問許可權。對於記憶體管理後面再做詳細分析。
mm_struct結構定義在include/linux/mm_types.h中,
struct mm_struct {
struct vm_area_struct * mmap; /* list of VMAs */
struct rb_root mm_rb;
struct vm_area_struct * mmap_cache; /* last find_vma result */

ifdef CONFIG_MMU

unsigned long (*get_unmapped_area) (struct file *filp,
            unsigned long addr, unsigned long len,
            unsigned long pgoff, unsigned long flags);
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);

endif

unsigned long mmap_base;        /* base of mmap area */
unsigned long task_size;        /* size of task vm space */
unsigned long cached_hole_size;     /* if non-zero, the largest hole below free_area_cache */
unsigned long free_area_cache;        /* first hole of size cached_hole_size or larger */
pgd_t * pgd;
atomic_t mm_users;            /* How many users with user space? */
atomic_t mm_count;            /* How many references to "struct mm_struct" (users count as 1) */
int map_count;                /* number of VMAs */
struct rw_semaphore mmap_sem;
spinlock_t page_table_lock;        /* Protects page tables and some counters */

struct list_head mmlist;        /* List of maybe swapped mm's.    These are globally strung
                     * together off init_mm.mmlist, and are protected
                     * by mmlist_lock
                     */


unsigned long hiwater_rss;    /* High-watermark of RSS usage */
unsigned long hiwater_vm;    /* High-water virtual memory usage */

unsigned long total_vm, locked_vm, shared_vm, exec_vm;
unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
unsigned long start_code, end_code, start_data, end_data;
unsigned long start_brk, brk, start_stack;
unsigned long arg_start, arg_end, env_start, env_end;

unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

/*
 * Special counters, in some configurations protected by the
 * page_table_lock, in other configurations by being atomic.
 */
struct mm_rss_stat rss_stat;

struct linux_binfmt *binfmt;

cpumask_t cpu_vm_mask;

/* Architecture-specific MM context */
mm_context_t context;

/* Swap token stuff */
/*
 * Last value of global fault stamp as seen by this process.
 * In other words, this value gives an indication of how long
 * it has been since this task got the token.
 * Look at mm/thrash.c
 */
unsigned int faultstamp;
unsigned int token_priority;
unsigned int last_interval;

unsigned long flags; /* Must use atomic bitops to access the bits */

struct core_state *core_state; /* coredumping support */

ifdef CONFIG_AIO

spinlock_t        ioctx_lock;
struct hlist_head    ioctx_list;

endif

ifdef CONFIG_MM_OWNER

/*
 * "owner" points to a task that is regarded as the canonical
 * user/owner of this mm. All of the following must be true in
 * order for it to be changed:
 *
 * current == mm->owner
 * current->mm != mm
 * new_owner->mm == mm
 * new_owner->alloc_lock is held
 */
struct task_struct *owner;

endif

ifdef CONFIG_PROC_FS

/* store ref to file /proc//exe symlink points to */
struct file *exe_file;
unsigned long num_exe_file_vmas;

endif

ifdef CONFIG_MMU_NOTIFIER

struct mmu_notifier_mm *mmu_notifier_mm;

endif

};

第814行,下面是對命令列的處理,剛才在引數列表處理parse_tag_cmdline函式已把命令列拷貝到了from空間(from前面定義成了default_command_line),這裡814行是把命令列拷貝到boot_command_line,boot_command_line在後面的parse_early_param為用到,boot_command_line在init/main.c中定義
char __initdata boot_command_line[COMMAND_LINE_SIZE];指定變數存放在__initdata區
第817行,填充cmd_line,以備以後使用,維護boot_command_line,cmd_line在127行定義
static char __initdata cmd_line[COMMAND_LINE_SIZE];指定變數存放在__initdata區
__initdata在前面的分析中出現了好幾次,在這裡我們來對它詳細分析
Linux在arch/arm/kernel/vmlinux.lds.S中定義了.init段。__init和__initdata屬性的資料都在這個段中,當核心啟動完畢後,這個段中的記憶體會被釋放掉供其他使用。
__init和__initdata巨集定義如下(include/linux/init.h):

define __init __section(.init.text) __cold notrace

define __initdata __section(.init.data)

vmlinux.lds.S有如下內容:
/* arch/arm/kernel/vmlinux.lds.S*/
20 SECTIONS
21 {
22 #ifdef CONFIG_XIP_KERNEL
23 . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);
24 #else
25 . = PAGE_OFFSET + TEXT_OFFSET;
26 #endif
27
28 .init : { /* Init code and data */
29 _stext = .;
30 _sinittext = .;
31 HEAD_TEXT
32 INIT_TEXT
33 _einittext = .;
34 __proc_info_begin = .;
35 *(.proc.info.init)
36 __proc_info_end = .;
37 __arch_info_begin = .;
38 *(.arch.info.init)
39 __arch_info_end = .;
40 __tagtable_begin = .;
41 *(.taglist.init)
42 __tagtable_end = .;
43
44 INIT_SETUP(16)
45
46 INIT_CALLS
47 CON_INITCALL
48 SECURITY_INITCALL
49 INIT_RAM_FS
50
51 #ifndef CONFIG_XIP_KERNEL
52 __init_begin = _stext;
53 INIT_DATA
54 #endif
55 }

第32的INIT_TEXT和第53行INIT_DATA在include/asm-generic/vmlinux.lds.h定義如下
457 /* init and exit section handling */
458 #define INIT_DATA \
459 *(.init.data) \
460 DEV_DISCARD(init.data) \
461 CPU_DISCARD(init.data) \
462 MEM_DISCARD(init.data) \
463 KERNEL_CTORS() \
464 *(.init.rodata) \
465 MCOUNT_REC() \
466 DEV_DISCARD(init.rodata) \
467 CPU_DISCARD(init.rodata) \
468 MEM_DISCARD(init.rodata)
469
470 #define INIT_TEXT \
471 *(.init.text) \
472 DEV_DISCARD(init.text) \
473 CPU_DISCARD(init.text) \
474 MEM_DISCARD(init.text)
475
可以發現__init對應的section(.init.text)和__initdata對應的section(.init.data)都在.init 段中。同樣,這裡定義的其他一些section也都會在使用完後被釋放,如.init.setup,.initcall1.init等。
釋放memory的大小會在系統啟動過程中打印出來:
eth0: link up
IP-Config: Complete:
device=eth0, addr=192.168.167.15, mask=255.255.255.0, gw=192.168.1.1,
host=192.168.167.15, domain=, nis-domain=(none),
bootserver=192.168.1.8, rootserver=192.168.1.54, rootpath=
Looking up port of RPC 100003/2 on 192.168.167.170
Looking up port of RPC 100005/1 on 192.168.167.170
VFS: Mounted root (nfs filesystem).
Freeing init memory: 128K

在分析820行的parse_early_param函式前,先說一下系統啟動時對bootloader傳遞引數的初始化,即linux啟動引數的實現,啟動引數的實現,我們知道boot傳遞給核心的引數都是 “name_varibale=value”這種形式的,如下:
CONFIG_CMDLINE=”root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200 mem=64M”
那麼核心如何知道傳遞進來的引數該怎麼去處理呢?
核心是通過__setup巨集或者early_param巨集來將引數與引數所處理的函式相關聯起來的。我們接下來就來看這個巨集以及相關的結構的定義:
include/linux/init.h
230 #define __setup_param(str, unique_id, fn, early) \
231 static const char _setup_str##unique_id[] __initconst \
232 __aligned(1) = str; \
233 static struct obs_kernel_param _setup##unique_id \
234 __used __section(.init.setup) \
235 attribute((aligned((sizeof(long))))) \
236 = { _setup_str##unique_id, fn, early }
237
238 #define __setup(str, fn) \
239 __setup_param(str, fn, fn, 0)
240
241 /* NOTE: fn is as per module_param, not __setup! Emits warning if fn
242 * returns non-zero. */
243 #define early_param(str, fn) \
244 __setup_param(str, fn, fn, 1)
看起來很複雜。 首先setup巨集第一個引數是一個key。比如”netdev=”這樣的,而第二個引數是這個key對應的處理函式。這裡要注意相同的handler能聯絡到不同的key。__setup與early_param不同的是,early_param巨集註冊的核心選項必須要在其他核心選項之前被處理。在函式start_kernel中,parse_early_param處理early_param定義的引數,parse_args處理__setup定義的引數。
early_param和setup唯一不同的就是傳遞給__setup_param的最後一個引數,這個引數下面會說明,而接下來_setup_param定義了一個struct obs_kernel_param型別的結構,然後通過_section巨集,使這個變數在連結的時候能夠放置在段.init.setup(後面會詳細介紹核心中的這些初始化段).

接下來來看struct obs_kernel_param結構:
include/linux/init.h
218 struct obs_kernel_param {
219 const char *str;
220 int (setup_func)(char );
221 int early;
222 };

前兩個引數很簡單,一個是key,一個是handler。最後一個引數其實也就是類似於優先順序的一個flag,因為傳遞給核心的引數中,有一些需要比另外的一些更早的解析。(這也就是為什麼early_param和setup傳遞的最後一個引數的不同的原因了。
啟動引數的實現
1,所有的系統啟動引數都是由形如
static int __init init_setup(char *str)
的函式來支援的
init/main.c
static int __init init_setup(char *str)
{
unsigned int i;

execute_command = str;
for (i = 1; i < MAX_INIT_ARGS; i++)
    argv_init[i] = NULL;
return 1;

}
__setup(“init=”, init_setup);
注:(include/linux/init.h):

define __init __section(.init.text) __cold notrace申明所有的啟動引數支援函式都放入.init.text段

2.1,用__setup巨集來匯出引數的支援函式
__setup(“init=”, init_setup);
展開後就是如下的形式

static const char __setup_str_init_setup[] __initdata = “init=”;
static struct obs_kernel_param __setup_init_setup
used __section(“.init.setup”)
attribute((aligned((sizeof(long)))))
= { __setup_str_init_setup, init_setup, 0 };//”init=”,init_setup,0
也就是說,啟動引數(函式指標)被封裝到obs_kernel_param結構中,
所有的核心啟動引數形成核心映像.init.setup段中的一個
obs_kernel_param陣列

2.2用early_param巨集來申明需要’早期’處理的啟動引數,例如在
arch/arm/kernel/setup.c就有如下的申明:
468 early_param(“mem”, early_mem);
展開後和__setup是一樣的只是early引數不一樣,因此會在do_early_param
中被處理
443 static int __init early_mem(char *p)
444 {
445 static int usermem __initdata = 0;
446 unsigned long size, start;
447 char *endp;
448
449 /*
453 */
454 if (usermem == 0) {
455 usermem = 1;
456 meminfo.nr_banks = 0;
457 }
458
459 start = PHYS_OFFSET;
460 size = memparse(p, &endp);
461 if (*endp == ‘@’)
462 start = memparse(endp + 1, NULL);
463
464 arm_add_memory(start, size);
465
466 return 0;
467 }

3,核心對啟動引數的解析:下面函式歷遍obs_kernel_param陣列,呼叫
支援函式
static int __init do_early_param(char *param, char *val)
這個函式在parse_early_param中被呼叫,而parse_early_param在start_kernel
中被呼叫,parse_early_param之後的parse_args會呼叫下面函式
static int __init obsolete_checksetup(char *line)
這兩個函式後面會講到。
init/main.c中啟動引數申明列表:
early_param(“nosmp”, nosmp);
early_param(“nr_cpus”, nrcpus);
early_param(“maxcpus”, maxcpus);
__setup(“reset_devices”, set_reset_devices);
early_param(“debug”, debug_kernel);
early_param(“quiet”, quiet_kernel);
early_param(“loglevel”, loglevel);
__setup(“init=”, init_setup);
__setup(“rdinit=”, rdinit_setup);
arch/arm/kernel/setup.c中啟動引數申明列表:
__setup(“fpe=”, fpe_setup);
early_param(“mem”, early_mem);
early_param(“elfcorehdr”, setup_elfcorehdr);

接下來我們來看核心解析bootloader傳遞給核心的引數的步驟:
先看下面的圖:

可以看到核心首先通過parse_early_param來解析優先順序更高的,也就是需要被更早解析的命令列引數,然後通過parse_ares來解析一般的命令列引數.
現在再來看看start_arch函式中第820行的parse_early_param函式
init/main.c
void __init parse_early_options(char *cmdline)
{
parse_args(“early options”, cmdline, NULL, 0, do_early_param);
}

/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];

if (done)
    return;

/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;

}
在上面我們可以看到最終呼叫的是 parse_args(“early options”, cmdline, NULL, 0, do_early_param);它呼叫do_early_param函式處理early_param定義的引數函式
這個函式還另一種呼叫形式
parse_args(“Booting kernel”, static_command_line, __start___param,_stop___param - __start___param,&unknown_bootoption); 它呼叫unknown_bootoption函式處理__setup定義的引數,unknown_bootoption函式在init/main.c中定義如下:
static int __init unknown_bootoption(char *param, char *val)
{
/* Change NUL term back to “=”, to make “param” the whole string. */
if (val) {
/* param=val or param=”val”? */
if (val == param+strlen(param)+1)
val[-1] = ‘=’;
else if (val == param+strlen(param)+2) {
val[-2] = ‘=’;
memmove(val-1, val, strlen(val)+1);
val–;
} else
BUG();
}

/* Handle obsolete-style parameters */
if (obsolete_checksetup(param))
    return 0;

/* Unused module parameter. */
if (strchr(param, '.') && (!val || strchr(param, '.') < val))
    return 0;

if (panic_later)
    return 0;

if (val) {
    /* Environment option */
    unsigned int i;
    for (i = 0; envp_init[i]; i++) {
        if (i == MAX_INIT_ENVS) {
            panic_later = "Too many boot env vars at `%s'";
            panic_param = param;
        }
        if (!strncmp(param, envp_init[i], val - param))
            break;
    }
    envp_init[i] = param;
} else {
    /* Command line option */
    unsigned int i;
    for (i = 0; argv_init[i]; i++) {
        if (i == MAX_INIT_ARGS) {
            panic_later = "Too many boot init vars at `%s'";
            panic_param = param;
        }
    }
    argv_init[i] = param;
}
return 0;

}
在這個函式中它呼叫了obsolete_checksetup函式,該函式也定義在init/main.c中
static int __init obsolete_checksetup(char *line)
{
const struct obs_kernel_param *p;
int had_early_param = 0;

p = __setup_start;
do {
    int n = strlen(p->str);
    if (!strncmp(line, p->str, n)) {
        if (p->early) {
            /* Already done in parse_early_param?
             * (Needs exact match on param part).
             * Keep iterating, as we can have early
             * params and __setups of same names 8( */
            if (line[n] == '\0' || line[n] == '=')
                had_early_param = 1;
        } else if (!p->setup_func) {
            printk(KERN_WARNING "Parameter %s is obsolete,"
                   " ignored\n", p->str);
            return 1;
        } else if (p->setup_func(line + n)) //呼叫支援函式
            return 1;
    }
    p++;
} while (p < __setup_end);

return had_early_param;

}
我們再來看一下do_early_param函式,它在init/main.c中
static int __init do_early_param(char *param, char *val)
{
const struct obs_kernel_param *p;
這裡的_setup_start和-setup_end分別是.init.setup段的起始和結束的地址
for (p = __setup_start; p < __setup_end; p++) { 如果沒有early標誌則跳過
if ((p->early && strcmp(param, p->str) == 0) ||
(strcmp(param, “console”) == 0 &&
strcmp(p->str, “earlycon”) == 0)
) {
if (p->setup_func(val) != 0) 呼叫處理函式
printk(KERN_WARNING
“Malformed early option ‘%s’\n”, param);
}
}
/* We accept everything at this stage. */
return 0;
}
parse_args在kernel/params.c中定義,注意它與前面講到的parse_args區別。
/* Args looks like “foo=bar,bar2 baz=fuz wiz”. */
int parse_args(const char *name,
char *args,
const struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
{
char *param, *val;

DEBUGP("Parsing ARGS: %s\n", args);

/* Chew leading spaces */
args = skip_spaces(args);

while (*args) {
    int ret;
    int irq_was_disabled;

    args = next_arg(args, ¶m, &val);
    irq_was_disabled = irqs_disabled();
    ret = parse_one(param, val, params, num, unknown);
    if (irq_was_disabled && !irqs_disabled()) {
        printk(KERN_WARNING "parse_args(): option '%s' enabled "
                "irq's!\n", param);
    }
    switch (ret) {
    case -ENOENT:
        printk(KERN_ERR "%s: Unknown parameter `%s'\n",
               name, param);
        return ret;
    case -ENOSPC:
        printk(KERN_ERR
               "%s: `%s' too large for parameter `%s'\n",
               name, val ?: "", param);
        return ret;
    case 0:
        break;
    default:
        printk(KERN_ERR
               "%s: `%s' invalid for parameter `%s'\n",
               name, val ?: "", param);
        return ret;
    }
}

/* All parsed OK. */
return 0;

}

接下來看下,一些內建模組的引數處理的問題。傳遞給模組的引數都是通過module_param來實現的:
include/linux/moduleparam.h
99 #define module_param(name, type, perm) \
100 module_param_named(name, name, type, perm)

113 #define module_param_named(name, value, type, perm) \
114 param_check_##type(name, &(value)); \
115 module_param_cb(name, ¶m_ops_##type, &value, perm); \
116 __MODULE_PARM_TYPE(name, #type)

126 #define module_param_cb(name, ops, arg, perm) \
127 __module_param_call(MODULE_PARAM_PREFIX, \
128 name, ops, arg, __same_type((arg), bool *), perm )

142 #define __module_param_call(prefix, name, ops, arg, isbool, perm) \
143 /* Default value instead of permissions? */ \
144 static int param_perm_check_##name __attribute((unused)) = \
145 BUILD_BUG_ON_ZERO((perm) < 0 || (perm) > 0777 || ((perm) & 2)) \
146 + BUILD_BUG_ON_ZERO(sizeof(“”prefix) > MAX_PARAM_PREFIX_LEN); \
147 static const char _param_str##name[] = prefix #name; \
148 static struct kernel_param _moduleparam_const __param##name \
149 __used \
150 attribute ((unused,section (“__param”),aligned(sizeof(void *)))) \
151 = { _param_str##name, ops, perm, isbool ? KPARAM_ISBOOL : 0, \
152 { arg } }
這裡也就是填充了 struct kernel_param的結構體,並將這個變數標記為__param段,以便於連結器將此變數裝載到指定的段
接下來我們來看struct kernel_param這個結構:
include/linux/moduleparam.h
struct kernel_param;

struct kernel_param_ops {
/* Returns 0, or -errno. arg is in kp->arg. */
int (*set)(const char *val, const struct kernel_param *kp);設定引數的函式
/* Returns length written or -errno. Buffer is 4k (ie. be short!) */
int (*get)(char *buffer, const struct kernel_param *kp);讀取引數的函式
/* Optional function to free kp->arg when module unloaded. */
void (*free)(void *arg);
};

/* Flag bits for kernel_param.flags */

define KPARAM_ISBOOL 2

struct kernel_param {
const char *name;
const struct kernel_param_ops *ops;
u16 perm;
u16 flags;
union { 傳遞給上面kernel_param_ops中兩個函式的引數
void *arg;
const struct kparam_string *str;
const struct kparam_array *arr;
};
};

/* Special one for strings we want to copy into */
struct kparam_string {
unsigned int maxlen;
char *string;
};

/* Special one for arrays */
struct kparam_array
{
unsigned int max;
unsigned int *num;
const struct kernel_param_ops *ops;
unsigned int elemsize;
void *elem;
};

接下來來看parse_one函式,其中early和一般的pase都是通過這個函式來解析:
kernel/params.c
static inline char dash2underscore(char c)
{
if (c == ‘-‘)
return ‘_’;
return c;
}

static inline int parameq(const char *input, const char *paramname)
{
unsigned int i;
for (i = 0; dash2underscore(input[i]) == paramname[i]; i++)
if (input[i] == ‘\0’)
return 1;
return 0;
}

static int parse_one(char *param,
char *val,
const struct kernel_param *params,
unsigned num_params,
int (*handle_unknown)(char *param, char *val))
{
unsigned int i;
int err;
如果是early_param則直接跳過這步,而非early的,則要通過這步來設定一些內建模組的引數。
/* Find parameter */
for (i = 0; i < num_params; i++) {
if (parameq(param, params[i].name)) {
/* Noone handled NULL, so do it here. */
if (!val && params[i].ops->set != param_set_bool) 呼叫引數設定函式來設定對應的引數。
return -EINVAL;
DEBUGP(“They are equal! Calling %p\n”,
params[i].ops->set);
mutex_lock(¶m_lock);
err = params[i].ops->set(val, ¶ms[i]);
mutex_unlock(¶m_lock);
return err;
}
}

if (handle_unknown) {
    DEBUGP("Unknown argument: calling %p\n", handle_unknown);
    return handle_unknown(param, val);
}

DEBUGP("Unknown argument `%s'\n", param);
return -ENOENT;

}
這裡之所以把所有的核心初始化引數都放在一個段裡,主要是為了初始化成功後,釋放這些空間。
我們下來看一下核心編譯完畢後的一些段的位置:

右邊就是每個段定義的相關的巨集。在__init_start和__init_end之間的段嚴格按照順序進行初始化,比如 core_initcall巨集修飾的函式就要比arch_initcall巨集定義的函式優先順序高,所以就更早的呼叫。這個特性就可以使我們將一些優先順序比較高的初始化函式放入相應比較高的段。。
這裡要注意,每個init巨集,基本都會有個對應的exit巨集,比如__initial和__exit等等。。
核心中修飾的巨集很多,我們下面只介紹一些常用的:
__devinit 用來標記一個裝置的初始化函式,比如pci裝置的probe函式,就是用這個巨集來修飾。
__initcall這個已被廢棄,現在實現為__devinit的別名。
剩下的巨集需要去看核心的相關文件。。

最後還要注意幾點:
1 當模組被靜態的加入到核心中時,module_init標記的函式只會被執行一次,因此當初始化成功後,核心會釋放掉這塊的記憶體。
2 當模組靜態假如核心,module_exit在連結時就會被刪除掉。
這裡的優化還有很多,比如熱拔插如果不支援的話,很多裝置的初始化函式都會在執行完一遍後,被丟棄掉。
這裡可以看到,如果模組被靜態的編譯進核心時,核心所做的記憶體優化會更多,雖然損失了更多的靈活性

回到start_arch中第822行,來看arm_memblock_init(&meminfo, mdesc);函式
arch/arm/mm/init.c
270 void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
271 {
272 int i;
273
274 memblock_init();
275 for (i = 0; i < mi->nr_banks; i++)
276 memblock_add(mi->bank[i].start, mi->bank[i].size);
277
278 /* Register the kernel text, kernel data and initrd with memblock. */
279 #ifdef CONFIG_XIP_KERNEL
280 memblock_reserve(__pa(_data), _end - _data);
281 #else
282 memblock_reserve(__pa(_stext), _end - _stext);
283 #endif
284 #ifdef CONFIG_BLK_DEV_INITRD
285 if (phys_initrd_size) {
286 memblock_reserve(phys_initrd_start, phys_initrd_size);
287
288 /* Now convert initrd to virtual addresses */
289 initrd_start = __phys_to_virt(phys_initrd_start);
290 initrd_end = initrd_start + phys_initrd_size;
291 }
292 #endif
293
294 arm_mm_memblock_reserve();
295
296 /* reserve any platform specific memblock areas */
297 if (mdesc->reserve)
298 mdesc->reserve();
299
300 memblock_analyze();
301 memblock_dump_all();
302 }
這個函式保留了記憶體, 包括linux核心佔用的程式碼資料段空間, initrd佔用的空間 以及一些平臺相關的記憶體
第274行,memblock_init()在mm/memblock.c裡面被定義。
void __init memblock_init(void)
{
/* Create a dummy zero size MEMBLOCK which will get coalesced away later.
* This simplifies the memblock_add() code below…
*/
memblock.memory.region[0].base = 0;
memblock.memory.region[0].size = 0;
memblock.memory.cnt = 1;

/* Ditto. */
memblock.reserved.region[0].base = 0;
memblock.reserved.region[0].size = 0;
memblock.reserved.cnt = 1;

}
其作用就是初始化memblock這個結構。memblock包含兩個重要的成員,分別是memblock.memory和memblock.reserved.其分別代表系統中可用的記憶體和已經被保留的記憶體。
memblock.memory和memblock.reserved被定義為以下結構:include/linux/memblock.h

define MAX_MEMBLOCK_REGIONS 128

struct memblock_property {
u64 base;
u64 size;
};

struct memblock_region {
unsigned long cnt;
u64 size;
struct memblock_property region[MAX_MEMBLOCK_REGIONS+1];
};

struct memblock {
unsigned long debug;
u64 rmo_size;
struct memblock_region memory;
struct memblock_region reserved;
};
276行,記憶體原始資料由u-boot傳入,並在start_arch中呼叫parse_tags對它初始化(其詳細過程請參看我後面的u-boot與linux核心間的引數傳遞過程分析),在初始化完memblock_init後,memblock_add呼叫memblock_add_region加入原始記憶體資料.