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)
总结
- 又学到了不错的东西