RTOS随笔之FreeRTOS启动与同步方法

文章详细介绍了FreeRTOS操作系统如何启动任务调度器,包括vTaskStartScheduler函数的作用,以及任务切换的关键函数如xPortStartScheduler和xPortPendSVHandler。此外,还阐述了任务同步的机制,如队列、信号量和事件组在任务通信中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值