你好,这里是风筝的博客,
欢迎和我一起交流。
上一章讲了信号量:自编STM32轻量级操作系统(四)------信号量的实现 但是信号量会出现一个问题:优先级反转!
什么是优先级反转呢?
优先级反转是指一个低优先级的任务持有一个被高优先级任务所需要的共享资源。高优先任务由于因资源缺乏而处于受阻状态,一直等到低优先级任务释放资源为止。而低优先级获得的CPU时间少,如果此时有优先级处于两者之间的任务,并且不需要那个共享资源,则该中优先级的任务反而超过这两个任务而获得CPU时间。
简单来说,就是我高优先级任务还没能运行呢,你中优先级任务就跑了,这弯道超车技术 有点six啊。
这显然不是我们希望看到的,但是怎么解决这个问题呢?
有一种方法:优先级继承!
当一个高优先级进程等待一个低优先级进程持有的资源时,低优先级进程将暂时获得高优先级进程的优先级别,在释放共享资源后,低优先级进程回到原来的优先级别。
这样,中优先级任务就不能实现弯道超车了!
下面来实现代码:
第一步,当然是创建了:
创建一个互斥量,并初始化优先级为32(OS_MAX_Task宏定义),这个优先级是不存在的(用来代表资源没被占用的状态),我们这个系统注里最小的优先级为31,再小没有了。注意,ECB事件控制块新增一个Prio成员。
typedef struct OS_Ecb
{
unsigned int Cnt;//计数器
unsigned char OSEventTbl;//事件等待表
unsigned int Prio;//优先级
}ECB; //事件控制块
注意,在这里,Cnt不再是计数器的意思,Prio记录的是占用资源任务的优先级,Cnt记录的是申请资源任务的优先级。所以我们把Prio设为OS_MAX_Task(32),意为没有任何任务占用资源。
接下来看下申请互斥量:
有点长,注意了。
申请互斥量,存在着三种情况:
1).此时互斥量没有被申请。
2).互斥量被申请了,但是占用互斥量的任务优先级比此次任务优先级高。
3).互斥量被申请了,但是占用互斥量的任务优先级比此次任务优先级低。
现在只需来实现这三种情况即可。
第一种情况:314~319行:
简单,直接记录此时任务的优先级,Prio被设置后,即不再等于OS_MAX_Task,表明互斥量被占用了。
第二种情况:320~327行:
如果互斥量被占用了,而且占用的优先级比较高,按照我们之前写信号量的写法即可,不再累述。
第三种情况:328~351行:
这里,才是我们的重点,实现优先级的伪提升。
为了帮助理解,我举个例子:A、B、C三个任务,
优先级A>B>C。此时,C占用了互斥量,A任务正在申请互斥量。
按照互斥量的概念,现在,要把C任务的优先级提升到与A任务优先级一样,这样,B任务不会得到运行,系统会全速运行C任务,保证互斥量尽快得到释放,从而使得A得到运行,而后再恢复C任务的优先级。
所以,首先,老规矩,先将任务添加到任务等待表中。
其次,检测低优先级任务(C任务)是否处于睡眠状态,如果是,直接调度,优先级不给于提升,因为任务本身就是睡眠状态,提升没意义,直到任务(C任务)就绪为止。
接着,从就绪表中删除占用互斥量的任务(C任务),同时记录申请互斥量任务的优先级,同时用一个while卡住程序,如果互斥量没被释放,就一直在while里打转。
那while里究竟是什么呢?
之前不是说要提升低优先级任务(C任务),加速互斥量的释放,使得高优先级任务(A任务)得到快速运行吗?就是在这里实现!
p_TCBHightRdy指针(记得前面说的吗?这是指向下一个要运行任务的任务控制块指针),现在直接把指针指向低优先级任务(C任务),OSCtxSw引发异常从而调度,使得低优先级任务(C任务)得到运行,while里就一直做这个事情,直到互斥量被释放。(即每当系统将CPU分配给A任务,都会使得程序人为跳转到C任务去执行,间接的提升了任务C的优先级)
这样做,每次都要人为的调度,开销比较大,但是没办法,我之前也想像UCOS里一样,直接提升低优先级任务的优先级,但是现在这个操作系统,有个很致命的缺点:每个任务的优先级即是TCB_Task数组(任务控制块)的下标,即优先级为1的任务,它的任务控制块就是TCB_Task[1]!这样做,任务调度时很方便,但是,如果修改任务的优先级,那就再也找不到他自身的任务控制块了......
最后,再来看下释放互斥量:
同样的和信号量那一章一个道理,从任务等待表中找出优先级最高的任务,添加到任务就绪表中,引发一次调度即可。
删除互斥量:
和信号也是一样的,千万要注意!!!删除之后千万不要再使用该互斥量了!!,否则后果自负!
好像在哪本书看到过,说的是,删除前,最后也把用到互斥量(或者信号量)的任务一并删除掉,这样就万无一失了...
最后,上一个例程:
void Task1(void)//高优先级
{
OSTimeDly(50);
while(1)
{
OS_MutexPend(m_msg);
LED0=!LED0;
OS_MutexPost(m_msg);
OSTimeDly(200);
}
}
void Task2(void)//中优先级
{
OSTimeDly(100);
while(1)
{
LED1=!LED1;
OSTimeDly(50);
}
}
void delay(unsigned int j )//无意义延时
{
unsigned int i = 0;
unsigned int k = j;
for(i=0;i<50000;i++)
{
while(--j);
j=k;
}
}
void Task3(void)//低优先级
{
unsigned int i = 0;
while(1)
{
i++;
if(i==1)
{
OS_MutexPend(m_msg);
LED1=0;
delay(1000);//一直霸占资源,7s左右//OSTimeDly(500);
OS_MutexPost(m_msg);
}
if(i==500)
i=10;
OSTimeDly(150);
}
}
开始时,Task1和Task2都会休眠,从而使得Task3申请到了互斥量。
接着,Task1休眠结束,抢占CPU,申请互斥量时,因为Task3占用了互斥量,使得Task1里跳转到Task3,全速助力Task3运行,即使期间Task2任务就绪,也得不到运行。
最后,Task3释放互斥量,Task1得以往下继续运行。
现象:LED1先亮起,大概7秒过后(任务3释放了互斥量),LED0开始闪烁(任务1得以运行),LED1开始闪烁(任务2得以运行);
例程下载:互斥量例程下载
最后,关于进程之间数据互斥访问,可以看看lamport算法,非常的棒!
多线程互斥锁访问算法(下)------Lamport算法(面包店算法)
lamport算法不需要原子(atomic)操作,即它是纯软件途径解决了互斥锁的实现。
上一章:自编STM32轻量级操作系统(四)------信号量的实现
下一章:自编STM32轻量级操作系统(六)------消息队列