一、同步互斥与通信
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
能实现同步、互斥的内核方法有:任务通知
(task notification)
、队列
(queue)
、事件组
(event group)
、 信号量(semaphoe)
、互斥量
(mutex)
。
内核区分方法:
二、队列
队列(queue)可以用于传输数据:在任务之间、任务和中断之间。
使用队列的流程:创建队列、写队列、读队列、删除队列。

2.1 传输数据的两种方法

2.2 队列的阻塞访问

2.3 创建队列
队列的创建有两种方法:动态分配内存、静态分配内存
动态分配内存:xQueueCreate,队列的内存在函数内部动态分配

静态分配内存:xQueueCreateStatic,队列的内存要事先分配好

2.4 复位队列

2.5 删除队列

2.6 写队列
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在
ISR
中使用。

这些函数用到的参数是类似的,统一说明如下:
2.7 读队列

2.8 查询队列

2.9 覆盖/偷看队列
当队列长度为1时,可以使用 xQueueOverwrite() 或 xQueueOverwriteFromISR() 来覆盖数据。 注意,队列长度必须为1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些函数不会被阻 塞。

如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用
"
窥视"
,也就是
xQueuePeek()
或
xQueuePeekFromISR()
。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"
偷看
"
时会导致阻塞;一旦队列中有数据,以后每次
"
偷看"
都会成功。
队列使用示例
本程序会创建一个队列,然后创建
2
个发送任务、
1
个接收任务:
发送任务优先级为
1
,分别往队列中写入
100
、
200
接收任务优先级为
2
,读队列、打印数值
main
函数中创建的队列、创建了发送任务、接收任务,代码如下:




三、信号量
前面介绍的队列
(queue)
可以用于传输数据:在任务之间、任务和中断之间。
有时候我们只需要传递状态,并不需要传递具体的信息,比如:我的事做完了,通知一下你
在这种情况下我们可以使用信号量
(semaphore)
,它更节省内存。
信号:起通知作用
量:还可以用来表示资源的数量
当
"
量
"
没有限制时,它就是
"
计数型信号量
"(Counting Semaphores)
当
"
量
"
只有
0
、
1
两个取值时,它就是
"
二进制信号量
"(Binary Semaphores)
支持的动作:
"give"
给出资源,计数值加
1
;
"take"
获得资源,计数值减
1
计数型信号:
计数:事件产生时
"give"
信号量,让计数值加
1
;处理事件时要先
"take"
信号量,就是获得信号量,
让计数值减
1
。
资源管理:要想访问资源需要先
"take"
信号量,让计数值减
1
;用完资源后
"give"
信号量,让计数值
加
1
。
二进制信号量:二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1
。
信号量跟队列的对比
3.1 信号量函数
使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。
3.2 创建信号量


3.3 删除信号量

3.4 give/take
xSemaphoreGive的函数原型如下:
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
xSemaphoreGive函数的参数与返回值列表如下:
pxHigherPriorityTaskWoken
的函数原型如下:
BaseType_t
xSemaphoreGiveFromISR
(
SemaphoreHandle_t xSemaphore
,
BaseType_t
*
pxHigherPriorityTaskWoken
);
xSemaphoreGiveFromISR函数的参数与返回值列表如下:

xSemaphoreTake的函数原型如下:
BaseType_t
xSemaphoreTake
(
SemaphoreHandle_t xSemaphore
,
TickType_t xTicksToWait
);
xSemaphoreTake函数的参数与返回值列表如下:
xSemaphoreTakeFromISR
的函数原型如下:
BaseType_t
xSemaphoreTakeFromISR
(
SemaphoreHandle_t xSemaphore
,
BaseType_t
*
pxHigherPriorityTaskWoken
);
xSemaphoreTakeFromISR函数的参数与返回值列表如下:
四、互斥量(mutex)
量:值为
0
、
1
互斥:用来实现互斥访问
它的核心在于:谁上锁,就只能由谁开锁。
很奇怪的是,
FreeRTOS
的互斥锁,并没有在代码上实现这点:
即使任务
A
获得了互斥锁,任务
B
竟然也可以释放互斥锁。
谁上锁、谁释放:只是约定。
4.1 互斥量使用场景
在多任务系统中,任务
A
正在使用某个资源,还没用完的情况下任务
B
也来使用的话,就可能导致问题。比如对于串口,任务A正使用它来打印,在打印过程中任务
B
也来打印,客户看到的结果就是
A
、
B
的信息混杂在一起。
上述问题的解决方法是:任务
A
访问这些全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源。
互斥量也被称为互斥锁,使用过程如下:
互斥量初始值为
1
任务
A
想访问临界资源,先获得并占有互斥量,然后开始访问
任务
B
也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
任务
A
使用完毕,释放互斥量;任务
B
被唤醒、得到并占有互斥量,然后开始访问临界资源
任务
B使用完毕,释放互斥量
正常来说:在任务
A
占有互斥量的过程中,任务
B
、任务
C
等等,都无法释放互斥量。
但是
FreeRTOS
未实现这点:任务
A
占有互斥量的情况下,任务
B
也可释放互斥量。
4.2 互斥量函数
互斥量是一种特殊的二进制信号量。
使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。
4.2.1 创建互斥量函数
创建互斥量的函数有
2
种:动态分配内存,静态分配内存,函数原型如下:
4.2.2
其他函数
要注意的是,互斥量不能在
ISR
中使用。
各类操作函数,比如删除、
give/take(信号量)
,跟一般是信号量是一样的。

五、事件组(event group)
学校组织秋游,组长在等待:
张三:我到了
李四:我到了
王五:我到了
组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。
在这个日常生活场景中:
出发:要等待这
3
个人都到齐,他们是
"
与
"
的关系
交报告:只需等待这
3
人中的任何一个,他们是
"
或
"
的关系
在
FreeRTOS
中,可以使用事件组
(event group)
来解决这些问题。
5.1.1
事件组概念与操作

5.1.2 事件组的操作

5.2 事件组函数
5.2.1
创建
使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。
有两种创建方法:动态分配内存、静态分配内存。函数原型如下:
5.2.2 删除
对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。
vEventGroupDelete可以用来删除事件组,函数原型如下:

5.2.3 设置事件
可以设置事件组的某个位、某些位,使用的函数有
2
个:
在任务中使用
xEventGroupSetBits()
在
ISR
中使用
xEventGroupSetBitsFromISR()
有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。
函数原型如下:
值得注意的是,
ISR
中的函数,比如队列函数
xQueueSendToBackFromISR
、信号量函数
xSemaphoreGiveFromISR
,它们会唤醒某个任务,最多只会唤醒
1
个任务。
但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以
xEventGroupSetBitsFromISR
函数不是直接去设置事件组,而是给一个
FreeRTOS
后台任务
(daemon task)发送队列数据,由这个任务来设置事件组。
如果后台任务的优先级比当前被中断的任务优先级高,
xEventGroupSetBitsFromISR
会设置
*pxHigherPriorityTaskWoken
为
pdTRUE
。
如果
daemon task
成功地把队列数据发送给了后台任务,那么
xEventGroupSetBitsFromISR
的返回值就是pdPASS
。
5.2.4
等待事件
使用
xEventGroupWaitBits
来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位
先引入一个概念:
unblock condition
。一个任务在等待事件发生时,它处于阻塞状态;当期望的时间发生时,这个状态就叫"unblock condition"
,非阻塞条件,或称为
"
非阻塞条件成立
"
;当
"
非阻塞条件成立"后,该任务就可以变为就绪态。
函数参数说明列表如下:

你可以使用
xEventGroupWaitBits()
等待期望的事件,它发生之后再使用
xEventGroupClearBits()
来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。
可以使用设置
xClearOnExit
为
pdTRUE
,使得对事件组的测试、清零都在
xEventGroupWaitBits()
函数内部完成,这是一个原子操作。
有一个事情需要多个任务协同,比如:
任务
A
:炒菜
任务
B
:买酒
任务
C
:摆台
A
、
B
、
C
做好自己的事后,还要等别人做完;大家一起做完,才可开饭
使用
xEventGroupSync()
函数可以同步多个任务:
可以设置某位、某些位,表示自己做了什么事
可以等待某位、某些位,表示要等等其他任务
期望的时间发生后,
xEventGroupSync()
才会成功返回。
xEventGroupSync
成功返回后,会清除事件