日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

RT-Thread FinSH控制台添加自定义msh命令原理

發布時間:2024/1/23 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 RT-Thread FinSH控制台添加自定义msh命令原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

FinSH 是 RT-Thread 的命令行組件,提供一套供用戶在命令行調用的操作接口,主要用于調試或查看系統信息。它可以使用串口 / 以太網 / USB 等與 PC 機進行通信。

FinSH 提供了多個宏接口來導出自定義命令,導出的命令可以直接在 FinSH 中執行。

自定義的 msh 命令,可以在 msh 模式下被運行,將一個命令導出到 msh 模式可以使用如下宏接口:

MSH_CMD_EXPORT(name, desc);

示例如下:

void hellort(void) {rt_kprintf("hello RT-Thread!\n"); }MSH_CMD_EXPORT(hellort , say hello to RT-Thread);

在命令行里輸入hellort\r\n就會觸發這個函數。

先探究MSH_CMD_EXPORT這個宏定義的實現。

1. #define MSH_CMD_EXPORT(command, desc) FINSH_FUNCTION_EXPORT_CMD(command, __cmd_##command, desc) //嵌套一層宏定義,把兩個參數變成3個參數,command用##與__cmd_連接起來,那么它的第二參數就變成__cmd_command

MSH_CMD_EXPORT(hellort , say hello to RT-Thread)展開:
FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)

2. #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 \}; 3. #define SECTION(x) __attribute__((section(x))) #define RT_UNUSED __attribute__((unused)) #define RT_USED __attribute__((used)) #define ALIGN(n) __attribute__((aligned(n))) FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)再展開: const char __fsym___cmd_hellort_name[] __attribute__((section(".rodata.name"))) = "__cmd_hellort"; const char __fsym___cmd_hellort_desc[] __attribute__((section(".rodata.name"))) = "say hello to RT-Thread"; __attribute__((used)) const struct finsh_syscall __fsym___cmd_hellort __attribute__((section("FSymTab")))={ __fsym___cmd_hellort_name, __fsym___cmd_hellort_desc, (syscall_func)&hellort};

上述代碼定義了兩個const char字符數組,分別保存了函數名和描述。
然后定義了一個const struct finsh_syscall類型的結構體并且初始化了,這個結構體原型看下面的代碼:
三個成員分別指向函數名字符串,描述字符串,和函數的首地址。

4.<finch_api.h> 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 */ #endifsyscall_func func; /* the function address of system call */ }; extern struct finsh_syscall *_syscall_table_begin, *_syscall_table_end;<symbol.c> #ifdef FINSH_USING_SYMTAB struct finsh_syscall *_syscall_table_begin = NULL; struct finsh_syscall *_syscall_table_end = NULL; struct finsh_sysvar *_sysvar_table_begin = NULL; struct finsh_sysvar *_sysvar_table_end = NULL; #else

要了解上面的內容需要了解__attribute__((used))和__attribute__((section))的用法:
【__attribute__編譯屬性】
編譯器的關鍵字 __attribute__ 用來指定變量或結構位域的特殊屬性。關鍵字后的

雙括弧中的內容是屬性說明。下面是目前支持的變量屬性:

? address (addr)

? aligned (alignment)

? boot

? deprecated

? fillupper

? far

? mode (mode)

? near

? noload

? packed

? persistent

? reverse (alignment)

? section (“section-name”)

? secure

? sfr (address)

? space (space)

? transparent_union

? unordered

? unused

? weak

__attribute__的section子項的使用格式為:

__attribute__((section(“section_name”)))

其作用是將作用的函數或數據放入指定名為"section_name"輸入段。

這里還要注意一下兩個概念:輸入段和輸出段

輸入段和輸出段是相對于要生成最終的elf或binary時的Link過程說的,Link過程的輸入大都是由源代碼編繹生成的目標文件.o,那么這些.o文件中包含的段相對link過程來說就是輸入段,而Link的輸出一般是可執行文件elf或庫等,這些輸出文件中也包含有段,這些輸出文件中的段就叫做輸出段。輸入段和輸出段本來沒有什么必然的聯系,是互相獨立,只是在Link過程中,Link程序會根據一定的規則(這些規則其實來源于Link Script),將不同的輸入段重新組合到不同的輸出段中,即使是段的名字,輸入段和輸出段可以完全不同。

其用法舉例如下:

int var __attribute__((section(".xdata"))) = 0;

這樣定義的變量var將被放入名為.xdata的輸入段,(注意:__attribute__這種用法中的括號一個也不能少。)

static int __attribute__((section(".xinit"))) functionA(void) { ..... }

這個例子將使函數functionA被放入名叫.xinit的輸入段。

需要著重注意的是,__attribute__的section屬性只指定對象的輸入段,它并不能影響所指定對象最終會放在可執行文件的什么段。

__attribute__((used))
unused:表示該函數或變量可能不使用,這個屬性可以避免編譯器產生警告信息。
used: 向編譯器說明這段代碼有用,即使在沒有用到的情況下編譯器也不會警告。
防止編譯的時候由于沒有加used導致變量被編譯器給優化掉。

回到正題

FINSH_FUNCTION_EXPORT_CMD(hellort , __cmd_hellort , say hello to RT-Thread)再展開: const char __fsym___cmd_hellort_name[] __attribute__((section(".rodata.name"))) = "__cmd_hellort"; const char __fsym___cmd_hellort_desc[] __attribute__((section(".rodata.name"))) = "say hello to RT-Thread"; __attribute__((used)) const struct finsh_syscall __fsym___cmd_hellort __attribute__((section("FSymTab")))={ __fsym___cmd_hellort_name, __fsym___cmd_hellort_desc, (syscall_func)&hellort};

一共用到三個__attribute__((section)),編譯工程后查看.map文件,找到這三個保存的地方:
__fsym___cmd_hellort_name[]大小是14字節,__fsym___cmd_hellort_desc[] 大小是 23字節,這里看地址都對上了。

__fsym___cmd_hellort_name 0x08008626 Data 14 main.o(.rodata.name)__fsym___cmd_hellort_desc 0x08008634 Data 23 main.o(.rodata.name)__fsym_list_mem_name 0x0800864b Data 9 mem.o(.rodata.name)

所有的rtthread的finsh命令都在一個名為FSymTab段里:
第一個__fsym___cmd_hellort就是我們自定義的命令。由于struct finsh_syscall大小是12字節(3個指針),所以0x08008ad8到0x08008ae4是間隔了12字節。

FSymTab$$Base 0x08008ad8 Number 0 main.o(FSymTab)__fsym___cmd_hellort 0x08008ad8 Data 12 main.o(FSymTab)__fsym_list_mem 0x08008ae4 Data 12 mem.o(FSymTab)__fsym_pinMode 0x08008af0 Data 12 pin.o(FSymTab)__fsym_pinWrite 0x08008afc Data 12 pin.o(FSymTab)__fsym_pinRead 0x08008b08 Data 12 pin.o(FSymTab)__fsym_hello 0x08008b14 Data 12 cmd.o(FSymTab)__fsym_version 0x08008b20 Data 12 cmd.o(FSymTab)__fsym___cmd_version 0x08008b2c Data 12 cmd.o(FSymTab)__fsym_list_thread 0x08008b38 Data 12 cmd.o(FSymTab)__fsym___cmd_list_thread 0x08008b44 Data 12 cmd.o(FSymTab)__fsym_list_sem 0x08008b50 Data 12 cmd.o(FSymTab)__fsym___cmd_list_sem 0x08008b5c Data 12 cmd.o(FSymTab)__fsym_list_event 0x08008b68 Data 12 cmd.o(FSymTab)__fsym___cmd_list_event 0x08008b74 Data 12 cmd.o(FSymTab)__fsym_list_mutex 0x08008b80 Data 12 cmd.o(FSymTab)__fsym___cmd_list_mutex 0x08008b8c Data 12 cmd.o(FSymTab)__fsym_list_mailbox 0x08008b98 Data 12 cmd.o(FSymTab)__fsym___cmd_list_mailbox 0x08008ba4 Data 12 cmd.o(FSymTab)__fsym_list_msgqueue 0x08008bb0 Data 12 cmd.o(FSymTab)__fsym___cmd_list_msgqueue 0x08008bbc Data 12 cmd.o(FSymTab)__fsym_list_mempool 0x08008bc8 Data 12 cmd.o(FSymTab)__fsym___cmd_list_mempool 0x08008bd4 Data 12 cmd.o(FSymTab)__fsym_list_timer 0x08008be0 Data 12 cmd.o(FSymTab)__fsym___cmd_list_timer 0x08008bec Data 12 cmd.o(FSymTab)__fsym_list_device 0x08008bf8 Data 12 cmd.o(FSymTab)__fsym___cmd_list_device 0x08008c04 Data 12 cmd.o(FSymTab)__fsym_list 0x08008c10 Data 12 cmd.o(FSymTab)__fsym___cmd_help 0x08008c1c Data 12 msh.o(FSymTab)__fsym___cmd_ps 0x08008c28 Data 12 msh_cmd.o(FSymTab)__fsym___cmd_time 0x08008c34 Data 12 msh_cmd.o(FSymTab)__fsym___cmd_free 0x08008c40 Data 12 msh_cmd.o(FSymTab)__fsym___cmd_reboot 0x08008c4c Data 12 board.o(FSymTab)FSymTab$$Limit 0x08008c58 Number 0 board.o(FSymTab)

上面我們知道了所有的命令在一個名為FSymTab段里,然后看輸入命令時在哪里實現了遍歷查表從而執行對應的函數。

5.<shell.c> #ifdef FINSH_USING_SYMTAB #if defined(__CC_ARM) || defined(__CLANG_ARM) /* ARM C Compiler */extern const int FSymTab$$Base;extern const int FSymTab$$Limit;extern const int VSymTab$$Base;extern const int VSymTab$$Limit;finsh_system_function_init(&FSymTab$$Base, &FSymTab$$Limit);6. struct finsh_syscall *_syscall_table_begin = NULL; struct finsh_syscall *_syscall_table_end = NULL; 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; }

finsh_system_function_init實現了將表頭和表尾賦值給 _syscall_table_begin 和 _syscall_table_end。
然后就可以通過_syscall_table_begin 和 _syscall_table_end 去遍歷;

void msh_auto_complete(char *prefix) {struct finsh_syscall *index;//省略/* checks in internal command */{for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index)){/* skip finsh shell function */if (strncmp(index->name, "__cmd_", 6) != 0) continue;cmd_name = (const char *) &index->name[6];if (strncmp(prefix, cmd_name, strlen(prefix)) == 0){if (min_length == 0){/* set name_ptr */name_ptr = cmd_name;/* set initial length */min_length = strlen(name_ptr);}length = str_common(name_ptr, cmd_name);if (length < min_length)min_length = length;rt_kprintf("%s\n", cmd_name);}}}//省略 }

上面msh_auto_complete函數就是實現了遍歷命令;可以看到for循環中還有一個FINSH_NEXT_SYSCALL(index)
由于不同編譯器,對這個表的處理方式不同,因此需要代碼以對應不同的遍歷方式兼容不同編譯器。
宏的代碼如下:

#if defined(_MSC_VER) || (defined(__GNUC__) && defined(__x86_64__)) struct finsh_syscall* finsh_syscall_next(struct finsh_syscall* call); struct finsh_sysvar* finsh_sysvar_next(struct finsh_sysvar* call); #define FINSH_NEXT_SYSCALL(index) index=finsh_syscall_next(index) #define FINSH_NEXT_SYSVAR(index) index=finsh_sysvar_next(index) #else #define FINSH_NEXT_SYSCALL(index) index++ #define FINSH_NEXT_SYSVAR(index) index++ #endif

KEIL編譯器使用的就是index++,index是一個struct finsh_syscall類型的指針。

以上所述的遍歷表的方法,在RT-Thread中還有不少應用,比如:
rtthread_startup函數→rt_hw_board_init函數→rt_components_board_init函數

void rt_components_board_init(void) {const init_fn_t *fn_ptr;for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++){(*fn_ptr)();} }

硬件初始化表是通過下面的宏添加:

#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")

這個問題暫時就看到這里了,更細節的后面有時間再更新。

參考鳴謝:
__attribute__((section(x))) 使用詳解

RT-Thread的FinSH控制臺自定義msh命令(附帶部分RT-Thread源碼分析)

__attribute__編譯屬性—section

RT-Thread下finsh原理淺析

總結

以上是生活随笔為你收集整理的RT-Thread FinSH控制台添加自定义msh命令原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。