大家好,又见面了,我是你们的朋友全栈君。
FinSH 是 RT-Thread 的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信。
FinSH 提供了多个宏接口来导出自定义命令,导出的命令可以直接在 FinSH 中执行。
自定义的 msh 命令,可以在 msh 模式下被运行,将一个命令导出到 msh 模式可以使用如下宏接口:
MSH_CMD_EXPORT(name, desc);
示例如下:
void hellort(void)
{
rt_kprintf("hello RT-Thread!\n");
}
MSH_CMD_EXPORT(hellort , say hello to RT-Thread);
在命令行里输入hellort\r\n就会触发这个函数。
先探究MSH_CMD_EXPORT这个宏定义的实现。
1.
#define MSH_CMD_EXPORT(command, desc) FINSH_FUNCTION_EXPORT_CMD(command, __cmd_##command, desc)
//嵌套一层宏定义,把两个参数变成3个参数,command用##与__cmd_连接起来,那么它的第二参数就变成__cmd_command
MSH_CMD_EXPORT(hellort , say hello to RT-Thread)展开:
FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)
2.
#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \ const char __fsym_##cmd##_name[] SECTION(".rodata.name") = #cmd; \ const char __fsym_##cmd##_desc[] SECTION(".rodata.name") = #desc; \ RT_USED const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")= \ {
\ __fsym_##cmd##_name, \ __fsym_##cmd##_desc, \ (syscall_func)&name \ };
3.
#define SECTION(x) __attribute__((section(x)))
#define RT_UNUSED __attribute__((unused))
#define RT_USED __attribute__((used))
#define ALIGN(n) __attribute__((aligned(n)))
FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)再展开:
const char __fsym___cmd_hellort_name[] __attribute__((section(".rodata.name"))) = "__cmd_hellort";
const char __fsym___cmd_hellort_desc[] __attribute__((section(".rodata.name"))) = "say hello to RT-Thread";
__attribute__((used)) const struct finsh_syscall __fsym___cmd_hellort __attribute__((section("FSymTab")))={
__fsym___cmd_hellort_name,
__fsym___cmd_hellort_desc,
(syscall_func)&hellort
};
上述代码定义了两个const char字符数组,分别保存了函数名和描述。
然后定义了一个const struct finsh_syscall类型的结构体并且初始化了,这个结构体原型看下面的代码:
三个成员分别指向函数名字符串,描述字符串,和函数的首地址。
4.<finch_api.h>
typedef long (*syscall_func)(void);
/* system call table */
struct finsh_syscall
{
const char* name; /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
const char* desc; /* description of system call */
#endif
syscall_func func; /* the function address of system call */
};
extern struct finsh_syscall *_syscall_table_begin, *_syscall_table_end;
<symbol.c>
#ifdef FINSH_USING_SYMTAB
struct finsh_syscall *_syscall_table_begin = NULL;
struct finsh_syscall *_syscall_table_end = NULL;
struct finsh_sysvar *_sysvar_table_begin = NULL;
struct finsh_sysvar *_sysvar_table_end = NULL;
#else
要了解上面的内容需要了解__attribute__((used))
和__attribute__((section))
的用法:
【__attribute__编译属性】
编译器的关键字 __attribute__ 用来指定变量或结构位域的特殊属性。关键字后的
双括弧中的内容是属性说明。下面是目前支持的变量属性:
• address (addr)
• aligned (alignment)
• boot
• deprecated
• fillupper
• far
• mode (mode)
• near
• noload
• packed
• persistent
• reverse (alignment)
• section (“section-name”)
• secure
• sfr (address)
• space (space)
• transparent_union
• unordered
• unused
• weak
__attribute__的section子项的使用格式为:
__attribute__((section(“section_name”)))
其作用是将作用的函数或数据放入指定名为”section_name”输入段。
这里还要注意一下两个概念:输入段和输出段
输入段和输出段是相对于要生成最终的elf或binary时的Link过程说的,Link过程的输入大都是由源代码编绎生成的目标文件.o,那么这些.o文件中包含的段相对link过程来说就是输入段,而Link的输出一般是可执行文件elf或库等,这些输出文件中也包含有段,这些输出文件中的段就叫做输出段。输入段和输出段本来没有什么必然的联系,是互相独立,只是在Link过程中,Link程序会根据一定的规则(这些规则其实来源于Link Script),将不同的输入段重新组合到不同的输出段中,即使是段的名字,输入段和输出段可以完全不同。
其用法举例如下:
int var __attribute__((section(".xdata"))) = 0;
这样定义的变量var将被放入名为.xdata的输入段,(注意:__attribute__这种用法中的括号一个也不能少。)
static int __attribute__((section(".xinit"))) functionA(void)
{
.....
}
这个例子将使函数functionA被放入名叫.xinit的输入段。
需要着重注意的是,__attribute__的section属性只指定对象的输入段,它并不能影响所指定对象最终会放在可执行文件的什么段。
__attribute__((used))
unused:表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
used: 向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告。
防止编译的时候由于没有加used导致变量被编译器给优化掉。
回到正题:
FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)再展开:
const char __fsym___cmd_hellort_name[] __attribute__((section(".rodata.name"))) = "__cmd_hellort";
const char __fsym___cmd_hellort_desc[] __attribute__((section(".rodata.name"))) = "say hello to RT-Thread";
__attribute__((used)) const struct finsh_syscall __fsym___cmd_hellort __attribute__((section("FSymTab")))={
__fsym___cmd_hellort_name,
__fsym___cmd_hellort_desc,
(syscall_func)&hellort
};
一共用到三个__attribute__((section))
,编译工程后查看.map文件,找到这三个保存的地方:
__fsym___cmd_hellort_name[]大小是14字节,__fsym___cmd_hellort_desc[] 大小是 23字节,这里看地址都对上了。
__fsym___cmd_hellort_name 0x08008626 Data 14 main.o(.rodata.name)
__fsym___cmd_hellort_desc 0x08008634 Data 23 main.o(.rodata.name)
__fsym_list_mem_name 0x0800864b Data 9 mem.o(.rodata.name)
所有的rtthread的finsh命令都在一个名为FSymTab段里:
第一个__fsym___cmd_hellort
就是我们自定义的命令。由于struct finsh_syscall大小是12字节(3个指针),所以0x08008ad8到0x08008ae4是间隔了12字节。
FSymTab$$Base 0x08008ad8 Number 0 main.o(FSymTab)
__fsym___cmd_hellort 0x08008ad8 Data 12 main.o(FSymTab)
__fsym_list_mem 0x08008ae4 Data 12 mem.o(FSymTab)
__fsym_pinMode 0x08008af0 Data 12 pin.o(FSymTab)
__fsym_pinWrite 0x08008afc Data 12 pin.o(FSymTab)
__fsym_pinRead 0x08008b08 Data 12 pin.o(FSymTab)
__fsym_hello 0x08008b14 Data 12 cmd.o(FSymTab)
__fsym_version 0x08008b20 Data 12 cmd.o(FSymTab)
__fsym___cmd_version 0x08008b2c Data 12 cmd.o(FSymTab)
__fsym_list_thread 0x08008b38 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_thread 0x08008b44 Data 12 cmd.o(FSymTab)
__fsym_list_sem 0x08008b50 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_sem 0x08008b5c Data 12 cmd.o(FSymTab)
__fsym_list_event 0x08008b68 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_event 0x08008b74 Data 12 cmd.o(FSymTab)
__fsym_list_mutex 0x08008b80 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mutex 0x08008b8c Data 12 cmd.o(FSymTab)
__fsym_list_mailbox 0x08008b98 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mailbox 0x08008ba4 Data 12 cmd.o(FSymTab)
__fsym_list_msgqueue 0x08008bb0 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_msgqueue 0x08008bbc Data 12 cmd.o(FSymTab)
__fsym_list_mempool 0x08008bc8 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_mempool 0x08008bd4 Data 12 cmd.o(FSymTab)
__fsym_list_timer 0x08008be0 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_timer 0x08008bec Data 12 cmd.o(FSymTab)
__fsym_list_device 0x08008bf8 Data 12 cmd.o(FSymTab)
__fsym___cmd_list_device 0x08008c04 Data 12 cmd.o(FSymTab)
__fsym_list 0x08008c10 Data 12 cmd.o(FSymTab)
__fsym___cmd_help 0x08008c1c Data 12 msh.o(FSymTab)
__fsym___cmd_ps 0x08008c28 Data 12 msh_cmd.o(FSymTab)
__fsym___cmd_time 0x08008c34 Data 12 msh_cmd.o(FSymTab)
__fsym___cmd_free 0x08008c40 Data 12 msh_cmd.o(FSymTab)
__fsym___cmd_reboot 0x08008c4c Data 12 board.o(FSymTab)
FSymTab$$Limit 0x08008c58 Number 0 board.o(FSymTab)
上面我们知道了所有的命令在一个名为FSymTab段里,然后看输入命令时在哪里实现了遍历查表从而执行对应的函数。
5.<shell.c>
#ifdef FINSH_USING_SYMTAB
#if defined(__CC_ARM) || defined(__CLANG_ARM) /* ARM C Compiler */
extern const int FSymTab$$Base;
extern const int FSymTab$$Limit;
extern const int VSymTab$$Base;
extern const int VSymTab$$Limit;
finsh_system_function_init(&FSymTab$$Base, &FSymTab$$Limit);
6.
struct finsh_syscall *_syscall_table_begin = NULL;
struct finsh_syscall *_syscall_table_end = NULL;
void finsh_system_function_init(const void *begin, const void *end)
{
_syscall_table_begin = (struct finsh_syscall *) begin;
_syscall_table_end = (struct finsh_syscall *) end;
}
finsh_system_function_init实现了将表头和表尾赋值给 _syscall_table_begin 和 _syscall_table_end。
然后就可以通过_syscall_table_begin 和 _syscall_table_end 去遍历;
void msh_auto_complete(char *prefix)
{
struct finsh_syscall *index;
//省略
/* checks in internal command */
{
for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index))
{
/* skip finsh shell function */
if (strncmp(index->name, "__cmd_", 6) != 0) continue;
cmd_name = (const char *) &index->name[6];
if (strncmp(prefix, cmd_name, strlen(prefix)) == 0)
{
if (min_length == 0)
{
/* set name_ptr */
name_ptr = cmd_name;
/* set initial length */
min_length = strlen(name_ptr);
}
length = str_common(name_ptr, cmd_name);
if (length < min_length)
min_length = length;
rt_kprintf("%s\n", cmd_name);
}
}
}
//省略
}
上面msh_auto_complete函数就是实现了遍历命令;可以看到for循环中还有一个FINSH_NEXT_SYSCALL(index)
由于不同编译器,对这个表的处理方式不同,因此需要代码以对应不同的遍历方式兼容不同编译器。
宏的代码如下:
#if defined(_MSC_VER) || (defined(__GNUC__) && defined(__x86_64__))
struct finsh_syscall* finsh_syscall_next(struct finsh_syscall* call);
struct finsh_sysvar* finsh_sysvar_next(struct finsh_sysvar* call);
#define FINSH_NEXT_SYSCALL(index) index=finsh_syscall_next(index)
#define FINSH_NEXT_SYSVAR(index) index=finsh_sysvar_next(index)
#else
#define FINSH_NEXT_SYSCALL(index) index++
#define FINSH_NEXT_SYSVAR(index) index++
#endif
KEIL编译器使用的就是index++,index是一个struct finsh_syscall类型的指针。
以上所述的遍历表的方法,在RT-Thread中还有不少应用,比如:
rtthread_startup函数→rt_hw_board_init函数→rt_components_board_init函数
void rt_components_board_init(void)
{
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
}
硬件初始化表是通过下面的宏添加:
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
这个问题暂时就看到这里了,更细节的后面有时间再更新。
参考鸣谢:
__attribute__((section(x))) 使用详解
RT-Thread的FinSH控制台自定义msh命令(附带部分RT-Thread源码分析)
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143553.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...