背景:
因为平时自己有空也在研究uboot的代码,发现在uboot中shell的命令体系很与众不同,因为自己平时理解的命令体系大概分为两种,一种是将shell命令定义在一段数组中,另一种则是用链表的方式将shell命令集串起来,但与uboot实现的命令体系相比,不管是数组还是链表都表现的不够优秀。若是用数组的方式,则必须在定义之时就确定好整个命令集的大小,并且一旦确定就无法更改,若是用链表的方式,则显的比较累赘,有点大材小用了。 uboot的命令体系的设计则是通过定义一个结构体,结构体中包括name, maxargs, cmd, usage, help;
name是命令名,maxargs是该命令接收的最大参数个数, cmd是执行命令的函数, usage和help都是该命令的帮助信息,
uboot把每个命令都定义成这样的结构体,然后将所有的结构体都增加一个自定义数据段的属性,在编译链接的过程中,链接器会将这些具有段属性的数据都链接在一起,只要知道这段具有该段属性数据的起始和结束地址就能对命令集进行遍历(地址可以从链接脚本/分散加载文件中获得),而且在命令的定义时不需要关心命令的个数,并且可以随时添加,因为在编译的时候会自行根据命令集的个数分配应有的内存空间。
因此,自己有种想法,想将这种命令系统移植到stm32的mdk平台下的armcc中(因为uboot用的arm-linux-gcc的交叉编译工具链),在移植的过程中还遇到了蛮多困难的,因为ST公司把STM32的启动,链接文件都完全写好了,一般是不需要改动的,因此搞得很多人对cpu的编译链接过程不甚了解(我曾经也是其中之一)
移植过程:
首先定义命令的结构体:
struct cmd_tbl_s
{
uint8_t *name; /* command name */
uint32_t maxargs; /* the maximum number of parameters the command can receive */
int32_t (*cmd)(struct cmd_tbl_s *, int32_t, uint8_t *[]); /* do command function */
uint8_t *usage; /* usage message(short) */
#if defined CFG_LONGHELP
uint8_t *help; /* help message(long) */
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
/* define macro */
#define STRUCT_SECTION __attribute__((unused, section("arm_cmd")))
#if defined CFG_LONGHELP
#define ARM_CMD(name, maxargs, cmd, usage, help) \
cmd_tbl_t __arm_cmd_##name STRUCT_SECTION = {#name, maxargs, cmd, usage, help}
#else
#define ARM_CMD(name, maxargs, cmd, usage) \
cmd_tbl_t __arm_cmd_##name STRUCT_SECTION = {#name, maxargs, cmd, usage}
#endif
其中__attribute__(unused, section("arm_cmd"))就是指定了该结构体的段属性为"arm_cmd"属性,unused的意思则是若该段没有用到不报警告,使用方法如下:
/**
* @brief print version command
* @author Hins Shum
* @data 2016/10/01
* @param cmdtp : command data
* argc : number of parameters
* argv : parameters string
* @retval return 0 if the function succeeds
*/
int32_t do_version(cmd_tbl_t *cmdtp, int32_t argc, uint8_t *argv[])
{
cmd_printf("%s\n", version_string); /* print version message */
return 0;
} /* end do_version */
ARM_CMD(
version,
1,
do_version,
"version - print monitor version",
NULL
);
这样,基本的定义就完成了,剩下的就是修改分散加载文件,告诉链接器将arm_cmd链接成一个段
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00080000 { ; load region size_region
ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
data 0x20000000 { ; .data
*(.data)
}
arm_cmd + 0 { ; .arm_cmd : command set address
*(arm_cmd)
}
bss + 0 { ; .bss
*(STACK)
*(HEAP)
*(.bss)
}
}
若这样修改后,直接编译会得到这样的结果
.\Link\stm32f10x_boot.sct(15): warning: L6329W: Pattern *(arm_cmd) only matches removed unused sections.
意思是"arm_cmd"这个段匹配到的段已经被移除
接着看一下。map文件,在Removing Unused input sections from the image下面有这么一行
Removing command.o(i.do_version), (28 bytes).
也就是说我们刚才定义的do_version这个函数的符号因为没有在整个工程中用到被显示的用到而移除了。
再看我们定义的arm_cmd段中
Execution Region arm_cmd (Base: 0x20000058, Size: 0x00000000, Max: 0xffffffff, ABSOLUTE)
**** No section assigned to this execution region ****
意味这arm_cmd段中没有任何的数据,从而导致整个命令系统的实现思想变得不可实现。
之所以遇到这些问题,其实问题在于对armlink的不了解,推荐一本书杜春雷的《ARM体系结构与编程》,其中只要看第11章"ARM链接器"即可。
解决方法是在misc controls中填写链接命令--keep=command.o(arm_cmd),就可以不让编译器擅自移除之前移除的符号。
或者将__arrtibute__((unused, secition("arm_cmd")))改为__arrtibute__((used, secition("arm_cmd")))同样可以实现保留未显示使用的段属性为arm_cmd的输入段(链接中有域、输出段、输入段的概念请参考前面提到的那本书)
最后就是通过遍历命令集的方式去查找和执行:
/**
* @brief find whether the command exists
* @author Hins Shum
* @data 2016/10/02
* @param cmd : command parameter argv[0]
* @retval command addr
* NULL : if command not find
*/
extern cmd_tbl_t arm_cmd$$Base;
extern cmd_tbl_t arm_cmd$$Limit;
static cmd_tbl_t *cmd_find(const uint8_t *cmd)
{
cmd_tbl_t *cmdtp = NULL;
uint32_t len = strlen((const char *)cmd);
for(cmdtp = &arm_cmd$$Base; cmdtp != &arm_cmd$$Limit; cmdtp++) {
if(0 == strncmp((const char *)cmd, (const char *)cmdtp->name, len)) {
if(len == strlen((const char *)cmdtp->name)) {
return cmdtp;
}
}
}
return NULL;
} /* end cmd_find */
命令集中命令的个数可通过下面的方法获得:
uint32_t cmd_item = &arm_cmd$$Limit - &arm_cmd$$Base; /* get a total of multiple commands */
完整的代码托管至github上,网址:github.com/HinsShum/stm32f10x