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 //复位栈顶MSP
cpsie i //使能中断(清除PRIMASK)
cpsie f //使能中断(清除FAULTMASK)
dsb //数据同步屏障
isb //指令同步屏障
svc 0 //触发SVC异常
nop
nop
}
vPortSVCHandler()SVC异常处理函数详解
__asm void vPortSVCHandler( void )
{
PRESERVE8
ldr r3, =pxCurrentTCB //获取pxCurrentTCB指针的地址赋值R3
ldr r1, [r3] //获取pxCurrentTCB指针指向的值赋值R1
ldr r0, [r1] //获取R1地址处的值赋值给R0,获取任务栈栈顶指针,即任务控制块的第一个数据就是任务栈顶指针
ldmia r0!, {r4-r11, r14}//R4~R11,R14寄存器出栈,栈顶移动36=4x9个字节,用户手动出栈
msr psp, r0 //设置进程栈指针,PSP=R0
isb //指令同步屏障
mov r0, #0 //R0=0
msr 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;
PRESERVE8
mrs r0, psp //读取进程栈指针,保存在R0寄存器中
isb
ldr r3, =pxCurrentTCB //获取当前任务控制块的指针
ldr r2, [r3] //将当前任务控制块的地址保存在R2寄存器中
tst r14, #0x10
it 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_PRIORITY
msr basepri, r0//关闭中断进入临界区
dsb
isb
bl vTaskSwitchContext//调用vTaskSwitchContext()获取下一个运行的任务
mov r0, #0
msr basepri, r0//打开中断,退出临界区
ldmia sp!, {r3}//R3的值出栈,新的任务控制块
ldr r1, [r3]
ldr r0, [r1]//获取新的任务的栈顶指针,保存到R0中
ldmia r0!, {r4-r11, r14}//R4~R11,R14出栈
tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31}//判断当前任务是否使用FPU,是的话手动恢复FPU寄存器
msr psp, r0//跟新进程栈指针
isb
#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata */
#if WORKAROUND_PMU_CM001 == 1
push { r14 }
pop { pc }
nop
#endif
#endif
bx 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