RT-Thread FinSH控制台添加自定义msh命令原理「建议收藏」

RT-Thread FinSH控制台添加自定义msh命令原理「建议收藏」FinSH是RT-Thread的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口/以太网/USB等与PC机进行通信。FinSH提供了多个宏接口来导出自定义命令,导出的命令可以直接在FinSH中执行。自定义的msh命令,可以在msh模式下被运行,将一个命令导出到msh模式可以使用如下宏接口:MSH_CMD_EXPORT(name,desc);示例如下:voidhellort(void){rt_kpr

大家好,又见面了,我是你们的朋友全栈君。

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源码分析)

__attribute__编译属性—section

RT-Thread下finsh原理浅析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/143553.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号