本帖最后由 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事件。这样,多线程同时输出的话,数据并不会打架,而是先到先得,依次输出。
|