文章目录
1、基本概念及分类(本质还是队列)
信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。
抽象的来说,信号量是一个非负整数,所有获取它的任务都会将该整数减一,所有获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数,具体含义如下:
0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上的阻塞的任务。
正值:表示有一个或多个释放信号量操作。
即其相当于是一个特殊的队列,还是具备休眠唤醒功能。
(1)二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
用作同步时:
信号量在被创建后应被置为空,任务一获取信号量而进入阻塞,任务二在某种条件发生后,释放信号量,于是任务一获得信号量得以进入就绪态,若任务一的优先级是最高的,那么就会立即切换任务,从而达到两个任务间的同步。
(2)计数信号量
计数信号量常用于事件计数与资源管理。
1)用于事件计数时:
每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加 1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减 1),信号量的计数值则表示还有多少个事件没被处理。
2)用于资源管理时:
我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必先获取到信号量才能获得资源访问权,当信号量的计数值为零时表示系统没有可用的资源。所以的注意使用完资源的时候必须归还信号量。
即此时计数型信号量允许多个任务对其进行操作,但限制了任务的数量。
(3)互斥信号量
互斥信号量为特殊的二值信号量,由于其特有的优先级继承机制,从而使它更适用于简单互锁,即保护临界资源。
临界资源:指任何时刻只能被一个任务访问的资源。
用作互斥时:
信号量创建后可用信号量个数应该是满的,任务在需要使用临界资源时,先获取互斥信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界资源的安全。
在操作系统中,使用任务量很多时候是为了给临界资源建立一个标志,信号量表示了该临界资源被占用情况。
这样当一个任务在访问临界资源的时候,就会先对目标资源信息进行查询,从而了解资源被占用的情况后再做处理,即临界资源得到了有效的保护。
(4)递归信号量
递归,即可以重复获取调用。
即对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。
但任务成功获取多少次递归互斥量,就要返还几次,在此前递归互斥量都处于无效状态,其他任务无法获取,只有持有递归信号量的任务才能获取和释放。
2、使用场景:用于表示资源的个数
二值信号量:即信号量资源被获取了,信号量值就是0,信号量资源被释放,信号量值就是1。
在多任务系统中,经常会使用二值信号量:
当任务获取信号量时,因为此时未发生特定事件,信号量为空,任务会进入阻塞状态;当事件的条件满足后,任务/中断会释放信号量,告知这个事件发生了,任务获得信号量便被唤醒去执行对应的操作,任务执行完后并不需要归还信号量。
1)在任务与任务中同步的应用场景
其原理同如上所述。
2)在任务与中断同步的应用场景
如在串口接收中,有一个任务就是接收数据处理,当没有数据到来的时候,任务进入阻塞态,不参与任务的调度;等到数据到了,释放一个二值信号量,任务就立刻从阻塞态中进入就绪态。
3、信号量的创建
(1)二值信号量的创建
在信号量创建之后,变量 uxMessageWaiting 的值为0,这说明当前信号量处于无效状态,此时的信号量是无法被获取的,在获取信号之前,应先释放一个信号量。(重点)即创建后二值信号量默认为无效的。
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 创建一个二进制信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的指
针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
(2)计数信号量的创建
创建的计数信号量只有消息队列控制块结构体存储空间而没有消息存储空间 ,这一点与二值信号量一致
/* 创建一个计数型信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的指
针
* uxMaxCount: 最大计数值
* uxInitialCount: 初始计数值
* pxSemaphoreBuffer: StaticSemaphore_t 结构体指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer );
4、信号量的删除
删除信号量的过程实质为删除消息队列的过程
/*
* xSemaphore: 信号量句柄,你要删除哪个信号量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore )
5、信号量的获取
获取信号量实际上是一次消息出队操作,阻塞时间由用户指定xBlockTime,当有任务试图获取信号量的时候,当且仅当信号量有效的时候,任务才能读获取到信号量。
如果信号量无效,在用户指定的阻塞超时时间中,该任务将保持阻塞状态以等待信号量有效。
获取信号量的过程:
1)如果有可用信号量,控制块结构体成员 uxMessageWaiting 就会减 1,然后返回获取成功信息(pdPASS);
2)如果信号量无效并且阻塞时间为 0,则返回错误代码(errQUEUE_EMPTY);
3)如果信号量无效并且用户指定了阻塞时间,则任务会因为等待信号量而进入阻塞状态,任务会被挂接到延时列表中。直到超过阻塞时间后才会变为就绪态。
(1)获取流程概述
(2)API函数
1)xSemaphoreTake
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
2)xSemaphoreTakeFromISR
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore,BaseType_t*pxHigherPriorityTaskWoken)
6、信号量的释放
实际上可以把释放信号量看为一次入队操作,且不允许入队阻塞,即阻塞时间为0。
释放信号量的过程如下:
若信号量未满,控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务,如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满,则返回错误代码(err_QUEUE_FULL)
(1)释放流程概述
(2)API函数
1)xSemaphoreGive
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
2)xSemaphoreGiveFromISR
SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);