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

2022-08-31 17:11:27 浏览数 (1)

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

FinSH 是 RT-Thread 的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信。

FinSH 提供了多个宏接口来导出自定义命令,导出的命令可以直接在 FinSH 中执行。

自定义的 msh 命令,可以在 msh 模式下被运行,将一个命令导出到 msh 模式可以使用如下宏接口:

MSH_CMD_EXPORT(name, desc);

示例如下:

代码语言:javascript复制
void hellort(void)
{ 
   
    rt_kprintf("hello RT-Thread!n");
}

MSH_CMD_EXPORT(hellort , say hello to RT-Thread);

在命令行里输入hellortrn就会触发这个函数。

先探究MSH_CMD_EXPORT这个宏定义的实现。

代码语言:javascript复制
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)

代码语言:javascript复制
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)))
代码语言:javascript复制
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类型的结构体并且初始化了,这个结构体原型看下面的代码: 三个成员分别指向函数名字符串,描述字符串,和函数的首地址。

代码语言:javascript复制
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),将不同的输入段重新组合到不同的输出段中,即使是段的名字,输入段和输出段可以完全不同。

其用法举例如下:

代码语言:javascript复制
int var __attribute__((section(".xdata"))) = 0;

这样定义的变量var将被放入名为.xdata的输入段,(注意:__attribute__这种用法中的括号一个也不能少。)

代码语言:javascript复制
static int __attribute__((section(".xinit"))) functionA(void)
{ 
   
.....
}

这个例子将使函数functionA被放入名叫.xinit的输入段。

需要着重注意的是,__attribute__的section属性只指定对象的输入段,它并不能影响所指定对象最终会放在可执行文件的什么段。

__attribute__((used)) unused:表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。 used: 向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告。 防止编译的时候由于没有加used导致变量被编译器给优化掉。

回到正题

代码语言:javascript复制
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字节,这里看地址都对上了。

代码语言:javascript复制
    __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字节。

代码语言:javascript复制
    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段里,然后看输入命令时在哪里实现了遍历查表从而执行对应的函数。

代码语言:javascript复制
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 去遍历;

代码语言:javascript复制
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("%sn", cmd_name);
            }
        }
    }
    //省略
}

上面msh_auto_complete函数就是实现了遍历命令;可以看到for循环中还有一个FINSH_NEXT_SYSCALL(index) 由于不同编译器,对这个表的处理方式不同,因此需要代码以对应不同的遍历方式兼容不同编译器。 宏的代码如下:

代码语言:javascript复制
#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函数

代码语言:javascript复制
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)();
    }
}

硬件初始化表是通过下面的宏添加:

代码语言:javascript复制
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")

这个问题暂时就看到这里了,更细节的后面有时间再更新。

参考鸣谢: __attribute__((section(x))) 使用详解

RT-Thread的FinSH控制台自定义msh命令(附带部分RT-Thread源码分析)

__attribute__编译属性—section

RT-Thread下finsh原理浅析

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

0 人点赞