Toriyung

导航

freertos-刘火良:内核实现

定义习惯

  变量

    将变量类型缩写当作前缀,如无符号字符uc,字符指针pc,数据结构、任务句柄等用x

  函数

    返回值类型缩写当作前缀,如无返回v,私有函数加pri前缀

  宏定义

    宏定义大写,所在头文件名字缩写为前缀,小写。(信号量函数是宏定义,但命名按函数定义)

    

 

    通用宏定义

     

 

链表实现

  节点定义

    根节点:根节点实际上就是一个链表(或者说链表头),所以其为第一个节点同时也是最后一个节点。属性有:链表上的节点个数(不包括根节点)、索引值(用来遍历节点,指向入口节点)、最后一个节点(本质是精简节点MINIitem):根节点自身item,和普通节点一样有辅助值、前一节点后一节点。

      

 

    节点:属性为辅助排序值(确定该节点在链表中顺序,作用是确定优先级进行排序)、前一个节点、后一个节点、owner(挂载的TCB结构体)、container(节点所在链表)

      

 

  节点初始化 

    根节点初始化:初始化时由于没有其他节点,则前一个节点和后一个节点都指向自身,index索引值指向最后一个节点即同样指向自身,链表上挂载节点数为0,根节点自身排序为最大MAX

    

 

 

 

    节点初始化:节点初始化时不挂载任何链表,即container = NULL

    

 

 

 

向链表添加节点  

  链表尾部添加节点

    尾部添加涉及到三个节点的属性更改

    新节点:新节点的前一个节点为根节点原前一个节点,即NewItem->pxPrevious = pxIndex->pxPrevious;新节点的下一个节点为根节点,即NewItem->pxNext = pxIndex;同时新节点挂载链表为指定链表,即container = list。

    根节点原前一个节点:作为根节点原前一个节点,其下一个节点不再是第一个节点即根节点,而是插入的新节点,即pxIndex->pxPrevious->pxNext = NewItem

    根节点:根节点前一个节点将变成新节点,即pxIndex->pxPrevious = NewItem;同时链表节点计数器加一

    

  按辅助顺序值添加节点

    设置一个临时节点iterator,使用for循环进行遍历,当iterator的下一个节点的辅助值大于要添加的节点的顺序值时,则将新节点位置确定在iterator和iterator->next之间。

    

 

创建任务

  所谓创建任务,其实就是给TCB对象内属性初始化

  首先看TCB结构体

typedef struct tasTaskControlerBlock
{
    volatile StackType_t *pxTopOfStack;                        //栈顶
    ListItem_t xStateListItem;                                 //任务节点(链表项)
    StackType_t *pxStack;                                      //任务栈起始地址
    char pcTaskName[configMAX_TASK_NAME_LEN];                //任务名称
}tskTCB;    
typedef tskTCB    TCB_t;

  可以看到TCB结构体内有栈顶、栈起始地址、任务节点即链表项、任务名称。我们要做的就是对这四个值进行赋值初始化

  

 

  从逻辑框图可以看到,初始化分为三个函数,第一个函数TaskCreate主要是用来初始化TCB和TCB栈,简单来说就是内部创建一个TCB,初始化为传进的TCB;同理TCB栈,然后下面调用属性初始化函数对其进行初始化,最后返回一个TCB指针的指针(二级指针),即句柄TaskHandle_t

TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * pvParameters, StackType_t puxStackBuffer, TCB_t * const pxTaskBuffer)
    {
        /*
            brief:创建新任务,实质就是给TCB对象内的成员初始化(赋值)
            param:
                pxTaskCode:任务函数
                pcName:任务名称
                ulStackDepth:任务栈深
                pvParameters:任务函数传参
                puxStackBuffer:任务栈起始地址
                pxTaskBuffer:TCB指针
            return:任务句柄
        */
        
        TCB_t *pxNewTCB;
        TaskHandle_t    xReturn;    
        
        if((pxTaskBuffer != NULL) && (puxStackBuffer != NULL))    //当TCB存在且任务栈存在才进行创建
        {
            pxNewTCB = (TCB_t * )pxTaskBuffer;                                        //初始化TCB
            pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;        //初始化TCB栈        
            prvInitialiseNewTask(pxTaskCode,pcName,ulStackDepth,pvParameters,&xReturn,pxNewTCB);        //    
        }
        else
        {
            xReturn = NULL;
        }
        
        return xReturn;
    }

  第二个函数InitTask初始化TCB,主要是计算栈顶,对齐,定义任务名称,挂载TCB到任务节点上,伪造现场

static void prvInitialiseNewTask(TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * pvParameters, TaskHandle_t * const pxCreatedTask, TCB_t *pxNewTCB)
{
    /*    
        brief:初始化新任务    
        param:
            pxTaskCode:任务函数
            pcName:任务名称
            ulStackDepth:任务栈深
            pvParameters:任务函数传参
            pxCreatedTask:任务句柄,指向TCB
            pxNewTCB:TCB
        return:NULL
    
    */
    
    StackType_t *pxTopOfStack;
    UBaseType_t x;
    
    //计算栈顶
    pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
    pxTopOfStack = (StackType_t *)((uint32_t)pxTopOfStack & ~((uint32_t)0x0007));    //向下对齐八个字节
    
    //初始化任务栈(伪造现场)
    pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters);
    
    //初始化任务名称
    for(x = (UBaseType_t)0;x < (UBaseType_t)configMAX_TASK_NAME_LEN;x++)
    {
        pxNewTCB->pcTaskName[x] = pcName[x];
        if(pcName[x] == 0x00)
        {
            break;
        }
    }
    pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN -1] = '\0';    //最后一个字符为结束符?
    
    //初始化任务节点
    vListInitialiseItem(&(pxNewTCB->xStateListItem));
    
    //设置节点owner
    listSET_LIST_ITEM_OWNER((pxNewTCB->xStateListItem),pxNewTCB);    //关联owner,令该任务节点挂载TCB
    
    //任务句柄指向TCB
    if((void *) pxCreatedTask != NULL)
    {
        *pxCreatedTask = (TaskHandle_t) pxNewTCB;
    }
}

  第三个函数InitStack主要就是伪造现场,由于初始化时是第一次对栈进行操作,此时寄存器并没有实际意义的值可以入栈,于是自定义大部分的值到栈中

StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
{
    /*
        初始化任务栈---伪造现场,按顺序进行填充各寄存器的值(伪)
    */
    pxTopOfStack--;        //暂时不明白为什么要先减一
    *pxTopOfStack = portINITIAL_XPSR;        //PSR寄存器
    pxTopOfStack--;
    *pxTopOfStack = ((StackType_t)pxCode) & portSTART_ADDRESS_MASK;        //任务函数入口地址:PC
    pxTopOfStack--;
    *pxTopOfStack = (StackType_t) prvTaskExitError;        //万一任务返回,直接进入error函数死循环
    pxTopOfStack -= 5;        //R1~R3,R12,R14
    *pxTopOfStack = (StackType_t) pvParameters;        //任务参数:r0
    
    pxTopOfStack -= 8;    //R4~R11寄存器手动加载,伪造现场时无需管
    
    return pxTopOfStack;
    
}

 

调度器(vTaskStartScheduler)

  rtos的本质就是调度器进行任务的切换,调度器的本质就是一个死循环和中断,通过中断进行当前任务TCB(pxCurrentTCB)的切换,从时间顺序来说,首先启动任务,然后后续就是不断切换任务。下面给出启动任务和切换任务的具体原理

启动任务

  pxCurrentTCB指向第一个任务TCB

  xPortStartScheduler:设置PendSV和SYSTICK优先级为最低

  内嵌汇编 prvStartFirstTask:寻找主栈位置,开启中断,触发SVC中断

  SVC中断 vPortSVCHandler:其为宏定义的SVC_Handler。手动出栈使得psp更新到r0位置即r11之后,然后硬件出栈让psp更新到栈顶,此时r15寄存器即PC值为任务1的函数

  

切换任务

  一般情况下,通过触发PendSV中断进行任务切换,由内嵌汇编 xPortPendSVHandler实现,切换任务具体分为三个部分

  1. Task1的现场保存 

    找到psp,在进入PendSV中断时xPSR等寄存器硬件自动压栈,如图1此时psp指向r11,赋值给r0,利用r0手动将r11到r4压栈;

    

 

图1 psp指向

 

 

    找到栈顶存放地址r2,利用其修改栈顶位置为r0,此时r0如图2

图2 r0指向

    

    将Task1的TCB指针和此时r14的值保存到主栈中

  

  2. 进入临界区,切换任务

    vTaskSwitchContext:实质上就是关闭其他中断,对“当前任务TCB指针”指向优先级最高的任务TCB,然后恢复其他中断出临界区

 

  3. Task2的现场恢复

    由2,从主栈中出栈TCB指针和r14值;根据TCB指针找到栈顶位置,由1可推断,上次Task2保存现场时栈顶位置为r4值所在位置,如图3

    

图3 栈顶位置

 

    然后手动进行出栈到r4~r11,此时r0位置为

 
图4 r0指向

 

    将r0值赋给psp,使用bx指令返回,硬件根据psp对剩下xPSR等寄存器进行出栈

 

  

 调度器总体框架示意图

 

 

  

posted on 2022-11-05 23:22  Toriyung  阅读(169)  评论(0编辑  收藏  举报