FreeRTOS学习 任务管理(一)

任务管理(一)

freertos学习 仓库地址:https://gitee.com/killerp/free-rtos_-study

freertos是一个实时操作系统,其相比裸机最大的特点就是支持多任务。学习rtos,核心就是学习任务相关知识及其实现。

多任务是指多个任务轮流使用CPU,每个任务是一个死循环的函数,“调度器”会合理安排这些任务去得到CPU的使用。因此任务就有了状态的区分,正在使用CPU的任务处于运行态,等待CPU的任务处于就绪态,而等待某一资源的任务处于阻塞态。最后还有处于挂起态的任务,他不会被调度器管理。

一、双向链表

在操作系统中有多个处于不同状态的任务,就需要将他们有效的组织起来,使系统更加高效。freertos中使用双向链表来管理这些任务的状态。其代码位于list.c

链表的定义如下:

//定义链表、被调度器使用
typedef struct xLIST
{
    volatile UBaseType_t uxNumberOfItems;       //链表的节点数量
    ListItem_t * configLIST_VOLATILE pxIndex;   //用于遍历链表
    MiniListItem_t xListEnd;                    //链表的最后一个节点              
} List_t;

链表节点的定义:

链表是为了方便管理任务,那么一个节点必然对应一个任务。另外还需要节点值来帮助链表进行排序。

//链表节点定义
struct xLIST_ITEM
{
    configLIST_VOLATILE TickType_t xItemValue;          //节点的值、决定节点在链表中的位置        
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;         
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;    
    void * pvOwner;                                     //节点的拥有者、一般是任务控制块        
    struct xLIST * configLIST_VOLATILE pxContainer;     //节点所在的链表           
};
typedef struct xLIST_ITEM ListItem_t;   

同时定义了一个处于链表末尾的最小节点,他不代表任何任务,只用于辅助在链表中的查找。

freertos的链表非常简单,提供了插入、删除、查询等最基本的操作。

二、任务控制块

任务是程序员抽象出来的概念,在代码中是通过任务控制块TCB来实现。任务控制块帮助调度器管理任务,任务控制块含有一个任务的所有信息。

如下是一个精简版的任务控制块:

/*
 * 任务控制块
 * 任务控制块包含一个任务运行的所有信息。任务的堆栈地址、状态等
 */
typedef struct tskTaskControlBlock
{
    volatile StackType_t *pxTopOfStack;       //指向任务栈顶 注意需要volatile修饰

    ListItem_t xStateListItem;                //任务状态节点、用于将任务挂在就绪、阻塞、挂起链表
    ListItem_t xEventListItem;                //将任务挂在时间链表
    UBaseType_t uxPriority;                   //任务优先级,0为最低
    StackType_t *pxStack;                     //指向任务栈基地址
    char pcTaskName[configMAX_TASK_NAME_LEN]; //任务名称

} tskTCB;
  • 任务需要有一个任务栈,来保存任务的上下文环境,需要栈基和栈顶指针。

  • 使用链表节点标记任务的状态、任务的等待的事件。通过节点将任务组织到对应的链表中。

  • freertos是支持抢占的实时操作系统,每个任务有优先级。

三、创建任务

很疑惑,任务不就是一个函数吗?那么函数的入口应该保存在哪里?

任务函数的入口地址应该保存在栈中,当任务开始运行时,调度器会将函数入口地址从栈中复制到PC,随后CPU就跳转到任务函数执行。

所以接下来就是创建一个任务。(这里以动态内存创建为例,freertos的内存管理详见:)

创建一个任务,需要创建一个任务控制块TCB,以及一个任务栈来保存任务的运行环境。接着初始化任务控制块以及任务栈,任务就可以投入运行了,就将任务插入到就绪链表中,等待调度器调度运行。一共三步:申请内存、初始化、插入就绪表,分别由三个函数完成:

BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
                       const char *const pcName,
                       const configSTACK_DEPTH_TYPE usStackDepth,
                       void *const pvParameters,
                       UBaseType_t uxPriority,
                       TaskHandle_t *const pxCreatedTask)
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

//若栈向上生长,则先分配tcb,后分配栈,防止栈覆盖了tcb(tcb地址小于栈基地址)
#if (portSTACK_GROWTH > 0)
    {
        pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));

        if (pxNewTCB != NULL)
        {
            //申请一段内存作为栈、地址保存在tcb中,后续可释放
            pxNewTCB->pxStack = (StackType_t *)pvPortMalloc((((size_t)usStackDepth) * sizeof(StackType_t)));

            if (pxNewTCB->pxStack == NULL)
            {

                vPortFree(pxNewTCB);
                pxNewTCB = NULL;
            }
        }
    }
#else  /* portSTACK_GROWTH */
    {
        //若栈向下生长,则先分配栈,后分配tcb,防止栈覆盖了tcb(tcb地址大于栈基地址)
        StackType_t *pxStack;

        pxStack = pvPortMalloc((((size_t)usStackDepth) * sizeof(StackType_t)));

        if (pxStack != NULL)
        {

            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));

            if (pxNewTCB != NULL)
            {

                pxNewTCB->pxStack = pxStack;
            }
            else
            {

                vPortFree(pxStack);
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
#endif /* portSTACK_GROWTH */

    if (pxNewTCB != NULL)
    {
		//初始化新任务
        prvInitialiseNewTask(pxTaskCode, pcName, (uint32_t)usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL);
        //插入就绪表
        prvAddNewTaskToReadyList(pxNewTCB);
        xReturn = pdPASS;
    }
    else
    {
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }

    return xReturn;
}

初始化:

static void prvInitialiseNewTask(TaskFunction_t pxTaskCode,
                                 const char *const pcName,
                                 const uint32_t ulStackDepth,
                                 void *const pvParameters,
                                 UBaseType_t uxPriority,
                                 TaskHandle_t *const pxCreatedTask,
                                 TCB_t *pxNewTCB,
                                 const MemoryRegion_t *const xRegions)
{
    StackType_t *pxTopOfStack;
    UBaseType_t x;
	//设置堆栈地址
    {
        //初始化堆栈为默认值、帮助debug
        (void)memset(pxNewTCB->pxStack, (int)tskSTACK_FILL_BYTE, (size_t)ulStackDepth * sizeof(StackType_t));
    }

#if (portSTACK_GROWTH < 0)
    {
        //栈向下生长、故初始化后的栈顶在高地址
        pxTopOfStack = &(pxNewTCB->pxStack[ulStackDepth - (uint32_t)1]);
        //TODO 堆栈的边沿对齐计算方式  pxTopOfStack & 0xfff8 地址是8的倍数
        pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE)pxTopOfStack) & (~((portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));

        //检测边沿对齐是否正确
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
    }
#else  /* portSTACK_GROWTH */
    {
        //堆栈向上生长、初始化栈顶指针在低地址 也就是数组起始地址
        pxTopOfStack = pxNewTCB->pxStack;

        //TODO 为何在这里没有内存对齐?可能是内存在分配时就考虑到?
        configASSERT((((portPOINTER_SIZE_TYPE)pxNewTCB->pxStack & (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));

        //设置堆栈最高有效地址
        pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
    }
#endif /* portSTACK_GROWTH */

    //复制任务名称到TCB
    if (pcName != NULL)
    {
        for (x = (UBaseType_t)0; x < (UBaseType_t)configMAX_TASK_NAME_LEN; x++)
        {
            pxNewTCB->pcTaskName[x] = pcName[x];

            if (pcName[x] == (char)0x00)
            {
                break;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        //确保字符串的正确结束
        pxNewTCB->pcTaskName[configMAX_TASK_NAME_LEN - 1] = '\0';
    }
    else
    {
        pxNewTCB->pcTaskName[0] = 0x00;
    }

    //设置任务优先级
    if (uxPriority >= (UBaseType_t)configMAX_PRIORITIES)
    {
        uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;

	//设置信号量优先级
    {
        pxNewTCB->uxBasePriority = uxPriority;
        pxNewTCB->uxMutexesHeld = 0;
    }

    //初始化任务的节点
    vListInitialiseItem(&(pxNewTCB->xStateListItem));
    vListInitialiseItem(&(pxNewTCB->xEventListItem));

    listSET_LIST_ITEM_OWNER(&(pxNewTCB->xStateListItem), pxNewTCB);

    //设置事件节点的值,优先级越高、节点值越小,在链表中排序越靠前
    listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
    listSET_LIST_ITEM_OWNER(&(pxNewTCB->xEventListItem), pxNewTCB);

    {
        pxNewTCB->uxCriticalNesting = (UBaseType_t)0U;
    }

#if (configUSE_TASK_NOTIFICATIONS == 1)
    {
        memset((void *)&(pxNewTCB->ulNotifiedValue[0]), 0x00, sizeof(pxNewTCB->ulNotifiedValue));
        memset((void *)&(pxNewTCB->ucNotifyState[0]), 0x00, sizeof(pxNewTCB->ucNotifyState));
    }
#endif

    //初始化任务的堆栈使其看起来任务好像已经在运行了,这里需要根据硬件实现
    pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters);

    if (pxCreatedTask != NULL)
    {
        //返回任务的句柄
        *pxCreatedTask = (TaskHandle_t)pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}
/*
 * 初始化任务栈 返回更新后的栈顶地址
 * 需要由硬件架构决定具体的实现
 */
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
                                     TaskFunction_t pxCode,
                                     void * pvParameters )
{
    //模拟任务已经在运行时的栈的情况
    pxTopOfStack--;                                                      /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
    *pxTopOfStack = portINITIAL_XPSR;                                    /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; //存放任务函数入口地址,载入PC
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError;                    //LR寄存器

    pxTopOfStack -= 5;                                                   //R12, R3, R2 and R1
    *pxTopOfStack = ( StackType_t ) pvParameters;                        //传递函数参数 R0
    pxTopOfStack -= 8;                                                   // R11, R10, R9, R8, R7, R6, R5 and R4.

    return pxTopOfStack;                                                   
}

从Cortex-M3权威指南中,我们可以查到,任务栈中一般存放的内容:(按入栈先后顺序)

xPSR ,PC,LR,R12,R3,R2,R1,R0,R11,R10,R9,R8,R7,R6,R5,R4,其中R11-R4需要通过代码入栈,其他寄存器由硬件自动操作。这一部分将在任务调度详解。

在这里插入图片描述

四、删除任务

有创建就有删除、而且删除基本就是创建的逆过程。当任务不需要再运行时、可删除任务,删除步骤:移出链表、释放内存、任务调度。(若当前正在运行的任务被删除,那么需要任务调度来选一个新任务运行)

void vTaskDelete(TaskHandle_t xTaskToDelete)
{
    TCB_t *pxTCB;
	//进入临界区
    taskENTER_CRITICAL();
    {
        //获取即将删除的tcb
        pxTCB = prvGetTCBFromHandle(xTaskToDelete);

        //将任务的状态节点移除链表 并重置就绪链表
        if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0)
        {
            taskRESET_READY_PRIORITY(pxTCB->uxPriority);
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        //若任务在等待某件事件、则移除该事件链表
        if (listLIST_ITEM_CONTAINER(&(pxTCB->xEventListItem)) != NULL)
        {
            (void)uxListRemove(&(pxTCB->xEventListItem));
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        uxTaskNumber++; //TODO 为何?

        if (pxTCB == pxCurrentTCB)
        {

            //任务删除自己、需要将任务插入任务终止链表、由空闲任务来释放内存
            vListInsertEnd(&xTasksWaitingTermination, &(pxTCB->xStateListItem));

            ++uxDeletedTasksWaitingCleanUp;	//标记有任务需要被释放内存

            traceTASK_DELETE(pxTCB);

            /* The pre-delete hook is primarily for the Windows simulator,
                 * in which Windows specific clean up operations are performed,
                 * after which it is not possible to yield away from this task -
                 * hence xYieldPending is used to latch that a context switch is
                 * required. */

            portPRE_TASK_DELETE_HOOK(pxTCB, &xYieldPending);
        }
        else
        {
            --uxCurrentNumberOfTasks;   //当前任务总数减少1
            traceTASK_DELETE(pxTCB);
            prvDeleteTCB(pxTCB);    //释放内存

            //重置下次解锁时间
            prvResetNextTaskUnblockTime();
        }
    }
    //退出临界区
    taskEXIT_CRITICAL();

    //若刚刚删除的是正在运行的任务 则强制任务调度
    if (xSchedulerRunning != pdFALSE)
    {
        if (pxTCB == pxCurrentTCB)
        {
            configASSERT(uxSchedulerSuspended == 0);
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
}

内存释放就是根据任务的内存分配方式,选择对应的释放函数。

/*
 * 释放TCB、堆栈的内存
 */
static void prvDeleteTCB(TCB_t *pxTCB)
{

#if ((configSUPPORT_DYNAMIC_ALLOCATION == 1) && (configSUPPORT_STATIC_ALLOCATION == 0))
    {
        //任务使用动态内存分配策略,直接释放任务控制块内存、堆栈内存
        vPortFree(pxTCB->pxStack);
        vPortFree(pxTCB);
    }
#elif (tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0)
    {
        //选择对应的内存释放函数
        if (pxTCB->ucStaticallyAllocated == tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB)
        {
            //tcb和堆栈都是动态内存分配的,所以必须释放
            vPortFree(pxTCB->pxStack);
            vPortFree(pxTCB);
        }
        else if (pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_ONLY)
        {

            vPortFree(pxTCB);
        }
        else
        {
            //tcb和堆栈都是静态内存分配的 不必释放
            configASSERT(pxTCB->ucStaticallyAllocated == tskSTATICALLY_ALLOCATED_STACK_AND_TCB);
            mtCOVERAGE_TEST_MARKER();
        }
    }
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值