任务也不是很复杂的东西,任务也就是一个函数xTaskCreate。简单得说,创建一个任务,你得提供它的执行函数,你得提供它的栈的大小,函数的执行空间,函数的优先级等重要的条件。因为任务在运行中,任务函数有调用关系,有局部变量,这些都保存在任务的栈里面;任务有可能被切换,有可能被暂停,这时候CPU寄存器中断现场数据都保存在栈里面。
- BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
- const char * const pcName,
- /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
- const configSTACK_DEPTH_TYPE usStackDepth,
- void * const pvParameters,
- UBaseType_t uxPriority,
- TaskHandle_t * const pxCreatedTask )
-
参数说明:
(1)TaskFunction_t : typedef void (*TaskFunction_t)( void * ); 函数指针
(2)const char * const pcName : 任务名字
(3)configSTACK_DEPTH_TYPE : #define configSTACK_DEPTH_TYPE uint16_t 是无符号的2字节数值,表示栈的深度大小,实际由malloc函数分配大小
(4)void * const pvParameters :是要传入的参数
(5)UBaseType_t uxPriority : typedef unsigned short UBaseType_t; 是一个无符号的整形数,表示优先级的大小,数值越大优先级越大
(6)TaskHandle_t * const pxCreatedTask :这里面有一个TCB结构体指针,传出去的参数
TCB_t的全称为Task Control Block,也就是任务控制块,这个结构体包含了一个任务所有的信息,但是源代码中存在大量的条件配置选项,以下屏蔽掉的都是可以通过条件来配置的选项,通过条件来决定哪些定义使用或者不用,暂时不需要用到这些,对条件配置项进行屏蔽,TCB最主要的参数在上面它的定义以及相关变量的解释如下
- typedef struct tskTaskControlBlock
- {
- // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
- volatile StackType_t *pxTopOfStack;
-
-
- // 表示任务状态,不同的状态会挂接在不同的状态链表下
- ListItem_t xStateListItem;
- // 事件链表项,会挂接到不同事件链表下
- ListItem_t xEventListItem;
- // 任务优先级,数值越大优先级越高
- UBaseType_t uxPriority;
- // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
- StackType_t *pxStack;
- // 任务名
- char pcTaskName[ configMAX_TASK_NAME_LEN ];
-
- /*
- //以下屏蔽掉的都是可以通过条件来配置的选项,通过条件来决定哪些定义使用或者不用,暂时不需要用到这
- //些,屏蔽掉,TCB最主要的参数在上面
- //#####################################################################################
-
- // MPU相关暂时不讨论
- #if ( portUSING_MPU_WRAPPERS == 1 )
- xMPU_SETTINGS xMPUSettings;
- #endif
- // 指向栈尾,可以用来检测堆栈是否溢出
- #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;
=========================================================================
以简单的任务创建函数为例,这里分别创建了三个简单任务vTask1,vTask2,vTask3
- void vTask1( void *pvParameters )
- { /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 1;
- flagTask2run = 0;
- flagTask3run = 0;
- /* 打印任务的信息 */
- printf("T1\r\n");
- }
- }
- void vTask2( void *pvParameters )
- {
- /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 0;
- flagTask2run = 1;
- flagTask3run = 0;
- /* 打印任务的信息 */
- printf("T2\r\n");
- }
- }
-
- void vTask3( void *pvParameters )
- {
- const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
-
- /* 任务函数的主体一般都是无限循环 */
- for( ;; )
- {
- flagIdleTaskrun = 0;
- flagTask1run = 0;
- flagTask2run = 0;
- flagTask3run = 1;
- /* 打印任务的信息 */
- printf("T3\r\n");
- // 如果不休眠的话, 其他任务无法得到执行
- vTaskDelay( xDelay5ms );
- }
- }
- //主函数的实现
- int main( void )
- {
- prvSetupHardware();
- xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
- xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
- xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
- /* 启动调度器 */
- vTaskStartScheduler();
- /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
- return 0;
- }
下面的图中,将一整个tsTaskControlBlock结构体代码数据放入RAM内存中,表示出在内存中分配一个TCB结构体的效果(只画图表示效果,实际上可能不完全一样);可以看到,在RAM内存中分配的栈空间保存的数据有:
栈顶指针pxTopOfStack; 指向划分出来的内存空间的最后一个数据保存的位置
状态链表xStateListItem;
事件链表 xEventListItem;
任务优先级 uxPriority;
指向堆栈起始位置指针 pxStack;指向划分出来的内存空间的起始地址位置
任务名 pcTaskName[ configMAX_TASK_NAME_LEN ];
=========================================================================
- //任务创建函数
- xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
在创建这个任务的时候,传进来的参数是保存在哪里呢?首先看传进来的栈的大小这个参数1000,这时需要弄明白两个问题。第一是栈是从哪里分配?第二是栈的大小怎么确定?
第一个问题栈是从哪里分配?栈其实就是一块空闲的内存,FreeRTOS的heap2.cpp文件中关于是定义了一个巨大的全局数组ucHeap,这个数组没人使用,作为一块空闲的内存,在以后的栈的空间的分配就从这个巨大的数组里面划分可用的内存,来给某个任务当做栈来使用,如上图所示;由宏定义可知数组的大小为17*1024个字节。
第二个问题,例子中栈的大小怎么确定?可以根据程序员出入的值的大小进行分配,例子中划分出1000*4字节的内存,然后内存的起始空间保存在pxStack指针中。
=========================================================================
- //任务创建函数
- xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
第一个参数是函数指针xTaskCreat其实就是函数的地址addrF,当我们想要去启动这个任务,或者调用这个函数的时候,我们可以让CPU中的R15寄存器,也就是PC寄存器的值等于函数的地址。addrF即可。
第四个参数是随着函数传入的参数值,通常保存在R0寄存器中。
=========================================================================
系统从定义的全局数组ucHeap中分配可用的空间给任务函数xTaskCreate使用,例子中分配了1000*4的内存空间,pxStack指向内存开始的地址位置,pxTopOfStack指向内存中最后一个数据保存的位置。
我们在创建任务的时候,任务函数xTaskCreate已经创建了这部分内存,帮我们修改了RAM内存里面的内容,在TCB结构体里面,我们没有看到传入的参数和函数指针,其实任务函数已经把这些值写入了CPU内存中的R15和R0等寄存器,以便恢复任务时使用。
刚创建任务的时候,任务还没有运行,属于某种暂停状态,当任务被中断,系统将当前任务的寄存器入栈进行现场保护,其中包括R15函数返回地址,R0参数等;然后开始运行中断任务,中断任务结束,则开始恢复RAM内存中栈里面的各种寄存器现场,从R15函数返回地址开始执行,跳转回上一个被中断的任务,继续执行。如上图所示。