# rtt_myshell **Repository Path**: ElectronSpark/rtt_myshell ## Basic Information - **Project Name**: rtt_myshell - **Description**: 本人的 rtt 新手村任务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-10-21 - **Last Updated**: 2021-10-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 ​ 这是一个功能很原始的 shell ,基于 rt-thread 实现,运行在 ART-PI 开发板上,默认通过串口 1 与用户进行交互。我对其的实现主要参考了 rt-thread 自带的 finsh 终端实现。 该 shell 目前主要实现了以下的功能: - 能够通过 **scons** 工具构建其所在项目,并且能通过 **menuconfig** 工具对其中的一些参数进行自定义设置。 - 支持 *help* 指令查看当前所有支持的命令。 - 能够向所输入的命令传递限定个数内的参数,每个参数都通过空格隔开(暂时不支持通过引号令含有空格的参数作为整体)。 - 用户每执行一个命令后,被键入的整行命令若不是重复出现,就会被加入历史记录中。用户可以通过键盘上下键调出历史记录中相应的条目。历史记录中存放的命令个数上限可自定义,在加入新的历史记录时,若已经存在的历史记录超过设定上限,则最老的一条历史记录会被抛弃。 - 执行命令的历史记录会以文件的方式存入 sd 卡或者 flash 中,在掉电重启后会被再次载入。 - 用户可以通过键入左右键改变光标位置,且可以通过退格键修改当前命令中错误输入的字符。 - 提供了可供在工程编译时静态添加新命令的接口,和程序运行时动态增、删命令的接口。 # 原理浅析 ### 总体结构 该 shell 主要由如下几个部分组成: - 主线程:在文件系统中加载完上次掉电前的历史记录后,一次一个字节地不断循环向串口请求按键输入,并根据接收到的字符做出相应的动作。 - 历史记录:当用户按下回车键执行命令时,被执行的命令若符合条件则会被加入到历史记录中。历史记录中存放的命令条数有限,当达到上限时,最早加入的条目会被抛弃。历史记录还会作为文件存放在 sd 卡或者 flash 中,在 shell 下次上电启动后会尝试加载之前的历史记录。 - 符号表:通过预先提供的 API ,用户可以在编译前或系统运行时向 shell 添加或删除命令。每条命令都有其对应的介绍、入口函数以及唯一的名称。 - 解释器:主线程会在用户按下回车键后将用户输入的字符串传递给解释器,解释器会根据空格键将传入字符串分割为多个参数,并从符号表中查找首个参数所对应的入口函数,调用该函数并传入分割后的参数。 所有关键的源码文件都位于工程根目录下的 ```rt-thread\components\myshell``` 目录中。 ### 初始化 ​ 该 shell 的初始化是在 main 函数中调用的函数 ```myshell_system_init()``` 中执行的,大体上进行了如下的操作。 ```mermaid graph TB main(main 函数) --调用-->s1[复位光标信息和命令输入缓存] -->s2[初始化串口] -->s3[初始化符号表] -->s4[初始化历史记录] -->s5[创建并激活 shell 运行的主线程] -->return(返回) ``` ​ 如果一切顺利, shell 的主线程在此时已经被创建,在进行了更多的初始化工作后,该线程就会不断地请求读取串口的输入,并根据得到的输入执行相应的动作。如果串口暂时没有接收到任何输入,该线程则会睡眠等待串口的输入。 shell 主线程自创建后的动作大致如下: ![shell主线程](./docs/shell主线程.jpg) 可打印字符包括大小写字母、数字、空格、标点符号等,不包括缩进与回车。处理过程大致如下: ![主线程处理可打印字符](./docs/主线程处理可打印字符.jpg) 而功能字符包括回车、缩进、退格、方向键。下面是这针对这几个按键的动作: - 回车键:将缓冲区内的指令写入历史记录,并传递给解释器进行执行,最后刷新缓冲区。 - 退格键:如果光标的左边有字符,删除该字符并将后面的字符串前移。 - 左右方向键:光标所在处往对应方向有字符的话就移动光标位置。 - 上下方向键:获取上一条、下一条历史记录。 - 缩进:暂时没有任何动作。 ### 解析和执行 ​ 用户输入的指令首先会被存入 shell 的缓冲区,在用户按下回车键后缓冲区中的内容就会被传递给解释器解释并执行。解释器的工作主要是以空格和 ```'\0'``` 符号作为分隔符,将传入解释器的字符串分割成一个或多个参数,并在指令符号表里查找分割得到的第一个参数所对应的入口函数地址,调用该函数并传入所有参数。如果传入解释器的字符串为空,或者全部为空格,解释器将直接返回;若分割得到的指令没有在指令符号表中找到,也会直接返回。但目前解释器的功能很原始,不支持管道等特殊操作,也不会特殊处理引号。 解释器的工作流程大致如下: ![解析命令的大致流程](./docs/解析命令的大致流程.jpg) ### 符号表 ​ 该 shell 中有两个符号表,分别用于储存 shell 可调用所有指令和可查看的所有变量。符号表的主体为一个 RT-Thread 的内核双链表,其中每个节点都储存了符号的字符串本身、符号的描述信息和指向该符号所关联的对象的内存地址。因为目前只完整实现了用于储存指令的符号表,所以这里只讨论指令符号表。 ​ 用户可以以静态或者动态的方式向符号表中添加新的符号,两种方式的具体细节如下: **静态添加:** ``` 在整个工程进行链接时,会开辟出一个特殊的段,用户通过该 shell 提供的特定宏向这个特殊的段中加入包含其自定义符号关键信息的节点对象。当 shell启动时,会遍历这个段,拷贝其中的节点并将其加入 shell 的符号表链表中。 静态添加的符号掉电后不会消失,且在如果某个符号被动态的删除或以任何方式修改,其任何改变都不会被保存。 ``` **动态添加和删除:** ``` 在 shell 运行时,应用程序同样可以通过 API 在符号表中增加或删除符号,而这时候的操作即为动态的添加和删除。动态添加的符号不会被保存,掉电后就会消失。 ```