Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
FinSH
RT-Thread
随笔、FinSH原理浅析
发布于 2023-01-25 19:44:16 浏览:1758
订阅该版
[tocm] ## 前置条件 [FinSH 简介](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh?id=finsh-%e7%ae%80%e4%bb%8b) [RT-Thread 自动初始化机制详解](https://club.rt-thread.org/ask/article/d686458bbba864f4.html) ## 详细分析 一直想弄懂FinSH是怎么扫描内部函数表,并执行相应函数的,看了自动初始化后,终于有了点头绪 ![screenshot_FinSH命令执行流程图.png](https://oss-club.rt-thread.org/uploads/20230125/5f6ed29a6914f4e01f075633a8357950.png.webp) 先展开`MSH_CMD_EXPORT (finsh.h,落后版本可能在finsh_api.h)` 源宏定义 ```c #define MSH_CMD_EXPORT(command, desc) \ FINSH_FUNCTION_EXPORT_CMD(command, __cmd_##command, desc) #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; \ const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")= \ { \ __fsym_##cmd##_name, \ __fsym_##cmd##_desc, \ (syscall_func)&name \ }; ``` 举个例子MSH_CMD_EXPORT(msh_test, this is a msh test) 展开后 ```c FINSH_FUNCTION_EXPORT_CMD(msh_test, __cmd_msh_test, this is a msh test) /*再展开*/ const char __fsym___cmd_msh_test_name[] SECTION(".rodata.name") = "__cmd_msh_test"; const char __fsym___cmd_msh_test_desc[] SECTION(".rodata.name") = "this is a msh test"; const struct finsh_syscall __fsym___cmd_msh_test SECTION("FSymTab")= { __fsym___cmd_msh_test_name,/* "__cmd_msh_test" */ __fsym___cmd_msh_test_desc,/* "this is a msh test" */ (syscall_func)&msh_test }; ``` 再看看`finsh_syscall`和`syscall_func (finsh.h)` ```c /*函数指针*/ 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; ``` 再看看finsh的初始化与线程创建函数`finsh_system_init (shell.c)`,(gcc下)我删减后的(解析都在注释里) ```c int finsh_system_init(void) { rt_err_t result = RT_EOK; rt_thread_t tid; /* GNU GCC Compiler and TI CCS */ extern const int __fsymtab_start; extern const int __fsymtab_end; extern const int __vsymtab_start; extern const int __vsymtab_end; /* 上面四个变量是链接脚本导出的符号,在ROM区,具体如下图 */ /* 初始化全局变量_syscall_table_begin(函数表结构体头)和_syscall_table_end(函数表结构体尾)*/ finsh_system_function_init(&__fsymtab_start, &__fsymtab_end); /* 导出变量,与导出函数类似 */ finsh_system_var_init(&__vsymtab_start, &__vsymtab_end); /* create or set shell structure */ /*shell 主要存放控制台相关,此处仅列举几种 *shell->line[FINSH_CMD_SIZE]:当前行输入 *shell->rx_sem:控制台串口信号量 *shell->device:控制台串口设备 */ shell = (struct finsh_shell *)rt_calloc(1, sizeof(struct finsh_shell)); /*创建finsh线程,当信号量被释放后读取串口数据,存储解析*/ tid = rt_thread_create(FINSH_THREAD_NAME, finsh_thread_entry, RT_NULL, FINSH_THREAD_STACK_SIZE, FINSH_THREAD_PRIORITY, 10); /*初始化信号量,再串口中断中释放*/ rt_sem_init(&(shell->rx_sem), "shrx", 0, 0); rt_thread_startup(tid); return 0; } /*自动初始化*/ INIT_APP_EXPORT(finsh_system_init); ``` 上面提到的`finsh_system_function_init` ```c 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; } ``` 上面提到的`extern const int __fsymtab_start`等变量在链接脚本中的体现 ![screenshot_finsh链接脚本部分.png](https://oss-club.rt-thread.org/uploads/20230125/f0e447ae08f82ad9c1ea4971f15a5d43.png) 再看看finsh线程`finsh_thread_entry (shell.c)`,只看是怎么扫描并执行函数的部分, 同样大量删减后的,解析全在注释里 ```c void finsh_thread_entry(void *parameter) { char ch; rt_kprintf(FINSH_PROMPT);/* 输出msh> */ while (1) { ch = finsh_getchar();/*阻塞等待信号量释放*/ .../*大量解析命令部分,不看*/ msh_exec(shell->line, shell->line_position);/*执行当前行*/ ... } } ``` 展开`msh_exec (msh.c)`精简版 ```c int msh_exec(char *cmd, rt_size_t length) { int cmd_ret; /* Exec sequence: * 1. built-in command * 2. module(if enabled) * 3. chdir to the directry(if possible) */ _msh_exec_cmd(cmd, length, &cmd_ret); return cmd_ret; } ``` 展开`_msh_exec_cmd (msh.c)`完整版 ```c static int _msh_exec_cmd(char *cmd, rt_size_t length, int *retp) { int argc; rt_size_t cmd0_size = 0; cmd_function_t cmd_func; char *argv[RT_FINSH_ARG_MAX]; RT_ASSERT(cmd); RT_ASSERT(retp); /* find the size of first command */ while ((cmd[cmd0_size] != ' ' && cmd[cmd0_size] != '\t') && cmd0_size < length) cmd0_size ++; if (cmd0_size == 0) return -RT_ERROR; /* 到前文提到的_syscall_table_begin与和_syscall_table_end之前查找到函数地址 */ cmd_func = msh_get_cmd(cmd, cmd0_size); if (cmd_func == RT_NULL) return -RT_ERROR; /* 下面是含参部分,没有研究 */ /* split arguments */ memset(argv, 0x00, sizeof(argv)); argc = msh_split(cmd, length, argv); if (argc == 0) return -RT_ERROR; /* 最终执行函数 */ /* exec this command */ *retp = cmd_func(argc, argv); return 0; } ``` 终于到了最后,展开`msh_get_cmd (msh.c)` ```c static cmd_function_t msh_get_cmd(char *cmd, int size) { struct finsh_syscall *index; cmd_function_t cmd_func = RT_NULL; for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index)) /* 每次自增一个finsh_syscall结构体 */ { /* MSH_CMD_EXPORT导出的函数名,前六个字符是"__cmd_" */ if (strncmp(index->name, "__cmd_", 6) != 0) continue; if (strncmp(&index->name[6], cmd, size) == 0 && index->name[6 + size] == '\0') { /* 把前面宏定义保存的函数名去掉__cmd_与控制台读取到的函数名比较 * 对了就返回函数地址,自此大功告成! */ cmd_func = (cmd_function_t)index->func; break; } } return cmd_func; } ``` 大功告成!(嘘,擦汗) ## 总结 - `MSH_CMD_EXPORT(command, desc)`将函数地址,函数名,函数描述保存到`finsh_syscall`结构体 - 再将每个`finsh_syscall`结构体通过`RT_SECTION("FSymTab")`强制保存到ROM空间的`FSymTab`段,并在链接脚本导出`FSymTab`的起始和结束地址`__fsymtab_start和__fsymtab_end`并赋值给`struct finsh_syscall *_syscall_table_begin, *_syscall_table_end` - 以供FinSH线程`finsh_thread_entry`根据控制台输入的函数名扫描函数表(保存在`_syscall_table_begin`与`_syscall_table_end`之间)并执行相应函数 ## 补流程图 (mermaid论坛好像不支持,直接贴图了) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230202/32a41cc374eca0d2975142ba6ca278d1.png.webp) ## 题外话 现在知道了我们导出的函数都是在`finsh_thread_entry`线程执行的,那么如果导出的函数比较大的话FinSH的线程栈就要设大点啦。 还有既然导出的函数会在`finsh_thread_entry`执行,那么我们就可以拦截控制台的串口输入来在控制台实现最入门的串口回显了(大部分都是用MSH导出含参函数,但就是折腾一下) ```c void msh_test() { rt_device_t uart=rt_device_find(RT_CONSOLE_DEVICE_NAME); rt_kprintf("enter msh test\n"); rt_size_t rsize=0; uint8_t ch=0; while(1){ /*串口接收中断时先把数据写入接收FIFO, *再释放信号量,所以这里是从接收FIFO读 *没有影响,而finsh线程被阻塞在这里, *所以信号量也不会被finsh持有 *总而言之,finsh线程被阻塞在这里(msh_test函数)了, *不会响应任何命令*/ rsize = rt_device_read(uart, 0, &ch, 1); if(rsize){ if(ch=='q') { rt_kprintf("leave msh test\n"); break; } rt_device_write(uart,0,&ch,1); rt_kprintf("\nrsize:%d\n", rsize); } rt_thread_mdelay(100); } } MSH_CMD_EXPORT(msh_test, block finsh) ``` ![screenshot_串口回显1.png](https://oss-club.rt-thread.org/uploads/20230125/1375041f5bab8168e07f449bba8b16d8.png) 屏蔽`rt_kprintf("\nrsize:%d\n", rsize);`,经典回显(笑) ![screenshot_串口回显2.png](https://oss-club.rt-thread.org/uploads/20230125/8d655727cb83fa0b0da7d5f2d80e8c18.png.webp)
9
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
初级踩坑仔
这家伙很懒,什么也没写!
文章
6
回答
4
被采纳
0
关注TA
发私信
相关文章
1
RT-THREAD shell无反应呢?
2
RT-thread2.0beta下用类似linux风格MSH,参数如何输入和导出
3
rt-thread finsh windows下的那个终端软件叫什么来着
4
板子上只有485接口,能把FINSH改造成485的么?
5
finsh最大字符问题
6
finsh命令个数是不是有限制啊
7
finsh支持转义字符吗
8
不用finsh如何知道堆栈使用量
9
强烈建议 RT-Thread下finsh原理深入分析
10
finsh输入命令全部返回null node
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
DMA
USB
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
keil_MDK
SFUD
msh
ulog
C++_cpp
MicroPython
本月问答贡献
三世执戟
5
个答案
1
次被采纳
KunYi
4
个答案
1
次被采纳
RTT_逍遥
3
个答案
1
次被采纳
xiaorui
1
个答案
1
次被采纳
JonasWen
1
个答案
1
次被采纳
本月文章贡献
出出啊
1
篇文章
3
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
3
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部