在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。
队列用于传输数据:在任务之间、任务和中断之间。
消息队列用于传输多个数据,但是有时候我们只需要传递状态,这个状态值需要用一个数值表示。
信号量就是特殊的队列。队列里使用环形缓冲区存放数据,信号量里只记录计数值。
信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。
1. 信号量的特性
1.1 信号量的常规操作:
信号量这个名字很恰当:
- 信号:起通知作用
- 量:还可以用来表示资源的数量
- 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
- 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
- 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
计数型信号量的典型场景是:
- 计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量,让计数值减1。
- 资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
- 生产者为任务A、B,消费者为任务C、D
- 一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果:
- 阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
- 即刻返回失败:不等
- 任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒
- 唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人
- 二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
1.2 信号量和队列的对比
1.3 信号量
1.3.1 二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细 微差别:
- 互斥量有优先级继承机制,二值信号量则没有这个机制。
- 这使得二值信号量更偏 向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于 临界资源的访问。
二值信号量可以被认为是长度为 1 的队列,因此这个队列只能为空或满(因此称为二 值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。
1.3.2 如何使用二进制信号量来同步任务之间的操作?
用作同步时,信号量在创建后应被置为0,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。
1.3.3 计数型信号量
二值信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。
计数型信号量的典型场景是:
- 计数:
- 事件产生时释放"give"一个信号量,让计数值加1;处理事件时要先获取"take"一个信号量,就是获得信号量,让计数值减1。
- 信号量的计数值表示还有多少个事件没被处理。
- 资源管理:
- 要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。 信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合。
- 信号量的计数值表示系统中可用的资源数目。
- 任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统 没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。
1.3.4 互斥信号量
互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用 于简单互锁,也就是保护临界资源 。
用作互斥时,信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时, (临界资源是指任何时刻只能被一个任务访问的资源),先获取互斥信号量,使其变空, 这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界 资源的安全。
1.3.5 递归信号量
对于已经获取递归互斥量的 任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递 归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只 有持有递归信号量的任务才能获取与释放。
1.4 计数型和二进制信号量
1.4.1 计数型和二进制信号量的区别
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。
1.4.2 二值信号量运作机制
1.4.3 计数信号量运作机制
1.5 释放信号量和获取信号量流程
释放信号量:give(要么成功,要么失败,不可能休眠)
- 关中断;
- count++;
xTasksWaitingToReceive
中是否有任务在等待信号量,如果有,将其唤醒- 把
xTasksWaitingToReceive;
中的第 1 个任务移除 - 将其从 DelayList——> ReadList 中
- 把
获取信号量:Take
- 关中断;
- 判断
count > 0 ?
- 否:
- 返回 ERR(不等待)
- 休眠
- 放入
xTasksWaitingToReceive
, - ReadList ——> DelayList中
- 放入
- 是
- 否:
- 是(count > 0):本来count > 0;休眠被唤醒(有任务释放了)
- count --;
- 有任务在等待释放信号量,唤醒它:
- 从
xTasksWaitingToSend
移除 - DelayList ——>ReadList
- 从
- return ok;
2. 信号量函数
2.1 信号量控制块
信号量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不 一样而已
- uxMessagesWaiting:
- 用于消息队列: 用来记录当前消息队列的消息个数;
- 用于信号量:这个值就表示有效信号量个数,
- 二值信号量、互斥信号量:这个值是 1 则表示有可用信号量,如果 是 0 则表示没有可用信号量。
- 计数信号量:这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始 值 uxMaxCount。
- uxLength :
- 用于消息队列: 表示队列的长度, 也就是能存放多少消息 ;
- 用于信号量: 表示最大的信号量可用个数
- 二值信号量、互斥信号量: uxLength 最大为 1,因为信号量要么是 有效的,要么是无效的。
- 计数信号量: 这个值表示最大的信号量个数,在创建计数信号量的时候将 由用户指定这个值 uxMaxCount。
- uxItemSize :
- 用于消息队列:表示单个消息的大小;
- 用于信号量:无需存储空间,为 0 即可 ;
2.2 创建二值信号量 xSemaphoreCreateBinary()
// 二进制型信号量 :初始值为0
g_xSemTicks = xSemaphoreCreateBinary();
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( ( UBaseType_t ) 1, \
semSEMAPHORE_QUEUE_ITEM_LENGTH, \
queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
创建函数xQueueGenericCreate也就是消息队列创建使用的函数,但参数不一样。
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
const uint8_t ucQueueType )
- 创建一个没有消息存储空间的队列,信号量用什么表示?
- 其实二值信号量的释放和获取都是通过操作队列结控制块构体成员
uxMessageWaiting
来实现的, 它表示信号量中当前可用的信号量个数。在信号量创建之后,变量uxMessageWaiting
的值 为 0,这说明当前信号量处于无效状态,此时的信号量是无法被获取的,在获取信号之前, 应先释放一个信号量。
2.3 创建计数信号量 xSemaphoreCreateCounting()
// 计数型信号量 :最大值为3,初始值为2
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )\
xQueueCreateCountingSemaphore( ( uxMaxCount ), \
( uxInitialCount ) )
#endif
- uxMaxCount: 计数信号量的最大值,当达到这个值的时候,信号量不能再被释放。
- uxInitialCount: 创建计数信号量的初始值。
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) &&
( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount,
const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
xHandle = xQueueGenericCreate( uxMaxCount,
queueSEMAPHORE_QUEUE_ITEM_LENGTH,
queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
traceCREATE_COUNTING_SEMAPHORE();
}else{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}
#endif
如果我们创建一个最大计数值uxMaxCount
为 5,并且默认有效的可用信号量个数uxInitialCount
为 5 的计数信号量:
2.4 删除 vSemaphoreDelete()
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
删除信号量过程其实就是删除消息队列过程,因为信号量其实就是消息队列,只不过是无法存储消息的队列而已。
#define vSemaphoreDelete( xSemaphore ) \
vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
2.5 信号量释放函数
SemaphoreHandle_t g_xSemTicks; // 信号量
xSemaphoreGive(g_xSemTicks);
xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
2.5.1 xSemaphoreGive()(任务)
释放的信号量对象必须是已 经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex()创建的递归互斥量。此外该函数不能在中断中使用。
#define xSemaphoreGive( xSemaphore )\
xQueueGenericSend( ( QueueHandle_t )( xSemaphore ),\
NULL,\
semGIVE_BLOCK_TIME,\
queueSEND_TO_BACK )
#define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U )
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
释放信号量实际上是一次入队操作,并且是不允许入队阻塞,因 为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。
如果信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满,则返回错误代码(err_QUEUE_FULL),
2.5.2 xSemaphoreGiveFromISR()(中断)
#define xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)\
xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),\
( pxHigherPriorityTaskWoken ) )
如果可用信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS),如果恢复的任务优先级比当前任务优先级高,那么在退出中断要进行任务切换一次;如果信号量满,则返回错误代码(err_QUEUE_FULL),表示信号量满。
一个或者多个任务有可能阻塞在同一个信号量上,调用函数 xSemaphoreGiveFromISR()可能会唤醒阻塞在该信号量上的任务,如果被唤醒的任务的优先级大于当前任务的优先级,那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE,然后在中断退出前执行一次 上下文切换。
2.6 信号量获取函数
2.6.1 xSemaphoreTake()(任务)
用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。
#define xSemaphoreTake( xSemaphore, xBlockTime )\
xQueueSemaphoreTake( ( xSemaphore ), \
( xBlockTime ) )
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
/* 信号量数= uxMessagesWaiting*/
const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;
/* 看看是否可以获取信号量 */
if( uxSemaphoreCount > ( UBaseType_t ) 0 )
{
traceQUEUE_RECEIVE( pxQueue );
/* 可以获取信号量,信号量值减一 */
pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
#if ( configUSE_MUTEXES == 1 )
{ /* 如果是互斥量的话 */
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* 当前互斥量 > 0,所以当前任务可以获得互斥量,
因此当前任务成为互斥量的持有者 */
pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
/* 看是否有任务在等待 唤醒 */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
{
/* 将事件链表中第一个任务(优先级最高)移除,从阻塞链表中移除,
放入就绪链表*/
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
/* 若恢复任务的优先级大于当前任务,则进行任务切换*/
queueYIELD_IF_USING_PREEMPTION();
}else{
mtCOVERAGE_TEST_MARKER();
}
}else{
mtCOVERAGE_TEST_MARKER();
}
taskEXIT_CRITICAL();
return pdPASS;
}
else // 信号量数 < 0,进入阻塞
{
if( xTicksToWait == ( TickType_t ) 0 )/* 不等待 */
{
#if( configUSE_MUTEXES == 1 )
{
configASSERT( xInheritanceOccurred == pdFALSE );
}
#endif /* configUSE_MUTEXES */
/* 直接返回 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* 要等待,初始化阻塞超时结构体变量,初始化进入阻塞的时间 xTickCount
和溢出次数 xNumOfOverflows */
vTaskInternalSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}else{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can give to and take from the semaphore
now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* 看阻塞时间是否超时 */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{ /* 没超时 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{/* 信号量数为 0 */
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{ /* 互斥量无效(不可获取,被别的任务持有),
判断互斥量的持有者的优先级是否小于当前任务的优先级,
若是,则进行优先级继承 */
xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
/* 将其放入链表xTasksWaitingToReceive */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else /* 信号量不为 0 */
{
/* 没超时,信号量不为0,尝试再次获取信号量 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
/* 超时 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
/* 信号量数为 0 ,且 超时 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
#if ( configUSE_MUTEXES == 1 )
{
/* 优先级继承*/
if( xInheritanceOccurred != pdFALSE )
{
taskENTER_CRITICAL();
{
UBaseType_t uxHighestWaitingPriority;
/* This task blocking on the mutex caused another
task to inherit this task's priority. Now this task
has timed out the priority should be disinherited
again, but only as low as the next highest priority
task that is waiting for the same mutex. */
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
vTaskPriorityDisinheritAfterTimeout( ( void * ) pxQueue->pxMutexHolder, uxHighestWaitingPriority );
}
taskEXIT_CRITICAL();
}
}
#endif /* configUSE_MUTEXES */
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
2.6.2 xSemaphoreTakeFromISR()(中断)
用于获取信号量,是 一个不带阻塞机制获取信号量的函数,获取对象必须由是已经创建的信号量,信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake()函数不同,它不能用于获取互斥 量,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起 作用,而在中断中毫无意义。
3. 信号量实验-控制车辆运行
3.1 计数型信号量实验
有3个car,但只有两张票,前2个car获取信号量( take)先跑到右边后,释放信号量(give)后,第3个car运行
static uint8_t *g_framebuffer;
SemaphoreHandle_t g_xSemTicks;
static void CarTask(void *params)
{
/* 获取信号量:获取到票后才能进城 */
xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
while(1)
{
vTaskDelay(50);
if (pcar->x == g_xres - CAR_LENGTH)
{
/* 释放信号量 */
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
void car_game(void)
{
//int x;
int i,j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 计数型信号量 :最大值为3,初始值为2 */
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
3.2 二进制信号量
- 对于二进制信号量,它的最大值是1,开始时第 1 个car获取信号量(take);
- 第 1 个car跑到最右边后,释放信号量(give);
- 第 2 个car 获取信号量(take),跑到最右边后,释放信号量(give);
- 第 3 个car 获取信号量(take),跑到最右边后,释放信号量(give);
static void CarTask(void *params)
{
/* 获取信号量:获取到票后才能进城 */
xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
while(1)
{
vTaskDelay(50);
if (pcar->x == g_xres - CAR_LENGTH)
{
/* 释放信号量 */
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
void car_game(void)
{
//int x;
int i,j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 计数型信号量 :最大值为3,初始值为2
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
*/
/* 二进制型信号量 :初始值为0 */
g_xSemTicks = xSemaphoreCreateBinary();
xSemaphoreGive(g_xSemTicks);/* 对于二进制信号量,它的最大值是1,后面两次give无效*/
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
xTaskCreate(CarTask, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car2", 128, &g_cars[1], osPriorityNormal, NULL);
xTaskCreate(CarTask, "car3", 128, &g_cars[2], osPriorityNormal, NULL);
}
3.3 信号量-优先级反转
car1:优先级为 0;
car2:优先级为 2;
car3:优先级为 3;
car1:获取了信号量(take),释放信号量(give)
car2:延迟1s(即进入阻塞)后再运行,无获取信号量(take),无释放信号量(give)
car3:延迟2s(即进入阻塞),获取信号量(take),释放信号量(give)
car1先获得信号量,所以car1先运行,跑了1s后,car2运行,过了一会car3任务启动,但它无法获得信号量而阻塞。当car2跑到最右边后,car1继续运行,跑到最右边后,释放信号量(give),car3获得信号量,跑到最右边,释放信号量
static void Car1Task(void *params)
{
/* 获取信号量:获取到票后才能进城 */
xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
while(1)
{
vTaskDelay(50);
if (pcar->x == g_xres - CAR_LENGTH)
{
/* 释放信号量 */
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
static void Car2Task(void *params)
{
/* 故意延迟1000,即1s后再让它运行 */
vTaskDelay(1000);
/* 获取信号量:获取到票后才能进城 */
//xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
while(1)
{
//vTaskDelay(50);
mdelay(50);// 不会进入阻塞状态
if (pcar->x == g_xres - CAR_LENGTH)
{
/* 释放信号量 */
//xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
static void Car3Task(void *params)
{
/* 延迟2000,即2s*/
vTaskDelay(2000);
/* 获取信号量:获取到票后才能进城 */
xSemaphoreTake(g_xSemTicks, portMAX_DELAY);
while(1)
{
//vTaskDelay(50);
mdelay(50);// 不会进入阻塞状态
if (pcar->x == g_xres - CAR_LENGTH)
{
/* 释放信号量 */
xSemaphoreGive(g_xSemTicks);
vTaskDelete(NULL);
}
}
}
void car_game(void)
{
//int x;
int i,j;
g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
draw_init();
draw_end();
/* 计数型信号量 :最大值为3,初始值为2
g_xSemTicks = xSemaphoreCreateCounting(3, 2);
*/
/* 二进制型信号量 :初始值为0 */
g_xSemTicks = xSemaphoreCreateBinary();
xSemaphoreGive(g_xSemTicks);/* 对于二进制信号量,它的最大值是1,后面两次give无效*/
xSemaphoreGive(g_xSemTicks);
xSemaphoreGive(g_xSemTicks);
xTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);
xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+2, NULL);
xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+3, NULL);
}