FreeRTOS:资源管理

1、几种场景分析

死锁场景:
        A任务已经获取了资源a,B任务已经获取了资源b,且这时A任务正在等待B任务释放资源b,而B任务正在等待A任务释放资源a;

资源冲突场景:
        A任务正在写某个缓冲区(buffer),还没写完发生了任务切换,切换到B任务;B任务执行该缓冲区的读操作,读完后任务切换到A任务;这样B任务读取的数据一部分是新的一部分是旧的,这可能会引起B任务执行异常。


        解决资源冲突的常用方法就是原子操作,即:任意任务从获取资源到释放资源是一个完整的操作,过程中不被中断;原子操作的实现方案一般有:关调度、关中断;然而原子操作比较影响OS实时性,更常用的技术是:互斥技术。
最好的解决资源冲突方法是:通过设计尽量不共享资源,每个资源由单一任务访问。

2、原子技术

        OS中原子操作的实现方案一般有:关调度、关中断;
        关中断:

taskENTER_CRITICAL();        taskEXIT_CRITICAL();

        关调度:

void vTaskSuspendAll( void );    BaseType_t xTaskResumeAll( void );

        关中断的本质是关系统调用中断,即中断号大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,因此该临界区的执行时间必须尽量短,否则会反过来影响中断响应时间;关调度的本质是让调度器不再执行任务切换,如果一个临界区太长,可以考虑用关调度方式;由于唤醒调度比较久,所以关调度方案也不是随便用的。

        特别注意:关中断、关调度器期间不可以使用OS的API函数。

        2.1开关中断

        关中断代码:

#define taskENTER_CRITICAL()		portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()

#define portENTER_CRITICAL()					vPortEnterCritical()

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
	}
}

#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

开中断代码:

#define taskEXIT_CRITICAL()			portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portEXIT_CRITICAL()						vPortExitCritical()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( uxCriticalNesting == 0 )
	{
		portENABLE_INTERRUPTS();
	}
}

#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}

        可见关中断的本质是配置中断屏蔽寄存器basepri,将优先级不高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断屏蔽,而高于该值得中断优先级较高,且不允许调用OS相关的API。

        2.2、开关调度

        关调度代码:

void vTaskSuspendAll( void )
{
	/* A critical section is not required as the variable is of type
	BaseType_t.  Please read Richard Barry's reply in the following link to a
	post in the FreeRTOS support forum before reporting this as a bug! -
	http://goo.gl/wu4acr */

	/* portSOFRWARE_BARRIER() is only implemented for emulated/simulated ports that
	do not otherwise exhibit real time behaviour. */
	portSOFTWARE_BARRIER();

	/* The scheduler is suspended if uxSchedulerSuspended is non-zero.  An increment
	is used to allow calls to vTaskSuspendAll() to nest. */
	++uxSchedulerSuspended;

	/* Enforces ordering for ports and optimised compilers that may otherwise place
	the above increment elsewhere. */
	portMEMORY_BARRIER();
}

        portSOFTWARE_BARRIER();与portMEMORY_BARRIER();两个函数实际没有实现,是空操作,不用理会;

        开调度代码:

BaseType_t xTaskResumeAll( void )
{
    ……
	taskENTER_CRITICAL();
	{
		--uxSchedulerSuspended;

		if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
		{
            ……
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	taskEXIT_CRITICAL();
    ……
}

        关调度的本质就是uxSchedulerSuspended加1;开调度的本质是uxSchedulerSuspended减1。

        2.3、单条指令

        如果确定某条语句的执行只由一条汇编语句即可完成,那么该语句也可认为是原子操作,如下C代码:

   114:                     TestParam * p = GetTestParam(); 
   115:                      
   116:                     Param1.value1= p->Set.test1; 
   117:                     Param2.value2= p->Set.test2; 

转换成汇编代码后为:

   114:                     TestParam * p = GetTestParam(); 
   115:                      
0x08008CB0 F7F9FD06  BL.W     0x080026C0 GetTestParam
0x08008CB4 4604      MOV      r4,r0
   116:                     Param1.value1= p->Set.test1; 
0x08008CB6 8860      LDRH     r0,[r4,#0x02]
0x08008CB8 4925      LDR      r1,[pc,#148]  ; @0x08008D50
0x08008CBA 8008      STRH     r0,[r1,#0x00]
   117:                     Param2.value2= p->Set.test2; 
0x08008CBC 88A0      LDRH     r0,[r4,#0x04]
0x08008CBE 4925      LDR      r1,[pc,#148]  ; @0x08008D54
0x08008CC0 8008      STRH     r0,[r1,#0x00]

        可以看出"LDRH     r0,[r4,#0x02]"读取变量test1,只用了一条语句;“STRH     r0,[r1,#0x00]”将test1值给到value1,也只用了一句。对于类似本例没有上下文要求的场合,这种原子操作也非常方便。本例只是针对16bit数据读写操作,如果是64bit的可能就不能这么玩了,因为我们单片机目前没有64bit的指令,都是拆分成32bit指令执行的,这样一条64bit的读写操作将至少分两步走,一旦被中断,就不再是原子操作了。

3、互斥技术

        互斥技术常用的有两种:互斥信号量、二值信号量。

// 创建互斥量
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType );
QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue );


BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait );


        从形式上看,两者相同:都是在临界区前获取信号量,导致其它任务获取不成功而阻塞;当前任务处理完临界区后释放信号量,然后其它任务才可以解除该阻塞。不同的在于内部处理机制,其中最大的区别就是互斥量内部提供了优先级继承机制,用于对抗优先级反转问题:A、B、C任务优先级依次为1/2/3,C任务因A任务先把资源占用而阻塞了(这是最普通的优先级反转问题),A任务在释放资源前又被B任务抢占了(严重的优先级反转问题)。
        优先级继承机制:高优先级C任务因低优先级A任务先把资源占用而阻塞了,同时将A任务的优先级临时改为C任务的优先级,待A任务释放资源后,其优先级还原。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值