STM32 FreeRTOS学习——任务创建

本文详细介绍了FreeRTOS中任务的概念,包括任务函数、任务栈的定义、任务控制块的结构以及任务创建过程。同时,提到了任务的延时函数vTaskDelay,任务列表和列表项的初始化、插入和删除操作。此外,还讨论了任务的堆栈初始化和上下文切换中的寄存器保存。
摘要由CSDN通过智能技术生成

任务创建

任务概念

什么是任务

任务函数没有返回值,并且参数是指针类型(很少使用,基本可以不管)

void entry_task(void *pvParameters)
{
    /* 任务主体,无限循环且不能返回值 */
    while(1)
    {
        /* 任务主题代码 */
    }
}

任务的实现过程

定义任务栈

下面的任务分别定义的任务栈大小为:128和64,展通空间大小为1284字节和644字节

//任务堆栈大小
#define TASK1_STK_SIZE 128
StackType_t Task1Stack[TASK1_STK_SIZE];

//任务堆栈大小
#define TASK2_STK_SIZE 64
StackType_t Task2Stack[TASK2_STK_SIZE];

定义任务函数

//任务1函数
void tsak1_task(void *pvParameters)
{
    while(1)
    {
        LED1 = 0;
        vTaskDelay(500);
        LED1 = 1;
        vTaskDelay(500);
    }
}

//任务2函数
void tsak2_task(void *pvParameters)
{
    while(1)
    {
        LED2 = 0;
        vTaskDelay(1000);
        LED2 = 1;
        vTaskDelay(1000);
    }
}

vTaskDelay()为FreeRTOS提供的延时函数,在FreeRTOSConfig.h文件中进行了时钟节拍的配置,如下

//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度
#define configTICK_RATE_HZ      (( TickType_t )1000)

任务控制块

人物的参数可以通过任务控制块指定

typedef struct tskTaskControlBlock             
{
    // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
    volatile StackType_t    *pxTopOfStack;   /* 栈顶指针 */ 
    // MPU相关暂时不讨论
    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS    xMPUSettings;        
    #endif
    // 表示任务状态,不同的状态会挂接在不同的状态链表下
    ListItem_t            xStateListItem;    /* 任务节点 */
    // 事件链表项,会挂接到不同事件链表下
    ListItem_t            xEventListItem;        
    // 任务优先级,数值越大优先级越高
    UBaseType_t            uxPriority;            
    // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
    StackType_t            *pxStack;         /* 任务栈气质地址 */  
    // 任务名称,字符串形式
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];
    // 指向栈尾,可以用来检测堆栈是否溢出
    #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t        *pxEndOfStack;        
    #endif
    // 记录临界段的嵌套层数
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t        uxCriticalNesting;    
    #endif
    // 跟踪调试用的变量
    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t        uxTCBNumber;        
        UBaseType_t        uxTaskNumber;        
    #endif
    // 任务优先级被临时提高时,保存任务原本的优先级
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t        uxBasePriority;        
        UBaseType_t        uxMutexesHeld;
    #endif
    // 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook    函数调用
    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif
    // 任务的线程本地存储指针,可以理解为这个任务私有的存储空间
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void            *pvThreadLocalStoragePointers[     configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif
    // 运行时间变量
    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t        ulRunTimeCounter;    
    #endif
    // 支持NEWLIB的一个变量
    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        struct    _reent xNewLib_reent;
    #endif
    // 任务通知功能需要用到的变量
    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        // 任务通知的值 
        volatile uint32_t ulNotifiedValue;
        // 任务通知的状态
        volatile uint8_t ucNotifyState;
    #endif
    // 用来标记这个任务的栈是不是静态分配的
    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) 
        uint8_t    ucStaticallyAllocated;         
    #endif
    // 延时是否被打断
    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
    // 错误标识
    #if( configUSE_POSIX_ERRNO == 1 )
        int iTaskErrno;
    #endif
} tskTCB;
typedef tskTCB TCB_t; 

列表和列表项

  1. 列表

    listFIRST_LIST_INTEGRITY_CHECK_VALUE和istSECOND_LIST_INTEGRITY_CHECK_VALUE用来检查列表的完整性,默认不开启;如果要开启需要将配置文件中的宏设置为1,开启之后分别会添加一个整型变量值1和2,初始化时会写入一个特殊的值

    typedef struct xLIST
    {
        listFIRST_LIST_INTEGRITY_CHECK_VALUE 
        /* 列表内有效列表项个数 */
        configLIST_VOLATILE UBaseType_t uxNumberOfItems;
        /* 记录当前列表项索引号,用于遍历列表 */
        ListItem_t * configLIST_VOLATILE pxIndex;           
        /* 列表中最后一个列表项,表示列表结束 */
        MiniListItem_t xListEnd;
        listSECOND_LIST_INTEGRITY_CHECK_VALUE
    } List_t;
    
  2. 列表项

    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE类似于列表中的两个参数,需要在配置文件中使能才能开启对应的功能——检查列表项完整性

    struct xLIST_ITEM
    {
        listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
        /* 列表项值 */
        configLIST_VOLATILE TickType_t xItemValue;          
        /* 指向下一个列表项值 */
        struct xLIST_ITEM * configLIST_VOLATILE pxNext;     
        /* 指向上一个列表项值 */
        struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
        /* 当前列表项的拥有者 */
        void * pvOwner;                                     
        /* 当前列表项归属的列表,指向就绪列表 */
        void * configLIST_VOLATILE pvContainer; 
        listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE            
    };
    typedef struct xLIST_ITEM ListItem_t;
    
  3. 迷你列表项

    基本和列表项相似,知识功能相对精简,没有对当前列表项拥有者的记录和当前列表项的归属的列表的记录

    struct xMINI_LIST_ITEM
    {
        listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE  
        /* 列表项值 */
        configLIST_VOLATILE TickType_t xItemValue;
        /* 指向下一个列表项值 */
        struct xLIST_ITEM * configLIST_VOLATILE pxNext;
        /* 指向上一个列表项值 */
        struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
        listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE 
    };
    typedef struct xMINI_LIST_ITEM MiniListItem_t;
    

列表与列表项初始化

  1. 列表初始化

    void vListInitialise( List_t * const pxList )
    {
        /* 此时列表中只有一个列表项xListEnd,索引地址指向尾节点 */
        pxList->pxIndex = ( ListItem_t * ) &(pxList->xListEnd );           
        /* xListEnd列表项值初始化为portMAX_DELAY(portmacro.h中定义的宏)*/
        pxList->xListEnd.xItemValue = portMAX_DELAY;
        /* 初始化xListEnd的pxNext变量,此时指向自身 */
        pxList->xListEnd.pxNext = ( ListItem_t * ) &(pxList->xListEnd );   
        /* 初始化xListEnd的pxPrevious变量,此时指向自身 */
        pxList->xListEnd.pxPrevious = ( ListItem_t * ) &(pxList->xListEnd );
        /* 当前有效的列表项个数为0,没有算xListEnd */
        pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
        /* 列表完整性检查,需要配置文件中设置有效才能启动 */
        listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
        listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
    }
    

    列表的初始化流程如下:
    在这里插入图片描述

  2. 列表项初始化

    列表项根据实际使用情况来初始化,所以其初始化函数很短。

    void vListInitialiseItem( ListItem_t * const pxItem )
    {
        /* 列表项初始化为不归属任何任务列表所有 */
        pxItem->pvContainer = NULL;
        /* 列表项完整性检查,需要配置文件中设置有效才能启动 */
        listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
        listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    }
    
  3. 列表项插入

    将指定列表项插入到列表中,通过vListInsert()函数完成列表项插入

    void vListInsert( List_t * const pxList,        //列表项要插入的列表
                  ListItem_t * const pxNewListItem) //要插入的列表项
    {    
        ListItem_t *pxIterator;
        /* 获取要插入的列表项值 */
        const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
        /* 检测列表和列表项的完整性,检测是否值被改变了 */
        listTEST_LIST_INTEGRITY( pxList );
        listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    
        /* 获取列表项的位置,就是获取列表的最末尾;如获取到的列表项值为最大时,直接插入到尾部 */
        if( xValueOfInsertion == portMAX_DELAY )
        {
            pxIterator = pxList->xListEnd.pxPrevious;
        }
        else{
            /* 从尾节点开始遍历,与下个节点的Value值进行比较,当要插入Value值大于遍历列表项Value时,就获得插入位置(按升序方式插入) */
            for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) 
            {
                //空循环,什么也不做
            }
        }
        /* 更新后面节点信息 (前后指针进行赋值) */
        pxNewListItem->pxNext = pxIterator->pxNext;
        pxNewListItem->pxNext->pxPrevious = pxNewListItem;
        /* 更新前面节点信息(前后指针进行赋值) */
        pxNewListItem->pxPrevious = pxIterator;
        pxIterator->pxNext = pxNewListItem;
        /* 把列表项归属于当前的列表 */
        pxNewListItem->pvContainer = ( void * ) pxList;
        /* 有效列表项数量进行累加 */
        ( pxList->uxNumberOfItems )++;
    }
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  4. 列表项的末尾插入

    void vListInsertEnd( List_t * const pxList,          //列表项要插入的列表
                      ListItem_t * const pxNewListItem ) //要插入的列表项
     {
         /* 获取当前列表索引值    */
         ListItem_t * const pxIndex = pxList->pxIndex;
         /* 检测列表和列表项的完整性,检测是否值被改变了 */
         listTEST_LIST_INTEGRITY( pxList );
         listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
         /* 插入到索引值之前,先进行尾部更新 */
         pxNewListItem->pxNext = pxIndex;
         pxNewListItem->pxPrevious = pxIndex->pxPrevious;
         /* 再进行头部更新 */
         pxIndex->pxPrevious->pxNext = pxNewListItem;
         pxIndex->pxPrevious = pxNewListItem;
         /* 列表项归属于列表 */
         pxNewListItem->pvContainer = ( void * ) pxList;
         /* 更新列表项数目 */
         ( pxList->uxNumberOfItems )++;
     }
    

    在这里插入图片描述
    在这里插入图片描述

  5. 列表项删除

    从列表中删除指定的列表项,通过uxListRemove()函数来完成

     UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
     {
         /* 获取列表项所在的列表地址 */
         List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
         /* 将要删除的列表项的前后两个列表项进行连接 */
         pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
         pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
         /* Only used during decision coverage testing. */
         mtCOVERAGE_TEST_DELAY();
         /* 索引是否需要更新 */
         if( pxList->pxIndex == pxItemToRemove )
         {
             pxList->pxIndex = pxItemToRemove->pxPrevious;
         }
         else
         {
             mtCOVERAGE_TEST_MARKER();
         }
         /* 被删除列表项的成员变量pvContainer清零 */
         pxItemToRemove->pvContainer = NULL;
         /* 有效列表项数量减一 */
         ( pxList->uxNumberOfItems )--;
         /* 返回当前列表的有效列表项数量 */
         return pxList->uxNumberOfItems;
     }
    
  6. 列表的遍历

    List_t中的成员变量pxIndex是用来遍历列表的,FreeRTOS使用如下函数(宏)来完成列表的遍历,每调用一次这个函数,列表的pxIndex变量就会指向下一个列表项,并返回这个列表项的pvOwner变量值

    /* pxTCB用来保存pxIndex所指向的列表项的pvOwner, pxList表示要遍历的列表 */
    #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                 \
    {                                                                    \
        /* 首先获取当前列表 */
        List_t * const pxConstList = ( pxList );                         \
        /* 列表的pxIndex变量指向下一个列表项 */
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;     \
        /* 如果pxIndex指向了列表的xListEnd成员变量,表示到了列表末尾 */
        if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )                                      \
        {   \
            /* 此时就跳过xListEnd,pxIndex再次指向列表头的列表项,这样就完成了一次列表遍历 */
            ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
        }   \
        /* 获取当前pxIndex指向的所有者(其实就是任务控制块)*/
        ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                    \
    }
    

任务创建函数

任务创建由函数xTaskCreate()完成

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 )

具体使用范例

//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

/* 任务创建 */
xTaskCreate((TaskFunction_t )start_task,            //任务函数
            (const char *   )"start_task",          //任务名称
            (uint16_t       )START_STK_SIZE,        //任务堆栈大小
            (void *         )NULL,                  //传递给任务函数的参数
            (UBaseType_t    )START_TASK_PRIO,       //任务优先级
            (TaskHandle_t * )&StartTask_Handler);   //任务句柄
vTaskStartScheduler();                              //开始任务调度

任务初始化函数分析

函数prvInitialiseNewTask()用于完成对任务的初始化

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)

任务堆栈初始化函数分析

任务初始化函数中会通过pxPortInitialiseStack()对任务堆栈初始化

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_XPSR; /* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; 
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */    
    /* Save code space by skipping register initialisation. */
    pxTopOfStack -= 5; /* R12, R3, R2 and R1. */            
    *pxTopOfStack = ( StackType_t ) pvParameters; /* R0 */              
    /* A save method is being used that requires each task to maintain its
    own exec return value. */
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_EXEC_RETURN;                                
    pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */          
    return pxTopOfStack;
}

堆栈是用来在进行上下文切换的时候保存现场的,一般在新创建好一个堆栈以后会对其先进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。这些初值就保存在任务堆栈中,保存的顺序按照:XPSR、R15(PC)、R14(LR)、R12、R3 ~ RO、R11 ~ R14。

创建任务到完成就绪列表

列表数组pxReadyTasksLists[]就是任务就绪列表,数组大小为configMAX_PRIORITIES,也就是说一个优先级一个列表,这样相同优先级的任务就使用一个列表。将一个新创建的任务添加到就绪列表中通过函数prvAddNewTaskToReadyList()来完成

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
    taskENTER_CRITICAL();
    {
        uxCurrentNumberOfTasks++;
        if( pxCurrentTCB == NULL )//正在运行任务块为NULL,表示没有任务运行
        {
            /* There are no other tasks, or all the other tasks are in
            the suspended state - make this the current task. */
            pxCurrentTCB = pxNewTCB;//将新任务的任务控制块赋值给当前运行任务块

            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
            {
                /* This is the first task to be created so do the preliminary
                initialisation required.  We will not recover if this call
                fails, but we will report the failure. */
                prvInitialiseTaskLists();             
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            /* If the scheduler is not already running, make this task the
            current task if it is the highest priority task to be created
            so far. */
            if( xSchedulerRunning == pdFALSE )
            {//新任务优先级比正在运行优先级高
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;    
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        uxTaskNumber++;//任务控制块编号 + 1

        #if ( configUSE_TRACE_FACILITY == 1 )
        {
            /* Add a counter into the TCB for tracing only. */
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
        #endif /* configUSE_TRACE_FACILITY */
        prvAddTaskToReadyList( pxNewTCB );          

        portSETUP_TCB( pxNewTCB );
    }
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )
    {
        /* If the created task is of a higher priority than the current task
        then it should run now. */
        //新任务优先级比正在运行优先级高,任务切换
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            taskYIELD_IF_USING_PREEMPTION();    
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值