• RTX——第7章 任务管理


    以下内容转载自安富莱电子: http://forum.armfly.com/forum.php

    单任务系统
    学习多任务系统之前,我们先来回顾下单任务系统的编程框架,即裸机时的编程框架。 裸机编程主要
    是采用超级循环(super-loops)系统,又称前后台系统。应用程序是一个无限的循环,循环中调用相应
    的函数完成相应的操作,这部分可以看做后台行为,中断服务程序处理异步事件,这部分可以看做是前台
    行为。 后台也可以叫做任务级,前台也叫作中断级。

    对于前后台系统的编程思路主要有以下两种方式:

    轮询方式
    对于一些简单的应用,处理器可以查询数据或者消息是否就绪,就绪后进行处理,然后再等待,如此
    循环下去。 对于简单的任务,这种方式简单易处理。但大多数情况下,需要处理多个接口数据或者消息,
    那就需要多次处理,如下面的流程图所示:

    用查询方式处理简单的应用,效果比较好,但是随着工程的复杂,采用查询方式实现的工程就变的很
    难维护,同时,由于无法定义查询任务的优先级,这种查询方式会使得重要的接口消息得不到及时响应。
    比如程序一直在等待一个非紧急消息就绪,如果这个消息后面还有一个紧急的消息需要处理,那么就会使
    得紧急消息长时间得不到执行。

    中断方式
    对于查询方式无法有效执行紧急任务的情况,采用中断方式就有效的解决了这个问题,下面是中断方
    式简单的流程图:

    采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了
    必须在中断(ISR)内处理时间关键运算:
    ISR 函数变得非常复杂,并且需要很长执行时间。
    ISR 嵌套可能产生不可预测的执行时间和堆栈需求。
    超级循环和 ISR 之间的数据交换是通过全局共享变量进行的:
    应用程序的程序员必须确保数据一致性。
    超级循环可以与系统计时器轻松同步,但:
    如果系统需要多种不同的周期时间,则会很难实现。
    超过超级循环周期的耗时函数需要做拆分。
    增加软件开销,应用程序难以理解。
    超级循环使得应用程序变得非常复杂,因此难以扩展:
    一个简单的更改就可能产生不可预测的副作用,对这种副作用进行分析非常耗时。
    超级循环 概念的这些缺点可以通过使用实时操作系统 (RTOS) 来解决。

    多任务系统
    针对这些情况,使用多任务系统就可以解决这些问题了。 下面是一个多任务系统的流程图:

    多任务系统或者说 RTOS 的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决
    定当前需要执行的任务。如上图所画的那样,创建了任务并完成 OS 初始化后,就可以通过调度器来决定
    任务 A,任务 B 和任务 C 的运行,从而实现多任务系统。 另外需要初学者注意的是,这里所说的多任务系
    统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更
    好的说明这个问题,再举一个详细的运行例子,运行条件如下:
    使用抢占式调度器
    1 个空闲任务,优先级最低。
    2 个应用任务,一个高优先级和一个低优先级,优先级都比空闲任务优先级高。
    中断服务程序,含 USB 中断,串口中断和系统滴答定时器中断
    下图 7.2 所示是任务的运行过程,其中横坐标是任务优先级由低到高排列,纵坐标是运行时间,时间
    刻度有小到大。

    (1) 启动 RTOS,首先执行高优先级任务
    (2) 高优先级任务等待事件标志(os_evt_wait_and)被挂起,低优先级任务得到执行。
    (3) 低优先级任务执行的过程中产生 USB 中断,进入 USB 中断服务程序。
    (4) 退出 USB 中断复位程序,回到低优先级任务继续执行。
    (5) 低优先级任务执行过程中产生串口接收中断,进入串口接收中断服务程序。
    (6) 退出串口接收中断复位程序,并发送事件标志设置消息(isr_evt_set),被挂起的高优先
    级任务就会重新进入就绪状态,这个时候高优先级任务和低优先级任务都在就绪态,基于优
    先级的调度器就会让高优先级的任务先执行,所有此时就会进入高优先级任务。
    (7) 高优先级任务由于等待事件标志(os_evt_wait_and)会再次被挂起,低优先级任务开始
    继续执行。
    (8) 低优先级任务调用函数 os_dly_wait,低优先级任务被挂起,从而空闲任务得到执行。
    (9) 空闲任务执行期间发生滴答定时器中断,进入滴答定时器中断服务程序。
    (10) 退出滴答定时器中断,由于低优先级任务延时时间到,低优先级任务继续执行。

    (11) 低优先级任务再次调用延迟函数 os_dly_wait,低优先级任务被挂起,从而切换到空闲任务。
    空闲任务得到执行。
    通过上面实例的讲解,大家应该对多任务系统完整的运行过程有了一个全面的认识。 随着教程后面对
    调度器,任务切换等知识点的讲解,大家会对这个运行过程有更深刻的理解。
    RTX 就是一款支持多任务运行的实时操作系统,具有时间片,抢占式和合作式三种调度方法。 通过
    RTX 实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。 同时 RTX 实时操作系
    统为多任务的执行提供了以下重要优势:
    任务调度 - 任务在需要时进行调用,从而确保了更好的程序执行和事件响应。
    多任务 - 任务调度会产生同时执行多个任务的效果。
    确定性的行为 - 在定义的时间内处理事件和中断。
    更短的 ISR - 实现更加确定的中断行为。
    任务间通信 - 管理多个任务之间的数据、内存和硬件资源共享。
    定义的堆栈使用 - 每个任务分配一个堆栈空间,从而实现可预测的内存使用。
    系统管理 - 可以专注于应用程序开发而不是资源管理。

    任务设置
    RTX 操作系统的配置工作是通过配置文件 RTX_Conf_CM.c 实现。 在 MDK 工程中打开文件
    RTX_Conf_CM.c,可以看到如下图 7.4 所示的工程配置向导:

    用于任务配置的主要是如下两个参数:
    Number of concurrent running tasks
    参数范围 0 – 250
    表示同时运行的最大任务数,这个数值一定要大于等于用户实际创建的任务数,空闲任务不包含
    在这个里面。比如当前的数值是 6,就表示用户最多可以创建 6 个任务。
    Number of tasks with user-provided stack
    参数范围 0 – 250
    表示自定义任务堆栈的任务数,如果这个参数定义为 0 的话,表示所有的任务都是使用的配置向
    导里面第三个参数 Task statck size 大小。 比如:
    Number of concurrent running tasks = 6
    Number of tasks with user-provided stack = 0
    表示允许用户创建 6 个任务,所有的 6 个任务都是分配第三个参数 Task statck size 大小的任务
    堆栈空间。
    Number of concurrent running tasks = 6
    Number of tasks with user-provided stack = 3
    表示允许用户创建 6 个任务,其中 3 个任务是用户自定义任务堆栈大小,另外 3 个任务是用的第
    三个参数 Task statck size 大小的任务堆栈空间。

    栈溢出检测
    如果怕任务栈溢出,那么此功能就非常的有用了,用户只需在 RTX 的配置向导里面使能使用任务栈检测即可: 

    RTX 初始化和启动
    使用如下 3 个函数可以实现 RTX 的初始化:
    os_sys_init()
    os_sys_init_prio()
    os_sys_init_user()

     关于这 3 个函数的讲解及其使用方法可以看参考资料 rlarm.chm 文件 :

    函数描述:
    函数 os_sys_init_user 用于实现 RTX 操作系统的初始化和启动任务的创建,并且使用这个函数做初始化还
    可以自定义任务栈的大小。
    第 1 个参数填启动任务的函数名。
    第 2 个参数是任务的优先级设置,用户可以设置的任务优先级范围是 1-254。 优先级 0 用于空闲任
    务,如果用户将这个参数设置为 0 的话,RTX 系统会将其更改为 1。 优先级 255 被保留用于最重要
    的任务。
    第 3 个参数是任务栈地址。
    第 4 个参数是任务栈大小。
    使用这个函数要注意以下几个问题
    1. 必须在 main 函数中调用 os_sys_init_user。
    2. 任务栈空间必须 8 字节对齐,可以将任务栈数组定义成 uint64_t 类型即可。 (补充说明,这就是为什么我们定义任务栈的时候要用大小除以8,因为sizeof所求大小需要乘以元素字节,这里定义的正好是uint64_t,八个字节的,这样能保证是八字节对齐的)

    3. 优先级 255 代表更重要的任务。
    4. 对于 RTX 操作系统来说,优先级参数中数值越小优先级越低,也就是说空闲任务的优先级是最低的,因为它的优先级数值是 0 。

    任务创建
    使用如下 4 个函数可以实现 RTX 的任务创建:
    os_tsk_create
    os_tsk_create_ex
    os_tsk_create_user
    os_tsk_create_user_ex

    ex后缀是extension的缩写,看英文文档就会知道的,表示扩展。

    函数描述:
    函数 os_tsk_create_use 用于实现 RTX 操作系统的任务创建,并且还可以自定义任务栈的大小。
    第 1 个参数填创建任务的函数名。
    第 2 个参数是任务的优先级设置,用户可以设置的任务优先级范围是 1-254。 优先级 0 用于空闲任
    务,如果用户将这个参数设置为 0 的话,RTX 系统会将其更改为 1。 优先级 255 被保留用于更重要
    的任务。 如果新创建的任务优先级比当前正在执行任务的优先级高,那么就会立即切换到高优先级
    任务去执行。
    第 3 个参数是任务栈地址。
    第 4 个参数是任务栈大小。
    函数的返回值是任务的 ID,使用 ID 号可以区分不同的任务。
    使用这个函数要注意以下问题
    1. 任务栈空间必须 8 字节对齐,可以将任务栈数组定义成 uint64_t 类型即可。

    任务删除
    使用如下 2 个函数可以实现 RTX 的任务删除:
    os_tsk_delete
    os_tsk_delete_self

    函数描述:
    函数 os_tsk_create_use 用于实现 RTX 操作系统的任务删除
    第 1 个参数填要删除任务的 ID。
    如果任务删除成功,函数返回 OS_R_OK,其余所有情况返回 OS_R_NOK,比如所写的任务 ID 不存在。

    使用这个函数要注意以下问题:
    1. 如果用往此函数里面填的任务 ID 是 0 的话,那么删除的就是当前正在执行的任务,此任务被删除后,
    RTX 会切换到任务就绪列表里面下一个要执行的高优先级任务。

     空闲任务
    几乎所有的小型 RTOS 中都会有一个空闲任务,空闲任务应该属于系统任务,是必须要执行的,用
    户程序不能将其关闭。不光小型系统中有空闲任务,大型的系统里面也有的,比如 WIN7,下面的截图就
    是 WIN7 中的空闲进程。

    空闲任务主要有以下几个作用:
    用户不能让系统一直在执行各个应用任务,这样的话系统利用率就是 100%,系统就会一直的超负荷
    运行,所以空闲任务很有必要。
    为了更好的实现低功耗,空闲任务也很有必要,用户可以在空闲任务中实现睡眠,停机等低功耗措施。

    代码练兵场:

    K1 按键按下,删除任务 AppTaskLED。
    K2 按键按下,重新创建任务 AppTaskLED。
    不按键的时候,LED和Beep翻转。

    int main ( void )
    {    
        Bsp_Init();
     
        /* 创建启动任务 */
         os_sys_init_user (AppTaskStart,             /* 任务函数 */
                          4,                        /* 任务优先级 */
                          &AppTaskStartStk,         /* 任务栈 */
                          sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
        while ( 1 )
        {
           
        }
    
    }
    __task void AppTaskUserIF(void)
    {
    
    
        while(1)
        {
                if(key1_flag==1)
                {
                    key1_flag=0;
                    printf("K1键按下,删除任务HandleTaskLED
    ");
                        if(HandleTaskLED != NULL)
                        {
                            if(os_tsk_delete(HandleTaskLED) == OS_R_OK)
                            {
                                HandleTaskLED = NULL;
                                printf("任务AppTaskLED删除成功
    ");
                            }
                            else
                            {
                                printf("任务AppTaskLED删除失败
    ");                    
                            }
                        }
                }
                if(key2_flag==1)
                {
                    key2_flag=0;
                    printf("K2键按下,重新创建任务AppTaskLED
    ");
                        if(HandleTaskLED == NULL)
                        {
                            HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任务函数 */ 
                                                               2,                       /* 任务优先级 */ 
                                                               &AppTaskLEDStk,          /* 任务栈 */
                                                               sizeof(AppTaskLEDStk));  /* 任务栈大小,单位字节数 */
                        }
                }
                os_dly_wait(20);
            }
    }

    输出打印:

    可以看到按下K1时,LED灯不再翻转,按下K2后,又开始翻转。

    RTX 配置:
    RTX 配置向导详情如下:

  • 相关阅读:
    顺序前缀改为随机性前缀 反转时间戳 反转年月日
    后台组件的治理思路
    干货 | 高耦合场景下,Trip.com如何做支付设计与落地
    每天响应数亿次请求,腾讯云如何提供高可用API服务?
    系统管理及操作命令
    远程连接及系统管理
    linux系统部署安装过程
    day 1 硬件组成概念及介绍笔记
    day 4
    day 3
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/7327612.html
Copyright © 2020-2023  润新知