读U-Boot源码-C语言编程技巧总结篇二
导读: 本文继续阅读U-Boot代码记录分析体悟,这次读到Boot命令构建宏U_BOOT_CMD这个令人叹为观止的嵌套宏定义,尝试深入解读。文中观点或有错误疏漏,诚请交流指正,不甚感激!
注:
文中绘图采用UML语言,或有不对也请一并指正。
代码分析基于u-boot-2016.09.y
https://gitlab.denx.de/u-boot/u-boot/-/tree/u-boot-2016.09.y
直接进入正题:
U_BOOT_CMD 是用于定义一个命令结构体所用。常见的U-Boot命令:
askenv - 获取环境变量
bdinfo - 打印板子基本信息
boot - 默认引导命令
bootelf - 在内存中引导ELF映像文件
bootm - 在内存中启动应用程序映象
lbootldr - Bootldr是康柏(Compaq)公司发布的,类似于compaq iPAQ Pocket PC,支持SA1100芯片。它被推荐用来引导Llinux,支持串口Y-modem协议以及jffs文件系统。
...
常见的命令在./cmd下实现,这些命令都有用到这个U_BOOT_CM宏:
以bootldr,bootz为例,位于./cmd/bootldr.c bootz.c:
这里阅读起来很整洁,那么怎么实现的呢?来一探究竟:
#ifdef CONFIG_AUTO_COMPLETE# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif
#ifdef CONFIG_CMDLINE
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,\
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage,\
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,\
_usage, _help, _comp);
#else
#define U_BOOT_SUBCMD_START(name)static cmd_tbl_t name[] = {};
#define U_BOOT_SUBCMD_END
#define _CMD_REMOVE(_name, _cmd) \
int __remove_ ## _name(void) \
{ \
if (0) \
_cmd(NULL, 0, 0, NULL); \
return 0; \
}
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, _usage,\
_help, _comp) \
{ #_name, _maxargs, _rep, 0 ? _cmd : NULL, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, \
_comp) \
_CMD_REMOVE(sub_ ## _name, _cmd)
#endif /* CONFIG_CMDLINE */
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, NULL)
U_BOOT_CMD根据CONFIG_CMDLINE是否使能,具有两个展开分支。当CONFIG_CMDLINE打开时是最复杂的一个分支,现在分析其中最复杂的一个编译分支,将其展开过程绘制如下(图中忽略CMD_COMPLETE,CMD_HELP)
U_BOOT_CMD(
bootldr, 2, 0, do_bootldr,
"boot ldr image from memory",
"[addr]\n"
""
);
bootldr为例,当CONFIG_CMDLINE宏打开时沿1-2-3-4路径展开为例(还有ll_entry_declare/CMD_HELP/CMD_COMPLETE没在上图体现,见下面兰色部分):
最终展开时为cmd_tbl_t结构体变量。上述过程是在预编译阶段实现,那么在bootldr.c用宏U_BOOT_CMD定义bootldr处相当于作了如下替换:
到了这里是不是大吃一鲸
转而看cmd_tbl_t为命令表结构体:
类型为cmd_tbl_t
结构体变量名为 _u_boot_list_2_cmd_2_bootldr
对齐方式为__aligned(4),四字节对齐
attribute((unused, section(xxx)))
存放位置为 未使用的section中
section(".u_boot_list_2""cmd""2_""bootldr")
并将该section命名为
".u_boot_list_2""cmd""2_""bootldr"
很多人会问,经过这一系列鬼神皆惊的骚操作,谁能想这么一句话定义了这么一个玩意。那么费了半天劲,这是作妖炫技吗?
这样做是非常有价值的,来总结一下我的体会:
利用宏的层层展开,实现了命令结构体的范式定义。可重用
所谓范式,通俗讲就是定义了一个模板,只需这样照着套用,就定义并初始化了此类结构体变量。
比如我们可以很容易按照这个范式,自己添加一个自定义的命令。将相关信息以及实现函数利用U_BOOT_CM传入,并实现其函数。
使程序可读性提高,隐藏了晦涩难懂的层次宏嵌套展开,使代码看起来更加优雅整洁。
对于具体要添加命令的开发人员而言,不需要知道其内部的机理。
实现了命令的(前文所谓的)链接绑定,其在text段的section位置变得确定可知。
这在工业/医疗/汽车安全领域编程会经常使用的一种技巧。
单片机编程里我也曾做过类似应用,不过没有如此复杂。
或许还有其他更绝妙的用处,欢迎留言交流!
<-END->