一、简介
信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。
在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分为两种情况:
- 0,表示没有积累下来的Post信号量操作,且有可能有再此信号量上阻塞的任务
- 正值,表示有一个或多个Post信号量操作。
以同步为目的的信号量和以互斥为目的的信号量在使用上是有不同的:
- 同步信号量:信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入READY或RUNNING态,从而达到两个任务间的同步。
- 互斥信号量:信号量创建后计数是满的,在需要使用临界资源时,先取信号量,使其变空。这样,其他任务需要使用临界资源时就会因为无法取得信号量而阻塞,从而保证了临界资源的安全。
更多信号量概念,可参考:FreeRTOS学习五(信号量)_freertos信号量用法_t_guest的博客-CSDN博客
二、运行机制
信号量初始化,为配置的N个信号量申请内存(N值可以由用户自行配置,受内存限制),并把所有的信号量初始化成未使用状态,并加入到未使用链表中供系统使用。
信号量创建,从未使用的信号量链表中获取一个信号量,并设定初值。
信号量申请,若其计数值大于0,则直接减1并返回成功。否则任务阻塞,等待其他任务释放该信号量,等待的超时时间可设定。当任务被一个信号量阻塞时,将该任务挂到信号量等待任务队列的队尾。
信号量释放,若没有任务等待该信号量,则直接将计数器加1返回。否则唤醒该信号量等待任务队列上的第一个任务。
信号量删除,将正在使用的信号量置为未使用的状态,并挂回到未使用链表中。
信号量允许多个任务在同一时刻访问同一资源,但会限制同一时刻访问此资源的最大任务数目。访问同一资源的任务数达到该资源的最大数量时,会阻塞其他试图获取该资源的任务,直到有任务释放该信号量。
三、API介绍
osSemaphoreNew
函数功能:
创建信号量。不可在中断服务中使用。
函数原型:
osSemaphoreId_t osSemaphoreNew(uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr)
参数:
max_count:信号量最大的可用数量
initial_count:初始化时可用数量
attr:相关属性,只有在自定义内存才用的到。默认为NULL
返回值:
NULL:失败
其他值:信号量标识符
实例:
osSemaphoreId_t sem1;
sem1 = osSemaphoreNew(4, 0, NULL);
osSemaphoreAcquire
函数功能:
阻塞任务,等待信号量。如果等待时间为0,可以在中断中调用。
函数原型:
osStatus_t osSemaphoreAcquire(osSemaphoreId_t semaphore_id, uint32_t timeout)
参数:
semaphore_id:信号量ID。由osSemaphoreNew信号量创建时获得。
timeout:等待超时时间。osWaitForever死等
返回值:
osOK:等到信号量
其他值:异常
typedef enum {
/** Operation completed successfully */
osOK = 0,
/** Unspecified error */
osError = -1,
/** Timeout */
osErrorTimeout = -2,
/** Resource error */
osErrorResource = -3,
/** Incorrect parameter */
osErrorParameter = -4,
/** Insufficient memory */
osErrorNoMemory = -5,
/** Service interruption */
osErrorISR = -6,
/** Reserved. It is used to prevent the compiler from optimizing enumerations. */
osStatusReserved = 0x7FFFFFFF
} osStatus_t;
实例:
osSemaphoreId_t sem1;
osStatus_t ret = osSemaphoreAcquire(sem1,osWaitForever);
osSemaphoreRelease
函数功能:
释放信号量。可以在中断中调用。
函数原型:
osStatus_t osSemaphoreRelease(osSemaphoreId_t semaphore_id)
参数:
semaphore_id:信号量ID。由osSemaphoreNew信号量创建时获得。
返回值:
osOK:成功
其他值:异常
实例:
osSemaphoreId_t sem1;
osStatus_t ret = osSemaphoreRelease(sem1);
osSemaphoreGetCount
函数功能:
获取当前可用的信号量数目。可以在中断中被调用。
函数原型:
uint32_t osSemaphoreGetCount(osSemaphoreId_t semaphore_id)
参数:
semaphore_id:信号量ID。由osSemaphoreNew信号量创建时获得。
返回值:
可用的信号量数
实例:
osSemaphoreId_t sem1;
osSemaphoreGetCount(sem1)
osSemaphoreDelete
函数功能:
删除信号量。不可在中断中被调用。
函数原型:
osStatus_t osSemaphoreDelete(osSemaphoreId_t semaphore_id)
参数:
semaphore_id:信号量ID。由osSemaphoreNew信号量创建时获得。
返回值:
osOK:成功
其他值:异常
实例:
osSemaphoreId_t sem1;
osStatus_t ret = osSemaphoreDelete(sem1);
四、实例
创建一个经典的生产者与消费者模型。其中,生产者一次生产3个,消费之每次消费一个。但是生产者每5秒生产一次。消费者有可消耗就消耗。
#define LOG_I(fmt, args...) printf("<%8ld> - [TIMER]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...) printf("<%8ld>-[TIMER_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);
osSemaphoreId_t sem1;
osThreadId_t g_thread1_id = NULL;
osThreadId_t g_thread2_id = NULL;
/*****任务一*****/
void thread1(void)
{
LOG_I("thread 1 start");
while (1)
{
LOG_I("thread 1 delay 5S");
osDelay(500);
LOG_I("thread 1 release semaphore before");
osSemaphoreRelease(sem1);
osSemaphoreRelease(sem1);
osSemaphoreRelease(sem1);
LOG_I("thread 1 release semaphore after,viable sema count:%d",osSemaphoreGetCount(sem1));
}
LOG_I("thread 1 break");
osThreadTerminate(g_thread1_id);
}
/*****任务二*****/
void thread2(void)
{
LOG_I("thread 2 start");
while (1)
{
LOG_I("thread2 acquire semaphore wait,sema count:%d",osSemaphoreGetCount(sem1));
osSemaphoreAcquire(sem1,osWaitForever);
LOG_I("thread2 acquire semaphore accepted");
}
LOG_I("thread 2 end");
}
void Hello_World(void)
{
LOG_I("Test semaphore");
sem1 = osSemaphoreNew(4, 0, NULL);
if (sem1 == NULL)
{
LOG_E("Falied to create Semaphore1!");
}
osThreadAttr_t attr;
attr.name = "thread1";
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = 1024 * 2;
attr.priority = osPriorityNormal;
g_thread1_id = osThreadNew((osThreadFunc_t)thread1, NULL, &attr);
if (g_thread1_id == NULL)
{
LOG_E("Falied to create thread1!");
}
attr.name = "thread2";
attr.stack_size = 1024 * 2;
g_thread2_id = osThreadNew((osThreadFunc_t)thread2, NULL, &attr);
if (g_thread2_id == NULL)
{
LOG_E("Falied to create thread2!");
}
}
看结果:
可以看到,在任务1里直接释放三个信号量,而此时调用osSemaphoreGetCount只能获取到2个可用。是因为在osSemaphoreRelease释放信号量时,因为任务2已经在等待信号量,所以任务1释放的一个信号量马上被任务2所获取。
那为什么任务2获取到信号量后并没有马上到任务2执行呢?是因为两个任务的优先级是一样的,只有等到任务1释放了系统后,任务2才能开始运行。如果我们把任务2的优先级设置比任务1高结果会如何呢?
在代码中添加一行代码:
再看一下运行结果。
这里可以看到,因为任务2的优先级比任务1的优先级高。所以,当任务1释放信号量后,任务2马上抢占了系统的使用权。任务1因为优先级低,被挂起。当任务2执行完后,任务1才能继续执行。