finsh是RT-Thread的命令行外殼(shell),提供一套供用戶在命令行的操做接口,主要用於調試、查看系統信息。在大部分嵌入式系統中,通常開發調試都使用硬件調試器和printf日誌打印,在有些狀況下,這兩種方式並非那麼好用。好比對於RT-Thread這個多線程系統,咱們想知道某個時刻系統中的線程運行狀態、手動控制系統狀態。若是有一個shell,就能夠輸入命令,直接相應的函數執行得到須要的信息,或者控制程序的行爲。這無疑會十分方便。shell
finsh支持兩種模式:bash
1. C語言解釋器模式, 爲行文方便稱之爲c-style;數據結構
2. 傳統命令行模式,此模式又稱爲msh(module shell)。C語言表達式解釋模式下, finsh可以解析執行大部分C語言的表達式,並使用相似C語言的函數調用方式訪問系統中的函數及全局變量,此外它也可以經過命令行方式建立變量。在msh模式下,finsh運行方式相似於dos/bash等傳統shell。多線程
大體工做流程函數
1、finsh組件初始化函數finsh_system_init(),而且添加了INIT_COMPONENT_EXPORT(finsh_system_init),支持組件初始化;測試
這個函數會初始化finsh組件,包括一些finsh變量以及相關數據結構。this
而後它會建立一個線程,代碼以下:spa
result = rt_thread_init(&finsh_thread, "tshell", finsh_thread_entry, RT_NULL, &finsh_thread_stack[0], sizeof(finsh_thread_stack), FINSH_THREAD_PRIORITY, 10); if (result == RT_EOK) rt_thread_startup(&finsh_thread);
能夠看到,線程函數是finsh_thread_entry,在下一節中咱們將分析它具體工做流程。命令行
2、void finsh_set_device(const char* device_name)函數爲finsh設置終端設備,在stm32中主要設置串口設備爲終端。該函數通常放在組件初始化函數rt_component_init()後面,由於要先完成finsh組件初始化才能設置終端設備。線程
void finsh_set_device(const char* device_name) { rt_device_t dev = RT_NULL; RT_ASSERT(shell != RT_NULL); dev = rt_device_find(device_name); if (dev == RT_NULL) { rt_kprintf("finsh: can not find device: %s\n", device_name); return; } /* check whether it's a same device */ if (dev == shell->device) return; /* open this device and set the new device in finsh shell */ if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |\ RT_DEVICE_FLAG_STREAM) == RT_EOK) { if (shell->device != RT_NULL) { /* close old finsh device */ rt_device_close(shell->device); rt_device_set_rx_indicate(shell->device, RT_NULL); } shell->device = dev; rt_device_set_rx_indicate(dev, finsh_rx_ind); } }
這個函數爲finsh組件設置使用的串口,從這個函數中咱們能夠總結出,如何使用串口設備。
* 對finsh來講,還使用了rt_device_set_rx_indicate函數設置了一個回調函數finsh_rx_ind,它的做用咱們後面會討論
到這裏設備就被打開了。
在serial.c中rt_hw_serial_isr()中有:
/* invoke callback */ if (serial->parent.rx_indicate != RT_NULL) { rt_size_t rx_length; /* get rx length */ level = rt_hw_interrupt_disable(); rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index): (serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index)); rt_hw_interrupt_enable(level); serial->parent.rx_indicate(&serial->parent, rx_length); }
上面計算獲得rx_length,而後觸發回調函數,也就是前面的finsh_rx_ind函數,即實際執行的是fins_rx_ind(device, rx_length)。
在shell.c中fins_rx_ind源碼爲:
static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size) { RT_ASSERT(shell != RT_NULL); /* release semaphore to let finsh thread rx data */ rt_sem_release(&shell->rx_sem); return RT_EOK; }
這個函數裏只是簡單的釋放信號量。也就是說,當串口硬件上接收到一個字節,就會調用finsh_rx_ind函數來釋放一個信號量。
3、finsh線程函數的工做流程概述
void finsh_thread_entry(void* parameter) { char ch; /* normal is echo mode */ shell->echo_mode = 1; #ifndef FINSH_USING_MSH_ONLY finsh_init(&shell->parser); #endif rt_kprintf(FINSH_PROMPT); /* set console device as shell device */ if (shell->device == RT_NULL) { #ifdef RT_USING_CONSOLE shell->device = rt_console_get_device(); RT_ASSERT(shell->device); rt_device_set_rx_indicate(shell->device, finsh_rx_ind); rt_device_open(shell->device, (RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_INT_RX)); #else RT_ASSERT(shell->device); #endif } while (1) { /* wait receive */ if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue; /* read one character from device */ while (rt_device_read(shell->device, 0, &ch, 1) == 1) { /* * handle control key * up key : 0x1b 0x5b 0x41 * down key: 0x1b 0x5b 0x42 * right key:0x1b 0x5b 0x43 * left key: 0x1b 0x5b 0x44 */ if (ch == 0x1b) { shell->stat = WAIT_SPEC_KEY; continue; } else if (shell->stat == WAIT_SPEC_KEY) { if (ch == 0x5b) { shell->stat = WAIT_FUNC_KEY; continue; } shell->stat = WAIT_NORMAL; } else if (shell->stat == WAIT_FUNC_KEY) { shell->stat = WAIT_NORMAL; if (ch == 0x41) /* up key */ { #ifdef FINSH_USING_HISTORY /* prev history */ if (shell->current_history > 0) shell->current_history --; else { shell->current_history = 0; continue; } /* copy the history command */ memcpy(shell->line, &shell->cmd_history[shell->current_history][0], FINSH_CMD_SIZE); shell->line_curpos = shell->line_position = strlen(shell->line); shell_handle_history(shell); #endif continue; } else if (ch == 0x42) /* down key */ { #ifdef FINSH_USING_HISTORY /* next history */ if (shell->current_history < shell->history_count - 1) shell->current_history ++; else { /* set to the end of history */ if (shell->history_count != 0) shell->current_history = shell->history_count - 1; else continue; } memcpy(shell->line, &shell->cmd_history[shell->current_history][0], FINSH_CMD_SIZE); shell->line_curpos = shell->line_position = strlen(shell->line); shell_handle_history(shell); #endif continue; } else if (ch == 0x44) /* left key */ { if (shell->line_curpos) { rt_kprintf("\b"); shell->line_curpos --; } continue; } else if (ch == 0x43) /* right key */ { if (shell->line_curpos < shell->line_position) { rt_kprintf("%c", shell->line[shell->line_curpos]); shell->line_curpos ++; } continue; } } /* handle CR key */ if (ch == '\r') { char next; if (rt_device_read(shell->device, 0, &next, 1) == 1) ch = next; else ch = '\r'; } /* handle tab key */ else if (ch == '\t') { int i; /* move the cursor to the beginning of line */ for (i = 0; i < shell->line_curpos; i++) rt_kprintf("\b"); /* auto complete */ shell_auto_complete(&shell->line[0]); /* re-calculate position */ shell->line_curpos = shell->line_position = strlen(shell->line); continue; } /* handle backspace key */ else if (ch == 0x7f || ch == 0x08) { /* note that shell->line_curpos >= 0 */ if (shell->line_curpos == 0) continue; shell->line_position--; shell->line_curpos--; if (shell->line_position > shell->line_curpos) { int i; rt_memmove(&shell->line[shell->line_curpos], &shell->line[shell->line_curpos + 1], shell->line_position - shell->line_curpos); shell->line[shell->line_position] = 0; rt_kprintf("\b%s \b", &shell->line[shell->line_curpos]); /* move the cursor to the origin position */ for (i = shell->line_curpos; i <= shell->line_position; i++) rt_kprintf("\b"); } else { rt_kprintf("\b \b"); shell->line[shell->line_position] = 0; } continue; } /* handle end of line, break */ if (ch == '\r' || ch == '\n') { #ifdef FINSH_USING_HISTORY shell_push_history(shell); #endif #ifdef FINSH_USING_MSH if (msh_is_used() == RT_TRUE) { rt_kprintf("\n"); msh_exec(shell->line, shell->line_position); } else #endif { #ifndef FINSH_USING_MSH_ONLY /* add ';' and run the command line */ shell->line[shell->line_position] = ';'; if (shell->line_position != 0) finsh_run_line(&shell->parser, shell->line); else rt_kprintf("\n"); #endif } rt_kprintf(FINSH_PROMPT); memset(shell->line, 0, sizeof(shell->line)); shell->line_curpos = shell->line_position = 0; break; } /* it's a large line, discard it */ if (shell->line_position >= FINSH_CMD_SIZE) shell->line_position = 0; /* normal character */ if (shell->line_curpos < shell->line_position) { int i; rt_memmove(&shell->line[shell->line_curpos + 1], &shell->line[shell->line_curpos], shell->line_position - shell->line_curpos); shell->line[shell->line_curpos] = ch; if (shell->echo_mode) rt_kprintf("%s", &shell->line[shell->line_curpos]); /* move the cursor to new position */ for (i = shell->line_curpos; i < shell->line_position; i++) rt_kprintf("\b"); } else { shell->line[shell->line_position] = ch; if (shell->echo_mode) rt_kprintf("%c", ch); } ch = 0; shell->line_position ++; shell->line_curpos++; if (shell->line_position >= 80) { /* clear command line */ shell->line_position = 0; shell->line_curpos = 0; } } /* end of device read */ } }
函數主體依然是一個while(1)循環,這是顯然的,由於finsh要不停的監聽終端上輸入。
if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue;
即,若是串口上沒有收到任何數據,而且串口緩衝區中也無數據,即shell→rx_sem信號量的值爲0,那麼這個函數會使finsh線程休眠,RTT內核會執行其餘線程。
當串口收到數據,串口終端調用回調函數finsh_rx_ind函數來釋放信號量,這會喚醒finsh線程,rt_sem_take函數會執行完畢,繼續執行接下來的代碼。
接下來的代碼調用rt_device_read函數從串口數據緩衝池中讀取一個字節。
而後判斷所讀取的到這個字節(判斷上下左右四個按鍵所表明的字節)。
(1) 若是是'\r',即表示用戶按下了回車鍵,再調用rt_device_read函數來讀取一個字節,若是讀到,則這將更新讀到的字節,通常狀況下,這個函數會返回0,即沒有讀到新的字節。
(2) 若是是'\t',即表示用戶按下了TAB鍵,則調用finsh_auto_complete函數,這個函數作自動補全操做,也就是根據當前已輸入的字符串,從finsh內部已註冊的函數/變量中查找匹配字符串,若是找到則會在終端上自動補全。
(3) 若是是0x7f或者0x08 說明:查ascii碼錶可知,0x08 表示按下了backspace鍵,【0x7f表示按下了DEL鍵,這個不對勁,如何知道當咱們按下了鍵盤按鍵時,串口都收到了什麼數據呢?】 這表示用戶指望刪除已經輸入的字符串,根據測試結果,發送」\0x08 \0x08」,能夠實現退格。
(4) 若是收到了'\r'或者'\n',則表示用戶按下了回車,但願處理這個命令,那麼finsh_run_line函數被執行,這個函數會從從finsh已註冊的函數/變量中匹配當前從終端裏獲取的字符串,若是匹配到,則執行對應的函數(若字符串爲函數名)或者打印變量的值(若字符串爲已變量)。
(5) 回顯字符,也就是將剛纔從串口接收到終端發送的字符發送到終端軟件上顯示出來。這就是說,咱們在終端軟件上輸入字符,而且能夠看到咱們輸入的字符,其實是板子上的串口從新發回來顯示的。在上面finsh的線程代碼中,rt_device_write函數是在rt_kprintf中調用的。
而後回到(1),重複這個過程。