全网最全最细 FreeRTOS 手册详解——3-Task Management

这一章节的概念是理解如何使用 FreeRTOS 和 FreeRTOS 应用行为的基础。

3.1 Task Function

// Task 函数原型
void ATaskFunction(void* pvParameters);

// 例子
void ATaskFunction(void* pvParameters) {
   
    int32_t lVariableExample = 0;
    
    for (;;) {
   };
    
    // NULL 表示的是销毁的任务应该是调用销毁函数的那个任务
    vTaskDelete(NULL);
}

每个任务都是一个小程序。任务函数有一个入口,并且通常会在死循环中执行,不会退出循环。任务禁止在他们的实现函数中以任何方式 return,不允许包含任何 return 语句,并且不允许执行到函数的结尾。如果一个任务不再被需要,他应该被显示的销毁。一个任务的定义可以用来创建任意数量的任务实例,每个实例都有其栈和任务函数中定义的变量的拷贝副本。

3.2 顶级任务状态(Top Level Task States)

在这里插入图片描述
一个应用可以由多个任务组成。单核上执行应用,则任意时刻最多只有一个任务在运行。因此一个任务可以看作两种状态:运行态或未运行。这是一个过度简化的模型。其实未运行态还有很多子状态。

  • 运行态:处理器执行相关 task 的代码
  • 非运行态:任务处于休眠,他的一些状态信息应该保存好,以便下次调度器决定让其进入运行态时可以恢复执行相关代码
  • 恢复执行:当一个任务恢复执行时,他应该从上次离开运行态之前将要执行的指令开始执行。

一个任务从非运行态切换到运行态叫做换入,相反叫做换出。在整个系统中只有 FreeRTOS 调度器可以切换任务进出。

3.3 创建任务

xTaskCreate API

FreeRTOS v9.0.0 也包含了 xTaskCreateStatic 函数,可以在编译阶段静态的创建一个任务,并为其分配所需内存。
差不多是所有 API 中最复杂的,上来就遇到不是很幸运,但是是必须的。因为创建任务是多任务系统的最基础的组件。

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
                        const char* const pcName,
                        uint16_t usStackDepth,
                        void* pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t* pxCreatedTask );
  • pvTaskCode:是简单的永远不退出的 C 函数。因此常用一个无限循环实现。 pvTaskCode 是一个指向任务实现函数的指针。(实际上函数指针,typedef void (* TaskFunction_t)( void * ) )
  • pcName:任务的描述性名称。FreeRTOS 不会使用,纯粹是为了 debug 方便。以人可阅读的方式标识一个任务。通过常量 configMAX_tASK_NAME_LEN 定义最大长度,超过则自动截断。长度 = 字符个数 + 终止符。
  • usStackDepth:当任务创建的时候,每个任务都会有一个由内核分配的栈。该参数告诉内核创建多大的栈。单位是字长。例如,栈是 32 位宽的,那么传入 100,将会分配 400 字节。栈深度乘以栈宽度不能超过 uint16_t 可以表示的最大值。在 demo 应用中,根据使用的处理器架构为常量所赋的值是推荐给任何任务的最小值。如果任务需要很多栈空间,那么值一定要比这个值大。没有很好的方法确定一个任务需要的栈空间。可以尝试去计算,但是大多数情况下是赋一个差不多合理的值,然后通过 FreeRTOS 提供的特性确保栈空间充足而且不会造成不必要的浪费。Stack Overflow 包含了如何查询任务使用过的最大栈空间
  • pvParameters:void* 类型的指针,指向了要传给任务的参数。
  • uxPriority:定义了任务以什么样的优先级执行。优先级范围 [0, configMAX_PRIORITIES - 1]。传入大于最大值的值,会默认赋予任务最大的合法优先级。
  • pxCreatedTask:传递一个正在创建的任务的 handle,这个 handle 可以在其他 API 调用中引用这个任务,例如 改变任务优先级,销毁任务等。如果不使用则传入 NULL。
  • Returned value:pdPASS、pdFAIL。前者说明任务成功创建,后者说明没有足够的 heap 空间可用,来分配给任务的数据结构和任务。

xTaskCreate 函数流程

  1. 如果栈增长方向,地址减小,那么先为任务分配栈,后分配 TCB(Task Control Block),否则先 TCB 后分配栈。(保证栈增长不会占用 TCB,那会不会越界到其他内核对象或应用的地址里呢?)下面 m1, m2 代指分配顺序第一个和第二个。为 Stack 分配的字节数为 depth * sizeof(StackType_t) 即深度 * 栈的位宽
    1. 分配 m1,成功则下一步,否则释放 m1 后结束分配
    2. 分配 m2,成功则用 TCB->Stack 指向 Stack 的地址,否则释放 m1 后结束分配
  2. 分配成功则进行下一步,否则返回指定的错误代码,函数结束
  3. 如果支持动态分配, 把 TCB 标记为动态创建的,防止之后被删除(【暂时不太理解为什么会被删除】源代码是这样备注的)。
  4. 根据函数指针、函数名字、栈深度、任务参数、优先级、handle、TCB 起始地址调用初始化新任务的函数
  5. 把 TCB 加入到 ReadList
  6. 返回 pdPASS

初始化新的任务

  1. 任务是否应以私有模式创建?(【暂时不理解私有模式创建任务】)
  2. 栈是否需要设置为已知的值(以帮助 debug。此外应尽量减少对 memset 的依赖【为什么需要减少未知】)
  3. 根据栈增长方向,取到栈顶,然后内存对齐。(TCB->pxEndOfStack 记录栈的高地址,而不是记录栈空间用完后的位置)
  4. 按 UBaseType_t 类型拷贝任务名字(遇到 0x00 就意味着肯定拷贝完了,就停止)
  5. 检查优先级是否合法(大于等于最大优先级,则置为最大优先级 - 1),并赋给 TCB
  6. 初始化列表项(State 列表项、Event 列表项,确保列表项不属于任何列表,并设置两个检查值,防止列表项被复写造成数据污染)
    1. 设置 State\Event 列表项所属的 TCB
    2. 设置 Event 列表项的 item value 值为 最高优先级减去其优先级(代码中这里注释,事件总是按优先级排序,我猜测这里就是从小到大排序,值越小表明优先级越高)
  7. 然后就是一些杂七杂八的配置(【MPU wrapper】这里重点是根据 MPU 及其是否有 wrapper 的工作比较重要,需要 CPU 一些知识)

加入创建新任务到 ReadList

  1. 使用 taskENTER_CRITICAL 进入操作任务队列的临界区,确保中断不会访问正在更新的列表
  2. 如果 pxCurremtTCB 当前没有指向一个任务(如果是第一个任务,则初始化一下任务列表,当前任务指向 创建的新任务。【当前任务指调度器开始运行应该运行的任务】)
  3. 否则如果 pxCurremtTCB 已经有指向,检查调度器如果还未开始运行(pxCurremtTCB 指向优先级较大的任务)
  4. 任务数量 ++
  5. 把 TCB 加入到就绪任务列表(其实是按优先级的指针数组),并设置其状态为就绪
  6. 退出临界区使用 taskEXIT_CRITICAL
  7. 如果调度器还未运行且当前任务优先级小于新创建的,那么执行 taskYIELD_IF_USING_PREEMPTION(当前任务让出,如果使用抢占式调度器)

example1:普通的创建任务

void vTask1 ( void* pvParameters ) 
{
   
    const char* pcTaskName = "task1 is running\r\n";
    volatile uint32_t ul;
    for (;;)
    {
   
        vPrintString( pcTaskName );
        for ( ul = 0; ul < mainDELAY_LOOP_COUNT; ul ++ ) 
        {
   
        }
    }
}

void vTask2 ( void* pvParameters ) 
{
   
    const char* pcTaskName = "task2 is running\r\n";
    volatile uint32_t ul;
    for (;;)
    {
   
        vPrintString( pcTaskName );
        for ( ul = 0; ul < mainDELAY_LOOP_COUNT; ul ++ ) 
        {
   
        }
    }
}
int main()
{
   
    /*......*/
    
    xTaskCreate(    vTask1,      // function pointer
                    "task 1",    // pcName
                    1000,        // stack depth
                    NULL,        // task parameters
                    1,           // task priority 
                    NULL );      // task handle
    
    
    xTaskCreate(    vTask2,      // function pointer
                    "task 2",    // pcName
                    1000,        // stack depth
                    NULL,        // task parameters
                    1,           // task priority 
                    NULL );      // task handle
                    
    vTaskStartScheduler();    // 任务调度器
    
    /*......*/
}

在这里插入图片描述
众所周知的一张图,就不详细介绍了,意思就是单核处理器上任务一、二肯定不是同时运行的,而是每个任务占一定的时间,之后就会被换出,换另一个任务执行。

example2:任务中创建任务

void vTask1 ( void* pvParameters ) 
{
   
    const char* pcTaskName = "task1 is running\r\n";
    volatile uint32_t ul;
    
    
    xTaskCreate(    vTask2,      // function pointer
                    "task 2",    // pcName
                    1000,        // stack depth
                    NULL
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥羊也

感谢给肥羊投喂!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值