freertos学习笔记

 

本文简介

本文简介

5 任务的定义和切换

下面是两个任务的切换代码

// 1. 初始化任务就绪列表
prvInitialiseTaskLists();

// 2. 创建任务
task1_handle = xTaskCreateStatic(
    task1_entry,    // 任务的函数名
    "task1",        // 名称
    TASK1_STACK_SIZE,// 栈大小
    NULL,           // 任务参数指针
    task1_stack,    // 栈的起始地址
    &task1_tcb      // 任务控制块
)

task2_handle = xTaskCreateStatic(
    task2_entry,    // 任务的函数名
    "task2",        // 名称
    TASK2_STACK_SIZE,// 栈大小
    NULL,           // 任务参数指针
    task2_stack,    // 栈的起始地址
    &task2_tcb      // 任务控制块
)

// 3. 把任务加入就绪列表
vListInsertEnd(&(pxReadyTasksLists[1]),
               &(task1_tcb->xStateListItem))

vListInsertEnd(&(pxReadyTasksLists[2]),
               &(task1_tcb->xStateListItem))

// 4. 启动调度器,开始任务调度
vTaskStartScheduler();

while(1);

5.1 就绪列表

init

  • 就绪列表就是一个全局数组
    • List_t pxReadyTasksLists[configMAX_PRIORITIES]
  • 初始化
    • 对数组中的每一个链表进行初始化

5.1 什么是任务

  • 一个不会返回的功能块就是一个任务
      void task(void *args) {
        while(1) {
          /* 任务代码 */
        }
      }
    
  • 任务是一个独立的函数,无限循环不返回

5.2 创建任务

  • 任务创建函数
      TaskHandle_t xTaskCreateStatic(
          TaskFunction_t pxTaskCode,
          const char *pcName,
          const uint32_t ulStackDepth,
          void *const pvParameters,
          StackType_t *const puxStackBuffer,
          TCB_t * const pxTaskBuffer
      )
    
  • 任务创建函数进行的三要素的统一
    • 任务的栈
    • 任务的主题
    • 任务的控制块TCB
  • 任务创建函数 xTaskCreateStatic()完成的工作
    • 把任务栈和任务控制块联系起来
      • TCB->pxStack = puxStackBuffer
    • 把任务名拷贝到TCB中
      • TCB->pcTaskName[i] = pcName
    • 初始化TCB中的任务节点
      • TCB->xStateListItem
      • 设置节点的拥有者为任务TCB
    • 初始化任务中函数的栈
      • pxPortInitialiseStack(pxTopOfStack)
    • 返回任务句柄,它指向TCB
      • task_handle = &TCB
  • 裸机所有的变量放在栈中
    • 栈是连续的内存空间
    • 栈在启动文件链接脚本中指定
    • 有C的__main函数初始化
  • 多任务系统
    • 每个任务分配一个栈
  • 创建任务
    • 动态创建任务
    • 静态创建任务
      • FreeRTOSConfig.h
      • configSUPPORT_STATIC_ALLOCATION = 1
  • 每个任务有一个任务控制块TCB
      typedef struct tskTaskControlBlock {
        /* 栈顶 */
        volaitle  StackType_t *pxTopOfStack; 
        /* 任务节点 */
        ListItem_t xStateListItem;
        /* 栈的起始位置 */
        StackType_t *pxStack;
        /* 任务名称 */
        char pcTaskName[configMAX_TASK_NAME_LEN];
      } tskTCB;
      typedef tskTCB TCB_t;
    
  • 调度系统通过TCB来调度任务的执行、删除

5.3 插入就绪链表

insert

把节点插入对应优先级的链表中

vListInsertEnd( &pxReadyTasksLists[1],
                &task1_tcb->xStateListItem)

5.4 调度器

调度器是OS的核心,实现了任务的切换,函数是vTaskStartScheduler()

  • vTaskStartScheduler()函数
    • 设置第一个启动的任务
      • pxCurrentTCB = &task1_tcb;
    • 调用调度器
      • xPortStartScheduler()
  • xPortStartScheduler()
    • PendSVCSysTick的延后级设为最高
    • 启动第一个任务
      • prvStartFristTask()
  • prvStartFristTask()
    • 设置MSP主栈指针
      • `msp = *(int *(0xE000ED08))
    • 打开所有中断和异常
      • cpsie i/f
    • 通过SVC指令调用中断服务程序0
      • svc 0
  • 系统开始调用中断服务程序0,即SVC_Handler
    • #define vPortSVCHandler SVC_Handler
  • vPortSVCHandler()
    • 为第一个任务装备运行环境
      • 即用task1的栈顶填充CPU寄存器
    • r0 = &task1_tcb
      • TCB的第一个成员就栈顶指针pxTopOfStack
      • 这时r0 = pxTopOfStack
        top_stack

        一个刚刚创建尚未运行的任务栈空间分布

    • 手动准备第一个任务函数的运行环境
      • 把r0向上的8个字 -> [r4:r11]
      • psp = r0,更新psp栈顶指针
    • 开启中断
      • basepri = 0
    • 设置后面使用进程栈指针psp
      • r14 = 0xD
      • r14(LR)寄存器
    • 函数返回 bx r14
      • cortex自动把psp指向的栈内容恢复到寄存器中
        bx
      • psp指向新的的栈顶

5.5 任务的切换

  • 任务的切换通过taskYIELD()完成
    • 将PendSV位 = 1
  • PendSV的处理函数是xPortPendSVHandler
    • 保存上一个函数的上文
      • 自动保存的部分
        • 进入函数前,cortex自动把xPSR, PC, R14, R13, R3-R0保存在当前函数的栈中
          save

          psp已经移动到图中位置

      • 手动保存的部分
        • r2 = pxCurrentTCB
          • r2保存上一个任务的栈顶指针
        • r0 = psp
        • 把CPU的[r4: r11]保存到r0开始的8个字中
          man
        • *pxCurrentTCB = r0
          • 把r0保存到上一个任务的栈顶
    • 关闭中断(进入临界状态)
    • 调用vTaskSwitchContext()修改要切换的任务
      • pxCurrentTCB = &task2_tcb
    • 开中断
    • 切换到要执行的任务
      • r0 = pxCurrentTCB
      • 这时,pxCurrentTCB已经更新
    • 更新函数运行环境
      • 手动
        • 把要运行的任务的任务栈加载到[r4:r11]中
        • 更新psp
        • bx r14退出SV异常
          • 这时,r14 = 0xffff_fffd,使用psp
      • 自动
        • 跳出异常后,cortex自动把psp后面的内容加载到cpu的[r0:r3], r14(LR), r15(PC), xPSR中
        • 由r15(PC)引导系统跳转到新任务

6. 阻塞延时

  • 任务调用阻塞延时后,任务放弃CPU使用,进入阻塞状态知道延时结束
    • TCB中加入xTicksDealy
  • vTaskDelay(xTicksToDelay)
    • 只需要获取当前TCB
    • TCB.xTicksToDelay = xTicksToDelay
    • taskYIELD()
  • 任务切换
    • 流程
      • 调用taskYIELD()产生PendSV中断
      • PendSV中调用vTaskSwitchContext()
      • 找到优先级最高的任务,更新pxCurrentTCB
    • 思路
      • 当前任务 = 空闲任务
        • 查看task1/2的延时时间是否结束
          • 到期更新pxCurrentTCB
          • 否则继续执行空闲任务
      • 当前任务 = task1/2
        • 查看下一个taskx
          • 到期更新pxCurrentTCB
        • 查看当前任务延时时间 != 0
          • 是,进入空闲任务
          • 否,直接退出
  • SysTick中断服务函数
    • 系统每次tick产生中断,执行SysTick_Handler函数
      • #define xPortSysTickHandler SysTick_Handler
    • OS中最小的时间单位就是SysTick中断周期
    • 这个函数
      • 关中断
      • 更新系统时基
        • 给全局xTickCount++
        • 遍历所有的TCB,让TCB.xTicksToDelay--
      • 开中断
  • SysTick的初始化函数vPortSetupTimerInterrupt()
    • 直接向寄存器NVIC_SYSTICK_CTRL_REG中写入数据
    • 在xPortStartScheduler()中调用