Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
RT-Thread一般讨论
RT-Thread下finsh原理浅析
发布于 2013-06-21 17:54:44 浏览:3757
订阅该版
一直想探寻rtt的finsh原理,最近终于下定决心跑一跑这段代码,若有不对之处还望多多指针。我的QQ 277153007 ? ?RT-Thread的Finsh Shell接口实际上是一个线程,入口在shell.c,入口函数为 ```void finsh_thread_entry(void* parameter)```该线程是典型的初始化---死循环结构 ```{ init(); while(1) { ...... } }``` 先初始化此shell的语法分析器parser ```finsh_init(&shell->parser);``` shell是一个指向finsh_shell结构的变量,finsh_shell定义于shell.h 可以看做rt_device_t的派生类 ```struct finsh_shell { struct rt_semaphore rx_sem; enum input_stat stat; rt_uint8_t echo_mode:1; rt_uint8_t use_history:1; #ifdef FINSH_USING_HISTORY rt_uint16_t current_history; rt_uint16_t history_count; char cmd_history[FINSH_HISTORY_LINES][FINSH_CMD_SIZE]; #endif struct finsh_parser parser; char line[FINSH_CMD_SIZE]; rt_uint8_t line_position; rt_device_t device; };``` 其中最重要的是一个finsh_parser的数据结构,这便是语法分析器 ```struct finsh_parser { u_char* parser_string; struct finsh_token token; struct finsh_node* root; };``` 其中 parser_string用于指向需要处理的字符串 即在命令行中输入的字符串 token表示一个词法单元处理器 root是一个指向finsh_token的指针,用于指向后面语法树的根节点 PS token的意思是 词法单元 比如 "12+14" ‘12’是一个token ‘+’是一个token ‘14’是一个token (更多内容参见《编译原理》) 让我们再回到finsh线程入口函数 finsh_thread_entry ```finsh_init(&shell->parser);``` 此初始化函数调用的结果是此parser(语法分析器)所占用的内存清0 接下来 while死循环中 ```if (rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER) != RT_EOK) continue;``` 即永久地等待1个信号量,正常情况下当键盘有键按下时释放此信号量,然后此线程得到此信号量使程序继续运行。```while (rt_device_read(shell->device, 0, &ch, 1) == 1)``` 用ch储存键盘按下的键值 ```#ifdef FINSH_USING_HISTORY if (finsh_handle_history(shell, ch) == RT_TRUE) continue; #endif``` 如果开启了宏定义FINSH_USING_HISTORY,则表示输入的前几条命令会被记忆起来,存储深度的见shell 本文假设FINSH_USING_HISTORY未被开启 此线程会根据输入的字符不同而进入后面的几个if或者else if分支,只有当按下一些诸如回车等特殊按键时,才会进入那些分支;而当键盘按下普通字符时,执行的是以下程序:将输入字符依次存入shell->line数组中 并回显到屏幕上。 ```shell->line[shell->line_position] = ch; ch = 0; if (shell->echo_mode) rt_kprintf("%c", shell->line[shell->line_position]); shell->line_position ++; shell->use_history = 0; ``` 当输完命令行,最后敲击回车,便会执行以下语句 ```/* handle end of line, break */ if (ch == ' ' || ch == ' ') { /* change to ';' and break */ shell->line[shell->line_position] = ';'; if (shell->line_position != 0) finsh_run_line(&shell->parser, shell->line); else rt_kprintf(" "); rt_kprintf(FINSH_PROMPT); memset(shell->line, 0, sizeof(shell->line)); shell->line_position = 0; break; }``` 上面的代码会在输入的字符串最后一个位置添一个‘;’ 然后运行 finsh_run_line(&shell->parser, shell->line),函数原型如下 ```void finsh_run_line(struct finsh_parser* parser, const char *line)``` 函数finsh_run_line主要完成三项工作: 1.分析输入的字符串,将其分割成一个一个的词法单元并构造成树形结构,每个节点为1个词法单元 2.编译语法树,生成中间代码并将其写入虚拟机所指定的内存 3.运行虚拟机的指令 首先来看第1项工作 在finsh_run_line中 调用finsh_parser_run(parser, (unsigned char*)line)运行语法分析器 ```void finsh_parser_run(struct finsh_parser* self, const u_char* string) { enum finsh_token_type token; struct finsh_node *node; node = NULL; /* init parser */ self->parser_string = (u_char*)string; /* init token */ finsh_token_init(&(self->token), self->parser_string);``` 该函数定义一个finsh_token_type类型的token 具体类型的种类 可以阅览文件finsh_token.h,那里面涵盖了finsh-shell系统所有词法单元的类型 接着定义一个指向finsh_node类型的指针node。 第7行 将输入的字符串复制到 self->parser_string所指向的地址中。 第9行 初始化词法单元分析器所占用的内存空间 并使该token->line指向输入的字符串 介绍一下词法分析器的数据结构,它被定义在finsh.h ```struct finsh_token { char eof; char replay; int position; u_char current_token; union { char char_value; int int_value; long long_value; } value; u_char string[128]; u_char* line; };``` eof 用于记录词法单元是否结束,若置位则表示已经解析到该词法单元的最后1个字符 replay表示正在解析词法单元以后是否需要重新解析 position记录正在解析词法单元中的哪个位置 current用于记录当前解析词法单元的类型 value用于记录当前解析词法单元的值(当类型为数值类型时) string用来存储id型的词法单元 回到函数finsh_parser_run ```/* get next token */ next_token(token, &(self->token));``` 这句话的意思是将获取下一个词法单元,并用token记录该词法单元的类型 举个例子 比如我在命令行输入的是 abc+12 运行 next_token(token, &(self->token))的结果就是 得到一个词法单元abc 它的类型是identifier,即token=finsh_token_type_identifier 再运行 next_token(token, &(self->token))的结果是 得到一个词法单元 + 它的类型是 加号类型 即token =finsh_token_type_add 以此类推 下面具体分析一下此流程 由宏定义 ```#define next_token(token, lex) (token) = finsh_token_token(lex)```得知实际调用的函数是finsh_token_token ```enum finsh_token_type finsh_token_token(struct finsh_token* self) { if ( self->replay ) self->replay = 0; else token_run(self); return (enum finsh_token_type)self->current_token; }``` 如果词法分析器的replay已经置位 需要再次解析 则清空此位 返回当前词法单元的类型 而若replay为0 则表示可以继续往后处理 即调用token_run 在token_run里 判别词法单元的种类 并更新current_token 接下来的的while循环 便是逐个提取词法单元 并将词法单元作为节点 构造语法树 ```while (token != finsh_token_type_eof && token != finsh_token_type_bad) { switch (token) { case finsh_token_type_identifier: /* process expr_statement */ finsh_token_replay(&(self->token)); if (self->root != NULL) { finsh_node_sibling(node) = proc_expr_statement(self); if (finsh_node_sibling(node) != NULL) node = finsh_node_sibling(node); } else { node = proc_expr_statement(self); self->root = node; } break; default: if (is_base_type(token) || token == finsh_token_type_unsigned) { /* variable decl */ finsh_token_replay(&(self->token)); if (self->root != NULL) { finsh_node_sibling(node) = proc_variable_decl(self); if (finsh_node_sibling(node) != NULL) node = finsh_node_sibling(node); } else { node = proc_variable_decl(self); self->root = node; } } else { /* process expr_statement */ finsh_token_replay(&(self->token)); if (self->root != NULL) { finsh_node_sibling(node) = proc_expr_statement(self); if (finsh_node_sibling(node) != NULL) node = finsh_node_sibling(node); else next_token(token, &(self->token)); } else { node = proc_expr_statement(self); self->root = node; } } break; } /* get next token */ next_token(token, &(self->token)); }``` 注意第54行的函数 proc_expr_statement 当我们追踪程序的时候 会发现它会接着调用proc_assign_expr, proc_inclusive_or_expr,proc_exclusive_or_expr,proc_and_expr,proc_shift_expr,proc_additive_expr,proc_multiplicative_expr,proc_cast_expr,proc_unary_expr,proc_postfix_expr。 其实这个过程便是决定树形结构层次的过程,越往后的运算实际上优先级设定的越高 举个例子 finsh>> 8+5*2 从上面的调用顺序可以看到proc_additive_expr在proc_multiplicative_expr前面,表示乘法优先级更高,这也符合我们的习惯。 到此 一棵完整的语法树就被构造出来了,下面来说函数finsh_run_line的第二项工作 "编译语法树,生成中间代码并将其写入虚拟机所指定的内存. ```int finsh_compiler_run(struct finsh_node* node) { struct finsh_node* sibling; /* type check */ finsh_type_check(node, FINSH_NODE_VALUE); /* clean text segment and vm stack */ memset(&text_segment[0], 0, sizeof(text_segment)); memset(&finsh_vm_stack[0], 0, sizeof(finsh_vm_stack[0])); /* reset compile stack pointer and pc */ finsh_compile_sp = &finsh_vm_stack[0]; finsh_compile_pc = &text_segment[0]; /* compile node */ sibling = node; while (sibling != NULL) { struct finsh_node* current_node; current_node = sibling; /* get sibling node */ sibling = current_node->sibling; /* clean sibling node */ current_node->sibling = NULL; finsh_compile(current_node); /* pop current value */ if (sibling != NULL) finsh_code_byte(FINSH_OP_POP); } return 0; }``` 上面第8行到第14行 清除虚拟机代码段和运行的栈所处的内存 初始化其SP指针和PC指针。while循环中最关键的函数是finsh_compile, 这个函数从root节点开始递归地深入到语法树的叶子节点进行编译。 下面截取finsh_compile函数(文件finsh_compiler.c中)的一小部分稍加解释 ```static int finsh_compile(struct finsh_node* node) { if (node != NULL) { /* compile child node */ if (finsh_node_child(node) != NULL) finsh_compile(finsh_node_child(node)); /* compile current node */ switch (node->node_type) { case FINSH_NODE_ID: { /* identifier::syscall */ if (node->idtype & FINSH_IDTYPE_SYSCALL) { /* load address */ finsh_code_byte(FINSH_OP_LD_DWORD); finsh_code_dword((long)node->id.syscall->func); }``` 第10行的switch语句 根据结点类型的不同 进入不同的分支。 此函数会用到finsh_compiler.c中3个常用的宏定义 ```#define finsh_code_byte(x) do { *finsh_compile_pc = (x); finsh_compile_pc ++; } while(0) #define finsh_code_word(x) do { FINSH_SET16(finsh_compile_pc, x); finsh_compile_pc +=2; } while(0) #define finsh_code_dword(x) do { FINSH_SET32(finsh_compile_pc, x); finsh_compile_pc +=4; } while(0)``` 这几个宏定义的作用是将x放入到finsh_compile_pc所指向的内存,finsh_compile_pc再向后移动。唯一不同的是移动的幅度,分别为字节,字和双字 举个例子 假设 节点是个‘系统函数’节点 而 finsh_compile_pc=0x20000000 所调用的系统函数地址是0x30000000 那么 当执行完 finsh_code_byte(FINSH_OP_LD_DWORD) finsh_code_dword((long)node->id.syscall->func)后 从0x20000000开始的内存会变为 24 00 00 00 00 30 …… 如此 完成语法树的编译,中间代码被存入虚拟机所占用内存,余下的工作便是运行虚拟机 ```/* run virtual machine */ if (finsh_errno() == 0) { char ch; finsh_vm_run(); ch = (unsigned char)finsh_stack_bottom(); if (ch > 0x20 && ch < 0x7e) { rt_kprintf(" '%c', %d, 0x%08x ", (unsigned char)finsh_stack_bottom(), (unsigned int)finsh_stack_bottom(), (unsigned int)finsh_stack_bottom()); } else { rt_kprintf(" %d, 0x%08x ", (unsigned int)finsh_stack_bottom(), (unsigned int)finsh_stack_bottom()); } }``` 在函数finsh_vm_run中 ```void finsh_vm_run() { u_char op; /* if want to disassemble the bytecode, please define VM_DISASSEMBLE */ #ifdef VM_DISASSEMBLE void finsh_disassemble(); finsh_disassemble(); #endif /* set sp(stack pointer) to the beginning of stack */ finsh_sp = &finsh_vm_stack[0]; /* set pc to the beginning of text segment */ finsh_pc = &text_segment[0]; while ((finsh_pc - &text_segment[0] >= 0) && (finsh_pc - &text_segment[0] < FINSH_TEXT_MAX)) { /* get op */ op = *finsh_pc++; /* call op function */ op_table[op](); } }``` ?设定好finsh_sp和finsh_pc后 便从虚拟机中一条一条的读取指令。 ? ? ?
查看更多
5
个回答
默认排序
按发布时间排序
bernard
2013-06-21
这家伙很懒,什么也没写!
这篇文章很赞,基本上是目前见到的分析finsh shell最深入的文章了。
rtthsl
2013-06-21
这家伙很懒,什么也没写!
赞,虽然对于编译和虚拟机部分不是很理解,但对finsh shell执行一条命令的过程已大概明白
grissiom
2013-06-22
这家伙很懒,什么也没写!
很赞的解析! 在 proc_*_expr 的时候有没有优化的可能?貌似现在是嵌套调用的,栈的需求量很大……
jeffwei
2013-06-25
这家伙很懒,什么也没写!
好帖子,finsh真的是调试利器,这几天正在想用PC直接发送finsh命令来控制STM32板子,代替自定义的通讯协议了
撰写答案
登录
注册新账号
关注者
0
被浏览
3.8k
关于作者
cmjs
这家伙很懒,什么也没写!
提问
3
回答
4
被采纳
0
关注TA
发私信
相关问题
1
有关动态模块加载的一篇论文
2
最近的调程序总结
3
晕掉了,这么久都不见layer2的踪影啊
4
继续K9ii的历程
5
[GUI相关] FreeType 2
6
[GUI相关]嵌入式系统中文输入法的设计
7
20081101 RT-Thread开发者聚会总结
8
嵌入式系统基础
9
linux2.4.19在at91rm9200 上的寄存器设置
10
[转]基于嵌入式Linux的通用触摸屏校准程序
推荐文章
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组件
最新文章
1
env中添加lvgl软件包后,keil编译包--c99错误
2
【NXP-MCXA153】 定时器驱动移植
3
GD32F450 看门狗驱动适配
4
【NXP-MCXA153】看门狗驱动移植
5
RT-Thread Studio V2.2.9 Release Note
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
freemodbus
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
编译报错
Debug
rt_mq_消息队列_msg_queue
SFUD
keil_MDK
msh
ulog
C++_cpp
MicroPython
本月问答贡献
踩姑娘的小蘑菇
7
个答案
2
次被采纳
a1012112796
18
个答案
1
次被采纳
红枫
5
个答案
1
次被采纳
Ryan_CW
5
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
本月文章贡献
YZRD
3
篇文章
6
次点赞
catcatbing
3
篇文章
6
次点赞
lizimu
2
篇文章
10
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部