RTOS随笔之FreeRTOS启动与同步方法
RTOS启动与同步机制
- RTOS启动
- 任务切换场景
- 任务同步机制
- 队列
- 信号量
- 事件组
- 任务通知
- 任务延时
RTOS启动
FreeRTOS在任务创建完成后调用函数vTaskStartScheduler()启动任务调度器。
vTaskStartScheduler()任务启动函数详解
void vTaskStartScheduler( void )
{BaseType_t xReturn;xReturn = xTaskCreate( prvIdleTask,"IDLE", configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),&xIdleTaskHandle );//创建空闲任务,空闲任务优先级最低#if ( configUSE_TIMERS == 1 )//是否使用软件定时器{if( xReturn == pdPASS ){xReturn = xTimerCreateTimerTask();//创建软件定时器任务}else{mtCOVERAGE_TEST_MARKER();}}#endif if( xReturn == pdPASS ){portDISABLE_INTERRUPTS();//关闭中断#if ( configUSE_NEWLIB_REENTRANT == 1 ){_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;//调度器开始运行xTickCount = ( TickType_t ) 0U;portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();//实现任务统计功能,具体由用户实现if( xPortStartScheduler() != pdFALSE )//初始化任务硬件相关的功能,Systick/PendSV/FPU{//如果调度器启动成功,xPortStartScheduler()没有返回值,不会运行到这里}else{//不会运行到这里,除非调用xTaskEndScheduler()}}else{//创建空闲任务或定时器任务失败,内存不足configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}//防止编译器报错,INCLUDE_xTaskGetIdleTaskHandle定义为0,编译器会提示( void ) xIdleTaskHandle;
}
xPortStartScheduler()任务硬件相关函数详解
BaseType_t xPortStartScheduler( void )
{portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;//设置PendSV中断为最低优先级portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;//设置Systick中断为最低优先级vPortSetupTimerInterrupt();//设置Systick周期uxCriticalNesting = 0;//初始化临界区嵌套计数prvEnableVFP();//使能FPU浮点运算*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;//采用惰性压栈的特性自动保存寄存器/*惰性压栈猜测:惰性压栈指的是异常产生时只将通用寄存器的值进行压栈,浮点寄存器值不压栈,除非异常处理函数中存在浮点运算才会将浮点寄存器压栈。具有浮点运算的处理器,总线错误和MemManage错误等异常可能会在惰性压栈期间产生。惰性压栈特性推迟了浮点寄存器的压栈,只有当异常处理中存在浮点运算时才会将浮点寄存器的值先压栈,后执行异常处理中的浮点运算。*/prvStartFirstTask();//在SVC异常中启动第一个任务return 0;
}
prvStartFirstTask()启动第一个任务函数详解
__asm void prvStartFirstTask( void )
{PRESERVE8 ldr r0, =0xE000ED08 //向量表偏移寄存器VTOR的地址,存储栈顶地址,对该寄存器VTOR的值进行解引用获得栈顶地址ldr r0, [r0] //取R0地址处的值赋给R0,R0地址处的值是一个地址ldr r0, [r0] //取R0地址处的值赋给R0, R0地址处的值是栈顶msr msp, r0 //复位栈顶MSPcpsie i //使能中断(清除PRIMASK)cpsie f //使能中断(清除FAULTMASK)dsb //数据同步屏障isb //指令同步屏障svc 0 //触发SVC异常nopnop
}
vPortSVCHandler()SVC异常处理函数详解
__asm void vPortSVCHandler( void )
{PRESERVE8ldr r3, =pxCurrentTCB //获取pxCurrentTCB指针的地址赋值R3ldr r1, [r3] //获取pxCurrentTCB指针指向的值赋值R1ldr r0, [r1] //获取R1地址处的值赋值给R0,获取任务栈栈顶指针,即任务控制块的第一个数据就是任务栈顶指针ldmia r0!, {r4-r11, r14}//R4~R11,R14寄存器出栈,栈顶移动36=4x9个字节,用户手动出栈msr psp, r0 //设置进程栈指针,PSP=R0isb //指令同步屏障mov r0, #0 //R0=0msr basepri, r0 //basepri寄存器=0,开中断bx r14 //硬件自动恢复R0~R3,R12,LR,PC,xPSP值,PC指向下一条指令
}
任务切换场景
任务切换场景:
1.执行系统调用接口(含有taskYIELD()的API)
2.Systick定时器中断
任务在哪里切换:任务在PendSV的异常中切换
__asm void xPortPendSVHandler( void )
{extern uxCriticalNesting;extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8mrs r0, psp //读取进程栈指针,保存在R0寄存器中isb ldr r3, =pxCurrentTCB //获取当前任务控制块的指针ldr r2, [r3] //将当前任务控制块的地址保存在R2寄存器中tst r14, #0x10it eq //判断是否使用FPU,使用的话在进行任务切换的时候要手动保存FPU寄存器到任务栈中vstmdbeq r0!, {s16-s31}//保存S16~S31这16个FPU寄存器stmdb r0!, {r4-r11, r14}//保存R4~R11,R14寄存器str r0, [r2]//将R0的值保存到R2所保存的地址中去,R2保存当前任务控制块的地址,等于更新任务块栈顶指针stmdb sp!, {r3}//将R3的值入栈mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITYmsr basepri, r0//关闭中断进入临界区dsbisbbl vTaskSwitchContext//调用vTaskSwitchContext()获取下一个运行的任务mov r0, #0msr basepri, r0//打开中断,退出临界区ldmia sp!, {r3}//R3的值出栈,新的任务控制块ldr r1, [r3]ldr r0, [r1]//获取新的任务的栈顶指针,保存到R0中ldmia r0!, {r4-r11, r14}//R4~R11,R14出栈tst r14, #0x10it eqvldmiaeq r0!, {s16-s31}//判断当前任务是否使用FPU,是的话手动恢复FPU寄存器msr psp, r0//跟新进程栈指针isb#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */#if WORKAROUND_PMU_CM001 == 1push { r14 }pop { pc }nop#endif#endifbx r14/*硬件自动恢复R0~R3,R12,LR,PC,xPSP值,确定异常返回后进入处理器模式还是进程模式,使用主栈指针MSP还是进程栈指针PSP。后续进入进程模式,使用进程栈指针,PC的值指向新任务的函数,新任务开始运行*/
}
任务同步机制
- 队列
- 信号量(二值/计数/互斥)
- 事件组
- 任务通知(消息邮箱)
队列
队列传输数据的两种方式:
- 拷贝:把数据复制进队列
- 引用:把数据地址复制进队列,数据要做保护
多任务发送队列,怎么区别数据来源:
- 结构体:ID+数据形式
- 位拼接:高位表示ID,低位表示数据
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
BaseType_t xQueueReset( QueueHandle_t pxQueue);
void vQueueDelete( QueueHandle_t xQueue );
BaseType_t xQueueSend(QueueHandle_t xQueue,const void *pvItemToQueue,TickType_t xTicksToWait);
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xQueueReceive( QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxTaskWoken);
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );//返回队列已有数据空间个数
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );//返回队列剩余数据空间个数
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,const void * pvItemToQueue);//队列覆盖写
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,const void * pvItemToQueue,BaseType_t*pxHigherPriorityTaskWoken);
BaseType_t xQueuePeek(QueueHandle_t xQueue,void * const pvBuffer,TickType_t xTicksToWait);//复制队列中的数据,不删除数据,队列中无数据时阻塞
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,void *pvBuffer,);
信号量
- 通知作用
- 计数型信号,信号初值自定义,发送加1,接收减1
- 二进制信号,信号初值是0,发送加1,接收减1
- 互斥信号,防止同时访问共享资源(共享资源:全局变量,静态变量,公共函数)
- 互斥信号禁止中断中使用
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
SemaphoreHandle_t xSemaphoreCreateBinary( void );
SemaphoreHandle_t xSemaphoreCreateMutex( void );//互斥锁,他人也能解锁
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );//递归锁,自己上锁自己解锁,他人无法解锁
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);
事件组
- 事件发生,广播所有任务
- 事件可以与或组合
- 多任务可以同步
EventGroupHandle_t xEventGroupCreate( void );
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
EventBits_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet ,BaseType_t * pxHigherPriorityTaskWoken);
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );
任务通知
- 明确通知的任务,数据任务独享
- 不额外开辟内存
- 效率更高
- 中断中只能发送数据给任务
/*任务通知计数功能函数*/
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
/*任务通知复杂功能函数*/
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyActioneAction );
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,uint32_t ulValue,eNotifyAction eAction,BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,uint32_t ulBitsToClearOnExit,uint32_t *pulNotificationValue,TickType_t xTicksToWait );
任务延时
vTaskDelay(n):进入vTaskDelay到退出间隔至少n个Tick中断
xTaskDelayUntil(&Pre, n):两次退出 xTaskDelayUntil 的时间至少是n个Tick中断,也就是任务运行时的Tick+Delay的Tick