打印
[应用相关]

VSF构架示例讲解之命令行界面的输入和输出处理

[复制链接]
1388|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Simon21ic|  楼主 | 2014-10-29 18:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 Simon21ic 于 2014-10-29 19:17 编辑

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

类似之前将的CDC的类驱动代码,命令行界面的代码的事件处理函数中,也是需要处理几个类似的事件:
static struct vsfsm_state_t *
vsfshell_evt_handler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
        struct vsfshell_t *shell = (struct vsfshell_t *)sm->user_data;
        
        switch (evt)
        {
        case VSFSM_EVT_INIT:
                shell->output_interrupted = false;
                shell->tbuffer.buffer.buffer = (uint8_t *)shell->cmd_buff;
                shell->tbuffer.buffer.size = sizeof(shell->cmd_buff);
                shell->tbuffer.position = 0;
                vsfsm_crit_init(&shell->output_crit, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
               
                shell->stream_rx->callback_rx.param = shell;
                shell->stream_rx->callback_rx.on_in_int =
                                                        vsfshell_streamrx_callback_on_in_int;
                shell->stream_rx->callback_rx.on_connect_tx =
                                                        vsfshell_streamrx_callback_on_txconn;
                shell->stream_tx->callback_tx.param = shell;
                shell->stream_tx->callback_tx.on_out_int =
                                                        vsfshell_streamtx_callback_on_out_int;
                shell->stream_tx->callback_tx.on_connect_rx =
                                                        vsfshell_streamtx_callback_on_rxconn;
               
                vsfshell_register_handlers(shell, vsfshell_handlers);
               
                // shell->output_pt is only called by shell->input_pt
                shell->output_pt.thread = (vsfsm_pt_thread_t)vsfshell_output_thread;
                shell->output_pt.sm = sm;
                shell->output_pt.user_data = shell;
                // shell->input_pt is used to hanlde the events from stream_rx
                shell->input_pt.thread = vsfshell_input_thread;
                shell->input_pt.sm = sm;
                shell->input_pt.user_data = shell;
                shell->input_pt.state = 0;
                shell->input_pt.thread(&shell->input_pt, VSFSM_EVT_INIT);
                // default input sm is shell itself
                shell->input_sm = &shell->sm;
               
                stream_connect_rx(shell->stream_rx);
                stream_connect_tx(shell->stream_tx);
                break;
        case VSFSHELL_EVT_STREAMRX_ONCONN:
                break;
        case VSFSHELL_EVT_STREAMTX_ONCONN:
                // pass to shell->input_pt
                shell->input_pt.thread(&shell->input_pt, evt);
                break;
        case VSFSHELL_EVT_STREAMRX_ONIN:
                if (shell->input_sm == &shell->sm)
                {
                        // pass to shell->input_sm
                        shell->input_pt.thread(&shell->input_pt, evt);
                }
                else if (shell->input_sm != NULL)
                {
                        vsfsm_post_evt(shell->input_sm, evt);
                }
                break;
        case VSFSHELL_EVT_STREAMTX_ONOUT:
                if (shell->input_sm == &shell->sm)
                {
                        // pass to shell->input_sm
                        shell->input_pt.thread(&shell->input_pt, evt);
                }
                else if (shell->output_sm != NULL)
                {
                        vsfsm_post_evt(shell->output_sm, evt);
                }
                break;
        }
        
        return NULL;
}
VSFSM_EVT_INIT一如既往的是初始化事件,初始化相关的各个子线程和资源。另外4个事件,就是流构架中的4个事件。
当然,首先收到的,应该是VSFSHELL_EVT_STREAMTX_ONCONN,连接事件,这里调用input_pt子线程来处理。结合代码如下:
// vsfshell_input_thread is used to process the events
//                 from the sender of the stream_rx
vsf_err_t vsfshell_input_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
        struct vsfshell_t *shell = (struct vsfshell_t *)pt->user_data;
        struct vsfsm_pt_t *output_pt = &shell->output_pt;
        char *cmd = (char *)shell->tbuffer.buffer.buffer;
        struct vsf_buffer_t buffer;
        
        vsfsm_pt_begin(pt);
        vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMTX_ONCONN);
        vsfshell_printf(output_pt,
                                        "vsfshell 0.1 beta by SimonQian" VSFSHELL_LINEEND);
        vsfshell_printf(output_pt, VSFSHELL_PROMPT);
        while (1)
        {
                vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMRX_ONIN);
                do
                {
                        buffer.buffer = (uint8_t *)&shell->ch;
                        buffer.size = 1;
                        buffer.size = stream_rx(shell->stream_rx, &buffer);
                        if (0 == buffer.size)
                        {
                                break;
                        }
                        
                        if ('\r' == shell->ch)
                        {
                                vsfshell_printf(output_pt, VSFSHELL_LINEEND);
                        }
                        else if ('\b' == shell->ch)
                        {
                                if (shell->tbuffer.position)
                                {
                                        vsfshell_printf(output_pt, "\b \b");
                                        shell->tbuffer.position--;
                                }
                                continue;
                        }
                        else if (!((shell->ch >= ' ') && (shell->ch <= '~')) ||
                                (shell->tbuffer.position >= shell->tbuffer.buffer.size - 1))
                        {
                                continue;
                        }
                        else
                        {
                                vsfshell_printf(output_pt, "%c", shell->ch);
                        }
                        
                        if ('\r' == shell->ch)
                        {
                                if (shell->tbuffer.position > 0)
                                {
                                        // create new handler thread
                                        cmd[shell->tbuffer.position] = '\0';
                                        if (vsfshell_new_handler_thread(shell, cmd))
                                        {
                                                vsfshell_printf(output_pt,
                                                        "Fail to execute : %s" VSFSHELL_LINEEND, cmd);
                                                vsfshell_printf(output_pt, VSFSHELL_PROMPT);
                                        }
                                        shell->tbuffer.position = 0;
                                }
                                break;
                        }
                        else
                        {
                                cmd[shell->tbuffer.position++] = shell->ch;
                        }
                } while (buffer.size > 0);
        }
        vsfsm_pt_end(pt);
        return VSFERR_NOT_READY;
}

input线程,首先调用vsfsm_pt_wfe来等待连接事件:
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。
typedef vsf_err_t (*vsfshell_printf_thread_t)(struct vsfsm_pt_t *pt,
                                                                        vsfsm_evt_t evt, const char *format, ...);
#define vsfshell_printf(output_pt, format, ...)\
        do {\
                (output_pt)->state = 0;\
                (output_pt)->sm = (pt)->sm;\
                vsfsm_pt_entry(pt);\
                {\
                        vsfshell_printf_thread_t thread =\
                                        (vsfshell_printf_thread_t)(output_pt)->thread;\
                        vsf_err_t __err = thread(output_pt, evt, format, ##__VA_ARGS__);\
                        if (__err != VSFERR_NONE)\
                        {\
                                return __err;\
                        }\
                }\
        } while (0)
看了这个代码就很明确了,vsfshell_printf其实只是初始化了output的子线程,并且调用换个现成,直到处理完成,这里可以对比一下vsfsm_pt_wfpt的定义:
#define vsfsm_pt_wfpt(pt, ptslave)        \
        do {\
                (ptslave)->state = 0;\
                (ptslave)->sm = (pt)->sm;\
                vsfsm_pt_entry(pt);\
                {\
                        vsf_err_t __err = (ptslave)->thread(ptslave, evt);\
                        if (__err != VSFERR_NONE)\
                        {\
                                return __err;\
                        }\
                }\
        } while (0)
原来只是vsfsm_pt_wfpt的一个特例,因为printf的参数问题,所以使用这个方式来实现。
那么关键的就是,output现成的处理代码了,这里做了一些简化:
// vsfshell_output_thread is used to process the events
//                 from the receiver of the stream_tx
vsf_err_t vsfshell_output_thread(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
                                                                        const char *format, ...)
{
        struct vsfshell_t *shell = (struct vsfshell_t *)pt->user_data;
        uint32_t str_len, size_avail;
        struct vsf_buffer_t buffer;
        va_list ap;
        char *printf_buff;
        uint32_t printf_size;
        
        vsfsm_pt_begin(pt);
        // get lock here
        if (vsfsm_crit_enter(pt->sm, &shell->output_crit))
        {
                vsfsm_pt_wfe(pt, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
        }
        shell->output_sm = pt->sm;
        printf_size = sizeof(shell->printf_buff);
        printf_buff = shell->printf_buff;
        str_len = vsnprintf(printf_buff, printf_size, format, ap);
        va_end(ap);
        shell->printf_pos = shell->printf_buff;
        
        while (str_len > 0)
        {
            size_avail = stream_get_free_size(shell->stream_tx);
            if (!size_avail)
            {
                vsfsm_pt_wfe(pt, VSFSHELL_EVT_STREAMTX_ONOUT);
                size_avail = stream_get_free_size(shell->stream_tx);
                str_len = strlen(shell->printf_pos);
            }
            
            if (size_avail)
            {
                buffer.buffer = (uint8_t *)shell->printf_pos;
                buffer.size = min(str_len, size_avail);
                buffer.size = stream_tx(shell->stream_tx, &buffer);
                shell->printf_pos += buffer.size;
                str_len = strlen(shell->printf_pos);
            }
        }
        shell->output_sm = NULL;
        vsfsm_crit_leave(pt->sm, &shell->output_crit);
        vsfsm_pt_end(pt);
        
        return VSFERR_NONE;
}
首先,通过一下代码申请输出的控制权,如果控制权不可用,就会等待VSFSHELL_EVT_OUTPUT_CRIT_AVAIL事件:
if (vsfsm_crit_enter(pt->sm, &shell->output_crit))
        {
                vsfsm_pt_wfe(pt, VSFSHELL_EVT_OUTPUT_CRIT_AVAIL);
        }
        shell->output_sm = pt->sm;
得到控制权后,就把当前的输出现成设置为自己了,之后的事件也会发送给自己处理。 之后就是va_arg的标准处理代码。然后就是不停的把数据写入的发送的流中去,当然如果流中空余数据不够,就需要等待VSFSHELL_EVT_STREAMTX_ONOUT事件,等流的另外一段把数据读取出来,使得可以再次写入数据到流中去。最后,就是释放输出的控制权,如果其他线程也要输出的话,就会得到一个VSFSHELL_EVT_OUTPUT_CRIT_AVAIL事件。这样,多线程同时输出的话,数据并不会打架,而是先到先得,依次输出。
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

266

主题

2597

帖子

104

粉丝