下面以u-boot 2016为例,一行一行分析armv7架构cpu的uboot启动流程,用到的soc是imx6ull为例。总体流程如下:分为2部分:
①arch级初始化(架构)
②板级初始化
1 reset 函数#
1.1 初始化异常向量表#
我们知道启动入口是arch/arm/lib/vectors.S文件中的_start:
从函数入口_start可以看到,入口的指令存放的就是中断向量表,0地址偏移存放reset, 0x4地址存放_undefined_instruction,0x8地址存放_software_interrupt...
CPU上电最开始进入_start,进而进入reset函数,该函数定义在arch/arm/cpu/armv7/start.S 里面。

函数调用关系如下:reset->save_boot_params->save_boot_params_ret。
1.2 save_boot_params_ret#
1.2.1 关中断,进入SVC模式#
save_boot_params_ret 函数代码如下:
该函数功能如注释所写主要是关闭IRQ和FIQ, 进入SVC模式,都是控制cpsr寄存器。
1 | //第一行读取 cpsr寄存器 |


1.2.2 cp15配置,设置中断向量表偏移VBAR#

这里一般2个宏都没有定义,进入,这段作用是设置中断向量表偏移VBAR。
这里是把CP15 SCTLR Register读出来,bic是位清0,CR_V 在 arch/arm/include/asm/system.h 中定义了:
1 | define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */ |
那么就是把CP15 SCTLR Register读出来,对bit13清0,再写进去SCTLR Register。
如下图是SCTLR 寄存器:bit13 为 V 位,此位是向量表控制位,当为 0 的时候向量表基地址 为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能 重定位向量表。
这里将 V 清零,目的就是为了接下来的向量表重定位。
最后将_start给到r0, r0写入到 CP15 的 c12 寄存器中,也就是VBAR寄存器,完成中断向量表偏移的设定。
中断向量表的初始定义在_start入口位置,0x87800000 就是向量表的起始地址。
1.2.3 cp15配置,关MMU,ICACHE#

bl cpu_init_cp15一看就是初始化协处理器寄存器CP15, 比如关闭MMU, ICACHE等。cpu_init_cp15函数如下:都是一些CP15协寄存器操作指令,就不再一一解释。
总结:save_boot_params_ret做的事情:
①初始化中断向量表
②关FIQ,IRQ
③进入SVC模式
④设置中断向量表偏移VBAR
⑤初始化CP15,关MMU,ICACHE等。
1.3 lowlevel_init对sram内存布局#
初始化CP15,关MMU,ICACHE后,会继续执行进入cpu_init_crit:cpu_init_crit也是直接跳到lowlevel_init函数。
lowlevel_init 在文件arch/arm/cpu/armv7/lowlevel_init.S中定义:
前面2行定义sp 指向 CONFIG_SYS_INIT_SP_ADDR设置SP指针,CONFIG_SYS_INIT_SP_ADDR 在 include/configs/mx6ullevk.h
IRAM_BASE_ADDR 和IRAM_SIZE在 文 件arch/arm/include/asm/arch-mx6/imx-regs.h中有定义,imx6ull这颗芯片内部有一个sram叫做ocram,基地址就是0x00900000,size为128k(0x20000)。

再来看GENERATED_GBL_DATA_SIZE的值:该定义是编译生成的include/generated/generic-asm-offsets.h中:
这里提一句,GENERATED_GBL_DATA_SIZE 的含义为 (sizeof(struct global_data) + 15) & ~15,该结构体后面会讲到。
综上所述,CONFIG_SYS_INIT_SP_ADDR 值如下:
1 | CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 – 256 = 0x1FF00 |

从图可知:
内部sram的地址范围:0x00900000~0x0091ffff (128k)SP指向:0x0091ff00
继续分析汇编代码,sp又继续减了一个GD_SIZE,然后8字节对齐,赋值给r9。GD_SIZE 同样在 generic-asm-offsets.h 中定义了,大小为 248。最后SP指针位置就定义好了,将ip,lr入栈,进入函数s_init函数,s_init函数返回后,ip和lr出栈,并将lr赋给pc。最终内存指向如下:SP指向:0x0091fe08。
总结:lowlevel_init函数就是在内部sram下设置SP指针,来为c语言的运行配置环境。
1.4 s_init函数#
s_init 函数定义在文件 arch/arm/cpu/armv7/mx6/soc.c。
由于我们的cpu是mx6ull,那么满足最开始的if判断,那么s_init函数直接返回,什么也没做。
总结_reset过程:
1 | s_init返回了,lowlevel_init也返回了,cpu_init_crit也返回了,回到 save_boot_params_ret。 |
总结下整个系统上电的reset过程:
2 _main函数#
_main 函数定义在文件arch/arm/lib/crt0.S中:
2.1 sram中指定SP#
如注释写的,第76行,初始化c语言运行环境,SP设置到CONFIG_SYS_INIT_SP_ADDR,也就是0x0091ff00。第83行,8字节对齐。
2.2 board_init_f_alloc_reserve(指定malloc/gd指针)#
第 86 行,调用数 board_init_f_alloc_reserve,r0作为形参传给该函数。
该函数主要是留出早期的malloc内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中定义, CONFIG_SYS_MALLOC_F也同样定义成1) , sizeof(struct global_data)=248(GD_SIZE 值)。rounddown是一个向下对齐的函数。
因此最终的内存分布如下图:最终top=0X0091FA00。
回到87行,r0存储了函数的返回值,进入89行,把top的值继续存到r9。记住r9寄存器,对uboot很重要,它是记录了全局变量gd的地址。
在文件arch/arm/include/asm/global_data.h中有定义:
可以看到gd是一个全局的指针变量,gd_t结构体类型,在include/asm-generic/global_data.h里面有定义:

2.3 board_init_f_init_reserve(初始化gd,gd内存清0)#
回到_main函数继续执行board_init_f_init_reserve,在文件common/init/board_init.c中有定义:
此函数用于初始化 gd,其实就是清零处理。再设置了 gd->malloc_base 为 gd 基地址+gd 大小=0X0091FA00+248=0X0091FAF8,在做 16 字节对齐,最 终 gd->malloc_base=0X0091FB00,这个也就是early malloc的起始地址。
2.4 board_init_f(外设初始化和DRAM划分)#
此函数定义在文件 common/board_f.c,该函数非常重要,主要功能如下:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分 分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。
所有外设模块初始化详见initcall_run_list函数:
init_sequence_f 也是定义在文件common/board_f.c中,init_sequence_f 的内容比较长,简要概括如下:实际list里面函数更多。
setup_mon_len函数设置gd的mon_len成员变量,此处为__bss_end -_start,也就 是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度。initf_malloc函数初始化gd中跟 malloc 有关的成员变量,比如malloc_limit,此函 数会设置gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。malloc_limit表示 malloc 内存池大小。initf_console_record,如果定义了宏CONFIG_CONSOLE_RECORD和 宏CONFIG_SYS_MALLOC_F_LEN的话此函数就会调用函数console_record_init,但是 IMX6ULL 的 uboot 没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回 0。arch_cpu_initinitf_dm函数,驱动模型的一些初始化。arch_cpu_init_dm函数未实现board_early_init_f函数,板子相关的早期的一些初始化设置,I.MX6ULL 用来初始 化串口的 IOMUX 配置timer_init,初始化定时器board_postclk_init,对于 I.MX6ULL 来说是设置 VDDSOC 电压env_init函数是和环境变量有关的,设置gd的成员变量 env_addr`,也就是环境变量的保存地址。init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化gd->baudrate。serial_init,初始化串口。console_init_f初始化控制台。display_options,通过串口输出一些信息:
display_text_info,打印一些文本信息,如果开启 UBOOT 的DEBUG功能的话就 会输出text_base、bss_start、bss_end,形式如下:debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
print_cpuinfo函数用于打印 CPU 信息。
show_board_info函数用于打印板子信息。
INIT_FUNC_WATCHDOG_INIT, INIT_FUNC_WATCHDOG_RESET表示初始化,复位看门狗。对于 I.MX6ULL 来说是空函数。init_func_i2c函数用于初始化 I2C。
announce_dram_init,dram_init初始化DDR,其实只是设置gd->ram_size,I.MX6ULL 开发板 EMMC 版本核心板来说就是 512MB。post_init_f,此函数用来完成一些测试,初始化gd->post_init_f_time。setup_dest_addr,设置目的地址。主要设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。修改 uboot 代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c的setup_dest_addr函数:
烧录运行打印出来如下:
可以看出:gd->ram_size = 0X20000000 //ram 大小为 0X20000000=512MBgd->ram_top = 0XA0000000 //ram 最高地址为 0X80000000+0X20000000=0XA0000000gd->relocaddr = 0XA0000000 //重定位后最高地址为 0XA0000000reserve_round_4k, 对gd->relocaddr做 4KB 对齐,因为gd->relocaddr=0XA0000000,已经是 4K 对齐了,所以调整后不变。reserve_mmu,留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会 对gd->relocaddr做 64K 字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr 和 gd->relocaddr如下图:
可以看出:gd->arch.tlb_size= 0X4000 //MMU 的 TLB 表大小gd->arch.tlb_addr=0X9FFF0000 //MMU 的 TLB 表起始地址,64KB 对齐以后gd->relocaddr=0X9FFF0000 //relocaddr 地址reserve_trace函数,留出跟踪调试的内存。reserve_uboot,留出重定位后的 uboot 所占用的内存区域,uboot 所占用大小由gd->mon_len所指定,留出 uboot 的空间以后还要对gd->relocaddr做 4K 字节对齐,并且重新设 置gd->start_addr_sp:
可以看出,relocaddr一直再减:gd->mon_len = 0XA8EF4gd->start_addr_sp = 0X9FF47000//0X9FFF0000 -0XA8EF4=9FF4710C,4k再对齐一下就是0X9FF47000gd->relocaddr = 0X9FF47000reserve_malloc,留出 malloc 区域,调整gd->start_addr_sp位置。malloc 区域由宏TOTAL_MALLOC_LEN定义:#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)mx6ull_alientek_emmc.h文件中定义宏CONFIG_SYS_MALLOC_LEN 为16MB=0X1000000, 宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start_addr_sp如下图:
可以看出:TOTAL_MALLOC_LEN=0X1002000gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000reserve_board, 预留bd 内存,bd是结构体bd_t,bd_t 大小为 80 字节:gd->start_addr_sp=0X9EF44FB0//0X9EF45000 -80gd->bd=0X9EF44FB0setup_machine,设置机器 ID,linux 启动的时候会和这个机器 ID 匹配,如果匹 配的话 linux 就会启动正常。以前老版本的 uboot 和 linux 使用的,新版本使用设备树了,因此此函数无效。reserve_global_data,保留出gd,gd_t 结构体大小为 248B:gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-248=0X9EF44EB8gd->new_gd=0X9EF44EB8reserve_fdt,留出设备树相关的内存。reserve_arch是个空函数。reserve_stacks,留出栈空间,先对gd->start_addr_sp减去 16,然后做 16 字节对齐。如果使能 IRQ 的话还要留出 IRQ 相应的内存,具体工作是由arch/arm/lib/stack.c文件中的 函数arch_reserve_stacks完成。这里uboot启动没有enable IRQ,因此不会留出 IRQ 相应的内存:gd->start_addr_sp=0X9EF44E90setup_dram_config,设置dram bank信息,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小:
show_dram_config,打印dram信息:
display_new_sp,显示新的sp位置,也就是gd->start_addr_sp,要开启宏DEBUG才能看到:
reloc_fdt,数用于重定位fdt,没有用到。setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以 前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出:
可以看出,uboot 重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。
至此,board_init_f函数就执行完成了,最终的内存分配如下:
2.5 relocate_code(代码重定位)#
既然dram划分好了,那么就开始需要对代码重定位了。
2.5.1 重定位后的LR设置#

回到_main第 103 行,获取gd->start_addr_sp的值赋给 sp, board_init_f 中会初始化gd的所有成员进行内存预留,其中gd->start_addr_sp=0X9EF44E90
所以这里sp=0X9EF44E90。
第 109 行,sp 做 8 字节对齐。
第 111 行,获取gd->bd的地址赋给 r9,之前r9存放的是老的 gd,GD_BD=0来自include\generated\generic-asm-offsets.h,bd就是gd结构体的首个成员,因此offset是0:
那么新gd地址(board_init_f时确定了gd->bd的地址为0X9EF44FB0)也是它。
第 112 行,r9再减一个GD_SIZE,得到gd->new_gd,也就是0X9EF44EB8:
重点来了:
第 114 行,设置lr寄存器为 here,这样后面执行其他函数返回的时候就返回到了第 122 行 的here位置处(比如执行完relocate_code就直接返回到here)。
后面第115行~116行用来让lr指向重定位后的here位置。
第 115,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=64:
第 116 行,lr 寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。 此时lr 就存放了重定位后的here位置,因为重定位完就去重定位后的DDR去执行了,无需返回sram了。
s3c2440裸机-代码重定位(1.重定位的引入,为什么要代码重定位) - fuzidage - 博客园 (cnblogs.com)
s3c2440裸机-代码重定位(2.编程实现代码重定位) - fuzidage - 博客园 (cnblogs.com)
s3c2440裸机编程-代码重定位和清bss | Hexo (fuzidage.github.io)
关键词:位置无关码、相对跳转指令,相对寻址
第 120 行,读取gd->relocaddr的值赋给 r0 寄存器,此时r0寄存器就保存着 uboot 要拷贝的目的地址,我们打印出来为0X9FF47000。r0作为形参给relocate_code函数。
2.5.2 代码段重定位#
重定位的具体实现在arch/arm/lib/relocate.S:

- 80-83行是设置重定位
r4偏移量,源拷贝地址,判断是否要拷贝。u-boot.map获取符号的地址:
__image_copy_start |
0x87800000 | uboot拷贝的首地址 |
|---|---|---|
__image_copy_end |
0x8785dd54 | uboot拷贝的结束地址 |
如果r4等于0(也就是目标地址r0就是uboot.map中的地址),那么无需拷贝,跳转到relocate_done即可。
2. 85-89是循环拷贝,多寄存器加载存储指令LDMIA,STMIA。
arm汇编和cpu运行模式 - fuzidage - 博客园 (cnblogs.com)
Tag: arm汇编 | Hexo (fuzidage.github.io)
88行判断是否拷贝结束,r1会每次加4,当r1等于r2表示拷贝完成了,否则继续执行copy_loop。
2.5.3 rel.dyn段重定位#
编译uboot使用-pie选项以后会生成一个.rel.dyn 段,需要对这个段进行重定位和拷贝:
1 | 94-97行,从.rel.dyn 段开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中,r0 存放低 4 字节的数据,也就是 Label 地址;r1 存放高 4 字节的数据,也就是 Label 标志。 |
2.6 relocate_vectors(向量表重定位)#
relocate.S 中同时也定义了中断向量表的重定位:
1 | 第 29 行,如果定义了 CONFIG_CPU_V7M 的话就执行第 30~36 行的代码,对于 I.MX6ULL 来说不是ARMv7-M,不执行。 |
2.7 board_init_r#
2.7.1 外设初始化#
前面board_init_f初始化了芯片级外设(uart,Timer,console等)并且进行了DRAM划分,为代码重定位做准备。接下来board_init_r进行板级的外设初始化。init_sequence_r 定义在文件 common/board_r.c,负责初始化各个硬件外设,比如flash,网卡,i2c,sdio等等:
initr_trace 函数,如果定义了宏CONFIG_TRACE的话就会调用函数 trace_init, 初始化和调试跟踪有关的内容。initr_reloc 函数用于设置 gd->flags,标记重定位完成。initr_caches 函数用于初始化 cache,使能 cache。initr_reloc_global_data 函数,初始化重定位后gd的一些成员变量。initr_barrier 函数,I.MX6ULL 未用到。initr_malloc 函数,初始化 malloc。initr_console_record 函数,初始化控制台相关的内容,I.MX6ULL 未用到,空函数。bootstage_relocate 函数,启动状态重定位。initr_bootstage 函数,初始化bootstage什么的。board_init 函数,板级初始化,包括FEC、USB 和 QSPI 等。 这里执行的是 mx6ull_alientek_emmc.c 文件中的 board_init 函数。stdio_init_tables 函数,stdio 相关初始化。initr_serial 函数,初始化串口。power_init_board 函数,初始化电源芯片,正点原子的 I.MX6ULL 开发板没有用到。initr_flash 函数,对于 I.MX6ULL 而言,没有定义宏 CONFIG_SYS_NO_FLASH 的话函数initr_flash才有效。但是 mx6_common.h 中定义了宏 CONFIG_SYS_NO_FLASH,所以 此函数无效。initr_nand 函数,初始化 NAND,如果使用 NAND 版本核心板的话就会初始化 NAND。initr_mmc 函数,初始化 EMMC,如果使用 EMMC 版本核心板的话就会初始化 EMMC:

initr_env 函数,初始化环境变量。initr_secondary_cpu 函数,初始化其他 CPU 核,I.MX6ULL 只有一个核,因此此函数没用。stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL 使用 drv_video_init 函数初始化 LCD:
initr_jumptable 函数,初始化跳转表。console_init_r 函数 , 控制台初始化, 初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备:
interrupt_init 函数,初始化中断。initr_enable_interrupts 函数,使能中断。initr_ethaddr 函数,初始化网络地址,也就是获取 MAC 地址。读取环境变量 ethaddr的值。board_late_init 函数,板子后续初始化,此函数定义在文件mx6ull_alientek_emmc.c中,如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数 初始化 EMMC/SD。会切换到正在时候用的 emmc 设备,如下图所示:
cmd构造成mmc dev 1,切换到正在使用的 EMMC 设备。因为dev0为sd卡,dev1为emmc:
initr_net 函数,初始化网络设备,调用关系initr_net->eth_initialize->board_eth_init()。
最后**run_main_loop**,主循环,处理命令。
2.7.2 run_main_loop 函数#
2.7.2.1 Uboot version环境变量设置#

第 48 行,调用 bootstage_mark_name 函数,打印出启动进度。
第 57 行,如果定义了宏 CONFIG_VERSION_VARIABLE,设置版本号环境变量,cmd/version.c 中定义了version_string字符串,version.h定义了U_BOOT_VERSION_STRING:
U_BOOT_VERSION 定义在文件include/generated/version_autogenerated.h中:
比如我的板子uboot命令行输入version打印版本信息:打印uboot版本,编译日期时间,CONFIG_IDENT_STRING为板子identity信息,我这里是cvitek_athena2,如下图:
最后打印出toolchain信息是因为编译生成version_autogenerated.h,输入version命令打印如下do_version函数:
Uboot中的命令都是调用cmd_process函数来解析执行。(会面有具体分析cmd_process)
2.7.2.2 bootdelay和bootcmd获取#
第 60 行,cli_init(),cli是一个命令行接口,一般裸机开发都喜欢移植cli,来注册各种命令参数。
第 62 行,run_preboot_environment_command()获取系统环境变量preboot的值,我们没有定义该环境变量。
第 68 行,bootdelay_process()设置倒计时全局变量。我的板子bootdelay环境变量是1,.config也配置的1。
该函数是获取bootcmd环境变量,返回bootcmd环境变量,如下图:
2.7.2.3 autoboot_command(执行bootcmd)#
main_loop的第72行,自动执行bootcmd。当然我们在命令行输入run bootcmd也会执行bootcmd里面指令,如下图:
autoboot_command函数定义在common/autoboot.c:
我这里stored_bootdelay全局变量等于1,s字符串是bootcmd环境变量不为空。最后abortboot函数的返回值也为0,因此执行run_command_list,执行bootcmd里面的命令,启动kernel rootfs。
2.7.2.3.1 bootdelay倒计时#
分析abortboot()函数:abortboot调用abortboot_normal,该函数就是判断bootdelay倒计时内是否有按键按下,倒计时结束没有按键按下,那么执行bootcmd,否则uboot命令行等待用户输入,代码逻辑如下:

总结:有按键输入,abortboot()函数返回1,否则返回0, 这样autoboot_command才会执行bootcmd。
2.7.2.4 cli_loop(uboot命令行)#
再来回到main_loop,如果倒计时结束有按键按下,abortboot返回1,那么autoboot_command函数什么都不干直接返回,执行cli_loop():
cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,就是cli_loop 来处理的,此函数定义在文件 common/cli.c:
这里我们有定义CONFIG_SYS_HUSH_PARSER,因此执行parse_file_outer->parse_stream_outer,common/cli_hush.c定义了该函数:
该函数调用parse_stream 进行命令解析。然后调用run_list 函数来执行解析出来的命令。
2.7.2.4.1 cmd_process 函数分析#
当命令行有用户输入命令,并且解析到匹配的uboot命令,执行run_list->run_list_real->run_pipe_real->cmd_process。
前面提到Uboot中的命令都是调用cmd_process函数来解析执行,如version命令:

①先调用find_cmd,返回cmd_tbl_t指针:
传入的cmd 字符串就是所查找的命令名字,uboot 中的命令表其实就是cmd_tbl_t结构体数组。ll_entry_start 得到数组的第一个元素,也就是命令表起始地址。通过函数ll_entry_count得到数组长度,也就是命令表的长度。find_cmd_tbl 在命令表中找到所需的命令,每个命令都有一个 name 成员,如下图根据字符串名字去匹配对应的命令:
②调用cmd_call,执行cmd_tbl_t 结构里面对应的处理函数:
2.7.2.4.2 uboot命令定义(dhcp为例)#
uboot 使用宏 U_BOOT_CMD 来定义命令,宏 U_BOOT_CMD 定义在 include/command.h:
①**ll_entry_declar**
定义在文件 include/linker_lists.h:
_type 为 cmd_tbl_t,因此ll_entry_declare就是定义了一个cmd_tbl_t变量,这里用到了 C 语 言中的##连接符。其中的##_list表示用_list 的值来替换,##_name就是用_name 的值来替换。
比如dhcp命令代入进去:
ll_entry_declare(cmd_tbl_t, dhcp, cmd)展开后
1 | cmd_tbl_t _u_boot_list_2_# |
②**U_BOOT_CMD_MKENT_COMPLETE**# 表示将 _name 传 递 过 来 的 值 字 符 串 化。
继续以dhcp命令代入:
1 | U_BOOT_CMD_MKENT_COMPLETE(dhcp, 3, 1, do_dhcp, \ |
继续代入:
1 | { "dhcp", 3, 1, do_dhcp, \ |
最后宏 U_BOOT_CMD对dhcp命令展开如下:
1 | cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \ |
可以看到就是定义了一个cmd_tbl_t结构体变量,对它进行初始化。这个变量名为_u_boot_list_2_cmd_2_dhcp,此变量 4 字节对齐。
使 用 __attribute__ 关 键 字 设 置 变 量_u_boot_list_2_cmd_2_dhcp存 储 在.u_boot_list_2_cmd_2_dhcp 段中。u-boot.lds 链接脚本中有一个名为.u_boot_list的段专门存放uboot命令表: