注意:關於前一篇文章中提到的《BLE4.0低功耗藍牙協議總結》等文章在公帳號發送"get"就能得到下載連結。
這裡選取第18章進行分享
第18章 finsh shell
使用finsh會明顯感覺其他RTOS都有點太單調了。這個東西是個啥東西呢?說白了就是開發者可以通過串口直接讓內核或者應用層已經寫好了的函數直接運行後返回結果到控制臺。抄一部分官網的簡介吧!以及後面的移植也是參考官網進行的。
18.1 finsh簡介finsh是RT-Thread的命令行外殼(shell),提供一套供用戶在命令行的操作接口,主要用於調試或查看系統信息。finsh支持兩種模式:
1. C語言解釋器模式,為行文方便稱之為c-style;
2. 傳統命令行模式,此模式又稱為msh(moduleshell)。
C語言表達式解釋模式下,finsh能夠解析執行大部分C語言的表達式,並使用類似C語言的函數調用方式訪問系統中的函數及全局變量,此外它也能夠通過命令行方式創建變量。
在msh模式下,finsh運行方式類似於dos/bash等傳統shell。
它的工作模式是用戶由設備埠輸入命令行,finsh 通過對設備輸入的讀取,解析輸入內容,然後自動掃描內部段(內部函數表),尋找對應函數名,執行函數後輸出回應。finsh的數據流結構如圖18-1所示。
圖18-1 finsh的數據流結構
18.2 finsh源碼獲取當前ZJ-SDK文件夾中是沒有finsh源碼的,通用從官方源碼拷貝到ZJ-SDK中,拷貝前的RT_THREAD目錄結構和之後的目錄結構對比圖如圖18-2所示。
圖18-2 添加finsh源碼到ZJ_SDK
18.3 移植finsh移植finsh的一般步驟就是添加源碼和頭文件到工程,然後開啟相關宏,但是開啟哪些宏呢?這裡需要一些參考。
18.3.1 移植參考在RT-Thread官網的文檔中有finsh的專門介紹,官網網址為:https://www.rt-thread.org/document/site/。官方finsh介紹如圖18-3所示。【部分文章在公帳號「Bluetooth-BLE」發送"get"就能得到下載連結】
圖18-3 官方finsh介紹
在finsh的最後介紹了宏選項,將原文介紹賦值如下表18-1:
表18-1 官方finsh宏介紹
finsh有一些宏定義可以簡單配置。
#define RT_USING_FINSH
此宏定義在rtconfig.h中,用於在RT-Thread中打開finsh,並將其作為shell。
#define FINSH_USING_SYMTAB
#define FINSH_USING_DESCRIPTION
此宏定義在rtconfig.h中。打開FINSH_USING_SYMTAB可以在finsh中使用符號表,打開FINSH_USING_DESCRIPTION後需要給每個finsh的符號添加一段字符串描述。這兩個宏一般都需要打開。
#define FINSH_USING_HISTORY
此宏定義在rtconfig.h中,打開後可以在finsh中使用方向鍵(上下)回溯歷史指令。
#define FINSH_USING_MSH
此宏定義在rtconfig.h中,打開後finsh將支持傳統shell模式。
#define FINSH_USING_MSH_ONLY
此宏定義在rtconfig.h中,打開後finsh僅支持msh模式。
如果打開了FINSH_USING_MSH而沒有打開FINSH_USING_MSH_ONLY,finsh同時支持兩種c-style模式與msh模式,但是默認進入c-style模式,執行 msh()即可切換到msh模式,在msh模式下執行 exit後即退回到c-style模式。
#define DFS_USING_WORKDIR
此宏定義在rtconfig.h中,它實際上是DFS組件的宏,但由於它與finsh有一定關係,因此在這裡也介紹一下。打開此宏後finsh可以支持工作目錄。當使用msh時,建議打開此宏。
#define FINSH_USING_AUTH
此宏定義在rtconfig.h中,打開則開啟權限驗證功能。系統在啟動後,只有權限驗證(目前僅支持密碼驗證)通過,才會開啟finsh功能,提升系統輸入的安全性。
#define FINSH_DEFAULT_PASSWORD "rtthread"
此宏定義在rtconfig.h中,設置finsh在密碼驗證模式下的默認密碼。密碼長度大於等於FINSH_PASSWORD_MIN(默認6),小於等於FINSH_PASSWORD_MAX(默認RT_NAME_MAX)。
先將工程007.uart複製粘貼為008.finsh_shell工程,根據官方介紹,將008.finsh_shell工程中的rtconfig中修改finsh宏如代碼清單18-1所示。
代碼清單18-1 rtconfig.h中修改finsh宏
/* RT-Thread config file */
#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__
.
// <h>Finsh Configuration
// <e>Using finsh shell
// <i>Using finsh shell
#define RT_USING_FINSH 1
#if RT_USING_FINSH == 0
#undef RT_USING_FINSH
#endif
// <e>Using msh support c-style and msh
// <i>Using msh support c-style and msh
#define FINSH_USING_MSH 1
#if FINSH_USING_MSH == 0
#undef FINSH_USING_MSH
#endif
// </e>
// <e>Using msh only supper msh
// <i>Using msh only supper msh
#define FINSH_USING_MSH_ONLY 0
#if FINSH_USING_MSH_ONLY == 0
#undef FINSH_USING_MSH_ONLY
#endif
// </e>
// <o>the priority of finsh thread <1-7>
// <i>the priority of finsh thread
// <i>Default: 6
#define __FINSH_THREAD_PRIORITY 5
#define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1)
// <o>the stack of finsh thread <1-4096>
// <i>the stack of finsh thread
// <i>Default: 4096 (4096Byte)
#define FINSH_THREAD_STACK_SIZE 2048
// <e>Using hestory in finsh shell
// <i>Using hestory in finsh shell
#define FINSH_USING_HISTORY 1
#if FINSH_USING_HISTORY == 0
#undef FINSH_USING_HISTORY
#endif
// <o>the history lines of finsh thread <1-32>
// <i>the history lines of finsh thread
// <i>Default: 5
#define FINSH_HISTORY_LINES 5
// </e>
// <e>Using symbol table in finsh shell
// <i>Using symbol table in finsh shell
#define FINSH_USING_SYMTAB 1
#if FINSH_USING_SYMTAB == 0
#undef FINSH_USING_SYMTAB
#endif
// <e>Using description in finsh shell
// <i>Using description table in finsh shell
#define FINSH_USING_DESCRIPTION 1
#if FINSH_USING_DESCRIPTION == 0
#undef FINSH_USING_DESCRIPTION
#endif
// </e>
// </e>
// <e>Using POSIX(Portable Operating System Interface of UNIX) in finsh shell
// <i>Using POSIX(Portable Operating System Interface of UNIX) in finsh shell
#define RT_USING_POSIX 0
#if RT_USING_POSIX == 0
#undef RT_USING_POSIX
#endif
// </e>
// <o>the cmd size of finsh thread <1-80>
// <i>the cmd size of finsh thread
#define FINSH_CMD_SIZE 80
// </e>
// </h>
.
// <<< end of configuration section >>>
#endif
這裡不解釋,對照官方介紹非常容易懂。
18.3.2 添加源文件將finsh文件夾下所有源文件添加到工程,將頭文件包含到工程,007.uart工程和008.finsh_shell工程對比如圖18-4所示。
圖18-4 添加finsh源碼工程
18.3.3 編譯排錯上面完成宏設置和源碼頭文件加入工程後,進行編譯時出現如圖18-5所示的錯誤。
圖18-5 finsh編譯沒有定義的符號
從錯誤提示可以看到,是變量沒有定義,從命名來看這就是一個段的開始和結束,段修改在14.2.3節中已經實踐過一次,這裡有同樣是在.xml文件中進行修改。那麼這個段是什麼樣的屬性呢?可以參考官方源碼bsp下的某個板子的工程,這裡隨便打開一個STM32的板子下的.ld文件,截取的部分如代碼清單18-2所示。【部分文章在公帳號「Bluetooth-BLE」發送"get"就能得到下載連結】
代碼清單18-2 RT-Thread官方STM32下.ld文件部分段
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);
/* section information for initial. */
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
從上面可以看到符號__fsymtab_start、__fsymtab_end、__vsymtab_start和__vsymtab_end。所以可以在.xml文件中添加代碼清單18-3所示的代碼。
代碼清單18-3 .xml中的finsh相關段
<!DOCTYPE Linker_Placement_File>
.
<ProgramSection alignment="4" keep="Yes" load="Yes" name=".rt_fsymtab" inputsections="*(FSymTab)" address_symbol="__fsymtab_start" end_symbol="__fsymtab_end" />
<ProgramSection alignment="4" keep="Yes" load="Yes" name=".rt_vsymtab" inputsections="*(VSymTab)" address_symbol="__vsymtab_start" end_symbol="__vsymtab_end" />
.
</Root>
上面修改後,再次進行編譯時,就可以看到存儲分布中有對應的段出現了,如圖18-6所示。
圖18-6 添加finsh後的存儲分布
18.4 體驗finsh將main函數中的循環列印屏蔽,然後將工程編譯下載到開發板,打開SecureCRT工具,波特率115200,復位開發板就能看到版本信息了,並以finsh >在控制臺顯示。按tab鍵就能看到能用的函數了。這裡先玩一下系統自帶的命令,然後自己添加一個函數到finsh中,調用看看效果。
finsh支持TAB鍵自動補全,當沒有輸入任何字符時按下TAB鍵將會列印當前所有的符號,包括當前導出的所有命令和變量。若已經輸入部分字符時按下TAB鍵,將會查找匹配的命令,並自動補全,並可以繼續輸入,多次補全。如果是msh狀態,輸入一個字符後,不僅僅會按系統導出函數命令方式自動補全,也會按照文件系統的當前目錄下的文件名進行補全。
上下鍵可以回溯最近輸入的歷史命令,左右鍵可移動光標,退格鍵刪除,回溯的歷史行數可通過修改rtconfig.h中宏FINSH_HISTORY_LINES控制,最多為32個歷史回溯。
目前finsh的按鍵處理還比較薄弱。不支持CTRL+C等控制鍵中斷命令,也不支持DELETE鍵刪除。
18.4.1 c-style模式這種模式下調用必須採用調用函數的方式,也就是後面必須跟()。如圖18-7所示的c-style模式下命令的操作。list()和直接按tab鍵的效果一樣。
圖18-7 finsh c-style模式命令操作
18.4.2 msh模式從c-style模式進入msh模式的命令是msh(),退出msh進入c-style模式通過在msh模式下輸入exit命令。圖18-8為msh模式下的命令操作。
圖18-7 finsh msh模式命令操作
18.5 使用finsh調試用戶代碼18.5.1 finsh_shell.c分析在008.finsh_shell\applications新建文件夾finsh_test並在文件夾下創建finsh_test.c文件,將finsh_test.c文件添加到008.finsh_shell工程applications文件夾下,並在finsh_shell.c中添加入代碼清單18-4所示代碼。
代碼清單18-4 finsh_test.c源碼
#include <finsh.h>
#ifdef FINSH_USING_MSH
#include "msh.h"
#endif
/*cstyle*/
int zj_t1(void) (1)
{
#ifdef FINSH_USING_MSH
if(msh_is_used()) (1)-1
{
rt_kprintf("Welcome to use ZJ-SDK! this is finsh msh test\n");
}
else
#endif
{
rt_kprintf("Welcome to use ZJ-SDK! this is finsh c-tyle test\n");
}
return 0;
}
FINSH_FUNCTION_EXPORT(zj_t1, this is finsh c-tyle test(zj_t1() will work)) (1)-2
FINSH_FUNCTION_EXPORT_ALIAS(zj_t1, zj_ft1, this is finsh c-tyle test(new name zj_ft1,zj_ft 1() will work)) (1)-3
FINSH_FUNCTION_EXPORT_ALIAS(zj_t1, __cmd_zj_t1, this is finsh msh test) (1)-4
int var;
int zj_t2(int a) (2)
{
#ifdef FINSH_USING_MSH
if(msh_is_used())
{
rt_kprintf("Welcome to use ZJ-SDK! this num is %d, this is finsh msh test with args\n", a++);
}
else
#endif
{
rt_kprintf("Welcome to use ZJ-SDK! this num is %d(%d), this is finsh c-tyle test with args\n", a++, var);
(2)-1
}
var = a; (2)-2
return a; (2)-3
}
FINSH_FUNCTION_EXPORT(zj_t2, this is finsh c-tyle test with args(zj_t2(number) will work)) (2)-4
FINSH_VAR_EXPORT(var, finsh_type_int, just a var for zj_t2) (2)-5
FINSH_FUNCTION_EXPORT_ALIAS(zj_t2, zj_ft2, this is finsh c-tyle test with args(new name zj_t2,zj_ft2(number) will work)) (2)-6
FINSH_FUNCTION_EXPORT_ALIAS(zj_t2, __cmd_zj_t2, this is finsh msh test with args(zj_t2 number will work))
/*msh*/
int zj_t3(int argc, char** argv) (3)
{
rt_kprintf("Welcome to use ZJ-SDK! this is finsh msh test with args\n");
for(rt_uint8_t i=0;i<argc;i++)
{
rt_kprintf("argv[%d]: %s\n",i, argv[i]); (3)-2
}
return 0;
}
MSH_CMD_EXPORT(zj_t3, this is finsh msh test with args(zj_t3 argv1 argv2...)); (3)-2
共有3個測試函數,zj_t1和zj_t2函數支持c-style和msh模式,zj_t3支持msh模式。每個函數的功能很簡單,這裡主要介紹幾個宏,這些宏是如何將相關信息放入到規定的段的。
代碼清單18-4(1):zj_t1的功能是根據c-style或者msh列印字符串,沒有參數傳入。
代碼清單18-4(1)-1:FINSH_FUNCTION_EXPORT是finsh 的函數導出宏,作用是導出成c-style 的命令。宏展開如代碼清單18-5:
代碼清單18-5 FINSH_FUNCTION_EXPORT展開
#define FINSH_FUNCTION_EXPORT(name, desc) \
FINSH_FUNCTION_EXPORT_CMD(name, name, 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; \
RT_USED const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")= \
{ \
__fsym_##cmd##_name, \
__fsym_##cmd##_desc, \
(syscall_func)&name \
};
代碼清單14-7已經分析了INIT_EXPORT宏源碼,這裡的宏和其類似。代碼清單18-4(1)解析如代碼清單18-6:
代碼清單18-6 FINSH_FUNCTION_EXPORT解析
FINSH_FUNCTION_EXPORT(zj_t1, this is finsh c-tyle test(zj_t1() will work))
----> FINSH_FUNCTION_EXPORT_CMD(zj_t1, zj_t1, this is finsh c-tyle test(zj_t1() will work))
---->const char __fsym_ zj_t1_name[] SECTION(".rodata.name") = " zj_t1"; \
const char __fsym_ zj_t1_desc[] SECTION(".rodata.name") =" this is finsh c-tyle test(zj_t1() will work)"; \
RT_USED const struct finsh_syscall __fsym_ zj_t1 SECTION("FSymTab")= \
{ \
__fsym_ zj_t1_name, \ (1)
__fsym_ zj_t1_desc, \ (2)
(syscall_func)& zj_t1 \ (3)
};
代碼清單18-6(1)和(2):存放的只是指針而已,實際的字符串是存放欄位".rodata.name"中的。
代碼清單18-6(3):實際上finsh段中僅僅存放了需要調用的函數指針以及兩個字符串指針。
代碼清單18-4(1)-3和(1)-4:FINSH_FUNCTION_EXPORT_ALIAS是finsh 的函數導出宏,作用是導出成c-style 或msh的命令並將函數重新命名,為什麼同一個宏可以導出c-style和msh命令呢?實際上兩者的差別是函數命令在實際存放時,msh的命令名字上會多出__cmd_的前綴,當執行帶__cmd_前綴命令時,它會被特殊對待,只能當成msh命令使用,所以FINSH_FUNCTION_EXPORT_ALIAS也是導出msh模式的一種方式。
那麼為什麼需要重命名呢?因為FINSH的函數名字長度有一定限制,它由finsh.h中的宏定義FINSH_NAME_MAX控制,默認是16位元組。意味著finsh命令長度不會超過16位元組。這裡有個潛在的問題。當一個函數名長度超過FINSH_NAME_MAX時,使用FINSH_FUNCTION_EXPORT導出這個函數到命令表中後,在finsh符號表中看到完整的函數名,但是完整輸入執行會出現null node錯誤。這是因為雖然顯示了完整的函數名,但是實際上finsh中卻保存了前16位元組作為命令,過多的輸入會導致無法正確找到命令,這時就可以使用FINSH_FUNCTION_EXPORT_ALIAS來對導出的命令進行重命名。
代碼清單18-4(2):zj_t2的功能列印函數入口參數,並將值賦值給變量var。
代碼清單18-4(2)-5:FINSH_VAR_EXPORT的是finsh 的變量導出宏,作用是導出成c-style 的變量,這裡不再展開此宏。
代碼清單18-4(3):zj_t3將導出msh模式命令。當使用msh模式時,finsh不支持C表達式,因此只能添加(函數)命令,並不能向c-style模式下動態創建變量。msh模式添加命令僅支持兩種宏方式添加命令:一張是前面提到的FINSH_FUNCTION_EXPORT_ALIAS(name, alias, desc);宏;另一個是MSH_CMD_EXPORT(command,desc);宏。
代碼清單18-4(3)-2:使用宏MSH_CMD_EXPORT導出msh命令。這裡注意,RT-Thread在最初的msh設計是按照主函數(intmain(int argc, char** argv))方式進行的,所以其命令行參數傳遞風格也和main函數完全一致。當對命令行參數進行完整的校驗時,就可以確保參數的合法性,並對非法的參數提供出相應的錯誤信息出來。所以msh模式下調用zj_t2(x)命令,不會像c-style模式下一樣輸出正確結果的。這裡使用main函數方式中的參數描述如下:
• argc - 反映的是總計有多少個命令行參數(也包含命令行自身);
• argv - 反映的是命令行參數組,且都是以字符形式存儲;
而這個MSH_CMD_EXPORT宏其實最終還是調用FINSH_FUNCTION_EXPORT_CMD(command,__cmd_##command, desc)宏,即將函數重命名為以__cmd_開頭的命令,所以這個宏只是換湯不換藥。
18.5.2 下載調試將008.finsh_shell工程進行編譯下載到開發板,按tab鍵會看到如圖18-9所示c-style模式下自定義的命令出現。
圖18-9 c-style模式自定義命令測試
在finsh c-style模式下使用msh()命令就能進入msh模式了,再按tab鍵可以看到如圖18-10所示的msh自定義命令調試。
圖18-110 msh模式自定義命令測試
公帳號
QQ群
論壇
原文連結即為論壇
不要臉的人才要打賞!!
---->wo jiu bu yao lian