什么网站做招聘收录好,赣州人才网暑假工,工商网官网查询企业信息,宁乡做网站地方1899_野火FreeRTOS教程阅读笔记_任务创建 全部学习汇总#xff1a; g_FreeRTOS: FreeRTOS学习笔记 (gitee.com) 关于这部分#xff0c;从一般前后台程序到RTOS的任务描述了很多。但是我觉得这本书的这部分描述没有描述到关键的信息点。其实#xff0c;RTOS存在的一个主要的目… 1899_野火FreeRTOS教程阅读笔记_任务创建 全部学习汇总 g_FreeRTOS: FreeRTOS学习笔记 (gitee.com) 关于这部分从一般前后台程序到RTOS的任务描述了很多。但是我觉得这本书的这部分描述没有描述到关键的信息点。其实RTOS存在的一个主要的目的就是让各个Task从Task自己的层面能够有一种感觉Task自己独占了整个CPU。而Task本身是没法独占全部CPU的我们多个Task都需要运行。这样就需要从软件的层面来“模拟”的形式让Task能够感受到自己似乎独占了整个CPU一样。而堆栈空间的设计其实就是为了实现这一点。 这个是一个精简的任务控制块的数据结构其中比较关键的信息是堆栈信息以及任务节点。其中任务节点会关联其他的用户代码。还剩下一个任务名字这个对于RTOS的实现来说并不是那么重要。 书中的例子采用了静态创建任务的方式这个其实我在自己使用这个OS的时候没用过我创建任务的时候都用的动态的形式。放一个之前的使用方式代码如下 xTaskCreate(prvPrintTaskA, TaskA, configMINIMAL_STACK_SIZE, NULL, mainQUEUE_RECEIVE_TASK_PRIORITY, NULL);xTaskCreate(prvPrintTaskB, TaskB, configMINIMAL_STACK_SIZE, NULL, mainQUEUE_SEND_TASK_PRIORITY, NULL);这个是动态创建任务时候的接口函数原型。两种方式的差异在于现在教程中的部分是没有优先级处理的。另外就是静态的方式需要多一部分存储的处理这个主要是因为静态不会再以内存申请分配的方式给任务分配内存因此需要用户自己做这个存储的分派。至于句柄的处理通过指针还是返回值的形式处理其实都差不多。但是目前的句柄也只能看得出来是一个系统堆栈空间中的一个临时变量数值。而这个数值应该会在进一步的初始化中进行修改。 要进一步了解这部分功能得借助以SICP提到的黑盒抽象。需要知道prvInitialiseNewTask()接口会完成实际的任务创建的工作而这个创建接口会同时给出是否创建成功的一个提示。而这个接口用的句柄处理形式其实是跟我之前使用的动态创建是类似的。 static void prvInitialiseNewTask(TaskFunction_t pxTaskCode, /* 任务入口 */const char *const pcName, /* 任务名称字符串形式 */const uint32_t ulStackDepth, /* 任务栈大小单位为字 */void *const pvParameters, /* 任务形参 */TaskHandle_t *const pxCreatedTask, /* 任务句柄 */TCB_t *pxNewTCB) /* 任务控制块指针 */{StackType_t *pxTopOfStack;UBaseType_t x; /* 获取栈顶地址 */pxTopOfStack pxNewTCB-pxStack (ulStackDepth - (uint32_t)1);/* 向下做8字节对齐 */pxTopOfStack (StackType_t *)(((uint32_t)pxTopOfStack) (~((uint32_t)0x0007))); /* 将任务的名字存储在TCB中 */for (x (UBaseType_t)0; x (UBaseType_t)configMAX_TASK_NAME_LEN; x){pxNewTCB-pcTaskName[x] pcName[x]; if (pcName[x] 0x00){break;}}/* 任务名字的长度不能超过configMAX_TASK_NAME_LEN */pxNewTCB-pcTaskName[configMAX_TASK_NAME_LEN - 1] \0; /* 初始化TCB中的xStateListItem节点 */vListInitialiseItem((pxNewTCB-xStateListItem));/* 设置xStateListItem节点的拥有者 */listSET_LIST_ITEM_OWNER((pxNewTCB-xStateListItem), pxNewTCB); /* 初始化任务栈 */pxNewTCB-pxTopOfStack pxPortInitialiseStack(pxTopOfStack, pxTaskCode, pvParameters); /* 让任务句柄指向任务控制块 */if ((void *)pxCreatedTask ! NULL){*pxCreatedTask (TaskHandle_t)pxNewTCB;}
}上面是这个新任务初始化接口prvInitialiseNewTask()的实现。既然处理的主要元素信息是堆栈以及任务控制块。那么具体的操作是做了什么呢 先看堆栈。堆栈在这个接口中其实主要的处理是做了一个对齐的处理对齐处理的操作是根据静态任务创建接口xTaskCreateStatic()中传入的静态创建所分配的存储buffer所指向的内存做一个对齐的处理。而这个buffer的信息在上一层的接口中已经完成了与TCB的信息绑定。这个对齐的要求主要是MCU的架构决定的这里是按照8个字节来对齐主要就是考虑了浮点运算时候的一个对齐。关于这个原因我之前的确是没有弄清楚还是从这个教材中学来的。这个对齐进一步划定了这个任务堆栈所用的RAM范围。至于下一步的堆栈如何处理再一步采用黑盒抽象。 任务控制块的处理是把任务控制块中绑定的链表节点信息进行初始化之后设置链表元素节点的处理对象绑定关系。即把TCB的信息绑定到这个链表节点上。不过到此为止看得出来暂时这个节点还是没有形成链表关系。因为少了插入的操作。 关于句柄的处理可以看得出来这个句柄最终被处理成了指向TCB的指针的数值也可以理解为是TCB的地址数值。 这样如果要理解任务的创建我们就还需要进一步分析前面黑盒抽象接口堆栈的初始化处理接口pxPortInitialiseStack()。 StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
{/* 异常发生时自动加载到CPU寄存器的内容 */pxTopOfStack--;*pxTopOfStack portINITIAL_XPSR; /* xPSR的bit24必须置1 */pxTopOfStack--;*pxTopOfStack ((StackType_t)pxCode) portSTART_ADDRESS_MASK; /* PC即任务入口函数 */pxTopOfStack--;*pxTopOfStack (StackType_t)prvTaskExitError; /* LR函数返回地址 */pxTopOfStack - 5; /* R12, R3, R2 and R1 默认初始化为0 */*pxTopOfStack (StackType_t)pvParameters; /* R0任务形参 */ /* 异常发生时手动加载到CPU寄存器的内容 */pxTopOfStack - 8; /* R11, R10, R9, R8, R7, R6, R5 and R4默认初始化为0 */ /* 返回栈顶指针此时pxTopOfStack指向空闲栈 */return pxTopOfStack;
}首先要理解这个栈的处理方式栈的增长是从上到下的因此上面的地址会是一个递减的处理过程。 至于堆栈中存储了什么内容这里有一个具体的说明。为什么这么安排之前的经验是直接看ARM相关内核手册中的编程模型。这样这段处理把指针挪到了这一堆寄存器镜像的最后面也就是图中空闲堆栈的最上面。该信息记录在任务控制块中后续在任务执行的时候使用。 至此为止整个操作其实还只是准备了数据结构的信息。暂时相应的TCB还没有任何与链表产生关联的动作。而任务调度其实是基于链表的因此到此还看不出任何调度可能出现的痕迹。