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

  • 在学习rtthread的过程中发现rthhread的控制台组件也可以支持带参数的命令写法。其实官网文档写得很详细了,但是还是记录一下。

不带参数的命令写法

  • 不带参数的命令写法十分简单,就是写一个普通的函数
  • 再利用这个宏导出
MSH_CMD_EXPORT(name, desc);

自己的demo节选,作为示例

void TEXT(void)
{
    const rt_uint8_t Buffer[] = "hello world\r\n";
    rt_uint8_t len = rt_strlen((const char*)Buffer);
    rt_device_write(Project_uart_Device,0,Buffer,len);
}
MSH_CMD_EXPORT(TEXT, RT-Thread TEXT sample);
  • 在命令行里输入TEXT\r\n就会触发这个函数。关键是这个宏定义的实现

带参数的命令的写法

  • 带参数的命令写法其实就类似main函数的参数一样(int argc,char **argv)

下面这个代码效果是查询或设置一个蓝牙设备的设备名称的代码,当没参数时,是查询蓝牙设备名称,带参数,就是修改蓝牙设备名称

static void AT_NAME(int argc, char **argv){//设置/查询设备名称
    if(argc < 2){
        const rt_uint8_t Buffer[] = "AT+NAME\r\n";
        rt_uint8_t len = rt_strlen((const char*)Buffer);
        rt_device_write(Project_uart_Device,0,Buffer,len);
    }else if(argc > 2){
        rt_kprintf("Only one parameter can be entered\r\n");
    }else{
        char Buffer[] = "AT+NAME";
        char* NewBuf = strcat(Buffer,argv[1]);
        NewBuf = strcat(NewBuf,"\r\n");
        rt_uint8_t len = rt_strlen((const char*)NewBuf);
        rt_device_write(Project_uart_Device,0,NewBuf,len);
    }
}
MSH_CMD_EXPORT(AT_NAME, AT_NAME sample: AT_NAME <drivers_name>);
  • 电脑终端输入命令给MCU,MCU再把信息解析然后发送到蓝牙设备
  • argv[1]直接就可以被拿来用了,参数与参数之间以空格分开,十分方便

尝试分析MSH_CMD_EXPORT宏定义源码

1.
#define MSH_CMD_EXPORT(command, desc) FINSH_FUNCTION_EXPORT_CMD(command, __cmd_##command, desc) 
//嵌套一层宏定义,把两个参数变成3个参数,command用##与__cmd_连接起来,那么它的第二参数就变成__cmd_command
2.
#pragma section("FSymTab$f",read)

#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc)      \
                const char __fsym_##cmd##_name[] = #cmd;            \
                const char __fsym_##cmd##_desc[] = #desc;           \
                __declspec(allocate("FSymTab$f"))                   \
                const struct finsh_syscall __fsym_##cmd =           \
                {                           \
                    __fsym_##cmd##_name,    \
                    __fsym_##cmd##_desc,    \
                    (syscall_func)&name     \
                };
//进入这层,可以看见宏定义的数据被放进一个叫finsh_syscall(系统调用)的结构体里。对象名字就是_fsym_cmd_command
//结构体的第一个成员是字符串首地址,因为是const char类型的数组(宏定义'#'代表让后面修饰的东西变成字符串)
//结构体的第二个成员也是字符串
//结构体的第三个成员是一个指向函数的指针,指向name函数,也就是名字为name的函数的地址
3.
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;

/* find out system call, which should be implemented in user program */
struct finsh_syscall* finsh_syscall_lookup(const char* name);
//进入下一层,可以看到第一行typedef long (*syscall_func)(void),这个就是一个函数指针。
//接下来是结构体主体的定义,不得不说rtthread无论是文档还是注释都太适合国人学习了
//紧接着可以看到引用了两个指向结构体的指针(指针定义在symbol.c),从命名就可以看出一个指针指向表的开始,一个指针指向表的尾部。应该就是个链表结构了(好像不是。。)
//再接着是一个函数的定义,它的注释写着这个函数的作用是遍历链表,然后利用函数指针调用相应的函数(但是在源码中没有找到实现)
  • 接下来跳到shell.c的函数---finsh_system_init开始
...
unsigned int *ptr_begin, *ptr_end;

    ptr_begin = (unsigned int *)&__fsym_begin;
    ptr_begin += (sizeof(struct finsh_syscall) / sizeof(unsigned int));
    while (*ptr_begin == 0) ptr_begin ++;

    ptr_end = (unsigned int *) &__fsym_end;
    ptr_end --;
    while (*ptr_end == 0) ptr_end --;

    finsh_system_function_init(ptr_begin, ptr_end);
...
  • 光看这里很迷茫,接着看__fsym_begin和__fsym_end是什么东西
#pragma section("FSymTab$a", read)
const char __fsym_begin_name[] = "__start";
const char __fsym_begin_desc[] = "begin of finsh";
__declspec(allocate("FSymTab$a")) const struct finsh_syscall __fsym_begin =
{
    __fsym_begin_name,
    __fsym_begin_desc,
    NULL
};

#pragma section("FSymTab$z", read)
const char __fsym_end_name[] = "__end";
const char __fsym_end_desc[] = "end of finsh";
__declspec(allocate("FSymTab$z")) const struct finsh_syscall __fsym_end =
{
    __fsym_end_name,
    __fsym_end_desc,
    NULL
};
//然后又引出了问题,这里的关键其实是#pragma section("xxxx",read)以及__declspec(allocate("xxxx"))这两个宏定义
//#pragma section("xxxx",read),创建一个段,段名是第一个参数,read表示可读
//__declspec(allocate("xxxx"))是一个修饰,用这个这个修饰可以把变量放进"xxxx"段中
//接下来回到2.中去,可以看到结构体前面有个__declspec(allocate("FSymTab$f"))修饰
//留意到FSymTab$a表示start,FSymTab$z表示end,而真正的结构体被存到FSymTab$f段中
//a-z表示一头一尾,这个我猜测应该是可以保证f段是在内存a段之后,z段之前的
//回过头来看上一part,答案似乎呼之欲出,ptr_begin, ptr_end两根指针都可以分别指向表头和表尾了。
//得到两根指针后,被传进finsh_system_function_init()中。
  • finsh_system_function_init
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;
}
//这个函数就是把这两根指针分别赋值给_syscall_table_begin和_syscall_table_end
  • 得到这两根指针,就可以为遍历提供起始节点和结束两个边界信息啦。但是还没结束。。
...
struct finsh_syscall *index;
...
for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index))
...
  • 可以看到还有一个宏定义FINSH_NEXT_SYSCALL()需要探究的,否则条件不足,不知道段之间的空间是否连续的,估计和编译器有关
#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
  • 可以看到,在_MSC_VER,GUNC,x86_64环境下,是需要有一定规则来遍历地址的,但是我在源码中没有找到finsh_syscall_next(index)的实现
  • 所以,那应该空间是连续的了。直接就是index++。
  • ok,这样在rtthread接收到串口消息的时候就可以通过遍历这个地址空间中的name,来识别相应的命令,适配成功,就可以利用结构体的函数指针,调用相应的动作了

最后,在编译后生成的map文件中查找,终于找到了答案。

  • 以下是自己写的demo里的自定义命令,可以看到,所有的rtthread的finsh命令都在一个名为FSymTab段里了
      FSymTab$$Base                          0x0800f52c   Number         0  commandanalyze.o(FSymTab)
    __fsym___cmd_AT                          0x0800f52c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_VERSION                  0x0800f538   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_RESET                    0x0800f544   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_LADDR                    0x0800f550   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_NAME                     0x0800f55c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_PIN                      0x0800f568   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_BAUD                     0x0800f574   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_SLEEP                    0x0800f580   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_ROLE                     0x0800f58c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_INQ                      0x0800f598   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_SHOW                     0x0800f5a4   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_CONN                     0x0800f5b0   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_show_temp                   0x0800f5bc   Data          12  humiture.o(FSymTab)
    __fsym___cmd_tick_now                    0x0800f5c8   Data          12  main.o(FSymTab)
    __fsym___cmd_w_hello_eeprom              0x0800f5d4   Data          12  eeprom.o(FSymTab)
    __fsym___cmd_r_hello_eeprom              0x0800f5e0   Data          12  eeprom.o(FSymTab)
    __fsym_list_mem                          0x0800f5ec   Data          12  mem.o(FSymTab)
    __fsym_pinMode                           0x0800f5f8   Data          12  pin.o(FSymTab)
    __fsym_pinWrite                          0x0800f604   Data          12  pin.o(FSymTab)
    __fsym_pinRead                           0x0800f610   Data          12  pin.o(FSymTab)
    __fsym_hello                             0x0800f61c   Data          12  cmd.o(FSymTab)
    __fsym_version                           0x0800f628   Data          12  cmd.o(FSymTab)
    __fsym___cmd_version                     0x0800f634   Data          12  cmd.o(FSymTab)
    __fsym_list_thread                       0x0800f640   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_thread                 0x0800f64c   Data          12  cmd.o(FSymTab)
    __fsym_list_sem                          0x0800f658   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_sem                    0x0800f664   Data          12  cmd.o(FSymTab)
    __fsym_list_event                        0x0800f670   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_event                  0x0800f67c   Data          12  cmd.o(FSymTab)
    __fsym_list_mutex                        0x0800f688   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mutex                  0x0800f694   Data          12  cmd.o(FSymTab)
    __fsym_list_mailbox                      0x0800f6a0   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mailbox                0x0800f6ac   Data          12  cmd.o(FSymTab)
    __fsym_list_msgqueue                     0x0800f6b8   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_msgqueue               0x0800f6c4   Data          12  cmd.o(FSymTab)
    __fsym_list_memheap                      0x0800f6d0   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_memheap                0x0800f6dc   Data          12  cmd.o(FSymTab)
    __fsym_list_mempool                      0x0800f6e8   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mempool                0x0800f6f4   Data          12  cmd.o(FSymTab)
    __fsym_list_timer                        0x0800f700   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_timer                  0x0800f70c   Data          12  cmd.o(FSymTab)
    __fsym_list_device                       0x0800f718   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_device                 0x0800f724   Data          12  cmd.o(FSymTab)
    __fsym_list                              0x0800f730   Data          12  cmd.o(FSymTab)
    __fsym___cmd_help                        0x0800f73c   Data          12  msh.o(FSymTab)
    FSymTab$$Limit                           0x0800f748   Number         0  msh.o(FSymTab)

总结

  • 又学到了不错的东西
posted @ 2020-09-25 13:52  JoyooO  阅读(3562)  评论(1编辑  收藏  举报