bootloader




版权声明:本文为博主原创文章,未经博主允许不得转



1、框架

MTK 平台的启动过程经过四个模块,分别是BootRom,Preloader,LK,Kernel.


2 、bootloader到kernel启动总逻辑流程图


3、Boot ROM

Boot ROM的主要功能流程如下:

1)设备上电起来后,跳转到Boot ROM,Boot ROM从Reset Vector 开始执行ARM 核。

2)内部SRAM 中初始化栈

3)初始化nand flash 或 emmc

4)把pre-loader加载起到ISRAM

5)跳到pre-loader

4、Preloader

4.1 Preloader主要功能

1)复位寄存器、堆栈的SP,禁止中断IRQ,建立起C运行环境

2)初始化Timer、Clock、UART、DDR等关键硬件

3)对代码进行鉴权

4)pre-loader初始化好DRAM后就将lk从flash(nand/emmc)中加载到DRAM中运行

4.2 Preloader- entry

 PreLoader  entry位于  mediatek /platform/MTXXXX//Preloader/src/init/init.s



源码流程如下:


    
    
  1. ./ bootloader/preloader/platform/mt6580/src/init/init.s
  2. .section .text .start
  3. ...
  4. .globl start
  5. ...
  6. /* set the cpu to SVC32 mode */
  7. MRS r0 , cpsr
  8. BIC r0 , r0 , #0x1f
  9. ORR r0 , r0 , #0xd3
  10. MSR cpsr,r0
  11. /* disable interrupt */
  12. MRS r0 , cpsr
  13. MOV r1 , #INT_BIT
  14. ORR r0 , r0 , r1
  15. MSR cpsr_cxsf, r0
  16. ...
  17. setup_stk :
  18. /* setup stack */
  19. LDR r0 , stack
  20. LDR r1 , stacksz
  21. ...
  22. entry :
  23. LDR r0 , =bldr_args_addr
  24. /* 跳转到C代码 main 入口 */
  25. B main

4.3 Preloader- main

alps/mediatek /platform/$MTXXXX//Preloader/



    
    
  1. void main(u32 *arg)
  2. {
  3. struct bldr_command_handler handler;
  4. u32 jump_addr, jump_arg;
  5. /* get the bldr argument */
  6. bldr_param = ( bl_param_t *)*arg;
  7. // 初始化uart
  8. mtk_uart_init(UART_SRC_CLK_FRQ, CFG_LOG_BAUDRATE);
  9. // 这里干了很多事情,包括各种的平台硬件(timer,pmic,gpio,wdt...)初始化工作.
  10. bldr_pre_process();
  11. handler.priv = NULL ;
  12. handler.attr = 0 ;
  13. handler.cb = bldr_cmd_handler;
  14. // 这里是获取启动模式等信息保存到全局变量g_boot_mode和g_meta_com_type 中.
  15. BOOTING_TIME_PROFILING_LOG( "before bldr_handshake" );
  16. bldr_handshake(&handler);
  17. BOOTING_TIME_PROFILING_LOG( "bldr_handshake" );
  18. // 下面跟 secro img 相关,跟平台设计强相关.
  19. /* security check */
  20. sec_lib_read_secro();
  21. sec_boot_check();
  22. device_APC_dom_setup();
  23. BOOTING_TIME_PROFILING_LOG( "sec_boot_check" );
  24. /* 如果已经实现EL3,那么进行tz预初始化 */
  25. #if CFG_ATF_SUPPORT
  26. trustzone_pre_init();
  27. #endif
  28. /* bldr_load_images
  29. 此函数要做的事情就是把lk从ROM中指定位置load到DRAM中,开机log中可以看到具体信息:
  30. [PART] load "lk" from 0x0000000001CC0200 (dev) to 0x81E00000 (mem) [SUCCESS]
  31. 这里准备好了jump到DRAM的具体地址,下面详细分析.
  32. */
  33. if ( 0 != bldr_load_images(&jump_addr)) {
  34. print( "%s Second Bootloader Load Failed\n" , MOD);
  35. goto error;
  36. }
  37. /*
  38. 该函数的实现体是platform_post_init,这里要干的事情其实比较简单,就是通过
  39. hw_check_battery去判断当前系统是否存在电池(判断是否有电池ntc脚来区分),
  40. 如果不存在就陷入while(1)卡住了,所以在es阶段调试有时候
  41. 需要接电源调试的,就需要改这里面的逻辑才可正常开机
  42. */
  43. bldr_post_process();
  44. // atf 正式初始化,使用特有的系统调用方式实现.
  45. #if CFG_ATF_SUPPORT
  46. trustzone_post_init();
  47. #endif
  48. /* 跳转传入lk的参数,包括boot time/mode/reason 等,这些参数在
  49. platform_set_boot_args 函数获取。
  50. */
  51. jump_arg = (u32)&(g_dram_buf->boottag);
  52. /* 执行jump系统调用,从 pre-loader 跳转到 lk执行,

5、lk

5.1 lk主要功能

1)从Preloader 获取参数

2)MMU cache 使能

3)外设初始化

4)设置boot 模式

5)加载kernel

6) 跳转到kernel


5.2 lk-reset



lk执行入口:

位于.text.boot 这个section(段),具体定义位置为:


     
     
  1. ./lk/arch/ arm/ system-onesegment.ld: 10: .text. boot : { *( .text. boot) }
  2. ./lk/arch/ arm/ system-twosegment.ld: 10: .text. boot : { *( .text. boot) }

该段的代码执行入口是crt0.S文件,位置为:

./lk/arch/arm/crt0.S
     
     

crt0.S 中会经过一系列的初始化准备操作,最终跳转到C代码入口kmain函数开始执行,这个是 我们需要重点分析关注的,kmain的位置:

./lk/kernel/main.c
     
     

5.3 lk-main

5.4代码分析


     
     
  1. 1、crt0.S
  2. .section ”.text.boot”
  3. .Lstack_setup:
  4. /* ==set up the stack for irq, fi==q, abort, undefined, system/user, and lastly supervisor mode */
  5. mrs r0 , cpsr
  6. bic r0 , r0 , #0x1f
  7. ldr r2 , =abort_stack_top
  8. orr r1 , r0 , #0x12 // irq
  9. msr cpsr_c, r1
  10. ldr r13 , =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */
  11. orr r1 , r0 , #0x11 // fiq
  12. msr cpsr_c, r1
  13. mov sp , r2
  14. orr r1 , r0 , #0x17 // abort
  15. msr cpsr_c, r1
  16. mov sp , r2
  17. orr r1 , r0 , #0x1b // undefined
  18. msr cpsr_c, r1
  19. mov sp , r2
  20. orr r1 , r0 , #0x1f // system
  21. msr cpsr_c, r1
  22. mov sp , r2
  23. orr r1 , r0 , #0x13 // supervisor
  24. msr cpsr_c, r1
  25. mov sp , r2
  26. bl kmain

crt0.S 小结:

这里主要干的事情就是建立fiq/irq/abort等各种模式的stack,初始化向量表,然后切换到管理模式(pre-loader运行在EL3, lk运行在EL1),最后跳转到C代码入口 kmain 执行.

2、kmain :


     
     
  1. void kmain(void)
  2. {
  3. boot_time = get_timer( 0);
  4. /* 早期初始化线程池的上下文,包括运行队列、线程链表的建立等,
  5. lk架构支持多线程,但是此阶段只有一个cpu处于online,所以也只有一条代码执行路径.
  6. */
  7. thread_init_early();
  8. /* 架构初始化,包括DRAM,MMU初始化使能,使能协处理器,
  9. preloader运行在ISRAM,属于物理地址,而lk运行在DRAM,可以选择开启MMU或者关闭,开启MMU可以加速lk的加载过程.
  10. */
  11. arch_early_init();
  12. /*
  13. 平台硬件早期初始化,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,
  14. 初始化平台硬件,建立lk基本运行环境。
  15. */
  16. platform_early_init();
  17. boot_time = get_timer( 0);
  18. // 这个是保留的空函数.
  19. target_early_init();
  20. dprintf(CRITICAL, "welcome to lk\n\n");
  21. /*
  22. 执行定义在system-onesegment.ld 描述段中的构造函数,不太清楚具体机制:
  23. ctor_list = .;
  24. .ctors : { *(.ctors) }
  25. __ctor_end = .;
  26. */
  27. call_constructors();
  28. //内核堆链表上下文初始化等.
  29. heap_init();
  30. // 线程池初始化,前提是PLATFORM_HAS_DYNAMIC_TIMER需要支持.
  31. thread_init();
  32. // dpc系统是什么?据说是一个类似work_queue的东东,dpc的简称是什么就不清楚了.
  33. dpc_init();
  34. // 初始化内核定时器
  35. timer_init();
  36. // 创建系统初始化工作线程,执行app初始化,lk把业务部分当成一个app.
  37. thread_resume(thread_create( "bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
  38. // 使能中断.
  39. exit_critical_section();
  40. // become the idle thread
  41. thread_become_idle();
  42. }

kmain 小结:

。初始化线程池,建立线程管理链表、运行队列等;

。初始化各种平台硬件,包括irq、timer,wdt,uart,led,pmic,i2c,gpio等,建立lk基本运行环境;

。初始化内核heap、内核timer等;

。创建系统初始化主线程,进入bootstrap2执行,使能中断,当前线程进入idle;

3、bootstrap2 分析:


     
     
  1. static int bootstrap2(void *arg)
  2. {
  3. ...
  4. /*
  5. 平台相关初始化,包括nand/emmc,LCM显示驱动,启动模式选择,加载logo资源,
  6. 具体代码流程如下时序图.
  7. */
  8. platform_init();
  9. ...
  10. /*
  11. app初始化,跳转到mt_boot_init入口开始执行,对应的 ".apps" 这个section.
  12. */
  13. apps_init();
  14. return 0;
  15. }


这里的 apps_init 跳转机制还有点特别:


     
     
  1. extern const struct app_descriptor apps_start;
  2. extern const struct app_descriptor apps_end;
  3. void apps_init(void)
  4. {
  5. const struct app_descriptor *app;
  6. /* 这里具体干了什么?如何跳转到mt_boot_init入口?有点不知所云
  7. 依次遍历 从__apps_start 到__apps_end 又是什么东东?
  8. */
  9. for (app = &__apps_start; app != &__apps_end; app++) {
  10. if (app->init)
  11. app->init(app);
  12. }
  13. ...
  14. }

这个__apps_start 跟 __apps_end哪里定义的? 是怎么回事呢? 这里就需要了解一点编译链接原理跟memory 布局的东东, 这个实际上是指memory中的一个只读数据段的起始&结束地址区间, 它定义在这个文件中:


     
     
  1. ./lk/arch/arm/ system-onesegment.ld: 47: __apps_start = . ;
  2. .rodata : {
  3. ...
  4. . = ALIGN( 4) ;
  5. __apps_start = . ;
  6. KEEP (*(.apps))
  7. __apps_end = . ;
  8. . = ALIGN( 4) ;
  9. __rodata_end = . ;
  10. }

该mem地址区间是[__apps_start, __apps_end],显然区间就是“.apps” 这个section内容了. 那么这个section是在哪里初始化的呢?继续看:


     
     
  1. ./lk/app/mt_boot/mt_boot .c: 1724:
  2. APP_START(mt_boot)
  3. .init = mt_boot_init,
  4. APP_END

展开APP_START:


     
     
  1. #define APP_START(appname) struct app_descriptor _app##appname _SECTION(".apps") = { .name = #appname,
  2. #define APP_END };

到这里就很明显了,编译链接系统会将mt_boot_init这个地址记录到”.apps”这个section中!所以下面代码要干的事情就很清晰了,执行app->init(app)后就等价于调用了void mt_boot_init(const struct app_descriptor *app) 函数.


     
     
  1. for ( app = &__apps_start; app != &__apps_end; app++) {
  2. if ( app-> init)
  3. app-> init( app);
  4. }

bootstrap2 函数小结:

。平台相关初始化,包括nand/emmc,显现相关驱动,启动模式选择,加载logo资源 检测是否DA模式,检测分区中是否有KE信息,如果就KE信息,就从分区load 到DRAM, 点亮背光,显示logo,禁止I/D-cache和MMU,跳转到DA(??),配置二级cache的size 获取bat电压,判断是否低电量是否显示充电logo等,总之此函数干的事情比较多.时序图(platform_init)可以比较清晰直观的描述具体细节

。跳转到到mt_boot_init函数,对应的 “.apps” 这个section,相关机制上面已经详细描述,不再复述.

4、mt_boot_init 分析


     
     
  1. void mt_boot_init(const struct app_descriptor *app)
  2. {
  3. unsigned usb_init = 0 ;
  4. unsigned sz = 0 ;
  5. int sec_ret = 0 ;
  6. char tmp[SN_BUF_LEN+ 1 ] = { 0 };
  7. unsigned ser_len = 0 ;
  8. u64 key;
  9. u32 chip_code;
  10. char serial_num[SERIALNO_LEN];
  11. /* 获取串号字符串 */
  12. key = get_devinfo_with_index( 13 );
  13. key = (key << 32 ) | ( unsigned int )get_devinfo_with_index( 12 );
  14. /* 芯片代码 */
  15. chip_code = board_machtype();
  16. if (key != 0 )
  17. get_serial(key, chip_code, serial_num);
  18. else
  19. memcpy (serial_num, DEFAULT_SERIAL_NUM, SN_BUF_LEN);
  20. /* copy serial from serial_num to sn_buf */
  21. memcpy (sn_buf, serial_num, SN_BUF_LEN);
  22. dprintf(CRITICAL, "serial number %s\n" ,serial_num);
  23. /* 从特定分区获取产品sn号,如果获取失败就使用默认值 DEFAULT_SERIAL_NUM */
  24. #ifdef SERIAL_NUM_FROM_BARCODE
  25. ser_len = read_product_info(tmp);
  26. if (ser_len == 0 ) {
  27. ser_len = strlen (DEFAULT_SERIAL_NUM);
  28. strncpy (tmp, DEFAULT_SERIAL_NUM, ser_len);
  29. }
  30. memset ( sn_buf, 0 , sizeof (sn_buf));
  31. strncpy ( sn_buf, tmp, ser_len);
  32. #endif
  33. sn_buf[SN_BUF_LEN] = '\0' ;
  34. surf_udc_device.serialno = sn_buf;
  35. /* mtk平台默认不支持 fastboot */
  36. if (g_boot_mode == FASTBOOT)
  37. goto fastboot;
  38. /* secure boot相关 */
  39. #ifdef MTK_SECURITY_SW_SUPPORT
  40. #if MTK_FORCE_VERIFIED_BOOT_SIG_VFY
  41. g_boot_state = BOOT_STATE_RED;
  42. #else
  43. if ( 0 != sec_boot_check( 0 )) {
  44. g_boot_state = BOOT_STATE_RED;
  45. }
  46. #endif
  47. #endif
  48. /* 这里干的事情就比较多了,跟进g_boot_mode选择各种启动模式,例如:
  49. normal、facotry、fastboot、recovery等,然后从ROM中的boot.img分区找到(解压)
  50. ramdisk跟zImage的地址loader到DRAM的特定地址中,kernel最终load到DRAM中的地址
  51. (DRAM_PHY_ADDR + 0x8000) == 0x00008000.
  52. read the data of boot (size = 0x811800)
  53. */
  54. boot_linux_from_storage ();
  55. fastboot:
  56. target_fastboot_init();
  57. if (!usb_init)
  58. /*Hong-Rong: wait for porting*/
  59. udc_init(&surf_udc_device);
  60. mt_part_dump();
  61. sz = target_get_max_flash_size();
  62. fastboot_init(target_get_scratch_address(), sz);
  63. udc_start();
  64. }

mt_boot_init 分析小结:

。获取设备串号字符串、芯片代码、sn号等.

。如果实现了secure boot则进行sec boot的check工作;

。进入 boot_linux_from_storage 函数初始化,该函数很重要,干了很多事情,如下分析.


5、boot_linux_from_storage 分析:


     
     
  1. int boot_linux_from_storage(void)
  2. {
  3. int ret= 0 ;
  4. ...
  5. switch (g_boot_mode) {
  6. case NORMAL_BOOT:
  7. case META_BOOT:
  8. case ADVMETA_BOOT:
  9. case SW_REBOOT:
  10. case ALARM_BOOT:
  11. case KERNEL_POWER_OFF_CHARGING_BOOT:
  12. case LOW_POWER_OFF_CHARGING_BOOT:
  13. /* 检查boot分区的头部是否有bootopt标识,如果没有就报错 */
  14. ret = mboot_android_load_bootimg_hdr( "boot" , CFG_BOOTIMG_LOAD_ADDR);
  15. if (ret < 0 ) {
  16. msg_header_error( "Android Boot Image" );
  17. }
  18. /* 64bit & 32bit kimg地址获取不一样*/
  19. if (g_is_64bit_kernel) {
  20. kimg_load_addr = ( unsigned int )target_get_scratch_address();
  21. } else {
  22. kimg_load_addr = (g_boot_hdr!= NULL ) ? g_boot_hdr->kernel_addr : CFG_BOOTIMG_LOAD_ADDR;
  23. }
  24. /*
  25. 从EMMC的boot分区取出bootimage载入到DRAM
  26. dprintf(CRITICAL, " > from - 0x%016llx (skip boot img hdr)\n",start_addr);
  27. dprintf(CRITICAL, " > to - 0x%x (starts with kernel img hdr)\n",addr);
  28. len = dev->read(dev, start_addr, (uchar*)addr, g_bimg_sz); <<= 系统调用load到DRAM
  29. 开机log:
  30. [3380] > from - 0x0000000001d20800 (skip boot img hdr)
  31. [3380] > to - 0x80008000 (starts with kernel img hdr)
  32. */
  33. ret = mboot_android_load_bootimg( "boot" , kimg_load_addr);
  34. if (ret < 0 ) {
  35. msg_img_error( "Android Boot Image" );
  36. }
  37. dprintf(CRITICAL, "[PROFILE] ------- load boot.img takes %d ms -------- \n" , ( int )get_timer(time_load_bootimg));
  38. break ;
  39. case RECOVERY_BOOT:
  40. ...
  41. break ;
  42. case FACTORY_BOOT:
  43. case ATE_FACTORY_BOOT:
  44. ...
  45. break ;
  46. ...
  47. }
  48. /* 重定位根文件系统(ramdisk)地址 */
  49. memcpy ((g_boot_hdr!= NULL ) ? ( char *)g_boot_hdr->ramdisk_addr : ( char *)CFG_RAMDISK_LOAD_ADDR, ( char *)(g_rmem_off), g_rimg_sz);
  50. g_rmem_off = (g_boot_hdr!= NULL ) ? g_boot_hdr->ramdisk_addr : CFG_RAMDISK_LOAD_ADDR;
  51. ...
  52. /* 传入cmdline,设置selinux */
  53. #if SELINUX_STATUS == 1
  54. cmdline_append( "androidboot.selinux=disabled" );
  55. #elif SELINUX_STATUS == 2
  56. cmdline_append( "androidboot.selinux=permissive" );
  57. #endif
  58. /* 准备启动linux kernel */
  59. boot_linux (( void *) CFG_BOOTIMG_LOAD_ADDR , ( unsigned *)CFG_BOOTARGS_ADDR,
  60. ( char *)cmdline_get(), board_machtype(), ( void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz);
  61. while ( 1 ) ;
  62. return 0 ;
  63. }

boot_linux_from_storage 小结:

。跟据g_boot_mode选择各种启动模式,例如: normal、facotry、fastboot、recovery等,然后从EMMC中的boot分区找到(解压) ramdisk跟zImage的地址通过read系统调用load到DRAM址中, kernel最终load到DRAM的地址:(DRAM_PHY_ADDR + 0x8000);

。重定位根文件系统地址;

。跳转到 boot_linux,正式拉起kernel;


6、boot_linux 分析: 

boot_linux 实际上跑的是boot_linux_fdt,这个函数有对dtb的加载做出来,期间操作相当复杂,这里只简单关注主流程.


     
     
  1. void boot_linux( void *kernel, unsigned *tags,
  2. char *cmdline, unsigned machtype,
  3. void *ramdisk, unsigned ramdisk_size)
  4. {
  5. ...
  6. // 新架构都是走fdt分支.
  7. #ifdef DEVICE_TREE_SUPPORT
  8. boot_linux_fdt(( void *)kernel, ( unsigned *)tags,
  9. ( char *)cmdline, machtype,
  10. ( void *)ramdisk, ramdisk_size);
  11. while ( 1) ;
  12. #endif
  13. ...
  14. int boot_linux_fdt( void *kernel, unsigned *tags,
  15. char *cmdline, unsigned machtype,
  16. void *ramdisk, unsigned ramdisk_size)
  17. {
  18. ...
  19. void (*entry)( unsigned, unsigned, unsigned*) = kernel;
  20. ...
  21. // find dt from kernel img
  22. if (fdt32_to_cpu(*( unsigned int *)dtb_addr) == FDT_MAGIC) {
  23. dtb_size = fdt32_to_cpu(*( unsigned int *)(dtb_addr+ 0x4));
  24. } else {
  25. dprintf(CRITI CAL, "Can't find device tree. Please check your kernel image\n");
  26. while ( 1) ;
  27. }
  28. ...
  29. if (!has_set_p2u) {
  30. /* 控制进入kernel后uart的输出,非eng版本默认是关闭的,如果调试需要就可以改这里为
  31. "printk.disable_uart=0"
  32. */
  33. #ifdef USER_BUILD
  34. sprintf(cmdline, "%s%s",cmdline, " printk.disable_uart=1");
  35. #else
  36. sprintf(cmdline, "%s%s",cmdline, " printk.disable_uart=0 ddebug_query=\"file *mediatek* +p ; file *gpu* =\"");
  37. #endif
  38. ...
  39. }
  40. ...
  41. // led,irq关闭
  42. platform_uninit();
  43. // 关闭I/D-cache,关闭MMU,今天kernel的条件.
  44. arch_disable_cache(U CACHE);
  45. arch_disable_mmu();
  46. // sec init
  47. extern void platform_sec_post_init( void)__attribute(( weak));
  48. if (platform_sec_post_init) {
  49. platform_sec_post_init();
  50. }
  51. // 如果是正在充电,检测到power key后执行reset.
  52. if (kernel_charging_boot() == 1) {
  53. if (pmic_detect_powerkey()) {
  54. dprintf(CRITI CAL, "[%s] PowerKey Pressed in Kernel Charging Mode Before Jumping to Kernel, Reboot Os\n", func);
  55. mtk_arch_reset( 1);
  56. }
  57. }
  58. #endif
  59. ...
  60. // 输出关键信息。
  61. dprintf(CRITI CAL, "cmdline: %s\n", cmdline);
  62. dprintf(CRITI CAL, "lk boot time = %d ms\n", lk_t);
  63. dprintf(CRITI CAL, "lk boot mode = %d\n", g_boot_mode);
  64. dprintf(CRITI CAL, "lk boot reason = %s\n", g_boot_reason[boot_reason]);
  65. dprintf(CRITI CAL, "lk finished --> jump to linux kernel %s\n\n", g_is_64bit_kernel ? "64Bit" : "32Bit");
  66. // 执行系统调用,跳转到kernel,这里的entry实际上就是前面的kernel在DRAM的入口地址.
  67. if (g_is_64bit_kernel) {
  68. lk_jump64((u32)entry, (u32)tags, 0, KERNEL_64BITS);
  69. } else {
  70. dprintf(CRITI CAL, "[mt_boot] boot_linux_fdt entry:0x%08x, machtype:%d\n",entry,machtype);
  71. entry( 0, machtype, tags);
  72. }
  73. while ( 1);
  74. return 0;
  75. }

开机log打印信息:


     
     
  1. [ 4260] cmdline: console=tty0 console=ttyMT0, 921600n1 root=/dev/ram vmalloc= 496M androidboot.hardware=mt6580 androidboot.verifiedbootstate=green bootopt= 64S3, 32S1, 32S1 printk.disable_uart= 1 bootprof. pl_t= 1718 bootprof. lk_t= 2178 boot_reason= 0 androidboot.serialno= 0123456789ABCDEF androidboot.bootreason=power_key gpt= 1
  2. [ 4260] lk boot time = 2178 ms
  3. [ 4260] lk boot mode = 0
  4. [ 4260] lk boot reason = power_key
  5. [ 4260] lk finished --> jump to linux kernel 32Bit
  6. [ 4260] [mt_boot] boot_linux_fdt entry: 0x80008000, machtype: 6580

boot_linux 小结:

。初始化DTB(device tree block);

。准备各种cmdline参数传入kernel;

。关闭I/D-cache、MMU;

。打印关键信息,正式拉起kernel.

bootloader两个阶段就分析完了!






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值