RT-Thread FinSH控制台添加自定义msh命令原理
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 */
#endifsyscall_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原理浅析
RT-Thread FinSH控制台添加自定义msh命令原理相关推荐
- 基于GD32F103C8T6添加RT Thread nano设备框架并添加串口设备(以控制台console( uart0 )为例)
最近没事琢磨了一下使用设备框架的问题.因为将串口注册到设备框架可以应用十分丰富的软件包. 于是就整理了一下手上的工程,重新将工程梳理了一遍. 像这样是十分清爽了,其中RTOS是操作系统源代码 并且学习 ...
- RT Thread之 Uart2 操作
官网连接:https://docs.rt-thread.org/#/rt-thread-version/rt-thread-standard/programming-manual/device/uar ...
- 使用RT Thread设备框架封装一个I2C设备——DS3231
使用RT Thread设备框架封装一个I2C设备--DS3231 前言 ENV配置 I2C测试 将ds3231封装成一个字符设备 结语 前言 学习rt thread的I2C的时候,恰巧手上的板子留了d ...
- RT Thread Studio 配置IIC并读取AS5600角度
RT Thread Studio 配置IIC并读取AS5600角度 一.RT Thread Studio 配置IIC 1.在RT Thread Seting 中开启IIC功能 并保存 一定要保存才能更 ...
- Qt 实现串口终端控制台,适配RT-Thread的FinSH控制台功能(提供qt源码)
开发环境:Window 10 64bit 开发工具:IAR Embedded Workbench 硬件:stm32f103c8t6 RT-Thread Nano 版本包含了 FinSH 组件,我们可以 ...
- rt thread studio使用QBOOT和片外flash实现OTA升级
我们这里要使用单片机外部flash作为OTA的下载分区,外部flash硬件连接关系 PB3-->SPI3_CLK PB4-->SPI3_MISO PB5-->SPI3_MOSI PE ...
- RT Thread根据开发板制作BSP方法
之前一直不懂怎么使用RT Thread的软件包,感谢网上的大神,看了你们的博客后大概了解一些,在此做下记录.用RT Thread软件包需要RT Thread的系统,但是RT Thread和RT Thr ...
- rt thread系统下添加wiznet软件包后,不插网线CPU利用率100%问题
rt thread系统下添加wiznet软件包后如果不插网线的话其他任务运行很卡,使用ps命令发现优先级低的任务很多都超时了 rt thread线程错误码 添加了一个可以查看CPU利用率的软件包CPU ...
- stm32f407单片机rt thread 片外spi flash OTA升级配置示例
参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...
最新文章
- html5--5-15 绘制阴影
- 2017乌鲁木齐区域赛I(带权并查集)
- spring mvc原理_Spring MVC的工作原理,我们来看看其源码实现
- asp.net服务器端对话框控件的简单实现(附源码)
- SqlCommand.ExecuteReader 方法
- 【基础教程】基于matlab生成Word+PPT报告【含Matlab源码 971期】
- adobe animate2022动画制作软件
- html5设计页面背景颜色,网页背景设计全攻略
- 坦白说php源码,qq坦白说新思路解密 附源码
- Linux文件--文件命名规则
- java课程设计动态祝福卡_巧用代码制作动态图文贺卡【平安夜祝福】
- python掷骰子实验代码_Python Tkinter实例——模拟掷骰子
- Android Banner Indicator 轮播图指示器
- Python+Fiddler5带你爬取6000+高清王者荣耀cosplay图
- 阿里实习总结(近期)
- Burnside引理Pólya定理
- Tomcat启动成功访问404:源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的资源表示。
- 汽车使用总结(四)--汽车空调怎么开暖气,汽车暖风开关标志图解
- SQL 语句耗时查询
- 服务器提示位置不可用 拒绝访问,关于解答Win10系统提示位置不可用拒绝访问的完全处理办法...