1、同步与互斥
一句话理解同步与互斥:我等你用完厕所,我再用厕所。
什么叫同步?就是:哎哎哎,我正在用厕所,你等会。
什么叫互斥?就是:哎哎哎,我正在用厕所,你不能进来。
同步与互斥经常放在一起讲,是因为它们之的关系很大,“互斥"操作可以使用“同步"来实现。我"等"你用完厕所,我再用厕所。这不就是用“同步"来实现“互斥吗?
设置一段伪代码如下:
void 抢厕所(void)
{
if(有人在用) 我眯一会;
用厕所;
喂,醒醒,有人要用厕所吗;
}
假设有A、B两人早起抢厕所,A先行一步占用了;B慢了一步,于是就眯一会;当A用完后叫醒B,B也就愉快地上厕所了。
在这个过程中,A、B是互斥地访问“厕所",“厕所'被称之为临界资源。我们使用了“休眠-唤醒"的同步机制实现了临界资源"的“互斥访问"。
同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时,应该是这样:A用完,B再用;B用完,A再用。
2、自己实现同步例子:有缺陷
static int sum = 0;
static volatile int flagCalcEnd = 0;
void Task1Function( void * param)
{
volatile int i = 0; //使用volatile修饰,让系统不要去优化这个变量
while(1){
for(i = 0; i < 10000000; i++){
sum++;
}
//printf("1");
flagCalcEnd = 1;
vTaskDelete(NULL);
}
}
void Task2Function( void * param)
{
while(1){
if(flagCalcEnd){
printf("sum = %d\r\n", sum);
}
//printf("2");
}
}
当任务一一直在累加时,任务二还一直在竞争cpu资源,此时完成10000000次累加用了4s。
如果注释掉任务二,此时完成10000000次累加用了2s。
因此可以看出在任务二中使用循环检测某个变量,来实现同步的方法是有缺陷的,如果在任务二中让其在等待的过程中,让任务二进入blocked状态,不要让其去抢占cpu资源,这个程序的运行效率就会大幅提高。
3、自己实现互斥例子:有缺陷
void TaskGenericFunction( void * param)
{
while(1){
printf("%s\r\n", (char *)param);
}
}
//main函数中
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 3 is running", 1, NULL);
可以发现任务三和任务四打印的语句掺杂在一起,任务三的语句还没打印完就被打断了,没有能在打印的时候互斥的去独占整个串口
在这基础上去做出改进,让打印信息时可以独占串口。但是从结果可以看出此时只有一个任务在一直执行,另一个任务抢不到cpu资源。
static volatile int flagUARTused = 0;
void TaskGenericFunction( void * param)
{
while(1){
if(!flagUARTused){
flagUARTused = 1;
printf("%s\r\n", (char *)param);
flagUARTused = 0;
}
}
}
如果想要另一个任务也能抢到资源,可以在上面的基础上加个delay函数。
void TaskGenericFunction( void * param)
{
while(1){
if(!flagUARTused){
flagUARTused = 1;
printf("%s\r\n", (char *)param);
flagUARTused = 0;
vTaskDelay(1);
}
}
}
至此,这个代码看似没问题,很好的完成了互斥的作用。 但是这个代码是有很大隐患的,因为在多任务系统中,使用全局变量来实现互斥是有隐患的。
假如任务三执行到flagUARTused=1;时被调度切换出去了,那么此时任务四就过来执行了,如果任务四在运行到flagUARTused=1;时也被调度切换出去了,那么后面如果两个任务都恢复过来,那么这两个任务都可以执行之后的语句,此时就无法通过flagUARTused来实现互斥的作用了。
4、FreeRTOS的解决方案
4.1 队列(queue,FIFO)
里面可以放任意数据,可以放多个数据
任务、ISR都可以放入数据;任务、ISR都可以从中读出数据
4.2 事件组(event grtoup)
一个事件用一bit表示,1表示事件发生了,0表示事件没发生
可以用来表示事件、事件的组合发生了,不能传递数据
有广播效果:事件或事件的组合发生了,等待它的多个任务都会被唤醒
4.3 信号量(semaphore)
核心是"计数值"
任务、ISR释放信号量时让计数值加1
任务、ISR获得信号量时,让计数值减1
4.4 任务通知(task notification)
核心是任务的TCB里的数值
会被覆盖
发通知给谁?必须指定接收任务
只能由接收任务本身获取该通知
4.5 互斥量(mutex)
数值只有0或1
谁获得互斥量,就必须由谁释放同一个互斥量