FreeRTOS任务创建
本文是《ALIENTEK STM32F429 FreeRTOS 开发教程》第八章学习笔记-1
第一章笔记–FreeRTOS简介与源码下载
第二章笔记–FreeRTOS在STM32F4上移植
第三章笔记-FreeRTOS系统配置
第四章笔记-FreeRTOS中断分析
第四章笔记补充-FreeRTOS临界段代码
第五章笔记-FreeRTOS任务基础
第六章笔记-FreeRTOS任务API函数的使用
第七章笔记-FreeRTOS列表和列表项
任务创建
1. 任务创建函数
FreeRTOS使用函数xTaskCreate()和xTaskCreateStatic()分别动态和静态创建任务
xTaskCreate()的源代码在tasks.c中:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,const char * const pcName,const uint16_t usStackDepth,void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask ){TCB_t *pxNewTCB;BaseType_t xReturn;#if( portSTACK_GROWTH > 0 ){pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );if( pxNewTCB != NULL ){pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); if( pxNewTCB->pxStack == NULL ){vPortFree( pxNewTCB );pxNewTCB = NULL;}}}#else /* portSTACK_GROWTH */{StackType_t *pxStack;pxStack = ( StackType_t * ) 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 ){#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ){pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;}#endif /* configSUPPORT_STATIC_ALLOCATION */prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );prvAddNewTaskToReadyList( pxNewTCB );xReturn = pdPASS;}else{xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;}return xReturn;}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ):可以看出使用此函数进行任务创建时必须将支持动态内存宏定为1
if( portSTACK_GROWTH > 0 ):如果宏定义堆栈向上增长则执行接下来的代码,portSTACK_GROWTH在portmacro.h里有宏定义
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL ){pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); if( pxNewTCB->pxStack == NULL ){vPortFree( pxNewTCB );pxNewTCB = NULL;}}
先使用函数pvPortMalloc()给任务控制块申请内存,如果成功的话,再给人物的任务堆栈申请内存,如果申请失败释放任务控制块内存
#else /* portSTACK_GROWTH */{StackType_t *pxStack;pxStack = ( StackType_t * ) 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 */
这段代码表示若向下增长堆栈时,先申请任务堆栈内存,再申请任务控制块内存
pxNewTCB->ucStaticallyAllocated=tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB:标记任务堆栈和任务控制块是使用动态内存分配方法得到的
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ):使用函数prvInitialiseNewTask()初始化任务,完成对任务控制块中各个字段的初始化工作
prvAddNewTaskToReadyList( pxNewTCB ):将新创建的任务加入到就绪表中
2. 任务初始化函数
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 )
任务初始化代码较多,只列出了函数定义,之后重点分析当中代码,源代码在tasks.c中
参数:pxTaskCode:任务函数;pcName:任务名称;ulStackDepth:任务堆栈大小,从任务创建函数中强制转化成uint32_t格式传入任务初始化函数;pvParameters:传递给任务函数的参数,一直是NULL没看出作用;uxPriority:任务优先级;pxCreatedTask:任务句柄;pxNewTCB:任务控制块;xRegions:使用xTaskCreateRestricted()函数建立微处理器保护任务时传递的内存地址参数,普通人物建立时此参数为NULL
#if( portUSING_MPU_WRAPPERS == 1 )/* Should the task be created in privileged mode? */BaseType_t xRunPrivileged;if( ( uxPriority & portPRIVILEGE_BIT ) != 0U ){xRunPrivileged = pdTRUE;}else{xRunPrivileged = pdFALSE;}uxPriority &= ~portPRIVILEGE_BIT;#endif /* portUSING_MPU_WRAPPERS == 1 */
如果宏定义portUSING_MPU_WRAPPERS为1,判断该任务是否在特权模式下创建,给xRunPrivileged赋值,并赋值优先级大小,由于我们将portUSING_MPU_WRAPPERS宏定义为0,所以不编译这段代码
#if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) ){/* Fill the stack with a known value to assist debugging. */( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );}
#endif
如果使能了堆栈溢出检测功能或者追踪可视化就使用一个定值tskSTACK_FILL_BYTE来填充任务堆栈,值为0xa5U
#if( portSTACK_GROWTH < 0 ){pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) ); /*lint !e923 MISRA exception. Avoiding casts between pointers and integers is not practical. Size differences accounted for using portPOINTER_SIZE_TYPE type. *//* Check the alignment of the calculated top of stack is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );}#else /* portSTACK_GROWTH */{pxTopOfStack = pxNewTCB->pxStack;/* Check the alignment of the stack buffer is correct. */configASSERT( ( ( ( portPOINTER_SIZE_TYPE ) pxNewTCB->pxStack & ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) == 0UL ) );/* The other extreme of the stack space is required if stack checking isperformed. */pxNewTCB->pxEndOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );}#endif /* portSTACK_GROWTH */
使用条件编译,在向上堆栈和向下堆栈不同时计算堆栈栈顶pxTopOfStack
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){pxNewTCB->pcTaskName[ x ] = pcName[ x ];/* Don't copy all configMAX_TASK_NAME_LEN if the string is shorter thanconfigMAX_TASK_NAME_LEN characters just in case the memory after thestring is not accessible (extremely unlikely). */if( pcName[ x ] == 0x00 ){break;}else{mtCOVERAGE_TEST_MARKER();}}
保存任务的任务名
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = ‘\0’:在任务名数组添加字符串结束符’\0’
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;}else{mtCOVERAGE_TEST_MARKER();}
判断任务优先级是否大于预先定义的最大优先级configMAX_PRIORITIES宏,若大于则将任务优先级设置为configMAX_PRIORITIES-1
pxNewTCB->uxPriority = uxPriority:将得到的uxPriority值赋给任务控制块的优先级(即初始化任务控制块的优先级)
#if ( configUSE_MUTEXES == 1 ){pxNewTCB->uxBasePriority = uxPriority;pxNewTCB->uxMutexesHeld = 0;}#endif /* configUSE_MUTEXES */
如果宏定义configUSE_MUTEXES为1,即使用了互斥信号量,这里初始化相应变量
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
这里初始化列表项xStateListItem和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 );#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue ) ( ( pxListItem )->xItemValue = ( xValue ) )
这里设置了列表项xStateListItem和xEventListItem属于当前任务控制块,即设置两个列表项的成员变量pvOwner为新创建的任务的任务控制块;并且设置了列表项xEventListItem的变量xItemValue为configMAX_PRIORITIES-uxPriority,意味着xItemValue的值越大优先级越小(列表插入按照xItemValue的值升序排列)
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
{pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
{pxNewTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 )
{pxNewTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
分别条件编译,portCRITICAL_NESTING_IN_TCB:临界区嵌套宏定义;configUSE_APPLICATION_TASK_TAG:任务标签功能;configGENERATE_RUN_TIME_STATS:时间统计功能;若使能这些功能宏定义,则给任务控制块的相关变量初始化赋值
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 ){for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ ){pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;}}
#endif
如果使能了宏configNUM_THREAD_LOCAL_STORAGE_POINTERS为1,初始化线程本地存储指针
#if ( configUSE_TASK_NOTIFICATIONS == 1 ){pxNewTCB->ulNotifiedValue = 0;pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;}
#endif#if ( configUSE_NEWLIB_REENTRANT == 1 ){/* Initialise this task's Newlib reent structure. */_REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );}
#endif#if( INCLUDE_xTaskAbortDelay == 1 ){pxNewTCB->ucDelayAborted = pdFALSE;}
#endif
条件编译,configUSE_TASK_NOTIFICATIONS:使能任务通知功能(默认开启);configUSE_NEWLIB_REENTRANT:使能NEWLIB;INCLUDE_xTaskAbortDelay:使能函数INCLUDE_xTaskAbortDelay();如果使能了相应功能,则给任务控制块的相关变量初始化赋值
#if( portUSING_MPU_WRAPPERS == 1 ){pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters, xRunPrivileged );}
#else /* portUSING_MPU_WRAPPERS */{pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );}
#endif /* portUSING_MPU_WRAPPERS */
调用了函数pxPortInitialiseStack()初始化任务堆栈
if( ( void * ) pxCreatedTask != NULL )
{*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
else
{mtCOVERAGE_TEST_MARKER();
}
生成任务句柄,返回给参数pxCreatedTask,任务句柄其实就是任务控制块
3. 任务堆栈初始化函数
堆栈用来进行上下文切换时候保存现场,新创建好一个堆栈后会对其进行初始化处理,即对Cortex-M内核的某些寄存器赋初值。初值保存在任务堆栈中,保存顺序为:xPSR,R15(PC),R14(LR),R12,R3\~R0,R11~R14
函数pxPortInitialiseStack()即为堆栈初始化函数,函数源码:
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{/* Simulate the stack frame as it would be created by a context switchinterrupt. *//* Offset added to account for the way the MCU uses the stack on entry/exitof interrupts, and to ensure alignment. */pxTopOfStack--;*pxTopOfStack = portINITIAL_XPSR; /* xPSR */pxTopOfStack--;*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */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 itsown exec return value. */pxTopOfStack--;*pxTopOfStack = portINITIAL_EXEC_RETURN;pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
参数:pxTopOfStack:堆栈栈顶;pxCode:任务函数;pvParameters:传递参数(null)
*pxTopOfStack = portINITIAL_XPSR: 寄存器xPSR值为portINITIAL_XPSR(0x01000000),此时表示xPSR寄存器的bit24为1,即处于Thumb状态(Cortex-m系列没有ARM状态)
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK:寄存器PC初始化为任务函数pxCode
*pxTopOfStack = ( StackType_t ) prvTaskExitError:寄存器LR初始化为函数prvTaskExitError()
pxTopOfStack -= 5; /* R12, R3, R2 and R1. */
*pxTopOfStack = ( StackType_t ) pvParameters
跳过4个寄存器R12,R3,R2和R1(这四个寄存器不初始化);并把寄存器R0初始化为pvParameters
*pxTopOfStack = portINITIAL_EXEC_RETURN:保存EXC_RETURN值,用于退出SVC和PendSV中断的时候处理器该处于什么状态。当处理器进入异常或中断服务程序时,链接寄存器R14(LR)的数值更新为EXC_RETURN,这里宏定义为0xfffffffd
pxTopOfStack -= 8:跳过8个寄存器,R11,R10,R8,R7,R6,R5,R4
初始化之后,堆栈结果为:
4. 添加任务到就绪表
任务创建后被添加到就绪列表中,FreeRTOS使用不同的列表表示任务的不同状态,tasks.c中定义了多个列表完成不同功能:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList; /*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
其中pxReadyTasksLists[]即是任务就绪列表,数组大小为configMAX_PRIORITIES即可使用的最大优先级,所以一个优先级一个列表,相同优先级的任务则使用一个列表。
函数prvAddNewTaskToReadyList()完成将一个新创建的任务添加到就绪列表中
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{taskENTER_CRITICAL();{uxCurrentNumberOfTasks++;if( pxCurrentTCB == NULL ){pxCurrentTCB = pxNewTCB;if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){prvInitialiseTaskLists();}else{mtCOVERAGE_TEST_MARKER();}}else{if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}}uxTaskNumber++;#if ( configUSE_TRACE_FACILITY == 1 ){pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif /* configUSE_TRACE_FACILITY */traceTASK_CREATE( pxNewTCB );prvAddTaskToReadyList( pxNewTCB );portSETUP_TCB( pxNewTCB );}taskEXIT_CRITICAL();if( xSchedulerRunning != pdFALSE ){if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){taskYIELD_IF_USING_PREEMPTION();}else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}
}
taskENTER_CRITICAL():首先先进入临界区,关闭中断,使接下来函数里代码完整运行不被打断
uxCurrentNumberOfTasks++:全局变量uxCurrentNumberOfTasks加1,统计任务数量
if( pxCurrentTCB == NULL ){pxCurrentTCB = pxNewTCB:如果正在运行任务控制块为NULL,即为没有任务运行,则将新任务的任务控制块赋值给pxCurrentTCB,新创建的任务便是第一个任务
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ){prvInitialiseTaskLists();}:如果任务数量为1,则说明创建的任务是第一个任务,此时通过函数prvInitialiseTaskLists()来初始化相应的列表(函数prvInitialiseTaskLists()即调用列表初始化函数vListInitialise()来初始化几个列表)
if( xSchedulerRunning == pdFALSE ){if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ){pxCurrentTCB = pxNewTCB;}:如果调度器尚未运行,新任务的优先级比正在运行的任务优先级高,则修改pxCurrentTCB为新建任务的任务控制块
uxTaskNumber++:uxTaskNumber加1,用作任务控制块编号
if ( configUSE_TRACE_FACILITY == 1 ){pxNewTCB->uxTCBNumber = uxTaskNumber;}#endif:条件编译如果启动了可视化跟踪调试,则将任务控制块编号赋值给新任务的任务控制块的uxTCBNumber成员变量
prvAddTaskToReadyList( pxNewTCB ):调用函数吧任务添加到就绪表中,这个函数其实是个宏:
#define prvAddTaskToReadyList( pxTCB ) \traceMOVED_TASK_TO_READY_STATE( pxTCB ); \taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
traceMOVED_TASK_TO_READY_STATE( pxTCB )和tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )都没什么用;taskRECORD_READY_PRIORITY用来记录处于就绪态任务,接下来使用函数vListInsertEnd()将任务添加到就绪列表末尾
taskEXIT_CRITICAL():之后退出临界区
if( xSchedulerRunning != pdFALSE ){if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ){
taskYIELD_IF_USING_PREEMPTION();}:如果调度器已经开始运行,并且新任务的优先级最高,则调用函数taskYIELD_IF_USING_PREEMPTION()完成一次任务切换
FreeRTOS任务创建相关推荐
- FreeRTOS任务创建和删除
任务创建和删除的API函数 xTaskCreate():使用动态方法创建一个任务 xTaskCreateStatic():使用静态方法创建一个任务 xTaskCreateRestricated():创 ...
- FreeRTOS动态创建任务
一.动态方式创建任务和删除任务 1>测试环境 系统:WIN7 MDK:keil v5.26 开发板:GD32F303C-EVAL 固件库:V1.0.2 FreeRTOS版本:V10.4.0 2& ...
- 【STM32】FreeRTOS创建和删除任务示例(静态方法)(了解)
00. 目录 文章目录 00. 目录 01. 概述 02. 功能描述 03. 任务设计 04. 程序设计 05. 结果验证 06. 附录 07. 参考 01. 概述 FreeRTOS中创建和删除任务A ...
- 【STM32】FreeRTOS创建和删除任务示例(动态方法)
00. 目录 文章目录 00. 目录 01. 概述 02. 功能描述 03. 任务设计 04. 程序设计 05. 结果验证 06. 附录 07. 参考 01. 概述 FreeRTOS中创建和删除任务A ...
- esp32 怎么分配freertos 堆栈大小_深度解剖~ FreeRtos阅读笔记2 任务创建、内核链表初始化...
2.FREERTOS任务创建.内核链表初始化 硬件环境:cortex m4 FreeRTOS版本:v8.0.1 今天开始阅读freertos,阅读同时做下笔记,等哪天碰到移植问题再翻出来看看. 2.1 ...
- 【FreeRTOS】02 任务的创建
上一节我们利用cubemx自动生成了一个实例,它建立了两个用户任务和一个默认任务,并成功运行起来了.本节我们将分析一下FreeRTOS任务创建的过程,并结合创建时需要的参数讲解一下FreeRTOS任务 ...
- FreeRtos源码分析之任务创建和管理(一)
一.任务控制块TCB 在开始学习FreeRtos任务相关源码之前,我们需要先了解一个重要的结构体-任务控制块.FreeRtos的每一个任务都有一个独立的任务控制块TCB,TCB中记录了任务的优先级.名 ...
- freeRtos学习笔记 (6)软件定时器
freeRtos学习笔记 freeRtos软件定时器 软件定时器需要注意事项 软件定时器的精度基于时钟节拍,例如系统时钟节拍为10ms, 软件定时器定时时间必须是10ms的整数倍,因此软件定时器一般用 ...
- FreeRTOS高级篇7---FreeRTOS内存管理分析
内存管理对应用程序和操作系统来说都非常重要.现在很多的程序漏洞和运行崩溃都和内存分配使用错误有关. FreeRTOS操作系统将内核与内存管理分开实现,操作系统内核仅规定了必要的内存管理 ...
最新文章
- day_06、面向对象
- Android性能调优篇之探索垃圾回收机制
- [BUUCTF-pwn]——picoctf_2018_buffer overflow 0
- WebLogic下WAR方式部署获取文件路径问题
- 使用参数来防止SQL注入
- 【英语学习】【Level 07】U06 First Time L6 Not your typical experience
- kafka自定义序列化器
- 初创IT公司里开发者最容易犯的九种错误
- 三进制计算机在线计算,计算器在线
- 视频播放控制:防盗链设置与视频加密及Android中的基础应用
- 日本作家将编程语言变成了动漫人物,你猜C语言是萝莉还是御姐?不得不说脑洞实在太大了!
- PNG格式图片怎么弄
- ARM GCC浮点相关总结
- 火车采集 PHP插件 post,火车采集器2010版PHP插件增加扩展的方法
- 安装黑群晖找不到局域网电脑_组建家庭存储群晖NAS(一)——详细安装篇
- 逻辑回归实例,特征预处理
- Elasticsearch学习(二)————搜索
- zimbra邮箱服务器迁移
- 【RDMA】RoCE网络QoS|应用层设置PFC等级|Tos|Priority|TC
- Navicate无法连接,提示is not allowed to connect to this mysql server
热门文章
- Java项目:图书进销存管理系统(java+SSM+JSP+bootstrap+Mysql)
- 计算机视觉在小机器人中应用,计算机视觉系统在工业机器人上的应用研究.doc...
- 微信公众号配置及测试
- 利用 Xunsearch 搭建搜索引擎、内容搜索实战
- Animator视图方法缩小
- Animator自定义动画播放
- Ossim java_ossim之添加资产全方法
- uglifyjs使用
- Android控件晃动效果实现
- 小型ssh工具dropbear 安装配置及使用详解