uboot启动流程
来源:互联网 发布:印刷体汉字识别数据集 编辑:程序博客网 时间:2024/06/04 18:27
u-boot支持许多CPU,以及一些常见的开发板。本文以u-boot-2011.06这个最新版本为例,简要介绍一下u-boot在smdk2410上的启动流程。首先系统是从arch/arm/cpu/arm920t目录下的start.s文件开始执行,并且实际开始执行的代码是从第117行开始:117:start_code:118: /*119: * set the cpu to SVC32 mode120: */121: mrs r0, cpsr122: bic r0, r0, #0x1f123: orr r0, r0, #0xd3124: msr cpsr, r0上述代码的含义是设置cpu为SVC32模式,即超级保护模式,用于操作系统使用。140:#ifdef CONFIG_S3C24X0141: /* turn off the watchdog */142:143:# if defined(CONFIG_S3C2400)144:# define pWTCON 0x15300000145:# define INTMSK 0x14400008 /* Interupt-Controller base addresses */146:# define CLKDIVN 0x14800014 /* clock divisor register */147:#else148:# define pWTCON 0x53000000149:# define INTMSK 0x4A000008 /* Interupt-Controller base addresses */150:# define INTSUBMSK 0x4A00001C151:# define CLKDIVN 0x4C000014 /* clock divisor register */152:# endif153:154: ldr r0, =pWTCON155: mov r1, #0x0156: str r1, [r0]157:158: /*159: * mask all IRQs by setting all bits in the INTMR - default160: */161: mov r1, #0xffffffff162: ldr r0, =INTMSK163: str r1, [r0]164:# if defined(CONFIG_S3C2410)165: ldr r1, =0x3ff166: ldr r0, =INTSUBMSK167: str r1, [r0]168:# endif169:170: /* FCLK:HCLK:PCLK = 1:2:4 */171: /* default FCLK is 120 MHz ! */172: ldr r0, =CLKDIVN173: mov r1, #3174: str r1, [r0]175:#endif /* CONFIG_S3C24X0 */该段代码的含义为,先定义几个需要的寄存器,然后关闭开门狗定时器,以及屏蔽所有中断和子中断,最后设置三个时钟频率之间的比值。181:#ifndef CONFIG_SKIP_LOWLEVEL_INIT182: bl cpu_init_crit183:#endif在第182行中,程序跳转到cpu_init_crit中,它也是在start.s文件中,函数的位置在第328行至第356行,它的作用是设置一些重要的寄存器(如MMU和caches等)以及内存时序。其中在第353行,程序又跳转到了lowlevel_init函数,它是在board/samsung/smdk2410目录下的lowlevel_init.s文件中定义的,这个文件的目的就是为了设置内存的时序。186:call_board_init_f:187: ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)188: bic sp, sp, #7 /* 8-byte alignment for ABI compliance */189: ldr r0,=0x00000000190: bl board_init_f从cpu_init_crit返回后,来到了调用board_init_f的函数处。首先进行堆栈的设置,然后就跳转到board_init_f函数,其中传递给该函数的参数为0。board_init_f这个函数是在arch/arm/lib目录下的board.c文件内定义的,函数的位置是在第268行至第422行,它的作用是初始化开发板。需要注意的是,此时程序是在flash中运行的。下面我们就来分析board_init_f函数。275: /* Pointer is writable since we allocated a register for it */276: gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);277: /* compiler optimization barrier needed for GCC >= 3.4 */278: __asm__ __volatile__("": : :"memory");279:280: memset ((void*)gd, 0, sizeof (gd_t));281:282: gd->mon_len = _bss_end_ofs;gd是一个保存在ARM的r8寄存器中的gd_t结构体的指针,该结构体包括了u-boot中所有重要的全局变量,它是在arch/arm/include/asm目录下的global_data.h文件内被定义的。上述代码的作用是为gd分配地址,并清零,最后得到整个u-boot的长度。284: for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {285: if ((*init_fnc_ptr)() != 0) {286: hang ();287: }288: }上述代码的作用是循环调用init_sequence函数指针数组中的成员,该数组成员函数主要完成一些初始化的工作,如:board_early_init_f函数(在board/samsung/smdk2410目录下的smdk2410.c文件内)完成ARM的时钟频率和IO的设置;timer_init函数(在arch/arm/cpu/arm920t/s3c24x0目录下的timer.c文件内)完成定时器4的设置;env_init函数(在common目录下的env_flash.c文件内,因为include/configs/smdk2410.h中定义了CONFIG_ENV_IS_IN_FLASH)完成环境变量的设置;init_baudrate函数(在arch/arm/lib目录下的board.c文件内)完成波特率的设置;serial_init函数(在drivers/serial目录下的serial_s3c24x0.c文件内,因为include/configs/smdk2410.h中定义了CONFIG_S3C24X0_SERIAL)完成串口通讯的设置;console_init_f函数(在common目录下的console.c文件内)完成第一阶段的控制台初始化;display_banner函数(在arch/arm/lib目录下的board.c文件内)用来打印输出一些信息;dram_init函数(在board/samsung/smdk2410目录下的smdk2410.c文件内)用来配置SDRAM的大小。309: addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;得到SDRAM的末位物理地址为0x3400 0000,即SDRAM的空间分布为0x3000 0000~0x33FF FFFF。329:#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))330: /* reserve TLB table */331: addr -= (4096 * 4);332:333: /* round down to next 64 kB limit */334: addr &= ~(0x10000 - 1);335:336: gd->tlb_addr = addr;337: debug ("TLB table at: %08lx\n", addr);338:#endif339:340: /* round down to next 4 kB limit */341: addr &= ~(4096 - 1);342: debug ("Top of RAM usable for U-Boot at: %08lx\n", addr);分配SDRAM的高64kB区域作为TLB,即0x33FF 0000~0x33FF FFFF,并且该区域也被用于U-Boot。354: /*355: * reserve memory for U-Boot code, data & bss356: * round down to next 4 kB limit357: */358: addr -= gd->mon_len;359: addr &= ~(4096 - 1);360:361: debug ("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);分配SDRAM的下一个单元为U-Boot代码段、数据段及BSS段。363:#ifndef CONFIG_PRELOADER364: /*365: * reserve memory for malloc() arena366: */367: addr_sp = addr - TOTAL_MALLOC_LEN;368: debug ("Reserving %dk for malloc() at: %08lx\n",369: TOTAL_MALLOC_LEN >> 10, addr_sp);370: /*371: * (permanently) allocate a Board Info struct372: * and a permanent copy of the "global" data373: */374: addr_sp -= sizeof (bd_t);375: bd = (bd_t *) addr_sp;376: gd->bd = bd;377: debug ("Reserving %zu Bytes for Board Info at: %08lx\n",378: sizeof (bd_t), addr_sp);379: addr_sp -= sizeof (gd_t);380: id = (gd_t *) addr_sp;381: debug ("Reserving %zu Bytes for Global Data at: %08lx\n",382: sizeof (gd_t), addr_sp);383:384: /* setup stackpointer for exeptions */385: gd->irq_sp = addr_sp;386:#ifdef CONFIG_USE_IRQ387: addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);388: debug ("Reserving %zu Bytes for IRQ stack at: %08lx\n",389: CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);390:#endif391: /* leave 3 words for abort-stack */392: addr_sp -= 12;393:394: /* 8-byte alignment for ABI compliance */395: addr_sp &= ~0x07;396:#else397: addr_sp += 128; /* leave 32 words for abort-stack */398: gd->irq_sp = addr_sp;399:#endif第367行的意思为在SDRAM中又开辟了一块malloc空间,该区域是紧挨着上面定义的U-Boot区域的下面。然后在SDRAM中又分别依次定义了bd结构体空间、gd结构体空间和3个字大小的异常中断堆空间。其中bd结构体的数据原型为bd_t数据结构,它表示的是“板级信息”结构体,这些信息包括开发板的波特率、IP地址、ID、以及DRAM等信息,它是在arch/arm/include/asm目录下的u-boot.h文件中定义的。下图详细描述了SDRAM的空间分配情况:408: gd->bd->bi_baudrate = gd->baudrate;409: /* Ram ist board specific, so move it to board code ... */410: dram_init_banksize();411: display_dram_config(); /* and display it */412:413: gd->relocaddr = addr;414: gd->start_addr_sp = addr_sp;415: gd->reloc_off = addr - _TEXT_BASE;上述代码主要的作用是为gd结构体赋值,其中display_dram_config函数的作用是计算SDRAM的大小,并把它通过串口显示在控制台上。417: memcpy (id, (void *)gd, sizeof (gd_t));418:419: relocate_code (addr_sp, id, addr);在board_init_f函数的最后是跳转到relocate_code函数体内,这个函数是在arch/arm/cpu/arm920t目录下的start.s文件内,也就是说从最开始的start.s跳到board.c,又从board.c跳回到了start.s中,这是因为此时程序需要重定向,即把代码从flash中搬运到ram中,这个过程是需要汇编这个低级语言来完成的。传递给relocate_code函数的三个参数分别栈顶地址、数据ID(即全局结构gd)在SDRAM中的起始地址和在SDRAM中存储U-Boot的起始地址。需要注意的是relocate_code函数执行完后,并不会返回到relocate_code (addr_sp, id, addr);的下一条语句继续执行。下面我们再回到start.s文件:201: .globl relocate_code202:relocate_code:203: mov r4, r0 /* save addr_sp */204: mov r5, r1 /* save addr of gd */205: mov r6, r2 /* save addr of destination */取得三个参数,分别放入寄存器r4、r5和r6。208:stack_setup:209: mov sp, r4设置堆栈地址。211: adr r0, _start212: cmp r0, r6213: beq clear_bss /* skip relocation */214: mov r1, r6 /* r1 <- scratch for copy_loop */215: ldr r3, _bss_start_ofs216: add r2, r0, r3 /* r2 <- source end address */217:218:copy_loop:219: ldmia r0!, {r9-r10} /* copy from source address [r0] */220: stmia r1!, {r9-r10} /* copy to target address [r1] */221: cmp r0, r2 /* until source end address [r2] */222: blo copy_loop判断U-Boot是在什么位置上,如果在SDRAM中,则直接跳到BSS段清零函数处即可;如果在FLASH中,则要把U-Boot复制到SDRAM中指定的位置处。224:#ifndef CONFIG_PRELOADER225: /*226: * fix .rel.dyn relocations227: */228: ldr r0, _TEXT_BASE /* r0 <- Text base */229: sub r9, r6, r0 /* r9 <- relocation offset */230: ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */231: add r10, r10, r0 /* r10 <- sym table in FLASH */232: ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */233: add r2, r2, r0 /* r2 <- rel dyn start in FLASH */234: ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */235: add r3, r3, r0 /* r3 <- rel dyn end in FLASH */236:fixloop:237: ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */238: add r0, r0, r9 /* r0 <- location to fix up in RAM */239: ldr r1, [r2, #4]240: and r7, r1, #0xff241: cmp r7, #23 /* relative fixup? */242: beq fixrel243: cmp r7, #2 /* absolute fixup? */244: beq fixabs245: /* ignore unknown type of fixup */246: b fixnext247:fixabs:248: /* absolute fix: set location to (offset) symbol value */249: mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */250: add r1, r10, r1 /* r1 <- address of symbol in table */251: ldr r1, [r1, #4] /* r1 <- symbol value */252: add r1, r1, r9 /* r1 <- relocated sym addr */253: b fixnext254:fixrel:255: /* relative fix: increase location by offset */256: ldr r1, [r0]257: add r1, r1, r9258:fixnext:259: str r1, [r0]260: add r2, r2, #8 /* each rel.dyn entry is 8 bytes */261: cmp r2, r3262: blo fixloop263:#endif上述代码的含义是对rel.dyn进行重定向。265:clear_bss:266:#ifndef CONFIG_PRELOADER267: ldr r0, _bss_start_ofs268: ldr r1, _bss_end_ofs269: mov r4, r6 /* reloc addr */270: add r0, r0, r4271: add r1, r1, r4272: mov r2, #0x00000000 /* clear */273:274:clbss_l:str r2, [r0] /* clear loop... */275: add r0, r0, #4276: cmp r0, r1277: bne clbss_l278:279: bl coloured_LED_init280: bl red_LED_on281:#endif对BSS段进行清零的函数。287:#ifdef CONFIG_NAND_SPL288: ldr r0, _nand_boot_ofs289: mov pc, r0290:291:_nand_boot_ofs:292: .word nand_boot293:#else294: ldr r0, _board_init_r_ofs295: adr r1, _start296: add lr, r0, r1297: add lr, lr, r9298: /* setup parameters for board_init_r */299: mov r0, r5 /* gd_t */300: mov r1, r6 /* dest_addr */301: /* jump to it ... */302: mov pc, lr303:304:_board_init_r_ofs:305: .word board_init_r - _start306:#endif由于没有定义CONFIG_NAND_SPL,所以程序是从第294行开始执行。该段代码的作用是跳转到board_init_r函数,并且给该函数传递了两个参数:全局结构gd在SDRAM中的起始地址和在SDRAM中存储U-Boot的起始地址。board_init_r函数是在arch/arm/lib目录下的board.c文件中,也就是又回到了上面执行过的board_init_f函数所在的board.c文件中。以后,程序就开始在SDRAM中运行了。下面我们来分析board_init_r函数:447: gd = id;448: bd = gd->bd;449: gd->flags |= GD_FLG_RELOC; /* tell others: relocation done */450:451: monitor_flash_len = _end_ofs;452: debug ("monitor flash len: %08lX\n", monitor_flash_len);453: board_init(); /* Setup chipselects */上述代码的作用是对gd和bd进行赋值,其中monitor_flash_len为整个U-Boot的长度。469: /* The Malloc area is immediately below the monitor copy in DRAM */470: malloc_start = dest_addr - TOTAL_MALLOC_LEN;471: mem_malloc_init (malloc_start, TOTAL_MALLOC_LEN);对SDRAM中的malloc空间进行清零初始化。473:#if !defined(CONFIG_SYS_NO_FLASH)474: puts ("Flash: ");475:476: if ((flash_size = flash_init ()) > 0) {477:# ifdef CONFIG_SYS_FLASH_CHECKSUM478: print_size (flash_size, "");479: /*480: * Compute and print flash CRC if flashchecksum is set to 'y'481: *482: * NOTE: Maybe we should add some WATCHDOG_RESET()? XXX483: */484: s = getenv ("flashchecksum");485: if (s && (*s == 'y')) {486: printf (" CRC: %08X",487: crc32 (0, (const unsigned char *) CONFIG_SYS_FLASH_BASE, flash_size)488: );489: }490: putc ('\n');491:# else /* !CONFIG_SYS_FLASH_CHECKSUM */492: print_size (flash_size, "\n");493:# endif /* CONFIG_SYS_FLASH_CHECKSUM */494: } else {495: puts (failed);496: hang ();497: }498:#endif上述代码的作用是计算FLASH的大小,并把它通过串口显示在控制台上。由于没有定义CONFIG_SYS_FLASH_CHECKSUM,所以没有执行CRC的校验和。其中flash_init函数是在drivers/mtd目录下的cfi_flash.c文件内(因为include/configs/smdk2410.h中定义了CONFIG_FLASH_CFI_DRIVER)。500:#if defined(CONFIG_CMD_NAND)501: puts ("NAND: ");502: nand_init(); /* go init the NAND */503:#endif上述代码的作用是初始化NANDFLASH,并把NANDFLASH的大小通过串口显示在控制台上。其中nand_init函数是在divers/mtd/nand目录下的nand.c文件内定义的。505:#if defined(CONFIG_CMD_ONENAND)506: onenand_init();507:#endif初始化ONENAND FLASH519: /* initialize environment */520: env_relocate ();初始化环境变量,由于gd->env_valid等于0,所以在这里设置的是缺省环境变量。env_relocate函数是在common目录下的env_common.c文件中定义的。522:#if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)523: arm_pci_init();524:#endif初始化PCI。526: /* IP Address */527: gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");设置IP地址。529: stdio_init (); /* get the devices list going. */初始化各类外设,如IIC、LCD、键盘、USB等,当然只有在定义了这些外设的前提下,才对这些外设进行初始化。该函数是在common目录下的stdio.c文件中定义的。531: jumptable_init ();初始化跳转表gd->jt,该跳转表是一个函数指针数组,它定义了U-Boot中基本的常用函数库。该函数是在common目录下的exports.c文件中定义的。538: console_init_r (); /* fully init console as a device */初始化控制台,即标准输入、标准输出和标准错误,在这里都是串口。该函数是在common目录下的console.c文件中定义的。549: /* set up exceptions */550: interrupt_init ();551: /* enable exceptions */552: enable_interrupts ();interrupt_init函数是建立IRQ中断堆栈,enable_interrupts函数是使能IRQ中断,它们都是在arch/arm/lib目录下的interrupts.c文件中定义的。564: /* Initialize from environment */565: if ((s = getenv ("loadaddr")) != NULL) {566: load_addr = simple_strtoul (s, NULL, 16);567: }从环境变量中获取loadaddr参数,得到需要加载的地址。568:#if defined(CONFIG_CMD_NET)569: if ((s = getenv ("bootfile")) != NULL) {570: copy_filename (BootFile, s, sizeof (BootFile));571: }572:#endif从环境变量中获取bootfile参数,得到通过TFTP加载的镜像文件名。581:#if defined(CONFIG_CMD_NET)582:#if defined(CONFIG_NET_MULTI)583: puts ("Net: ");584:#endif585: eth_initialize(gd->bd);586:#if defined(CONFIG_RESET_PHY_R)587: debug ("Reset Ethernet PHY\n");588: reset_phy();589:#endif590:#endif上面代码主要的作用是初始化以太网,其中eth_initialize函数是在net目录下的eth.c文件的第209行至第298行定义的。626: /* main_loop() can return to retry autoboot, if so just run it again. */627: for (;;) {628: main_loop ();629: }board_init_r函数的最后就是执行一个死循环,调用main_loop函数。该函数是在common目录下的main.c文件内定义的。下面我们就来分析main_loop函数270:#ifndef CONFIG_SYS_HUSH_PARSER271: static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };272: int len;273: int rc = 1;274: int flag;275:#endif声明一些hush参数。277:#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)278: char *s;279: int bootdelay;280:#endif声明启动延时需要的参数。320:#ifdef CONFIG_SYS_HUSH_PARSER321: u_boot_hush_start ();322:#endif初始化hush功能。351:#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)352: s = getenv ("bootdelay");353: bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;354:355: debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);356:357:# ifdef CONFIG_BOOT_RETRY_TIME358: init_cmd_timeout ();359:# endif /* CONFIG_BOOT_RETRY_TIME */360:361:#ifdef CONFIG_POST362: if (gd->flags & GD_FLG_POSTFAIL) {363: s = getenv("failbootcmd");364: }365: else366:#endif /* CONFIG_POST */367:#ifdef CONFIG_BOOTCOUNT_LIMIT368: if (bootlimit && (bootcount > bootlimit)) {369: printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",370: (unsigned)bootlimit);371: s = getenv ("altbootcmd");372: }373: else374:#endif /* CONFIG_BOOTCOUNT_LIMIT */375: s = getenv ("bootcmd");376:377: debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");378:379: if (bootdelay >= 0 && s && !abortboot (bootdelay)) {380:# ifdef CONFIG_AUTOBOOT_KEYED381: int prev = disable_ctrlc(1); /* disable Control C checking */382:# endif383:384:# ifndef CONFIG_SYS_HUSH_PARSER385: run_command (s, 0);386:# else387: parse_string_outer(s, FLAG_PARSE_SEMICOLON |388: FLAG_EXIT_FROM_LOOP);389:# endif390:391:# ifdef CONFIG_AUTOBOOT_KEYED392: disable_ctrlc(prev); /* restore Control C checking */393:# endif394: }395:396:# ifdef CONFIG_MENUKEY397: if (menukey == CONFIG_MENUKEY) {398: s = getenv("menucmd");399: if (s) {400:# ifndef CONFIG_SYS_HUSH_PARSER401: run_command (s, 0);402:# else403: parse_string_outer(s, FLAG_PARSE_SEMICOLON |404: FLAG_EXIT_FROM_LOOP);405:# endif406: }407: }408:#endif /* CONFIG_MENUKEY */409:#endif /* CONFIG_BOOTDELAY */第352行和第353行的含义是从环境变量中获取bootdelay参数,得到自动启动缺省镜像文件的延时(单位是秒)。第358行的含义是初始化命令行超时机制。第375行的含义是从环境变量中获取bootcmd参数,得到在启动延时过程中自动执行的命令。当我们得到了bootcmd参数,bootdelay参数也是大于等于0,并且在启动延时过程中没有按下任意键时,执行第387行的parse_string_outer函数,该函数的作用是解释bootcmd参数并执行,它是在common目录下的hush.c文件内定义的。414:#ifdef CONFIG_SYS_HUSH_PARSER415: parse_file_outer();416: /* This point is never reached */417: for (;;);418:#else419: for (;;) {420:#ifdef CONFIG_BOOT_RETRY_TIME421: if (rc >= 0) {422: /* Saw enough of a valid command to423: * restart the timeout.424: */425: reset_cmd_timeout();426: }427:#endif428: len = readline (CONFIG_SYS_PROMPT);429:430: flag = 0; /* assume no special flags for now */431: if (len > 0)432: strcpy (lastcommand, console_buffer);433: else if (len == 0)434: flag |= CMD_FLAG_REPEAT;435:#ifdef CONFIG_BOOT_RETRY_TIME436: else if (len == -2) {437: /* -2 means timed out, retry autoboot438: */439: puts ("\nTimed out waiting for command\n");440:# ifdef CONFIG_RESET_TO_RETRY441: /* Reinit board to run initialization code again */442: do_reset (NULL, 0, 0, NULL);443:# else444: return; /* retry autoboot */445:# endif446: }447:#endif448:449: if (len == -1)450: puts ("<INTERRUPT>\n");451: else452: rc = run_command (lastcommand, flag);453:454: if (rc <= 0) {455: /* invalid command or not repeatable, forget it */456: lastcommand[0] = 0;457: }458: }459:#endif /*CONFIG_SYS_HUSH_PARSER*/由于在include/configs/smdk2410.h文件中定义了CONFIG_SYS_HUSH_PARSER,所以上面的代码仅仅执行的是第415行至第417行的内容。第415行的parse_file_outer函数是在common目录下的hush.c文件中定义的,它的含义是依次读取命令序列中的命令并执行之,其中在该函数还调用了parse_stream_outer函数,这个函数体内有一个do-while循环,只有发生语法错误的时候才会跳出该循环,因此一般情况下永远也不会执行上面代码中的第417行内容,而是始终在那个do-while循环体内。
原文地址:
http://www.arm8.net/thread-178-1-1.html
1 0
- uboot启动流程
- uboot启动流程
- uboot 启动流程分析
- Uboot 启动流程简介
- Uboot启动流程分析
- uboot启动流程
- uboot启动流程
- Uboot启动流程分析
- Uboot启动流程
- uboot 启动流程 freescale
- uboot启动流程-s3c2410
- G870 uboot启动流程
- uboot启动流程二
- uboot 启动流程
- Uboot 启动流程简介
- UBOOT启动流程分析
- Uboot启动流程
- Uboot启动流程分析
- maven打包,报错“编码 GBK 的不可映射字符”
- 矩阵相加(十字链表)
- 第一行代码基础源码
- 【总结】IE和Firefox的Javascript兼容性总结
- 如何快速查看SQL Server版本信息?
- uboot启动流程
- [查异常网]-说在开始的话
- linux下部分常用命令
- VS2013发布网站详细步骤
- JSON语句解析代码
- 浏览器打开显示中文是乱码
- leetcode 26. Remove Duplicates from Sorted Array
- iOS数据存储--SQLite数据库
- 2016年3月25日博客 开通