搜索
bottom↓
回复: 20

RT-Thread下finsh原理浅析

  [复制链接]

出0入0汤圆

发表于 2013-6-21 13:20:42 | 显示全部楼层 |阅读模式

一直想探寻rtt的finsh原理,最近终于下定决心跑一跑这段代码,若有不对之处还望多多指针。我的QQ 277153007

​RT-Thread的Finsh Shell接口实际上是一个线程,入口在shell.c,入口函数为
  1. void finsh_thread_entry(void* parameter)
复制代码
该线程是典型的初始化---死循环结构
  1. {
  2.      init();
  3.      while(1)
  4.      {
  5.          ......
  6.      }
  7. }
复制代码
先初始化此shell的语法分析器parser

  1. finsh_init(&shell->parser);
复制代码
shell是一个指向finsh_shell结构的变量,finsh_shell定义于shell.h 可以看做rt_device_t的派生类
  1. struct finsh_shell
  2. {
  3.     struct rt_semaphore rx_sem;
  4.     enum input_stat stat;
  5.     rt_uint8_t echo_mode:1;
  6.     rt_uint8_t use_history:1;
  7. #ifdef FINSH_USING_HISTORY
  8.     rt_uint16_t current_history;
  9.     rt_uint16_t history_count;
  10.     char cmd_history[FINSH_HISTORY_LINES][FINSH_CMD_SIZE];
  11. #endif
  12.     struct finsh_parser parser;
  13.     char line[FINSH_CMD_SIZE];
  14.     rt_uint8_t line_position;
  15.     rt_device_t device;
  16. };
复制代码
其中最重要的是一个finsh_parser的数据结构,这便是语法分析器
  1. struct finsh_parser
  2. {
  3.     u_char* parser_string;
  4.     struct finsh_token token;
  5.     struct finsh_node* root;
  6. };
复制代码
其中 parser_string用于指向需要处理的字符串  即在命令行中输入的字符串
      token表示一个词法单元处理器
      root是一个指向finsh_token的指针,用于指向后面语法树的根节点
PS    token的意思是 词法单元 比如 "12+14"  ‘12’是一个token   ‘+’是一个token   ‘14’是一个token (更多内容参见《编译原理》)

让我们再回到finsh线程入口函数 finsh_thread_entry
  1. finsh_init(&shell->parser);
复制代码
此初始化函数调用的结果是此parser(语法分析器)所占用的内存清0

接下来 while死循环中
  1. if (rt_sem_take(&shell->rx_sem,RT_WAITING_FOREVER) != RT_EOK) continue;
复制代码
即永久地等待1个信号量,正常情况下当键盘有键按下时释放此信号量,然后此线程得到此信号量使程序继续运行。
  1. while (rt_device_read(shell->device, 0, &ch, 1) == 1)
复制代码
用ch储存键盘按下的键值
  1. #ifdef FINSH_USING_HISTORY
  2. if (finsh_handle_history(shell, ch) == RT_TRUE) continue;
  3. #endif
复制代码
如果开启了宏定义FINSH_USING_HISTORY,则表示输入的前几条命令会被记忆起来,存储深度的见shell
本文假设FINSH_USING_HISTORY未被开启

此线程会根据输入的字符不同而进入后面的几个if或者else if分支,只有当按下一些诸如回车等特殊按键时,才会进入那些分支;而当键盘按下普通字符时,执行的是以下程序:将输入字符依次存入shell->line数组中  并回显到屏幕上。
  1. shell->line[shell->line_position] = ch;
  2. ch = 0;
  3. if (shell->echo_mode)
  4.    rt_kprintf("%c", shell->line[shell->line_position]);
  5. shell->line_position ++;
  6. shell->use_history = 0;
复制代码
当输完命令行,最后敲击回车,便会执行以下语句
  1. /* handle end of line, break */
  2. if (ch == '\r' || ch == '\n')
  3. {            
  4.     /* change to ';' and break */
  5.     shell->line[shell->line_position] = ';';
  6.            
  7.     if (shell->line_position != 0)
  8.         finsh_run_line(&shell->parser, shell->line);        
  9.     else rt_kprintf("\n");

  10.     rt_kprintf(FINSH_PROMPT);   
  11.     memset(shell->line, 0, sizeof(shell->line));            
  12.     shell->line_position = 0;
  13.     break;
  14. }
复制代码
上面的代码会在输入的字符串最后一个位置添一个‘;’  然后运行 finsh_run_line(&shell->parser, shell->line),函数原型如下
  1. 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)运行语法分析器
  1. void finsh_parser_run(struct finsh_parser* self, const u_char* string)
  2. {
  3.     enum finsh_token_type token;
  4.     struct finsh_node *node;
  5.     node = NULL;
  6.     /* init parser */
  7.     self->parser_string = (u_char*)string;
  8.     /* init token */
  9.     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
  1. struct finsh_token
  2. {
  3.     char eof;
  4.     char replay;

  5.     int position;
  6.     u_char current_token;

  7.     union {
  8.         char char_value;
  9.         int int_value;
  10.         long long_value;
  11.     } value;
  12.     u_char string[128];

  13.     u_char* line;
  14. };
复制代码
eof 用于记录词法单元是否结束,若置位则表示已经解析到该词法单元的最后1个字符
replay表示正在解析词法单元以后是否需要重新解析
position记录正在解析词法单元中的哪个位置
current用于记录当前解析词法单元的类型
value用于记录当前解析词法单元的值(当类型为数值类型时)
string用来存储id型的词法单元

回到函数finsh_parser_run
  1. /* get next token */
  2.     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
以此类推

下面具体分析一下此流程
由宏定义
  1. #define next_token(token, lex)    (token) = finsh_token_token(lex)
复制代码
得知实际调用的函数是finsh_token_token
  1. enum finsh_token_type finsh_token_token(struct finsh_token* self)
  2. {
  3.     if ( self->replay )    self->replay = 0;
  4.     else token_run(self);

  5.     return (enum finsh_token_type)self->current_token;
  6. }
复制代码
如果词法分析器的replay已经置位   需要再次解析  则清空此位  返回当前词法单元的类型
  而若replay为0  则表示可以继续往后处理   即调用token_run
在token_run里  判别词法单元的种类 并更新current_token

接下来的的while循环   便是逐个提取词法单元   并将词法单元作为节点   构造语法树
  1. while (token != finsh_token_type_eof && token != finsh_token_type_bad)
  2.     {
  3.         switch (token)
  4.         {
  5.         case finsh_token_type_identifier:
  6.             /* process expr_statement */
  7.             finsh_token_replay(&(self->token));

  8.             if (self->root != NULL)
  9.             {
  10.                 finsh_node_sibling(node) = proc_expr_statement(self);
  11.                 if (finsh_node_sibling(node) != NULL)
  12.                     node = finsh_node_sibling(node);
  13.             }
  14.             else
  15.             {
  16.                 node = proc_expr_statement(self);
  17.                 self->root = node;
  18.             }
  19.             break;

  20.         default:
  21.             if (is_base_type(token) || token == finsh_token_type_unsigned)
  22.             {
  23.                 /* variable decl */
  24.                 finsh_token_replay(&(self->token));   

  25.                 if (self->root != NULL)
  26.                 {
  27.                     finsh_node_sibling(node) = proc_variable_decl(self);
  28.                     if (finsh_node_sibling(node) != NULL)
  29.                         node = finsh_node_sibling(node);
  30.                 }
  31.                 else
  32.                 {
  33.                     node = proc_variable_decl(self);
  34.                     self->root = node;
  35.                 }
  36.             }
  37.             else
  38.             {
  39.                 /* process expr_statement */
  40.                 finsh_token_replay(&(self->token));

  41.                 if (self->root != NULL)
  42.                 {
  43.                     finsh_node_sibling(node) = proc_expr_statement(self);
  44.                     if (finsh_node_sibling(node) != NULL)
  45.                         node = finsh_node_sibling(node);
  46.                     else next_token(token, &(self->token));
  47.                 }
  48.                 else
  49.                 {
  50.                     node = proc_expr_statement(self);
  51.                     self->root = node;
  52.                 }
  53.             }
  54.             break;
  55.         }
  56.         /* get next token */
  57.         next_token(token, &(self->token));
  58.     }
复制代码
注意第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的第二项工作  "编译语法树,生成中间代码并将其写入虚拟机所指定的内存“
  1. int finsh_compiler_run(struct finsh_node* node)
  2. {
  3.     struct finsh_node* sibling;

  4.     /* type check */
  5.     finsh_type_check(node, FINSH_NODE_VALUE);

  6.     /* clean text segment and vm stack */
  7.     memset(&text_segment[0], 0, sizeof(text_segment));
  8.     memset(&finsh_vm_stack[0], 0, sizeof(finsh_vm_stack[0]));

  9.     /* reset compile stack pointer and pc */
  10.     finsh_compile_sp = &finsh_vm_stack[0];
  11.     finsh_compile_pc = &text_segment[0];

  12.     /* compile node */
  13.     sibling = node;
  14.     while (sibling != NULL)
  15.     {
  16.         struct finsh_node* current_node;
  17.         current_node = sibling;

  18.         /* get sibling node */
  19.         sibling = current_node->sibling;

  20.         /* clean sibling node */
  21.         current_node->sibling = NULL;
  22.         finsh_compile(current_node);

  23.         /* pop current value */
  24.         if (sibling != NULL) finsh_code_byte(FINSH_OP_POP);
  25.     }

  26.     return 0;
  27. }
复制代码
上面第8行到第14行  清除虚拟机代码段和运行的栈所处的内存  初始化其SP指针和PC指针。while循环中最关键的函数是finsh_compile,
这个函数从root节点开始递归地深入到语法树的叶子节点进行编译。

下面截取finsh_compile函数(文件finsh_compiler.c中)的一小部分稍加解释
  1. static int finsh_compile(struct finsh_node* node)
  2. {
  3.     if (node != NULL)
  4.     {
  5.         /* compile child node */
  6.         if (finsh_node_child(node) != NULL)
  7.             finsh_compile(finsh_node_child(node));

  8.         /* compile current node */
  9.         switch (node->node_type)
  10.         {
  11.         case FINSH_NODE_ID:
  12.             {
  13.                 /* identifier::syscall */
  14.                 if (node->idtype & FINSH_IDTYPE_SYSCALL)
  15.                 {
  16.                     /* load address */
  17.                     finsh_code_byte(FINSH_OP_LD_DWORD);
  18.                     finsh_code_dword((long)node->id.syscall->func);
  19.                 }
复制代码
第10行的switch语句  根据结点类型的不同  进入不同的分支。
此函数会用到finsh_compiler.c中3个常用的宏定义
  1. #define finsh_code_byte(x)    do { *finsh_compile_pc = (x); finsh_compile_pc ++; } while(0)
  2. #define finsh_code_word(x)    do { FINSH_SET16(finsh_compile_pc, x); finsh_compile_pc +=2; } while(0)
  3. #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 ……

如此   完成语法树的编译,中间代码被存入虚拟机所占用内存,余下的工作便是运行虚拟机
  1. /* run virtual machine */
  2.     if (finsh_errno() == 0)
  3.     {
  4.         char ch;
  5.         finsh_vm_run();

  6.         ch = (unsigned char)finsh_stack_bottom();
  7.         if (ch > 0x20 && ch < 0x7e)
  8.         {
  9.             rt_kprintf("\t'%c', %d, 0x%08x\n",
  10.                 (unsigned char)finsh_stack_bottom(),
  11.                 (unsigned int)finsh_stack_bottom(),
  12.                 (unsigned int)finsh_stack_bottom());
  13.         }
  14.         else
  15.         {
  16.             rt_kprintf("\t%d, 0x%08x\n",
  17.                 (unsigned int)finsh_stack_bottom(),
  18.                 (unsigned int)finsh_stack_bottom());
  19.         }
  20.     }
复制代码
在函数finsh_vm_run中
  1. void finsh_vm_run()
  2. {
  3.     u_char op;

  4.     /* if want to disassemble the bytecode, please define VM_DISASSEMBLE */
  5. #ifdef VM_DISASSEMBLE
  6.     void finsh_disassemble();
  7.     finsh_disassemble();
  8. #endif

  9.     /* set sp(stack pointer) to the beginning of stack */
  10.     finsh_sp = &finsh_vm_stack[0];

  11.     /* set pc to the beginning of text segment */
  12.     finsh_pc = &text_segment[0];

  13.     while ((finsh_pc - &text_segment[0] >= 0) &&
  14.         (finsh_pc - &text_segment[0] < FINSH_TEXT_MAX))
  15.     {
  16.         /* get op */
  17.         op = *finsh_pc++;

  18.         /* call op function */
  19.         op_table[op]();
  20.     }
  21. }
复制代码
​设定好finsh_sp和finsh_pc后  便从虚拟机中一条一条的读取指令。


出20入70汤圆

发表于 2013-6-21 13:48:34 | 显示全部楼层
标记,楼主的解说不错!

出0入0汤圆

发表于 2013-6-21 15:00:12 | 显示全部楼层
受益了。

出0入0汤圆

发表于 2013-6-22 22:54:16 | 显示全部楼层
这个帖分析得很好,不过还缺少语法分析这些代码的详细分析。不过这些也可以从finsh shell的代码中获得:

  1. /* 这个是分析好的语法树情况
  2. * the structure of abstract syntax tree:
  3. * root____________
  4. * |               \
  5. * child__        sibling__
  6. * |      \       |        \
  7. * child sibling  child   sibling
  8. *                          ...
  9. */

  10. /* 规约推导范式 */
  11. /* 这个是推导起点
  12. start -> statement_expr | decl_variable
  13. */

  14. /* 函数或变量的声明
  15. process for function and variable declaration.
  16. decl_variable -> type declaration_list ';'
  17. declarator_list -> declarator_list ',' declarator
  18.         | declarator
  19. declarator -> identifier
  20.         | identifier ASSIGN expr_assign
  21. */

  22. /* 类型分析
  23. type -> type_prefix type_basic | type_basic
  24. type_prefix -> UNSIGNED
  25. type_basic -> VOID
  26.         | CHAR
  27.         | SHORT
  28.         | INT
  29.         | STRING
  30. */

  31. /*
  32. identifier -> IDENTIFIER
  33. */

  34. /* 表达式推导
  35. statement_expr -> ';'
  36.         | expr ';'
  37. */

  38. /*
  39. expr -> expr_assign
  40. */

  41. /*
  42. expr_assign -> expr_inclusive_or
  43.         | expr_unary ASSIGN expr_assign
  44. */

  45. /*
  46. expr_inclusive_or -> expr_exclusive_or
  47.         | expr_inclusive_or '|' expr_exclusive_or
  48. */

  49. /*
  50. expr_exclusive_or -> expr_and
  51.         | expr_exclusive '^' expr_and
  52. */

  53. /*
  54. expr_and -> expr_shift
  55.         | expr_and '&' expr_shift
  56. */

  57. /*
  58. expr_shift -> expr_additive
  59.         | expr_shift '<<' expr_additive
  60.         | expr_shift '>>' expr_additive
  61. */

  62. /*
  63. expr_additive -> expr_multiplicative
  64.         | expr_additive SUB expr_multiplicative
  65.         | expr_additive ADD expr_multiplicative
  66. */

  67. /*
  68. expr_multiplicative -> expr_cast
  69.         | expr_multiplicative '*' expr_cast
  70.         | expr_multiplicative '/' expr_cast
  71.         | expr_multiplicative '%' expr_cast
  72. */

  73. /*
  74. 20060313, add recast parse
  75. expr_cast -> expr_unary
  76.         | '(' type ')' expr_cast
  77. */

  78. /*
  79. 20050921, add '*' and '&'
  80. expr_unary -> expr_postfix
  81.         | ADD expr_cast
  82.         | INC expr_cast
  83.         | SUB expr_cast
  84.         | DEC expr_cast
  85.         | '~' expr_cast
  86.         | '*' expr_cast
  87.         | '&' expr_cast
  88. */

  89. /*
  90. expr_postfix -> expr_primary
  91.         | expr_postfix INC
  92.         | expr_postfix DEC
  93.         | expr_postfix '(' param_list ')'
  94. */

  95. /*
  96. expr_primary -> literal
  97.         | '(' expr ')'
  98.         | identifier
  99. */

  100. /*
  101. param_list -> empty
  102.         | expr_assign
  103.         | param_list ',' expr_assign
  104. */

复制代码
这个范式是完全按照C语言的表达式来进行的,显然还有优化的空间,但是为了保持它的可读性,所以没有做进一步的优化。

出0入0汤圆

发表于 2013-10-12 11:00:49 | 显示全部楼层
mark

出0入0汤圆

发表于 2013-10-12 11:08:30 | 显示全部楼层
这种贴自要顶 建议加精

出0入0汤圆

发表于 2013-12-5 22:56:33 | 显示全部楼层
受教良多,感谢!

出0入0汤圆

发表于 2013-12-6 09:01:55 | 显示全部楼层
不错。

出0入0汤圆

发表于 2013-12-6 09:44:34 | 显示全部楼层
RT-Thread下finsh原理浅析 MARK 好好学习

出0入0汤圆

发表于 2013-12-6 09:56:45 | 显示全部楼层
好东西啊!

出0入0汤圆

发表于 2013-12-6 10:09:06 来自手机 | 显示全部楼层
好文!mark下~

出0入0汤圆

发表于 2014-1-1 11:14:28 | 显示全部楼层
mark。好东东

出0入0汤圆

发表于 2014-1-2 17:08:59 | 显示全部楼层
1.2.0的分支加入了msh功能,在裁剪finsh shell的前提下,可以把相应的flash & sram空间都节省下来

最近在看nRF51822,可以把msh塞进去了

出0入0汤圆

发表于 2014-1-9 13:07:40 | 显示全部楼层

出0入0汤圆

发表于 2014-1-9 14:56:06 | 显示全部楼层
ffxz 发表于 2014-1-2 17:08
1.2.0的分支加入了msh功能,在裁剪finsh shell的前提下,可以把相应的flash & sram空间都节省下来

最近在 ...

懒问一个问题  有没有例程把MCU虚拟化,完全执行 finsh脚本程序

出0入0汤圆

发表于 2014-3-6 22:38:45 | 显示全部楼层
LZ分析得很不错啊!

出0入0汤圆

发表于 2014-7-10 13:48:09 | 显示全部楼层
mark.........

出0入0汤圆

发表于 2016-2-18 13:11:43 | 显示全部楼层
mark,finsh分析。感谢楼主!

出0入0汤圆

发表于 2016-2-23 14:52:40 | 显示全部楼层
好文,必须mark

出0入0汤圆

发表于 2017-1-10 08:55:16 | 显示全部楼层
厉害了 谢谢分享

出0入0汤圆

发表于 2017-5-31 13:26:17 | 显示全部楼层
好吧,很复杂
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-19 00:36

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表