RT-thread finsh工作流程

finsh是RT-Thread的命令行外壳(shell),提供一套供用户在命令行的操作接口,主要用于调试、查看系统信息。在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会十分方便。

finsh支持两种模式:

1. C语言解释器模式, 为行文方便称之为c-style;

2. 传统命令行模式,此模式又称为msh(module shell)。C语言表达式解释模式下, finsh能够解析执行大部分C语言的表达式,并使用类似C语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在msh模式下,finsh运行方式类似于dos/bash等传统shell。

大致工作流程

一、finsh组件初始化函数finsh_system_init(),并且添加了INIT_COMPONENT_EXPORT(finsh_system_init),支持组件初始化;

这个函数会初始化finsh组件,包括一些finsh变量以及相关数据结构。

然后它会创建一个线程,代码如下:

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,在下一节中我们将分析它具体工作流程。

二、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组件设置使用的串口,从这个函数中我们可以总结出,如何使用串口设备。

  1. 调用rt_device_find使用设备的字符串名字查找设备,得到设备数据结构指针
  2. 调用rt_devcie_open打开设备,open_flag为RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |RT_DEVICE_FLAG_STREAM

* 对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函数来释放一个信号量。

三、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),重复这个过程。

时间: 2024-12-20 23:33:17

RT-thread finsh工作流程的相关文章

apache shiro的工作流程分析

本文基于shiro的web环境,用宏观(也就是不精确)的角度去理解shiro的工作流程,先看shiro官方的一张图. 和应用程序直接交互的对象是Subject,securitymanager为Subject服务.可以把Subject看成一个用户,你的所有的代码都由用户来执行.suject.execute(callable),这个callable里面就是你的代码. 一.shiro如何介入你的webapp 它是如何初始化的?servletContextListener.它是如何在每个http请求中介

Android 4.4 Kitkat Phone工作流程浅析(十)__&quot;通话显示&quot;查询流程

本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉. 前置文章: <Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划> <Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析> <Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程

Struts2工作流程个人解析

前面稍微学习了下Strust2基本使用,对Struts2的工作流程以及底层源码完全不懂,今天打算把Struts2的工作流程好好的摸索一遍. 1.这是一张网上download的struts2工作流程图, 对上图稍做解释: 1.首先客户端/浏览器发送一个请求到服务器,即HttpServletRequest会经过一系列(Fliter)过滤器(ActionContextCleanUp该过滤器是可选过滤器,主要作用就是对ActionContext进行CleanUp操作,不让后续的Fliter清除,延长ac

XtraBackup全备工作流程解读与总结

背景 出于对XtraBackup工作原理好奇,做了下面的日志解读 工作流程解读 [[email protected] 09:23:35 /root] #time innobackupex --defaults-file=/data/mysql/mysql3306/my3306.cnf -S /tmp/mysql3306.sock -uroot -plmlm /data/backup/ 180525 09:24:24 innobackupex: Starting the backup operat

大数据技术之_10_Kafka学习_Kafka概述+Kafka集群部署+Kafka工作流程分析+Kafka API实战+Kafka Producer拦截器+Kafka Streams

第1章 Kafka概述1.1 消息队列1.2 为什么需要消息队列1.3 什么是Kafka1.4 Kafka架构第2章 Kafka集群部署2.1 环境准备2.1.1 集群规划2.1.2 jar包下载2.2 Kafka集群部署2.3 Kafka命令行操作第3章 Kafka工作流程分析3.1 Kafka 生产过程分析3.1.1 写入方式3.1.2 分区(Partition)3.1.3 副本(Replication)3.1.4 写入流程3.2 Broker 保存消息3.2.1 存储方式3.2.2 存储策

GitHub超详细图文攻略 - Git客户端下载安装 GitHub提交修改源码工作流程 Git分支 标签 过滤 Git版本工作流(转载)

最近听同事说他都在使用GitHub,GitHub是程序员的社区,在里面可以学到很多书上学不到的东西,所以最近在准备入手这方面的知识去尝试学习,正好碰到这么详细完整的文章,就转载了,希望对自己和大家有帮助. GitHub操作总结 : 总结看不明白就看下面的详细讲解. GitHub操作流程 : 第一次提交 : 方案一 : 本地创建项目根目录, 然后与远程GitHub关联, 之后的操作一样; -- 初始化Git仓库 :git init ; -- 提交改变到缓存 :git commit -m 'desc

Struts2工作流程

Struts2是一个非常优秀的MVC框架,它主要通过StrutsPrepareAndExecuteFilter过滤器将Struts2集成到Web应用中的. 基本工作流程: 1.客户端提交一个HttpServletRequest请求(action或JSP页面): 2.请求经过一系列的过滤器(如ActionContextCleanUp,SiteMesh等): 3.Struts2的核心过滤器StrutsPrepareAndExecuteFilter被调用,并询问ActionMapper来决定这个请求是

Laravel 5系列教程二:路由,视图,控制器工作流程

免费视频教程地址https://laravist.com/series/laravel-5-basic 上一篇教程我们走了那么长的路,终于把Laravel安装好了,这一篇教程我们就要进入Laravel的神奇世界了,主要是讲解Laravel的Router,Views,Controllers的工作流程,目的也就是让大家明白Laravel在处理一个get请求的时候是如何工作的. 在开始之前,我们首先得将我们的服务器启动起来,如果你使用Laravel的artisan,你可以直接: php artisan

[Servlet]Servlet的工作流程及注意事项

Servlet工作流程 利用Servlet完成的Web应用的实际工作流程是通过Tomcat服务器发布服务,客户端与服务器端之间的交互遵循Http协议完成的.具体工作流程如下: 客户端浏览器向服务器端发送请求. 服务器端由Tomcat服务器提供的Servlet容器解析接收到的请求. 通过Web应用程序的配置文件web.xml,解析到对应执行的Servlet. Servlet完成客户端发送的请求逻辑,并完成向客户端发送的响应内容. 由Tomcat服务器提供的Servlet容器向客户端浏览器进行响应.