[应用相关] VSF构架示例讲解之命令行界面的输入和输出处理

[复制链接]
1594|0
 楼主| Simon21ic 发表于 2014-10-29 18:50 | 显示全部楼层 |阅读模式
本帖最后由 Simon21ic 于 2014-10-29 19:17 编辑

接着事件驱动的流构架(https://bbs.21ic.com/icview-821180-1-1.html)后,现在可以讲一下命令行的输入输出的处理了。

类似之前将的CDC的类驱动代码,命令行界面的代码的事件处理函数中,也是需要处理几个类似的事件:
  1. static struct vsfsm_state_t *
  2. vsfshell_evt_handler(struct vsfsm_t *sm, vsfsm_evt_t evt)
  3. {
  4.         struct vsfshell_t *shell = (struct vsfshell_t *)sm->user_data;
  5.         
  6.         switch (evt)
  7.         {
  8.         case VSFSM_EVT_INIT:
  9.                 shell->output_interrupted = false;
  10.                 shell->tbuffer.buffer.buffer = (uint8_t *)shell->cmd_buff;
  11.                 shell->tbuffer.buffer.size = sizeof(shell->cmd_buff);
  12.                 shell->tbuffer.position = 0;
  13.                 vsfsm_crit_init(&shell->output_crit, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
  14.                
  15.                 shell->stream_rx->callback_rx.param = shell;
  16.                 shell->stream_rx->callback_rx.on_in_int =
  17.                                                         vsfshell_streamrx_callback_on_in_int;
  18.                 shell->stream_rx->callback_rx.on_connect_tx =
  19.                                                         vsfshell_streamrx_callback_on_txconn;
  20.                 shell->stream_tx->callback_tx.param = shell;
  21.                 shell->stream_tx->callback_tx.on_out_int =
  22.                                                         vsfshell_streamtx_callback_on_out_int;
  23.                 shell->stream_tx->callback_tx.on_connect_rx =
  24.                                                         vsfshell_streamtx_callback_on_rxconn;
  25.                
  26.                 vsfshell_register_handlers(shell, vsfshell_handlers);
  27.                
  28.                 // shell->output_pt is only called by shell->input_pt
  29.                 shell->output_pt.thread = (vsfsm_pt_thread_t)vsfshell_output_thread;
  30.                 shell->output_pt.sm = sm;
  31.                 shell->output_pt.user_data = shell;
  32.                 // shell->input_pt is used to hanlde the events from stream_rx
  33.                 shell->input_pt.thread = vsfshell_input_thread;
  34.                 shell->input_pt.sm = sm;
  35.                 shell->input_pt.user_data = shell;
  36.                 shell->input_pt.state = 0;
  37.                 shell->input_pt.thread(&shell->input_pt, VSFSM_EVT_INIT);
  38.                 // default input sm is shell itself
  39.                 shell->input_sm = &shell->sm;
  40.                
  41.                 stream_connect_rx(shell->stream_rx);
  42.                 stream_connect_tx(shell->stream_tx);
  43.                 break;
  44.         case VSFSHELL_EVT_STREAMRX_ONCONN:
  45.                 break;
  46.         case VSFSHELL_EVT_STREAMTX_ONCONN:
  47.                 // pass to shell->input_pt
  48.                 shell->input_pt.thread(&shell->input_pt, evt);
  49.                 break;
  50.         case VSFSHELL_EVT_STREAMRX_ONIN:
  51.                 if (shell->input_sm == &shell->sm)
  52.                 {
  53.                         // pass to shell->input_sm
  54.                         shell->input_pt.thread(&shell->input_pt, evt);
  55.                 }
  56.                 else if (shell->input_sm != NULL)
  57.                 {
  58.                         vsfsm_post_evt(shell->input_sm, evt);
  59.                 }
  60.                 break;
  61.         case VSFSHELL_EVT_STREAMTX_ONOUT:
  62.                 if (shell->input_sm == &shell->sm)
  63.                 {
  64.                         // pass to shell->input_sm
  65.                         shell->input_pt.thread(&shell->input_pt, evt);
  66.                 }
  67.                 else if (shell->output_sm != NULL)
  68.                 {
  69.                         vsfsm_post_evt(shell->output_sm, evt);
  70.                 }
  71.                 break;
  72.         }
  73.         
  74.         return NULL;
  75. }
VSFSM_EVT_INIT一如既往的是初始化事件,初始化相关的各个子线程和资源。另外4个事件,就是流构架中的4个事件。
当然,首先收到的,应该是VSFSHELL_EVT_STREAMTX_ONCONN,连接事件,这里调用input_pt子线程来处理。结合代码如下:
  1. // vsfshell_input_thread is used to process the events
  2. //                 from the sender of the stream_rx
  3. vsf_err_t vsfshell_input_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
  4. {
  5.         struct vsfshell_t *shell = (struct vsfshell_t *)pt->user_data;
  6.         struct vsfsm_pt_t *output_pt = &shell->output_pt;
  7.         char *cmd = (char *)shell->tbuffer.buffer.buffer;
  8.         struct vsf_buffer_t buffer;
  9.         
  10.         vsfsm_pt_begin(pt);
  11.         vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMTX_ONCONN);
  12.         vsfshell_printf(output_pt,
  13.                                         "vsfshell 0.1 beta by SimonQian" VSFSHELL_LINEEND);
  14.         vsfshell_printf(output_pt, VSFSHELL_PROMPT);
  15.         while (1)
  16.         {
  17.                 vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMRX_ONIN);
  18.                 do
  19.                 {
  20.                         buffer.buffer = (uint8_t *)&shell->ch;
  21.                         buffer.size = 1;
  22.                         buffer.size = stream_rx(shell->stream_rx, &buffer);
  23.                         if (0 == buffer.size)
  24.                         {
  25.                                 break;
  26.                         }
  27.                         
  28.                         if ('\r' == shell->ch)
  29.                         {
  30.                                 vsfshell_printf(output_pt, VSFSHELL_LINEEND);
  31.                         }
  32.                         else if ('\b' == shell->ch)
  33.                         {
  34.                                 if (shell->tbuffer.position)
  35.                                 {
  36.                                         vsfshell_printf(output_pt, "\b \b");
  37.                                         shell->tbuffer.position--;
  38.                                 }
  39.                                 continue;
  40.                         }
  41.                         else if (!((shell->ch >= ' ') && (shell->ch <= '~')) ||
  42.                                 (shell->tbuffer.position >= shell->tbuffer.buffer.size - 1))
  43.                         {
  44.                                 continue;
  45.                         }
  46.                         else
  47.                         {
  48.                                 vsfshell_printf(output_pt, "%c", shell->ch);
  49.                         }
  50.                         
  51.                         if ('\r' == shell->ch)
  52.                         {
  53.                                 if (shell->tbuffer.position > 0)
  54.                                 {
  55.                                         // create new handler thread
  56.                                         cmd[shell->tbuffer.position] = '\0';
  57.                                         if (vsfshell_new_handler_thread(shell, cmd))
  58.                                         {
  59.                                                 vsfshell_printf(output_pt,
  60.                                                         "Fail to execute : %s" VSFSHELL_LINEEND, cmd);
  61.                                                 vsfshell_printf(output_pt, VSFSHELL_PROMPT);
  62.                                         }
  63.                                         shell->tbuffer.position = 0;
  64.                                 }
  65.                                 break;
  66.                         }
  67.                         else
  68.                         {
  69.                                 cmd[shell->tbuffer.position++] = shell->ch;
  70.                         }
  71.                 } while (buffer.size > 0);
  72.         }
  73.         vsfsm_pt_end(pt);
  74.         return VSFERR_NOT_READY;
  75. }

input线程,首先调用vsfsm_pt_wfe来等待连接事件:
  1. vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMTX_ONCONN);
之后,打印了命令行的名称和提示符后,就在while (1)中,等待VSFSHELL_EVT_STREAMRX_ONIN事件,也就是流接收到数据的事件。当然,这里貌似while (1)是阻塞的代码,实际还请看相关的PT线程的介绍。按键的事件处理代码很简单,无非就是处理退格、回车等特殊按键,把可现实的字符回发显示而已。如果按下回车后并且命令缓冲中有命令,就会调用vsfshell_new_handler_thread来动态的生成这个命令的处理函数,之后,流的输入和输出事件,就会有新的当前的命令处理线程来处理。直到命令处理线程调用vsfshell_handler_release_io释放对流的输入和输出的控制权。

对于输出流(用于显示字符)的处理,要稍微复杂一些。各个现成都可以输出字符给屏幕(包括释放了IO的后台运行的现成,用于打印一些信息),所以,各个输出之间的隔离保护就非常重要。每个线程都有自己的输出数据的子线程,这样,各个线程可以同时输出数据,不过只有得到控制权的现成能够输出,等控制权释放后,其他现成才能得到控制权。
线程中,输出数据使用vsfshell_printf,类似于printf。
  1. typedef vsf_err_t (*vsfshell_printf_thread_t)(struct vsfsm_pt_t *pt,
  2.                                                                         vsfsm_evt_t evt, const char *format, ...);
  3. #define vsfshell_printf(output_pt, format, ...)\
  4.         do {\
  5.                 (output_pt)->state = 0;\
  6.                 (output_pt)->sm = (pt)->sm;\
  7.                 vsfsm_pt_entry(pt);\
  8.                 {\
  9.                         vsfshell_printf_thread_t thread =\
  10.                                         (vsfshell_printf_thread_t)(output_pt)->thread;\
  11.                         vsf_err_t __err = thread(output_pt, evt, format, ##__VA_ARGS__);\
  12.                         if (__err != VSFERR_NONE)\
  13.                         {\
  14.                                 return __err;\
  15.                         }\
  16.                 }\
  17.         } while (0)
看了这个代码就很明确了,vsfshell_printf其实只是初始化了output的子线程,并且调用换个现成,直到处理完成,这里可以对比一下vsfsm_pt_wfpt的定义:
  1. #define vsfsm_pt_wfpt(pt, ptslave)        \
  2.         do {\
  3.                 (ptslave)->state = 0;\
  4.                 (ptslave)->sm = (pt)->sm;\
  5.                 vsfsm_pt_entry(pt);\
  6.                 {\
  7.                         vsf_err_t __err = (ptslave)->thread(ptslave, evt);\
  8.                         if (__err != VSFERR_NONE)\
  9.                         {\
  10.                                 return __err;\
  11.                         }\
  12.                 }\
  13.         } while (0)
原来只是vsfsm_pt_wfpt的一个特例,因为printf的参数问题,所以使用这个方式来实现。
那么关键的就是,output现成的处理代码了,这里做了一些简化:
  1. // vsfshell_output_thread is used to process the events
  2. //                 from the receiver of the stream_tx
  3. vsf_err_t vsfshell_output_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
  4.                                                                         const char *format, ...)
  5. {
  6.         struct vsfshell_t *shell = (struct vsfshell_t *)pt->user_data;
  7.         uint32_t str_len, size_avail;
  8.         struct vsf_buffer_t buffer;
  9.         va_list ap;
  10.         char *printf_buff;
  11.         uint32_t printf_size;
  12.         
  13.         vsfsm_pt_begin(pt);
  14.         // get lock here
  15.         if (vsfsm_crit_enter(pt->sm, &shell->output_crit))
  16.         {
  17.                 vsfsm_pt_wfe(pt, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
  18.         }
  19.         shell->output_sm = pt->sm;
  20.         printf_size = sizeof(shell->printf_buff);
  21.         printf_buff = shell->printf_buff;
  22.         str_len = vsnprintf(printf_buff, printf_size, format, ap);
  23.         va_end(ap);
  24.         shell->printf_pos = shell->printf_buff;
  25.         
  26.         while (str_len > 0)
  27.         {
  28.             size_avail = stream_get_free_size(shell->stream_tx);
  29.             if (!size_avail)
  30.             {
  31.                 vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMTX_ONOUT);
  32.                 size_avail = stream_get_free_size(shell->stream_tx);
  33.                 str_len = strlen(shell->printf_pos);
  34.             }
  35.             
  36.             if (size_avail)
  37.             {
  38.                 buffer.buffer = (uint8_t *)shell->printf_pos;
  39.                 buffer.size = min(str_len, size_avail);
  40.                 buffer.size = stream_tx(shell->stream_tx, &buffer);
  41.                 shell->printf_pos += buffer.size;
  42.                 str_len = strlen(shell->printf_pos);
  43.             }
  44.         }
  45.         shell->output_sm = NULL;
  46.         vsfsm_crit_leave(pt->sm, &shell->output_crit);
  47.         vsfsm_pt_end(pt);
  48.         
  49.         return VSFERR_NONE;
  50. }
首先,通过一下代码申请输出的控制权,如果控制权不可用,就会等待VSFSHELL_EVT_OUTPUT_CRIT_AVAIL事件:
  1. if (vsfsm_crit_enter(pt->sm, &shell->output_crit))
  2.         {
  3.                 vsfsm_pt_wfe(pt, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
  4.         }
  5.         shell->output_sm = pt->sm;
得到控制权后,就把当前的输出现成设置为自己了,之后的事件也会发送给自己处理。 之后就是va_arg的标准处理代码。然后就是不停的把数据写入的发送的流中去,当然如果流中空余数据不够,就需要等待VSFSHELL_EVT_STREAMTX_ONOUT事件,等流的另外一段把数据读取出来,使得可以再次写入数据到流中去。最后,就是释放输出的控制权,如果其他线程也要输出的话,就会得到一个VSFSHELL_EVT_OUTPUT_CRIT_AVAIL事件。这样,多线程同时输出的话,数据并不会打架,而是先到先得,依次输出。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:www.versaloon.com --- under construction

266

主题

2597

帖子

104

粉丝
快速回复 在线客服 返回列表 返回顶部