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


  • 在學習rtthread的過程中發現rthhread的控制台組件也可以支持帶參數的命令寫法。其實官網文檔寫得很詳細了,但是還是記錄一下。

不帶參數的命令寫法

  • 不帶參數的命令寫法十分簡單,就是寫一個普通的函數
  • 再利用這個宏導出
MSH_CMD_EXPORT(name, desc);

自己的demo節選,作為示例

void TEXT(void)
{
    const rt_uint8_t Buffer[] = "hello world\r\n";
    rt_uint8_t len = rt_strlen((const char*)Buffer);
    rt_device_write(Project_uart_Device,0,Buffer,len);
}
MSH_CMD_EXPORT(TEXT, RT-Thread TEXT sample);
  • 在命令行里輸入TEXT\r\n就會觸發這個函數。關鍵是這個宏定義的實現

帶參數的命令的寫法

  • 帶參數的命令寫法其實就類似main函數的參數一樣(int argc,char **argv)

下面這個代碼效果是查詢或設置一個藍牙設備的設備名稱的代碼,當沒參數時,是查詢藍牙設備名稱,帶參數,就是修改藍牙設備名稱

static void AT_NAME(int argc, char **argv){//設置/查詢設備名稱
    if(argc < 2){
        const rt_uint8_t Buffer[] = "AT+NAME\r\n";
        rt_uint8_t len = rt_strlen((const char*)Buffer);
        rt_device_write(Project_uart_Device,0,Buffer,len);
    }else if(argc > 2){
        rt_kprintf("Only one parameter can be entered\r\n");
    }else{
        char Buffer[] = "AT+NAME";
        char* NewBuf = strcat(Buffer,argv[1]);
        NewBuf = strcat(NewBuf,"\r\n");
        rt_uint8_t len = rt_strlen((const char*)NewBuf);
        rt_device_write(Project_uart_Device,0,NewBuf,len);
    }
}
MSH_CMD_EXPORT(AT_NAME, AT_NAME sample: AT_NAME <drivers_name>);
  • 電腦終端輸入命令給MCU,MCU再把信息解析然后發送到藍牙設備
  • argv[1]直接就可以被拿來用了,參數與參數之間以空格分開,十分方便

嘗試分析MSH_CMD_EXPORT宏定義源碼

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

#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc)      \
                const char __fsym_##cmd##_name[] = #cmd;            \
                const char __fsym_##cmd##_desc[] = #desc;           \
                __declspec(allocate("FSymTab$f"))                   \
                const struct finsh_syscall __fsym_##cmd =           \
                {                           \
                    __fsym_##cmd##_name,    \
                    __fsym_##cmd##_desc,    \
                    (syscall_func)&name     \
                };
//進入這層,可以看見宏定義的數據被放進一個叫finsh_syscall(系統調用)的結構體里。對象名字就是_fsym_cmd_command
//結構體的第一個成員是字符串首地址,因為是const char類型的數組(宏定義'#'代表讓后面修飾的東西變成字符串)
//結構體的第二個成員也是字符串
//結構體的第三個成員是一個指向函數的指針,指向name函數,也就是名字為name的函數的地址
3.
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 */
#endif
    syscall_func func;      /* the function address of system call */
};
extern struct finsh_syscall *_syscall_table_begin, *_syscall_table_end;

/* find out system call, which should be implemented in user program */
struct finsh_syscall* finsh_syscall_lookup(const char* name);
//進入下一層,可以看到第一行typedef long (*syscall_func)(void),這個就是一個函數指針。
//接下來是結構體主體的定義,不得不說rtthread無論是文檔還是注釋都太適合國人學習了
//緊接着可以看到引用了兩個指向結構體的指針(指針定義在symbol.c),從命名就可以看出一個指針指向表的開始,一個指針指向表的尾部。應該就是個鏈表結構了(好像不是。。)
//再接着是一個函數的定義,它的注釋寫着這個函數的作用是遍歷鏈表,然后利用函數指針調用相應的函數(但是在源碼中沒有找到實現)
  • 接下來跳到shell.c的函數---finsh_system_init開始
...
unsigned int *ptr_begin, *ptr_end;

    ptr_begin = (unsigned int *)&__fsym_begin;
    ptr_begin += (sizeof(struct finsh_syscall) / sizeof(unsigned int));
    while (*ptr_begin == 0) ptr_begin ++;

    ptr_end = (unsigned int *) &__fsym_end;
    ptr_end --;
    while (*ptr_end == 0) ptr_end --;

    finsh_system_function_init(ptr_begin, ptr_end);
...
  • 光看這里很迷茫,接着看__fsym_begin和__fsym_end是什么東西
#pragma section("FSymTab$a", read)
const char __fsym_begin_name[] = "__start";
const char __fsym_begin_desc[] = "begin of finsh";
__declspec(allocate("FSymTab$a")) const struct finsh_syscall __fsym_begin =
{
    __fsym_begin_name,
    __fsym_begin_desc,
    NULL
};

#pragma section("FSymTab$z", read)
const char __fsym_end_name[] = "__end";
const char __fsym_end_desc[] = "end of finsh";
__declspec(allocate("FSymTab$z")) const struct finsh_syscall __fsym_end =
{
    __fsym_end_name,
    __fsym_end_desc,
    NULL
};
//然后又引出了問題,這里的關鍵其實是#pragma section("xxxx",read)以及__declspec(allocate("xxxx"))這兩個宏定義
//#pragma section("xxxx",read),創建一個段,段名是第一個參數,read表示可讀
//__declspec(allocate("xxxx"))是一個修飾,用這個這個修飾可以把變量放進"xxxx"段中
//接下來回到2.中去,可以看到結構體前面有個__declspec(allocate("FSymTab$f"))修飾
//留意到FSymTab$a表示start,FSymTab$z表示end,而真正的結構體被存到FSymTab$f段中
//a-z表示一頭一尾,這個我猜測應該是可以保證f段是在內存a段之后,z段之前的
//回過頭來看上一part,答案似乎呼之欲出,ptr_begin, ptr_end兩根指針都可以分別指向表頭和表尾了。
//得到兩根指針后,被傳進finsh_system_function_init()中。
  • finsh_system_function_init
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;
}
//這個函數就是把這兩根指針分別賦值給_syscall_table_begin和_syscall_table_end
  • 得到這兩根指針,就可以為遍歷提供起始節點和結束兩個邊界信息啦。但是還沒結束。。
...
struct finsh_syscall *index;
...
for (index = _syscall_table_begin; index < _syscall_table_end; FINSH_NEXT_SYSCALL(index))
...
  • 可以看到還有一個宏定義FINSH_NEXT_SYSCALL()需要探究的,否則條件不足,不知道段之間的空間是否連續的,估計和編譯器有關
#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
  • 可以看到,在_MSC_VER,GUNC,x86_64環境下,是需要有一定規則來遍歷地址的,但是我在源碼中沒有找到finsh_syscall_next(index)的實現
  • 所以,那應該空間是連續的了。直接就是index++。
  • ok,這樣在rtthread接收到串口消息的時候就可以通過遍歷這個地址空間中的name,來識別相應的命令,適配成功,就可以利用結構體的函數指針,調用相應的動作了

最后,在編譯后生成的map文件中查找,終於找到了答案。

  • 以下是自己寫的demo里的自定義命令,可以看到,所有的rtthread的finsh命令都在一個名為FSymTab段里了
      FSymTab$$Base                          0x0800f52c   Number         0  commandanalyze.o(FSymTab)
    __fsym___cmd_AT                          0x0800f52c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_VERSION                  0x0800f538   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_RESET                    0x0800f544   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_LADDR                    0x0800f550   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_NAME                     0x0800f55c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_PIN                      0x0800f568   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_BAUD                     0x0800f574   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_SLEEP                    0x0800f580   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_ROLE                     0x0800f58c   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_INQ                      0x0800f598   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_SHOW                     0x0800f5a4   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_AT_CONN                     0x0800f5b0   Data          12  commandanalyze.o(FSymTab)
    __fsym___cmd_show_temp                   0x0800f5bc   Data          12  humiture.o(FSymTab)
    __fsym___cmd_tick_now                    0x0800f5c8   Data          12  main.o(FSymTab)
    __fsym___cmd_w_hello_eeprom              0x0800f5d4   Data          12  eeprom.o(FSymTab)
    __fsym___cmd_r_hello_eeprom              0x0800f5e0   Data          12  eeprom.o(FSymTab)
    __fsym_list_mem                          0x0800f5ec   Data          12  mem.o(FSymTab)
    __fsym_pinMode                           0x0800f5f8   Data          12  pin.o(FSymTab)
    __fsym_pinWrite                          0x0800f604   Data          12  pin.o(FSymTab)
    __fsym_pinRead                           0x0800f610   Data          12  pin.o(FSymTab)
    __fsym_hello                             0x0800f61c   Data          12  cmd.o(FSymTab)
    __fsym_version                           0x0800f628   Data          12  cmd.o(FSymTab)
    __fsym___cmd_version                     0x0800f634   Data          12  cmd.o(FSymTab)
    __fsym_list_thread                       0x0800f640   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_thread                 0x0800f64c   Data          12  cmd.o(FSymTab)
    __fsym_list_sem                          0x0800f658   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_sem                    0x0800f664   Data          12  cmd.o(FSymTab)
    __fsym_list_event                        0x0800f670   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_event                  0x0800f67c   Data          12  cmd.o(FSymTab)
    __fsym_list_mutex                        0x0800f688   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mutex                  0x0800f694   Data          12  cmd.o(FSymTab)
    __fsym_list_mailbox                      0x0800f6a0   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mailbox                0x0800f6ac   Data          12  cmd.o(FSymTab)
    __fsym_list_msgqueue                     0x0800f6b8   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_msgqueue               0x0800f6c4   Data          12  cmd.o(FSymTab)
    __fsym_list_memheap                      0x0800f6d0   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_memheap                0x0800f6dc   Data          12  cmd.o(FSymTab)
    __fsym_list_mempool                      0x0800f6e8   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_mempool                0x0800f6f4   Data          12  cmd.o(FSymTab)
    __fsym_list_timer                        0x0800f700   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_timer                  0x0800f70c   Data          12  cmd.o(FSymTab)
    __fsym_list_device                       0x0800f718   Data          12  cmd.o(FSymTab)
    __fsym___cmd_list_device                 0x0800f724   Data          12  cmd.o(FSymTab)
    __fsym_list                              0x0800f730   Data          12  cmd.o(FSymTab)
    __fsym___cmd_help                        0x0800f73c   Data          12  msh.o(FSymTab)
    FSymTab$$Limit                           0x0800f748   Number         0  msh.o(FSymTab)

總結

  • 又學到了不錯的東西


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM