ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

RT-Thread之控制台线程工作流程学习记录

2020-09-26 12:00:23  阅读:379  来源: 互联网

标签:RT rt shell Thread FINSH cmd 线程 device


RT-Thread控制台工作流程

要使用RT-Thread需要在rtconfig.h头文件添加如下宏定义,RT-Thread官方定义的,不想深究这个

/* Command shell */
#define RT_USING_FINSH
#define FINSH_THREAD_NAME "tshell"
#define FINSH_USING_HISTORY
#define FINSH_HISTORY_LINES 5
#define FINSH_USING_SYMTAB
#define FINSH_USING_DESCRIPTION
#define FINSH_THREAD_PRIORITY 20
#define FINSH_THREAD_STACK_SIZE 512
#define FINSH_CMD_SIZE 80
#define FINSH_USING_MSH
#define FINSH_USING_MSH_DEFAULT
#define FINSH_USING_MSH_ONLY

从finsh_system_init(void)开始

...
#ifdef RT_USING_HEAP
    /* create or set shell structure */
    shell = (struct finsh_shell *)rt_calloc(1, sizeof(struct finsh_shell)); //<------创建1个长度为sizeof(struct finsh_shell)的连续空间,返回这个空间的首地址。与malloc不同之处是calloc会把这个空间初始化为0
    if (shell == RT_NULL)
    {
        rt_kprintf("no memory for shell\n");
        return -1;
    }
    tid = rt_thread_create(FINSH_THREAD_NAME,
                           finsh_thread_entry, RT_NULL,
                           FINSH_THREAD_STACK_SIZE, FINSH_THREAD_PRIORITY, 10);
#else
    shell = &_shell;
    tid = &finsh_thread;
    result = rt_thread_init(&finsh_thread,
                            FINSH_THREAD_NAME,
                            finsh_thread_entry, RT_NULL,
                            &finsh_thread_stack[0], sizeof(finsh_thread_stack),
                            FINSH_THREAD_PRIORITY, 10);
#endif /* RT_USING_HEAP */

    rt_sem_init(&(shell->rx_sem), "shrx", 0, 0);

    if (tid != NULL && result == RT_EOK)
        rt_thread_startup(tid);
...
  • 这个函数的主要功能就是做finsh的初始化,前面是获取系统或用户命令的首尾地址,之前分析过了。
  • 然后主要就是创建控制台线程。
  • 创建一个信号量。
  • 接着调用rt_thread_startup(tid)。

这里有一个结构体finsh_shell,几乎是最重要的东西了

struct finsh_shell
{
    struct rt_semaphore rx_sem;

    enum input_stat stat;

    rt_uint8_t echo_mode:1;

#ifdef FINSH_USING_HISTORY
    rt_uint16_t current_history;
    rt_uint16_t history_count;

    char cmd_history[FINSH_HISTORY_LINES][FINSH_CMD_SIZE];
#endif

#ifndef FINSH_USING_MSH_ONLY
    struct finsh_parser parser;
#endif

    char line[FINSH_CMD_SIZE];
    rt_uint8_t line_position;
    rt_uint8_t line_curpos;

#ifndef RT_USING_POSIX
    rt_device_t device;
#endif

#ifdef FINSH_USING_AUTH
    char password[FINSH_PASSWORD_MAX];
#endif
};
  • 可以看到这个结构体定义了一些控制台组件的参数
  • struct rt_semaphore rx_sem; 作用应该是控制台的串口接收到消息就释放信号的
  • enum input_stat stat; 用来判断命令有没有输完,比如说没有\n作为结束符,命令是不算数的
  • rt_uint8_t echo_mode:1; 位域,只能表示0和1,这个的作用是代表是否开启回显模式
  • 下面3个数据成员应该是用于像linux终端那样,可以用上键查找上一个信息用的
  • struct finsh_parser parser;是语法解析器,我的工程没有用到。只使用msh功能。用C-Style模式能用到,具体看文档了
  • char line[FINSH_CMD_SIZE]; 应该是存放一次性命令行的,命令行长度最大是80字节
  • 接着两个变量是辅助上面line的
  • rt_device_t device; 就是指向控制台串口的设备信息了,关于设备框架慢慢再看
  • 最后char password[FINSH_PASSWORD_MAX];这个是要求使用控制台之前输入密码的,暂时用不上

至此可以进入控制台线程中看看它干了什么

struct finsh_shell *shell;
  • shell.c的开头处先定义了finsh_shell类型的全局变量shell
...
    /* normal is echo mode */
#ifndef FINSH_ECHO_DISABLE_DEFAULT
    shell->echo_mode = 1;
#else
    shell->echo_mode = 0;
#endif

#ifndef FINSH_USING_MSH_ONLY
    finsh_init(&shell->parser);
#endif

#ifndef RT_USING_POSIX
    /* set console device as shell device */
    if (shell->device == RT_NULL)
    {
        rt_device_t console = rt_console_get_device();
        if (console)
        {
            finsh_set_device(console->parent.name);
        }
    }
#endif
...
  • while(1)部分之前,它把初始化shell结构体的工作放到这里来了
  • 比较重要的是绑定设备的部分,设备框架之后会看,暂时不在这里看了
  • 获得设备框架的结构体数据后调用finsh_set_device(console->parent.name);
  • 传入的参数是一个const char*类型的数据,也就是描述设备名称的字符串
  • 下面是finsh_set_device的内部
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);
        }

        /* clear line buffer before switch to new device */
        memset(shell->line, 0, sizeof(shell->line));
        shell->line_curpos = shell->line_position = 0;

        shell->device = dev;
        rt_device_set_rx_indicate(dev, finsh_rx_ind);
    }
  • 前面是判断传进来的这个设备是否被正确加载到设备框架中了
  • 因为shell已经绑定了这个设备了,就无需再绑定了所以 if (dev == shell->device) return;
  • 下面是打开设备。其过程是先判断shell被旧设备占用,先关闭它,再重新绑定新设备。具体就是设置串口接收消息回调函数
  • rt_device_set_rx_indicate(dev, finsh_rx_ind); 这个函数是设备框架的东西,先不看
  • finsh_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;
}
  • 可以看到,接收信息回调函数触发后释放shell结构体定义的rx_sem信号量,让阻塞的线程知道收到信息,从而开始下一步动作

接下来进入死循环的代码,主要逻辑代码部分

  • 第一行就是一个阻塞
ch = finsh_getchar(); // ch的定义在前面,忽略了
            |
            |
            v
static char finsh_getchar(void)
{
#ifdef RT_USING_POSIX
    return getchar();
#else
    char ch;

    RT_ASSERT(shell != RT_NULL);
    while (rt_device_read(shell->device, -1, &ch, 1) != 1)
        rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER);

    return ch;
#endif
}
  • 这个函数只返回一个字符,while那两行是rtthread官方文档推荐的写法
  • 在没消息到时,while为真,rt_sem_tack阻塞住了
  • 直到有消息到,回调函数释放信号量,rt_sem_tack获得信号量,就返回成功一个字符
  • 接下来继续往下看while里的代码
  • 前面很长一段都是用ifelse来处理特殊字符的情况,忽略不看了。。
  • 从shell.c源码602行开始,描述的是普通命令的情况,主要就看下面的了
/* 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)
            {
                if (shell->echo_mode)
                    rt_kprintf("\n");
                msh_exec(shell->line, shell->line_position);
            }
            else //这个else就是在这的,下面的代码懒得贴
#endif
  • 这里描述的当接收到\r或者\n符号的时候,代表控制台接收到完整的命令格式的信息了
  • 既然程序认为一个完整命令已经被接收到了,那不管对错,都把它先存放到shell结构体的存放历史信息那里。所以调用shell_push_history,不深究了
  • 然后做个判断msh是否正常工作呀,在只使用msh的模式下,源码直接就return true了...所以等于必执行进去
  • 进入msh_exec(shell->line, shell->line_position),由于宏定义没用到的代码就不贴了...
    int cmd_ret;

    /* strim the beginning of command */
    while (*cmd  == ' ' || *cmd == '\t')
    {
        cmd++;
        length--;
    }

    if (length == 0)
        return 0;

    /* Exec sequence:
     * 1. built-in command
     * 2. module(if enabled)
     * 3. chdir to the directry(if possible)
     */
    if (_msh_exec_cmd(cmd, length, &cmd_ret) == 0)
    {
        return cmd_ret;
    }
  • 第一个参数传进一个字符串,第二个参数是字符串长度(shell->line,shell->line_position)line_position在前面用strlen(shell->line)赋值过了
  • 第一个while,很贴心的把命令前的空格字符给忽略掉了
  • 如果命令是正确的,_msh_exec_cmd(cmd, length, &cmd_ret) == 0为真,就返回cmd_ret,进去看看那是什么吧
  • 不过在此之前先把命令是错误的讲完,_msh_exec_cmd挺长的
  • 下面的代码在源码中紧接着上面的代码
/* truncate the cmd at the first space. */
    {
        char *tcmd;
        tcmd = cmd;
        while (*tcmd != ' ' && *tcmd != '\0')
        {
            tcmd++;
        }
        *tcmd = '\0';
    }
    rt_kprintf("%s: command not found.\n", cmd);
    return -1;
  • 上面已经返回0了,也就是找不到这条命令的情况,跑到这里来,看看是怎么处理的
  • 中括号表示一个作用域,char* tcmd的生命周期只在此作用域内
  • 然后,由于可能命令是带参数的,参数也许很长,所以用指针tcmd扫描cmd字符串第一个出现空格的位置
  • 让这个位置等于'\0'表示字符串的结束,从而只保留命令的主体,舍弃后面的参数
  • 然后,贴心的打印出提示信息,没有找到该命令啊,然后把命令贴出来告诉你是哪条命令。

ok,现在回到_msh_exec_cmd函数中去,看看它里面是如何实现,直接贴上整个函数体代码

  • 三个参数分别是命令字符串,字符串长度,一个状态量的赋值指针(但其实源码中并没有找到数据接收这个msh_exec的返回值)
static int _msh_exec_cmd(char *cmd, rt_size_t length, int *retp)

    int argc;
    rt_size_t cmd0_size = 0;
    cmd_function_t cmd_func;
    char *argv[RT_FINSH_ARG_MAX];

    RT_ASSERT(cmd);
    RT_ASSERT(retp);

    /* find the size of first command */
    while ((cmd[cmd0_size] != ' ' && cmd[cmd0_size] != '\t') && cmd0_size < length)
        cmd0_size ++;
    if (cmd0_size == 0)
        return -RT_ERROR;

    cmd_func = msh_get_cmd(cmd, cmd0_size);
    if (cmd_func == RT_NULL)
        return -RT_ERROR;
      ...
  • 第一个while就是查找命令的主体的大小,边界条件是遇到' '或'\t'或cmd0size == length
  • 第一个主体就是主要命令,如果还没结束,空格后面的就是参数,分开处理它
  • cmd_func = msh_get_cmd(cmd, cmd0_size);
  • cmd_func是一个函数指针------>typedef int (*cmd_function_t)(int argc, char **argv);
  • msh_get_cmd(cmd,cmd0_size)的作用是找到这个字符串所对应的在FSymTab段中的函数的地址,然后返回给cmd_func
  • 它的具体实现:
static cmd_function_t msh_get_cmd(char *cmd, int size)
{
    struct finsh_syscall *index;
    cmd_function_t cmd_func = RT_NULL;

    for (index = _syscall_table_begin;
            index < _syscall_table_end;
            FINSH_NEXT_SYSCALL(index))
    {
        if (strncmp(index->name, "__cmd_", 6) != 0) continue;

        if (strncmp(&index->name[6], cmd, size) == 0 &&
                index->name[6 + size] == '\0')
        {
            cmd_func = (cmd_function_t)index->func;
            break;
        }
    }

    return cmd_func;
}
  • 创建一个finsh_syscall*类型局部变量用于迭代
  • 创建一个返回值存放结果
  • for循环遍历FSymTab段中的地址,这个段的生成在第一篇rtthread的记录中详细讲过了
  • 里面的具体实现就是匹配字符串,他有自己的一套命名规则
  • 最后找到就返回那个代码执行的地址,找不到就返回RT_NULL
  • 返回RT_NULL的话_msh_exec_cmd会返回错误的
  • 接着继续_msh_exec_cmd的代码
//前面定义了int argc和char *argv[RT_FINSH_ARG_MAX];
//RT_FINSH_ARG_MAX = 10
/* split arguments */
    memset(argv, 0x00, sizeof(argv));
    argc = msh_split(cmd, length, argv);
    if (argc == 0)
        return -RT_ERROR;

    /* exec this command */
    *retp = cmd_func(argc, argv);
    return 0;
  • 跑到这里,是等于已经成功解析到命令的存在了,接下来是处理后面的参数
  • memset先把argv清0,到这里才调用它,应该是避免了每次先清理,损失性能。因为不一定到这里
  • argc接收msh_split的返回值,msh_split三个参数分别是命令行的字符串,字符串长度,还有argv参数二维数组
  • 懒得一行行看msh_split了,功能肯定是把命令行后面的参数分别解析出来然后放进argv二维数组里去,然后统计参数个数,返回给argc
  • 然后就执行cmd_func(argc,argv)这个函数了,命令行函数的真正入口---->至此,一个正确的命令,被执行完毕。

然后又回到线程执行函数里,继续往下走...待续

#ifdef FINSH_USING_MSH
            if (msh_is_used() == RT_TRUE)
            {
                if (shell->echo_mode)
                    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
                    if (shell->echo_mode) rt_kprintf("\n");
#endif
            }

            rt_kprintf(FINSH_PROMPT);
            memset(shell->line, 0, sizeof(shell->line));
            shell->line_curpos = shell->line_position = 0;
            continue;
        }

标签:RT,rt,shell,Thread,FINSH,cmd,线程,device
来源: https://www.cnblogs.com/Joy2013Ru/p/13733913.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有