写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.04.02:UCOSIII第三十节:信号量
- 四十四、UCOSIII:信号量
- 1、信号量基本概念
- 1. 二值信号量
- 2. 计数信号量
- 2、信号量应用场景
- 1. 为什么叫二值信号量呢?
- 2. 二值信号量在任务与任务中同步的应用场景
- 3. 计数信号量的应用场景
- 3、二值信号量运作机制
- 4、计数信号量运作机制
- 5、信号量控制块
四十四、UCOSIII:信号量
回想一下,你是否在裸机编程中这样使用过一个变量:
用于标记某个事件是否发生,或者标志一下某个东西是否正在被使用,如果是被占用了的或者没发生,我们就不对它进行操作。
1、信号量基本概念
信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。
在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它当然是为了使用资源),当该整数值为零时, 所有试图获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下可被占用的临界资源数, 其值的含义分两种情况:
- 0:表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞的任务。
- 正值:表示有一个或多个释放信号量操作。
注意:μC/OS的信号量并没有区分二值信号量与计数信号量,下面是作者为了更详细解释信号量的相关内容,自行区分二值信号量与计数信号量, 其实原理都是一样的,只不过用途不一样而已。
注意:μC/OS中的信号量不具备传递数据的功能。
1. 二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量(以下使用互斥量表示互斥信号量)非常相似,但是有一些细微差别:
互斥量有优先级继承机制,二值信号量则没有这个机制。
这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步),而互斥量更偏向应用于临界资源的互斥访问。
用作同步时,信号量在创建后应被置为空,任务1获取信号量而进入阻塞,任务2在某种条件发生后,释放信号量,于是任务1获得信号量得以进入就绪态。
如果任务1的优先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。
同样的,在中断服务函数中释放信号量,任务1也会得到信号量,从而达到任务与中断间的同步。
还记得我们经常说的中断要快进快出吗,在裸机开发中我们经常是在中断中做一个标记,然后在退出的时候进行轮询处理。
这个就是类似我们使用信号量进行同步的,当标记发生了,我们再做其他事情。
在μC/OS中我们用信号量用于同步,任务与任务的同步,中断与任务的同步,可以大大提高效率。
2. 计数信号量
顾名思义,计数信号量肯定是用于计数的,在实际的使用中,我们常将计数信号量用于事件计数与资源管理。
每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减1), 信号量的计数值则表示还有多少个事件没被处理。
此外,系统还有很多资源,我们也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目, 任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源。
但是要注意,在使用完资源的时候必须归还信号量, 否则当计数值为0的时候任务就无法访问该资源了。
计数型信号量允许多个任务对其进行操作,但限制了任务的数量。
比如有一个停车场,里面只有100个车位,那么能停的车只有100辆,也相当于我们的信号量有100个, 假如一开始停车场的车位还有100个,那么每进去一辆车就要消耗一个停车位,车位的数量就要减一,对应的,我们的信号量在使用之后也需要减一。
当停车场停满了100辆车的时候,此时的停车位为0,再来的车就不能停进去了,否则将造成事故,也相当于我们的信号量为0, 后面的任务对这个停车场资源的访问也无法进行,当有车从停车场离开的时候,车位又空余出来了,后面的车就能停进去了。
我们信号量的操作也是一样的,当我们释放了这个资源,后面的任务才能对这个资源进行访问。
2、信号量应用场景
在嵌入式操作系统中二值信号量是任务间、任务与中断间同步的重要手段,信号量使用最多的一般都是二值信号量与互斥量(互斥量在下一章讲解)。
1. 为什么叫二值信号量呢?
因为信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1,把这种只有 0和 1 两种情况的信号量称之为二值信号量。
在多任务系统中,我们经常会使用这个二值信号量。
比如某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位。
但是这样子做,就会很消耗CPU资源并且妨碍其他任务执行,更好的做法是任务大部分时间处于阻塞状态(允许其他任务执行),直到某些事件发生该任务才被唤醒去执行。
可以使用二进制信号量实现这种同步,当任务取信号量时,因为此时尚未发生特定事件, 信号量为空,任务会进入阻塞状态;当事件的条件满足后,任务/中断便会释放信号量,告知任务这个事件发生了,任务取得信号量便被唤醒去执行对应的操作, 任务执行完毕并不需要归还信号量,这样子的CPU的效率可以大大提高,而且实时响应也是最快的。
再比如某个任务使用信号量在等中断的标记的发生,在这之前任务已经进入了阻塞态,在等待着中断的发生,当在中断发生之后,释放一个信号量, 也就是我们常说的标记。
当它退出中断之后,操作系统会进行任务的调度,如果这个任务能够运行,系统就会去执行这个任务,这样子就大大提高了我们的效率。
2. 二值信号量在任务与任务中同步的应用场景
假设我们有一个温湿度的传感器,假设是1s采集一次数据,那么我们让他在液晶屏中显示数据出来, 这个周期也是要1s一次的。
如果液晶屏刷新的周期是100ms更新一次,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在1s后温湿度数据更新的时候刷新即可,否则CPU就是白白做了多次的无效数据更新,CPU的资源就被刷新数据这个任务占用了大半, 造成CPU资源浪费。
如果液晶屏刷新的周期是10s更新一次,那么温湿度的数据都变化了10次,液晶屏才来更新数据,那拿这个产品有啥用, 根本就是不准确的。
所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费CPU的资源。
同理,二值信号量在任务与中断同步的应用场景:
我们在串口接收中,我们不知道啥时候有数据发送过来,有一个任务是做接收这些数据处理, 总不能在任务中每时每刻都在任务查询有没有数据到来,那样会浪费CPU资源,所以在这种情况下使用二值信号量是很好的办法。
当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除, 进入就绪态,然后运行的时候处理数据,这样系统的资源就会很好的被利用起来。
3. 计数信号量的应用场景
计数信号量则用于资源统计,比如当前任务来了很多个消息,但是这些消息都放在缓冲区中,尚未处理。
这时候就可以利用计数信号量对这些资源进行统计, 每来一个消息就加一,每处理完一个消息就减一,这样系统就知道有多少资源未处理的。
3、二值信号量运作机制
创建信号量时,系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数,二值信号量的最大可用信号量个数为1。
二值信号量获取,任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确, 否则任务会根据用户指定的阻塞超时时间来等待其他任务/中断释放信号量。
在等待这段时间,系统将任务变成阻塞态,任务将被挂到该信号量的阻塞等待列表中。
在二值信号量无效的时候,假如此时有任务获取该信号量的话,那么任务将进入阻塞状态,具体见图
假如某个时间中断/任务释放了信号量,其过程具体见图
那么, 由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态,其过程具体见图
4、计数信号量运作机制
计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任务的最大数目。
访问的任务数达到可支持的最大数目时, 会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量。
这就是计数型信号量的运作机制,虽然计数信号量允许多个任务访问同一个资源, 但是也有限定,比如某个资源限定只能有3个任务访问,那么第4个任务访问的时候,会因为获取不到信号量而进入阻塞, 等到有任务(比如任务1)释放掉该资源的时候,第4个任务才能获取到信号量从而进行资源的访问,其运作的机制具体见图
5、信号量控制块
μC/OS的信号量由多个元素组成,在信号量被创建时,需要由我们自己定义信号量控制块(也可以称之为信号量句柄), 因为它是用于保存信号量的一些信息的,其数据结构OS_SEM除了信号量必须的一些基本信息外,还有PendList链表与Ctr, 为的是方便系统来管理信号量。
示意图具体见图
其数据结构具体如下:
struct os_sem
{OS_OBJ_TYPE Type; //(1)CPU_CHAR *NamePtr; //(2)OS_PEND_LIST PendList; //(3)
#if OS_CFG_DBG_EN > 0uOS_SEM *DbgPrevPtr;OS_SEM *DbgNextPtr;CPU_CHAR *DbgNamePtr;
#endifOS_SEM_CTR Ctr; //(4)CPU_TS TS; //(5)
};
- (1):信号量的类型,用户无需理会。
- (2):信号量的名字。
- (3):等待信号量的任务列表。
- (4):可用信号量的个数,如果为0则表示无可用信号量。
- (5):用于记录时间戳。