本文主要介绍u-boot的命令实现。
文中以命令tftpboot为例,来说明这个过程。
当我们在命令行中输入tftpboot命令时,u-boot会调用相应的命令处理函数do_tftpb(),如下:
U_BOOT_CMD(
tftpboot, 3, 1, do_tftpb, /* do_tftpb为实际的命令处理函数*/
"boot image via network using TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
那么系统是如何找到这个函数的呢?
当u-boot从串口上读取到用户的输入命令后,进入命令处理函数cmd_process()
cmd_process()首先通过find_cmd()查找用户命令想对应的数据结构,找到之后就调用命令处理函数cmd_call()来处理。
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;
result = (cmdtp->cmd)(cmdtp, flag, argc, argv); /*调用命令处理函数*/
if (result)
debug("Command failed, result=%d", result);
return result;
}
由此我们看出,find_cmd()就是在查找个重要的数据结构。
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
const int len = ll_entry_count(cmd_tbl_t, cmd);
return find_cmd_tbl(cmd, start, len);
}
#define ll_entry_start(_type, _section_u) \
({ \
extern _type _u_boot_list_##_section_u##__start; \
_type *_ll_result = &_u_boot_list_##_section_u##__start;\
_ll_result; \
})
find_cmd()是从&_u_boot_list_cmd__start这个位置开始查找的,通过比较命令名字name.
cmd_tbl_t *find_cmd_tbl (const char *cmd, cmd_tbl_t *table, int table_len)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = table; /*Init value */
const char *p;
int len;
int n_found = 0;
if (!cmd)
return NULL;
/*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
for (cmdtp = table;
cmdtp != table + table_len;
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) { /* 比较命令字符串 */
if (len == strlen (cmdtp->name))
return cmdtp; /* full match */
cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}
return NULL; /* not found or ambiguous command */
}
到这里我们只知道命令去哪找,但是它是怎么生成的还没有答案。
然后,我们去u-boot.lds查看如下脚本段:
PROVIDE (edata = .);
. = .;
. = ALIGN(4);
.u_boot_list : {
_u_boot_list__start = .; /* 定义符号_u_boot_list_cmd__start,并且安排在当前定位计数器指定的位置,
* 也就是.u_boot_list节的0地址处
*/
_u_boot_list_cmd__start = .;
*(SORT(.u_boot_list.cmd.*));
_u_boot_list_cmd__end = .;
_u_boot_list_env_clbk__start = .;
*(SORT(.u_boot_list.env_clbk.*));
_u_boot_list_env_clbk__end = .;
*(SORT(.u_boot_list.*));
_u_boot_list__end = .;
}
这个脚本中,描述了一个.u_boot.list节,_u_boot_list_cmd__start就是在这定义的。
然后,我们回到命令定义处cmd_net.c
U_BOOT_CMD(
tftpboot, 3, 1, do_tftpb,
"boot image via network using TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
这个U_BOOT_CMD是个宏:
#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_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
#define ll_entry_declare(_type, _name, _section_u, _section_d) \
_type _u_boot_list_##_section_u##_##_name __attribute__(( \
unused, aligned(4), \
section(".u_boot_list."#_section_d"."#_name)))
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
通过上述的定义,我们对命令tftpboot的的实现就展现为如下的形式:
cmd_tbl_t _u_boot_list_cmd_tftpboot __attribute__(( \
unused, aligned(4), \ /* 4字节对齐 */
section(".u_boot_list."#_section_d"."#_name))) /* 指定存放在.u_boot.list节 */
=
{
"tftpboot", /* name */
3, /* 最大参数数目 */
1, /* 自动重复开关 */
do_tftpb, /* 命令函数 */
"", /* 命令用法usage,字符串 */
"", /* 帮助help,字符串 */
NULL
}
这个变量_u_boot_list_cmd_tftpboot的定义时指示了属性,连接器会将其链接到 .u_boot_list节中,这样,我们就通过_u_boot_list_cmd__start 比较name,来得到具体命令的结构了。