查看原文
其他

读U-Boot源码-C语言编程技巧总结篇二

逸珺 嵌入式客栈 2021-01-31

导读: 本文继续阅读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->

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存