1、前言
在前面的文章《Uboot啟動流程分析(一)》中,鏈接如下:
https://www.cnblogs.com/Cqlismy/p/12000889.html
已經簡單地分析了low_level_init函數,其調用流程如下:
save_boot_params_ret | cpu_init_crit | | | lowlevel_init | | | s_init | _main
接下來,則繼續往下分析_main函數。
2、_main函數
在save_boot_params_ret的最后,會運行bl _main這句代碼,Uboot則將會跳轉到_main函數中去運行,該函數的定義在arch/arm/lib/crt0.S文件中,_main函數的功能已經在文件中注釋得很清楚了,先來看看_main函數實現的功能是什么,注釋如下:
/* * This file handles the target-independent stages of the U-Boot * start-up where a C runtime environment is needed. Its entry point * is _main and is branched into from the target's start.S file. * * _main execution sequence is: * * 1. Set up initial environment for calling board_init_f(). * This environment only provides a stack and a place to store * the GD ('global data') structure, both located in some readily * available RAM (SRAM, locked cache...). In this context, VARIABLE * global data, initialized or not (BSS), are UNAVAILABLE; only * CONSTANT initialized data are available. GD should be zeroed * before board_init_f() is called. * * 2. Call board_init_f(). This function prepares the hardware for * execution from system RAM (DRAM, DDR...) As system RAM may not * be available yet, , board_init_f() must use the current GD to * store any data which must be passed on to later stages. These * data include the relocation destination, the future stack, and * the future GD location. * * 3. Set up intermediate environment where the stack and GD are the * ones allocated by board_init_f() in system RAM, but BSS and * initialized non-const data are still not available. * * 4a.For U-Boot proper (not SPL), call relocate_code(). This function * relocates U-Boot from its current location into the relocation * destination computed by board_init_f(). * * 4b.For SPL, board_init_f() just returns (to crt0). There is no * code relocation in SPL. * * 5. Set up final environment for calling board_init_r(). This * environment has BSS (initialized to 0), initialized non-const * data (initialized to their intended value), and stack in system * RAM (for SPL moving the stack and GD into RAM is optional - see * CONFIG_SPL_STACK_R). GD has retained values set by board_init_f(). * * 6. For U-Boot proper (not SPL), some CPUs have some work left to do * at this point regarding memory, so call c_runtime_cpu_setup. * * 7. Branch to board_init_r(). * * For more information see 'Board Initialisation Flow in README. */
從注釋中,我們可以大概知道_main函數的執行順序為:
- 先設置用於調用board_init_f()函數的初始環境,該環境僅僅是提供了堆棧和存儲位置GD('global data')結構,兩者都是位於可以使用的RAM(SRAM,locked cache...)中,在調用board_init_f()函數前,GD應該被清0;
- 調用board_init_f()函數,該函數的功能為從system RAM(DRAM,DDR...)中執行准備硬件,當system RAM還不能夠使用的時,必須要使用目前的GD存儲傳遞到后續階段的所有數據,這些數據包括重定位的目標,將來的堆棧和GD的位置;
- 設置中間環境,其中堆棧和GD是由board_init_f()函數在system RAM中進行分配的,但此時的bss和初始化的非常量仍然不能使用;
- 對於正常的uboot引導(非SPL),調用relocate_code()函數,該函數的功能將uboot從當前的位置重新轉移到由board_init_f()函數計算的目標位置;
- 對於SPL,board_init_f()函數僅僅是返回(crt0),沒有代碼的重定位;
- 設置用於調用board_init_r()函數的最終環境,該環境將有bss段(初始化為0),初始化非常量數據(初始化為預期值),並入棧到system RAM中,GD保留了board_init_f()函數設置的值;
- 為了使uboot正常運行(非SPL),某些CPU還有一些關於內存的工作要做,調用c_runtime_cpu_setup()函數;
- 調用board_init_r()函數。
_main函數的定義如下所示:
ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //sp指針指向CONFIG_SYS_INIT_SP_ADDR(0x0091FF00) #endif #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endif mov r0, sp //將sp指針保存到r0寄存器,此時r0的值為0x0091FF00 bl board_init_f_alloc_reserve //調用函數board_init_f_alloc_reserve,參數為r0的值 mov sp, r0 //將函數返回值寫到sp指針,r0的值為0x0091FA00 /* set up gd here, outside any C code */ mov r9, r0 //將r0寄存器的值保存到r9寄存器,也就是gd結構體的地址,r9 = 0x0091FA00 bl board_init_f_init_reserve //調用board_init_f_init_reserve,參數為r0的值0x0091FA00 mov r0, #0 //將r0寄存器的值設置為0 bl board_init_f //調用board_init_f函數 #if ! defined(CONFIG_SPL_BUILD) /* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ //獲取新的sp指針值 #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ //sp指針8字節對齊 #endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ //獲取gd->bd的值 sub r9, r9, #GD_SIZE /* new GD is below bd */ //r9 = r9 - GD_SIZE,r9 = gd->new_gd adr lr, here /* 獲取here的鏈接地址 */ ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ //r0保存重定位的偏移量 add lr, lr, r0 //lr = lr + r0,here的鏈接地址需要加上偏移,因為uboot將被重定位 #if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */ #endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ //r0保存重定位的地址 b relocate_code //跳轉到relocate_code函數 here: /* * now relocate vectors */ bl relocate_vectors /* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ #endif #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK) # ifdef CONFIG_SPL_BUILD /* Use a DRAM stack for the rest of SPL, if requested */ bl spl_relocate_stack_gd cmp r0, #0 movne sp, r0 movne r9, r0 # endif ldr r0, =__bss_start /* this is auto-relocated! */ #ifdef CONFIG_USE_ARCH_MEMSET ldr r3, =__bss_end /* this is auto-relocated! */ mov r1, #0x00000000 /* prepare zero to clear BSS */ subs r2, r3, r0 /* r2 = memset len */ bl memset #else ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ clbss_l:cmp r0, r1 /* while not at end of BSS */ #if defined(CONFIG_CPU_V7M) itt lo #endif strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l #endif #if ! defined(CONFIG_SPL_BUILD) bl coloured_LED_init bl red_led_on #endif /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ //設置第一個參數值gd指針 ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ //設置第二個參數值gd->relocaddr /* call board_init_r */ #if defined(CONFIG_SYS_THUMB_BUILD) ldr lr, =board_init_r /* this is auto-relocated! */ bx lr #else ldr pc, =board_init_r /* this is auto-relocated! */ //調用board_init_r函數 #endif /* we should not return here. */ #endif ENDPROC(_main)
_main函數的實現比較復雜,需要對其分部分進行分析。
第一部分,首先是設置初始化C運行環境,然后調用board_init_f(0)函數,功能實現的代碼如下:
/* * Set up initial C runtime environment and call board_init_f(0). */ #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //sp指針指向CONFIG_SYS_INIT_SP_ADDR(0x0091FF00) #endif #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endif mov r0, sp //將sp指針保存到r0寄存器,此時r0的值為0x0091FF00 bl board_init_f_alloc_reserve //調用函數board_init_f_alloc_reserve,參數為r0的值 mov sp, r0 //將函數返回值寫到sp指針,r0的值為0x0091FA00 /* set up gd here, outside any C code */ mov r9, r0 //將r0寄存器的值保存到r9寄存器,也就是gd結構體的地址,r9 = 0x0091FA00 bl board_init_f_init_reserve //調用board_init_f_init_reserve,參數為r0的值0x0091FA00 mov r0, #0 //將r0寄存器的值設置為0 bl board_init_f //調用board_init_f函數
從上面的代碼中可以知道,_main函數進來后,設置sp指針的值為CONFIG_SYS_INIT_SP_ADDR,並對其進行8字節對齊,然后將sp指針的值保存到r0寄存器中,接下來開始調用board_init_f_alloc_reserve函數,傳入的參數為r0寄存器的值,也就是sp指針,board_init_f_alloc_reserve函數的定義在common/init/board_init.c文件中,定義如下所示:
ulong board_init_f_alloc_reserve(ulong top) //top = 0x0091FF00 { /* Reserve early malloc arena */ #if defined(CONFIG_SYS_MALLOC_F) top -= CONFIG_SYS_MALLOC_F_LEN; //top = top - 0x400,此時top = 0x0091FF00 - 0x400 = 0x0091FB00 #endif /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */ //sizeof(struct global_data) = 248 top = rounddown(top-sizeof(struct global_data), 16);
//top = top - sizeof(struct global_data),16字節對齊 //top = 0x0091FB00 - 248 - 8 = 0x0091FA00 return top; }
在上面的代碼中,可以知道,board_init_f_alloc_reserve()函數主要是留出早期的malloc內存區域和global_data結構體內存區域,對於CONFIG_SYS_MALLOC_F_LEN宏定義在文件include/generated/autoconf.h文件中,為0x400,而sizeof(struct global_data)為GD_SIZE宏的值,為248,並且top的值需要16字節對齊,該函數返回top指針的值,函數調用后,OCRAM的內存分配如下所示:

board_init_f_alloc_reserve()函數是具有返回值,將top=0x0091FA00返回,繼續往下分析,因此r0寄存器的值為0x0091FA00,並將r0寄存器的值回寫sp指針,此時,sp=0x0091FA00,另外,r0寄存器的值也寫到了r9寄存器,因此r9=0x0091FA00,為啥要將分配好的global_data結構體首地址寫到r9寄存器呢?
在uboot源碼文件arch/arm/include/asm/global_data.h中,具有下面的兩個宏定義:
#ifdef CONFIG_ARM64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18") #else #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") #endif #endif
對於ARM32的CPU,uboot定義了一個指向gd_t類型的gd指針,gd是一個全局變量,存放在r9寄存器,gd_t的類型定義在文件include/asm-generic/global_data.h中,其實就是struct global_data,該結構體的成員比較多,定義如下:
typedef struct global_data { bd_t *bd; //指向bd_t結構體內存空間 unsigned long flags; unsigned int baudrate; unsigned long cpu_clk; /* CPU clock in Hz! */ unsigned long bus_clk; /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */ unsigned long pci_clk; unsigned long mem_clk; #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO) unsigned long fb_base; /* Base address of framebuffer mem */ #endif #if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER) unsigned long post_log_word; /* Record POST activities */ unsigned long post_log_res; /* success of POST test */ unsigned long post_init_f_time; /* When post_init_f started */ #endif #ifdef CONFIG_BOARD_TYPES unsigned long board_type; #endif unsigned long have_console; /* serial_init() was called */ //標志位:是否具有console #ifdef CONFIG_PRE_CONSOLE_BUFFER unsigned long precon_buf_idx; /* Pre-Console buffer index */ #endif unsigned long env_addr; /* Address of Environment struct */ unsigned long env_valid; /* Checksum of Environment valid? */ unsigned long ram_top; /* Top address of RAM used by U-Boot */ //DRAM的最終地址 unsigned long relocaddr; /* Start address of U-Boot in RAM */ //uboot重定位的地址 phys_size_t ram_size; /* RAM size */ //DRAM的大小 #ifdef CONFIG_SYS_MEM_RESERVE_SECURE #define MEM_RESERVE_SECURE_SECURED 0x1 #define MEM_RESERVE_SECURE_MAINTAINED 0x2 #define MEM_RESERVE_SECURE_ADDR_MASK (~0x3) /* * Secure memory addr * This variable needs maintenance if the RAM base is not zero, * or if RAM splits into non-consecutive banks. It also has a * flag indicating the secure memory is marked as secure by MMU. * Flags used: 0x1 secured * 0x2 maintained */ phys_addr_t secure_ram; #endif unsigned long mon_len; /* monitor len */ //整個uboot的長度 unsigned long irq_sp; /* irq stack pointer */ unsigned long start_addr_sp; /* start_addr_stackpointer */ //棧指針 unsigned long reloc_off; //uboot重定位的偏移 struct global_data *new_gd; /* relocated global data */ //uboot重定位后新的gd_t結構體首地址 #ifdef CONFIG_DM struct udevice *dm_root; /* Root instance for Driver Model */ struct udevice *dm_root_f; /* Pre-relocation root instance */ struct list_head uclass_root; /* Head of core tree */ #endif #ifdef CONFIG_TIMER struct udevice *timer; /* Timer instance for Driver Model */ #endif const void *fdt_blob; /* Our device tree, NULL if none */ void *new_fdt; /* Relocated FDT */ unsigned long fdt_size; /* Space reserved for relocated FDT */ struct jt_funcs *jt; /* jump table */ char env_buf[32]; /* buffer for getenv() before reloc. */ #ifdef CONFIG_TRACE void *trace_buff; /* The trace buffer */ #endif #if defined(CONFIG_SYS_I2C) int cur_i2c_bus; /* current used i2c bus */ #endif #ifdef CONFIG_SYS_I2C_MXC void *srdata[10]; #endif unsigned long timebase_h; unsigned long timebase_l; #ifdef CONFIG_SYS_MALLOC_F_LEN unsigned long malloc_base; /* base address of early malloc() */ //早期malloc相關成員 unsigned long malloc_limit; /* limit address */ unsigned long malloc_ptr; /* current address */ #endif #ifdef CONFIG_PCI struct pci_controller *hose; /* PCI hose for early use */ phys_addr_t pci_ram_top; /* top of region accessible to PCI */ #endif #ifdef CONFIG_PCI_BOOTDELAY int pcidelay_done; #endif struct udevice *cur_serial_dev; /* current serial device */ struct arch_global_data arch; /* architecture-specific data */ #ifdef CONFIG_CONSOLE_RECORD struct membuff console_out; /* console output */ struct membuff console_in; /* console input */ #endif #ifdef CONFIG_DM_VIDEO ulong video_top; /* Top of video frame buffer area */ ulong video_bottom; /* Bottom of video frame buffer area */ #endif } gd_t;
所以,將r0寄存器的值寫到r9寄存器,其實就是設置gd指針的值為0x0x0091FA00,也就是指向在OCRAM中分配的struct global_data結構體的首地址。
接下來,帶參調用board_init_f_init_reserve()函數,參數為r0寄存器的值,也就是struct global_data結構體首地址0x0091FA00,board_init_f_init_reserve()函數的定義如下:
void board_init_f_init_reserve(ulong base) { struct global_data *gd_ptr; #ifndef _USE_MEMCPY int *ptr; #endif /* * clear GD entirely and set it up. * Use gd_ptr, as gd may not be properly set yet. */ gd_ptr = (struct global_data *)base; //獲取global_data結構體首地址 /* zero the area */ #ifdef _USE_MEMCPY memset(gd_ptr, '\0', sizeof(*gd)); //將global_data結構體清0操作 #else for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); ) *ptr++ = 0; #endif /* set GD unless architecture did it already */ #if !defined(CONFIG_ARM) arch_setup_gd(gd_ptr); #endif /* next alloc will be higher by one GD plus 16-byte alignment */ base += roundup(sizeof(struct global_data), 16); //指向early_alloc的起始地址0x0091FB00 /* * record early malloc arena start. * Use gd as it is now properly set for all architectures. */ #if defined(CONFIG_SYS_MALLOC_F) /* go down one 'early malloc arena' */ gd->malloc_base = base; //設置gd->malloc_base指向early malloc首地址 /* next alloc will be higher by one 'early malloc arena' size */ base += CONFIG_SYS_MALLOC_F_LEN; #endif }
在上面的代碼中可以看出,該函數主要是將OCRAM中分配的struct global_data結構體區域進行清0操作,另外設置gd->malloc_base成員的值為early malloc區域的首地址。
接下來就是設置r0寄存器的值為0,然后調用board_init_f()函數,傳入的參數為r0寄存器的值0,該函數的定義在文件common/board_f.c文件中,該函數實現比較復雜,主要實現的功能是完成DDR、定時器、uboot代碼拷貝等,另起篇章進行分析,本篇主要內容是分析_main函數的實現流程。
將board_init_f()函數跳過后,接下來就是到了_main函數的第二部分,設置中間環境(新的sp指針值和gd指針值),並且調用relocate_code函數,代碼如下所示:
/* * Set up intermediate environment (new sp and gd) and call * relocate_code(addr_moni). Trick here is that we'll return * 'here' but relocated. */ ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ //獲取新的sp指針值 #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ //sp指針8字節對齊 #endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ //設置新的gd指針值 sub r9, r9, #GD_SIZE /* new GD is below bd */ adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0 #if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */ #endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ b relocate_code //跳轉到relocate_code函數 here: /* * now relocate vectors */ bl relocate_vectors
代碼執行后,首先是重新對sp指針的值進行賦值,sp = gd->start_addr_sp,在board_init_f()函數中會初始化struct global_data結構體的成員變量,代碼中的宏GD_START_ADDR_SP為64,為struct global_data結構體中start_addr_sp指針的偏移量,該成員變量的值為DDR內存中的地址,新的sp指針值和gd指針值將會存放DDR的內存地址,而不再是OCRAM的地址,獲取到新的sp指針值后,對其進行8字節對齊,同樣,對r9寄存器重新賦值,r9 = gd->bd,在上面說過,r9寄存器里面保存着gd指針的值,此時gd指針的值已經為DDR的內存地址,然后r9 = r9 - GD_SIZE,新的gd指針值在bd下面,在這,就完成了新的sp和gd指針值賦值。
繼續往下分析,設置lr寄存器為here,該技巧用於后期執行完其它函數后,進行返回,則直接返回到here處繼續執行,宏GD_RELOC_OFF的值為68,設置r0寄存器的值,r0 = gd->reloc_off,lr寄存器的值加上r0寄存器的值,將結果重新賦值給lr寄存器,因為接下來,要開始重新定位uboot代碼了,把代碼拷貝到DDR中新的內存地址里面去,在uboot.map分析中,可以知道,當前的uboot起始地址為0x87800000,重新定位的代碼包括here,因此,需要將lr寄存器的值加上r0寄存器的值,並將新的結果賦給lr寄存器,lr寄存器需要使用的是重新定位后的here。
宏GD_RELOCADDR為48,重新將r0寄存器賦值,r0 = gd->relocaddr,struct global_data結構體中的relocaddr成員變量保存着uboot要拷貝的RAM目標地址,所以,此時r0寄存器中保存着uboot重新定位的目標地址,接下來,帶參調用函數relocate_code,傳入的參數為r0寄存器的值,該函數的定義在arch/arm/lib/relocate.S文件中。
函數relocate_code調用完成后,返回到here執行,繼續調用函數relocate_vectors重新定位中斷向量表vectors,該函數的定義也在arch/arm/lib/relocate.S文件中,到這里,第二部分的代碼就執行完成了。
繼續分析_main函數的第三部分,主要是完成最終的環境設置,並且調用board_init_r()函數,第三部分的代碼如下所示:
/* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ #endif #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK) # ifdef CONFIG_SPL_BUILD /* Use a DRAM stack for the rest of SPL, if requested */ bl spl_relocate_stack_gd cmp r0, #0 movne sp, r0 movne r9, r0 # endif ldr r0, =__bss_start /* this is auto-relocated! */ #ifdef CONFIG_USE_ARCH_MEMSET ldr r3, =__bss_end /* this is auto-relocated! */ mov r1, #0x00000000 /* prepare zero to clear BSS */ subs r2, r3, r0 /* r2 = memset len */ bl memset #else ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ clbss_l:cmp r0, r1 /* while not at end of BSS */ #if defined(CONFIG_CPU_V7M) itt lo #endif strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l #endif #if ! defined(CONFIG_SPL_BUILD) bl coloured_LED_init bl red_led_on #endif /* call board_init_r(gd_t *id, ulong dest_addr) */ mov r0, r9 /* gd_t */ ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */ /* call board_init_r */ #if defined(CONFIG_SYS_THUMB_BUILD) ldr lr, =board_init_r /* this is auto-relocated! */ bx lr #else ldr pc, =board_init_r /* this is auto-relocated! */ #endif /* we should not return here. */ #endif
代碼執行后,首先是調用了函數c_runtime_cpu_setup,該函數的定義在文件arch/arm/cpu/armv7/start.S文件中,主要是完成與ARM處理器的配置,定義如下:
ENTRY(c_runtime_cpu_setup) /* * If I-cache is enabled invalidate it */ #ifndef CONFIG_SYS_ICACHE_OFF mcr p15, 0, r0, c7, c5, 0 @ invalidate icache mcr p15, 0, r0, c7, c10, 4 @ DSB mcr p15, 0, r0, c7, c5, 4 @ ISB #endif bx lr ENDPROC(c_runtime_cpu_setup)
接下來,則是清除bss段,board_init_r(gd_t *id, ulong dest_addr)函數具有兩個參數,第一個參數為gd指針的值,第二個參數為gd->relocaddr,設置完成后,則是調用board_init_r()函數,該函數的定義在文件common/board_r.c文件中。
到這里,基本分析完了_main函數調用的大概流程了,該函數實現的功能非常多,比較復雜,比較重要的包括board_init_f函數、relocate_code函數、relocate_vectors函數和board_init_r函數,這些函數都將分篇章進行分析。
3、小結
大概對_main函數的總體流程以及實現的功能有初步了解后,最后,對_main函數的調用流程思路整理一下,如下所示:
_main | board_init_f_alloc_reserve-->reserve gd and early malloc area | board_init_f_init_reserve-->initialize global data | board_init_f-->initialize ddr,timer...,and fill gd_t | relocate_code-->relocate uboot code | relocate_vectors-->relocate vectors | board_init_r-->calling board_init_r
接下來,將在下一篇文章中對board_init_f函數進行分析。
