• 嵌入式FreeRTOS学习八,xTaskCreate创建任务的细节以及恢复中断任务实现


    一.创建任务函数xTaskCreate

    任务也不是很复杂的东西,任务也就是一个函数xTaskCreate。简单得说,创建一个任务,你得提供它的执行函数,你得提供它的栈的大小,函数的执行空间,函数的优先级等重要的条件。因为任务在运行中,任务函数有调用关系,有局部变量,这些都保存在任务的栈里面;任务有可能被切换,有可能被暂停,这时候CPU寄存器中断现场数据都保存在栈里面。

    1. BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
    2. const char * const pcName,
    3. /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
    4. const configSTACK_DEPTH_TYPE usStackDepth,
    5. void * const pvParameters,
    6. UBaseType_t uxPriority,
    7. 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最主要的参数在上面它的定义以及相关变量的解释如下

    1. typedef struct tskTaskControlBlock
    2. {
    3. // 这里栈顶指针必须位于TCB第一项是为了便于上下文切换操作,详见xPortPendSVHandler中任务切换的操作。
    4. volatile StackType_t *pxTopOfStack;
    5. // 表示任务状态,不同的状态会挂接在不同的状态链表下
    6. ListItem_t xStateListItem;
    7. // 事件链表项,会挂接到不同事件链表下
    8. ListItem_t xEventListItem;
    9. // 任务优先级,数值越大优先级越高
    10. UBaseType_t uxPriority;
    11. // 指向堆栈起始位置,这只是单纯的一个分配空间的地址,可以用来检测堆栈是否溢出
    12. StackType_t *pxStack;
    13. // 任务名
    14. char pcTaskName[ configMAX_TASK_NAME_LEN ];
    15. /*
    16. //以下屏蔽掉的都是可以通过条件来配置的选项,通过条件来决定哪些定义使用或者不用,暂时不需要用到这
    17. //些,屏蔽掉,TCB最主要的参数在上面
    18. //#####################################################################################
    19. // MPU相关暂时不讨论
    20. #if ( portUSING_MPU_WRAPPERS == 1 )
    21. xMPU_SETTINGS xMPUSettings;
    22. #endif
    23. // 指向栈尾,可以用来检测堆栈是否溢出
    24. #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
    25. StackType_t *pxEndOfStack;
    26. #endif
    27. // 记录临界段的嵌套层数
    28. #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    29. UBaseType_t uxCriticalNesting;
    30. #endif
    31. // 跟踪调试用的变量
    32. #if ( configUSE_TRACE_FACILITY == 1 )
    33. UBaseType_t uxTCBNumber;
    34. UBaseType_t uxTaskNumber;
    35. #endif
    36. // 任务优先级被临时提高时,保存任务原本的优先级
    37. #if ( configUSE_MUTEXES == 1 )
    38. UBaseType_t uxBasePriority;
    39. UBaseType_t uxMutexesHeld;
    40. #endif
    41. // 任务的一个标签值,可以由用户自定义它的意义,例如可以传入一个函数指针可以用来做Hook 函数调用
    42. #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    43. TaskHookFunction_t pxTaskTag;
    44. #endif
    45. // 任务的线程本地存储指针,可以理解为这个任务私有的存储空间
    46. #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
    47. void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    48. #endif
    49. // 运行时间变量
    50. #if( configGENERATE_RUN_TIME_STATS == 1 )
    51. uint32_t ulRunTimeCounter;
    52. #endif
    53. // 支持NEWLIB的一个变量
    54. #if ( configUSE_NEWLIB_REENTRANT == 1 )
    55. struct _reent xNewLib_reent;
    56. #endif
    57. // 任务通知功能需要用到的变量
    58. #if( configUSE_TASK_NOTIFICATIONS == 1 )
    59. // 任务通知的值
    60. volatile uint32_t ulNotifiedValue;
    61. // 任务通知的状态
    62. volatile uint8_t ucNotifyState;
    63. #endif
    64. // 用来标记这个任务的栈是不是静态分配的
    65. #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
    66. uint8_t ucStaticallyAllocated;
    67. #endif
    68. // 延时是否被打断
    69. #if( INCLUDE_xTaskAbortDelay == 1 )
    70. uint8_t ucDelayAborted;
    71. #endif
    72. // 错误标识
    73. #if( configUSE_POSIX_ERRNO == 1 )
    74. int iTaskErrno;
    75. #endif
    76. //###################################################################################
    77. */
    78. } tskTCB;
    79. typedef tskTCB TCB_t;

    =========================================================================

    二.创建任务的具体内部细节

    以简单的任务创建函数为例,这里分别创建了三个简单任务vTask1,vTask2,vTask3

    1. void vTask1( void *pvParameters )
    2. { /* 任务函数的主体一般都是无限循环 */
    3. for( ;; )
    4. {
    5. flagIdleTaskrun = 0;
    6. flagTask1run = 1;
    7. flagTask2run = 0;
    8. flagTask3run = 0;
    9. /* 打印任务的信息 */
    10. printf("T1\r\n");
    11. }
    12. }
    13. void vTask2( void *pvParameters )
    14. {
    15. /* 任务函数的主体一般都是无限循环 */
    16. for( ;; )
    17. {
    18. flagIdleTaskrun = 0;
    19. flagTask1run = 0;
    20. flagTask2run = 1;
    21. flagTask3run = 0;
    22. /* 打印任务的信息 */
    23. printf("T2\r\n");
    24. }
    25. }
    26. void vTask3( void *pvParameters )
    27. {
    28. const TickType_t xDelay5ms = pdMS_TO_TICKS( 5UL );
    29. /* 任务函数的主体一般都是无限循环 */
    30. for( ;; )
    31. {
    32. flagIdleTaskrun = 0;
    33. flagTask1run = 0;
    34. flagTask2run = 0;
    35. flagTask3run = 1;
    36. /* 打印任务的信息 */
    37. printf("T3\r\n");
    38. // 如果不休眠的话, 其他任务无法得到执行
    39. vTaskDelay( xDelay5ms );
    40. }
    41. }
    42. //主函数的实现
    43. int main( void )
    44. {
    45. prvSetupHardware();
    46. xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);
    47. xTaskCreate(vTask2, "Task 2", 1000, NULL, 0, NULL);
    48. xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);
    49. /* 启动调度器 */
    50. vTaskStartScheduler();
    51. /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    52. return 0;
    53. }

    三.TCB任务结构体在RAM内存中的存在

    下面的图中,将一整个tsTaskControlBlock结构体代码数据放入RAM内存中,表示出在内存中分配一个TCB结构体的效果(只画图表示效果,实际上可能不完全一样);可以看到,在RAM内存中分配的栈空间保存的数据有:

    栈顶指针pxTopOfStack;  指向划分出来的内存空间的最后一个数据保存的位置
    状态链表xStateListItem;
    事件链表 xEventListItem;    
    任务优先级 uxPriority;         
    指向堆栈起始位置指针 pxStack;指向划分出来的内存空间的起始地址位置          
    任务名 pcTaskName[ configMAX_TASK_NAME_LEN ];

     =========================================================================

    四.任务函数创建时栈空间分配和大小的问题

    1. //任务创建函数
    2. xTaskCreate(vTask1, "Task 1", 1000, NULL, 0, NULL);

    在创建这个任务的时候,传进来的参数是保存在哪里呢?首先看传进来的栈的大小这个参数1000,这时需要弄明白两个问题。第一是栈是从哪里分配?第二是栈的大小怎么确定?

    第一个问题栈是从哪里分配?栈其实就是一块空闲的内存,FreeRTOS的heap2.cpp文件中关于是定义了一个巨大的全局数组ucHeap,这个数组没人使用,作为一块空闲的内存,在以后的栈的空间的分配就从这个巨大的数组里面划分可用的内存,来给某个任务当做栈来使用,如上图所示;由宏定义可知数组的大小为17*1024个字节。

    第二个问题,例子中栈的大小怎么确定?可以根据程序员出入的值的大小进行分配,例子中划分出1000*4字节的内存,然后内存的起始空间保存在pxStack指针中。

     =========================================================================

    五.任务函数创建时函数指针和参数保存问题和作用

    1. //任务创建函数
    2. 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函数返回地址开始执行,跳转回上一个被中断的任务,继续执行。如上图所示。

     

     

  • 相关阅读:
    040_小驰私房菜_MTK平台,添加camera客制化size
    macOS 使用 X11 运行远端 linux 中的 x11 client 图形程序
    CRC碰撞概率 与CRC校验长度的理解
    MSQL系列(七) Mysql实战-SQL语句Join,exists,in的区别
    Java8时间日期库DateTime API及示例
    【数据结构】详解链表(一)——单链表(动图讲解)
    网络安全——终端安全
    HashSet源码阅读理解
    java加sqlite3右键菜单版圣经,还没检查错误
    公司电脑屏幕录制软件有什么功能
  • 原文地址:https://blog.csdn.net/weixin_44651073/article/details/126923015