队列
二进制信号灯
计数信号灯
互斥
递归互斥, 以及更多
--------------------------------------------------------------------------------
队列
队列是内部通信的主要形式。它可以用于在任务和任务之间以及任务和中断之间发送消息。在大多数情况下使用线程安全
FIFO(先进先出)缓存,新数据放在队列的最后,虽然数据也可以放在前面。
队列可以包含固定大小的 '项目' - 每个项目的大小和队列可以保存项目的最大数量在创建队列时就已经定义了。
项目以复制而不是引用的方式放入队列,因此最好使放入队列项目的大小成为最小。以复制的方式放入队列可以使你的系统设计极大的简化,因为两个任务不会同时访问数据。队列帮助你管理所有的互斥问题。
如果你希望在队列中使用大的项目,可能最好用插入队列指针 -
但是这样做必须注意要确保你的系统明确定义任务和/或中断是数据的"所以者"。
队列 API
函数可以指定阻塞的时间。阻塞时间代表任务进入阻塞状态或者等待队列中数据时(当任务读取队列但是队列是空的时候)的最大'节拍'数,或者等待队列空间变为可以使用(当任务需要写数据到队列,但是队列已满时)。当一个以上任务在同一个队列中被阻塞时,高优先级的任务先解除阻塞。
查看用户文档中 队列管理 小节中与队列相关的 API 函数列表。搜索 FreeRTOS/Demo/Common/Minimal
文件夹下的文件可以发现多个这种用法的例子。注意中断里不能使用不是用 "FromISR" 结束的 API 函数。
写入和读取队列。在这个例子中队列保存5个项目,并且从不变满。
--------------------------------------------------------------------------------
二进制信号灯
二进制信号灯同时用于互斥和同步的目的。
二进制信号灯和互斥非常相似,但是有一些细微的不同:互斥包括优先级继承机制,而二级制信号没有。这使得二进制信号灯用于同步更方便
(在任务之间或任务与中断之间),而互斥用在简单的互相排斥更好。在怎样用互斥作为互相排斥的机制中 说明
二进制信号灯用法,这个子章节只说明使用二进制信号灯进行同步。
信号灯 API
函数可以指定阻塞的时间。阻塞时间代表任务因为等待获取信号灯而进入阻塞状态的最大'节拍'数。如果超过一个以上的任务因为同一个信号灯被阻塞,那么在信号灯可以使用时高优先级的任务先解除阻塞。
将一个二进制信号灯看作为队列,它只能保存一个项目,这个队列只能是空的或者是满的 (二进制)。使用队列的任务和中断不关心队列保存了什么
- 它们只关系队列是空的还是满的。这个机制可以用于同步任务和中断。
考虑这样一个情况,任务使用一个外设,轮询外设将浪费 CPU 资源,并阻止了其他任务的运行。因此最好是任务将大部分时间用于阻塞状态
(允许其他任务运行),只有真正需要操作时才去轮询。这就是在 '试图'
获得信号时使用二进制信号灯进行任务阻塞,而一个中断服务程序用于外设,当需要使用外设时 '给出' 信号。任务总是 '获取' 信号
(从队列读取直到队列为空),但是从不 '给出'。中断服务程序总是 '给出' 信号 (写入队列直到队列变满) 而从不获取。
xSemaphoreGiveFromISR() 源代码的文档中清楚的解释了这个方法。
可以使用任务的优先级保证及时的外设服务 - 有效的产生 '不同的中断'
方式。另外一种方法是使用队列代替信号,在中断服务程序中捕捉外设的数据并通过队列发送给任务。当队列的数据可以使用后任务解除阻塞,从队列读取数据后,进行数据处理。第二种方法允许中断程序尽可能的短,通过
post 处理而不是发生在一个任务中。
参考用户文档的 信号灯/互斥 小节中关于信号灯的 API 函数。搜索 FreeRTOS/Demo/Common/Minimal
文件夹下的文件可以获得很多这种用法的例子。注意中断里不能使用不是以 "FromISR" 结束的 API 函数。
下载 (29.87 KB)
2009-7-23 23:20
使用信号灯同步任务和中断。中断只 '给出' 信号,而任务只 '获取' 信号。
--------------------------------------------------------------------------------
计数信号灯
正如二进制信号灯可以认为长度是 1 的队列,计数信号灯可以看成是长度大于 1 的队列。同样,用户的信号不是对队列数据的数值感兴趣 -
只需要看队列是不是空的。
计数信号灯典型用于两个方面:
计数事件。
在这个情况下使用一个事件处理程序将在每次事件发生时 '给出' 信号 (增加信号计数值), 同时每次处理事件时处理任务将 '获得' 信号
(减少信号计数值)。因此计数值是事件发生次数和处理次数的差,信号创建时,这个计数值是0。
资源管理。
在这个情况下,计数值代表可用的资源数。为了获得一个资源的控制,任务必须先获得信号 - 减少信号的计数值。当计数值达到
0,代表没有剩余的自由资源。当任务使用完资源后,需要 '返还' 信号 -
增加信号计数值。在这种方式下信号创建时的计数值等于最大计数值。
参考 信号灯/互斥 小节查看相关的 API 函数。搜索 FreeRTOS/Demo/Common/Minimal
文件夹下的文件可以查看关于这种用法的例子。注意中断里不能使用不以 "FromISR" 结束的 API 函数。
--------------------------------------------------------------------------------
互斥
互斥是包含优先级继承机制的
二进制信号灯。对于同步(在任务之间或者任务与中断之间)来说二进制信号灯是更好的选择,互斥对于简单的互相排斥更方便 (mutex 就是
'MUT'ual 'EX'clusion).
当用于互相排斥时,互斥就像是资源保护。当一个任务需要访问资源,它必须先获得 ('take') 令牌;当访问结束后,它必须释放令牌 -
允许其他任务能够访问这个资源。
互斥使用了和信号灯相同的 API
函数,所以也可以指定阻塞时间。阻塞时间代表了任务因为试图获取一个不可马上使用的信号而进入阻塞状态的最大 '节拍'
数。和二进制信号灯不同 -
互斥采用了优先级继承关系。这意味着如果高优先级任务因为试图获取被低优先级任务保持的互斥信号(令牌)而阻塞,那么低优先级任务的优先级将临时上升到被阻塞的任务。这个机制保证了高优先级任务被阻塞的时间最短,并减少了已经发生的
'优先级反转'。
优先级继承并不能解决优先级反转的问题!它只是在某些条件下降低了它的影响。硬实时系统要把防止优先级反转放在第一位来考虑。
下载 (77.37 KB)
2009-7-23 23:20
使用互斥来保护共享的资源
--------------------------------------------------------------------------------
递归互斥
一个使用了递归的互斥可以反复被所有者 '获取'。互斥变为不可用直到所有者为每次 xSemaphoreTakeRecursive()
请求调用 xSemaphoreGiveRecursive()。例如,如果任务成功 '获取' 相同的互斥 5
次,然后互斥将对其他任务变为不可用,直到它正好 '返还' 互斥 5 次。
这个类型的信号灯使用了优先级继承机制,所以任务 '获取' 信号后必须总是在不再需要信号后 '返还'。
互斥类型信号不能用于中断服务程序。
|
浅析FreeRTOS_v4.5.0队列、信号和Mutex互斥量共用函数体Queue.c文件
typedef struct QueueDefinition
{
signed char
*pcHead;
signed char
*pcWriteTo;
signed char
*pcReadFrom;
xList
xTasksWaitingToSend;
xList
xTasksWaitingToReceive;
volatile unsigned portBASE_TYPE uxMessagesWaiting
队列总的条目个数 */
unsigned portBASE_TYPE
uxLength;
unsigned portBASE_TYPE
uxItemSize;
、
signed portBASE_TYPE
xRxLock;
signed portBASE_TYPE
xTxLock;
} xQUEUE;
xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
unsigned portBASE_TYPE uxItemSize )
{
xQUEUE *pxNewQueue;
size_t xQueueSizeInBytes;
if( uxQueueLength > ( unsigned portBASE_TYPE ) 0
)
{
pxNewQueue = ( xQUEUE * ) pvPortMalloc( sizeof( xQUEUE ) );
if( pxNewQueue != NULL )
{
xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ) + (
size_t ) 1;
pxNewQueue->pcHead = ( signed char * ) pvPortMalloc(
xQueueSizeInBytes );
if( pxNewQueue->pcHead != NULL )
{
pxNewQueue->pcTail =
pxNewQueue->pcHead + ( uxQueueLength * uxItemSize
);
pxNewQueue->uxMessagesWaiting = 0;
pxNewQueue->pcWriteTo =
pxNewQueue->pcHead;
//从头写入
pxNewQueue->pcReadFrom =
pxNewQueue->pcHead + ( ( uxQueueLength - 1 ) *
uxItemSize ); //尾部读出
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
pxNewQueue->xRxLock = queueUNLOCKED;
pxNewQueue->xTxLock = queueUNLOCKED;
vListInitialise( &(
pxNewQueue->xTasksWaitingToSend ) );
vListInitialise( &(
pxNewQueue->xTasksWaitingToReceive ) );
traceQUEUE_CREATE( pxNewQueue );
return pxNewQueue;
}
else
{
traceQUEUE_CREATE_FAILED();
vPortFree( pxNewQueue );
}
}
}
return NULL;
}
//-----------------------------------------------------------------------------------
signed portBASE_TYPE xQueueGenericSend( xQueueHandle pxQueue, const
void * const pvItemToQueue, portTickType xTicksToWait,
portBASE_TYPE xCopyPosition )
{
signed portBASE_TYPE xEntryTimeSet = pdFALSE;
xTimeOutType xTimeOut;
for( ;; )
{
taskENTER_CRITICAL();
{
if( pxQueue->uxMessagesWaiting
< pxQueue->uxLength )
{
traceQUEUE_SEND( pxQueue );
prvCopyDataToQueue( pxQueue, pvItemToQueue,
xCopyPosition );
if( listLIST_IS_EMPTY( &(
pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &(
pxQueue->xTasksWaitingToReceive ) ) == pdTRUE )
{
如果唤醒的任务比当前任务优先级高,进行调度。
portYIELD_WITHIN_API();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
if( xTicksToWait == ( portTickType ) 0 )
{
没有加等待延时,直接返回。
taskEXIT_CRITICAL();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
else if( xEntryTimeSet == pdFALSE )
{
vTaskSetTimeOutState( &xTimeOut
);
xEntryTimeSet = pdTRUE;
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
if( xTaskCheckForTimeOut(
&xTimeOut, &xTicksToWait ) ==
pdFALSE )
{
if( prvIsQueueFull( pxQueue )
)
{
traceBLOCKING_ON_QUEUE_SEND( pxQueue );
如上边提到的,当这里正要执行的时候,ISR突然到达,那么
//ISR可能把数据读出去了,所以空出了数据,但是这里还会进行队列调整
//因为ISR中发现Q已经被锁,所以Q不会在ISR中发生调度,仅仅是把数据读出去而已
//下面耗费多长时间也无所谓了,因为现在ISR硬件中断系统并没有关闭
vTaskPlaceOnEventList( &(
pxQueue->xTasksWaitingToSend ), xTicksToWait );
上面解释:解锁queue意味着queue操作可以影响事件列表,中断(释放信号量)可以移出等待信号量任务,但是,如果此时任务调度被关闭,任务不直接放到就绪表,而是放到pending
redaylist.///
prvUnlockQueue( pxQueue );
如果上面的ISR中断发生,那么本task已经被放到了Event事件双向链表上,
//下面的Unlock将使等待在Event事件队列双向链表上的最应该执行的task
//从事件队列双向链表上摘下,因为当下内核调度器被锁,所以恢复的task将被
//暂时放到PendingReady队列上
允许任务调度,移pending 到 readylist.必须进行调度。
if( !xTaskResumeAll()
)
{
portYIELD_WITHIN_API();
}
}
else
{
prvUnlockQueue( pxQueue
);
解锁函数,会检查在解锁期间,由中断函数send,,receive
的信号量,重新移出等待列表的任务,如果调度被禁止,任务会放到pendinglist .
( void ) xTaskResumeAll();
xTastResumeALL函数代码是:
while( ( pxTCB = ( tskTCB * )
listGET_OWNER_OF_HEAD_ENTRY( ( ( xList * )
&xPendingReadyList ) ) ) != NULL )
{
vListRemove( &(
pxTCB->xEventListItem ) );
vListRemove( &(
pxTCB->xGenericListItem ) );
prvAddTaskToReadyQueue( pxTCB );
}
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_SEND_FAILED( pxQueue );
return errQUEUE_FULL;
}
}
}
//-----------------------------------------------------------------------------------
signed portBASE_TYPE xQueueGenericReceive( xQueueHandle pxQueue,
const void * const pvBuffer, portTickType xTicksToWait,
portBASE_TYPE xJustPeeking )
{
signed portBASE_TYPE xEntryTimeSet = pdFALSE;
xTimeOutType xTimeOut;
signed char *pcOriginalReadPosition;
for( ;; )
{
taskENTER_CRITICAL();
{
if( pxQueue->uxMessagesWaiting
> ( unsigned portBASE_TYPE ) 0 )
{
pcOriginalReadPosition =
pxQueue->pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );
if( xJustPeeking == pdFALSE )
{
traceQUEUE_RECEIVE( pxQueue );
--( pxQueue->uxMessagesWaiting
);
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType ==
queueQUEUE_IS_MUTEX )
{
pxQueue->pxMutexHolder =
xTaskGetCurrentTaskHandle();
}
}
#endif
if( listLIST_IS_EMPTY( &(
pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
if( xTaskRemoveFromEventList( &(
pxQueue->xTasksWaitingToSend ) ) == pdTRUE )
{
portYIELD_WITHIN_API();
}
}
}
else
{
traceQUEUE_PEEK( pxQueue );
pxQueue->pcReadFrom =
pcOriginalReadPosition;
if( !listLIST_IS_EMPTY( &(
pxQueue->xTasksWaitingToReceive ) ) )
{
if( xTaskRemoveFromEventList( &(
pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
portYIELD_WITHIN_API();
}
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else
{
if( xTicksToWait == ( portTickType ) 0 )
{
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
vTaskSetTimeOutState( &xTimeOut
);
xEntryTimeSet = pdTRUE;
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
if( xTaskCheckForTimeOut(
&xTimeOut, &xTicksToWait ) ==
pdFALSE )
{
if( prvIsQueueEmpty( pxQueue ) )
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType ==
queueQUEUE_IS_MUTEX )
{
portENTER_CRITICAL();
//2007-09-28 gliethttp
//优先级继承
//如果将要悬挂在该Q上的本task对应的优先级高于Q队列上正在持有Q使用权的pxMutexHolder对应的task的优先级
//,那么提升正在使用Q资源的pxMutexHolder对应的task的优先级到达本task的优先级
//2007-09-29 gliethttp
//低于持有mutex互斥量的task优先级的task-C不会抢占C
//高于task-C优先级的task应该抢占C
//比悬停在Q上的最高优先级task-A的优先级低的同时比持有mutex互斥量的C优先级高的task们
//才会出现优先级翻转现象
//[注:对于优先级翻转问题,可以参看《浅析μCOS/II
v2.85内核OSMboxPend()和OSMboxPost()函数工作原理》
// 和《关于uC/OS-II中优先级翻转问题(转)》.文章地址:http://gliethttp.
//]
{
vTaskPriorityInherit( ( void * )
pxQueue->pxMutexHolder );
}
portEXIT_CRITICAL();
}
}
#endif
vTaskPlaceOnEventList( &(
pxQueue->xTasksWaitingToReceive ), xTicksToWait
);
prvUnlockQueue( pxQueue );
if( !xTaskResumeAll() )
{
portYIELD_WITHIN_API();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
}
}
。
//-----------------------------------------------------------------------------------
static void prvCopyDataToQueue( xQUEUE *pxQueue, const void
*pvItemToQueue, portBASE_TYPE xPosition )
{
if( pxQueue->uxItemSize == 0 )
{
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX
)
{
//2007-09-28 gliethttp
//说是恢复Q上task的优先级,倒不如说调用了mutex_unlock()
//其中xQueueReceive等效于mutex_lock();
// xQueueSend 等效于mutex_unlock();
//所以对于mutex,就没有什么Q的概念了
//任何一个对mutex发送的数据,即:xQueueSend,都会使mutex解锁,因为
//下一次肯定是等待在Q-mutex上最高优先级的task获得执行
//2007-09-29 gliethttp
//低于持有mutex互斥量的task优先级的task-C不会抢占C
//高于task-C优先级的task应该抢占C
//比悬停在Q上的最高优先级task-A的优先级低的同时比持有mutex互斥量的C优先级高的task们
//才会出现优先级翻转现象
vTaskPriorityDisinherit( ( void * const )
pxQueue->pxMutexHolder );
}
}
#endif
}
else if( xPosition == queueSEND_TO_BACK )
{
memcpy( ( void * ) pxQueue->pcWriteTo,
pvItemToQueue, ( unsigned ) pxQueue->uxItemSize
);
pxQueue->pcWriteTo +=
pxQueue->uxItemSize;
if( pxQueue->pcWriteTo >=
pxQueue->pcTail )
{
pxQueue->pcWriteTo =
pxQueue->pcHead;
}
}
else
{
memcpy( ( void * ) pxQueue->pcReadFrom,
pvItemToQueue, ( unsigned ) pxQueue->uxItemSize
);
pxQueue->pcReadFrom -=
pxQueue->uxItemSize;
if( pxQueue->pcReadFrom <
pxQueue->pcHead )
{
pxQueue->pcReadFrom = (
pxQueue->pcTail - pxQueue->uxItemSize
);
}
}
++( pxQueue->uxMessagesWaiting );
}
|