uC/OS-II学习笔记——优先级反转与优先级继承机制
1. 优先级反转(Priority Inversion)
优先级反转,是指某同步资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同步资源未获得该资源,而使得较高优先级进程/线程反而推迟被调度执行的现象。
假定一个进程中有三个线程Thread1、Thread2和Thread3,它们的优先级顺序是Priority(Thread1) > Priority(Thread2) > Priority(Thread3)。考虑图一的执行情况。
图一、优先级反转现象(无优先级继承)
1,T0时刻,只有低优先级任务Thread3处于可运行状态,运行过程中,Thread3拥有了一个互斥资源SYNCH1;
2,T1时刻,中优先级任务Thread2就绪进入可运行状态,由于优先级高于正在运行的Thread3,Thread3被抢占(未释放互斥资源SYNCH1),Thread2被调度执行;
3,T2时刻,高优先级任务Thread1就绪抢占Thread2;
4,T3时刻,Thread1请求互斥资源SYNCH1,但信号量已被占有,所以Thread1阻塞等待该资源,而此时处于可运行状态的线程Thread2和Thread3中,Thread2的优先级高于Thread3的优先级,Thread2被调度执行。
上述现象中,优先级最高的Thread1既要等优先级低的Thread2运行完,还要等优先级更低的Thread3运行完之后才能被调度,如果Thread2和Thread3执行的很费时的操作,显然Thread1的被调度时机就不能保证,整个实时调度的性能就很差了。
2. 优先级继承(Priority Inheritance)
为了解决上述由于优先级反转引起的问题,uC/OS引入了优先级继承的解决方法。优先级继承也就是,高优先级进程TH在等待低优先级的线程TL继承占用的竞争资源时,为了使TH能够尽快获得调度运行,由操作系统把TL的优先级提高到一个很高的优先级,从而让TL优先参与调度,尽快让TL执行并释放调TH欲获得的竞争资源,然后TL的优先级调整到继承前的水平,此时TH可获得竞争资源而继续执行。
有了优先级继承之后的上述现象的执行情况如图二所示。
图二、优先级继承执行情况
与图一比较,图二中,到了T3时刻,Thread1需要Thread3占用的互斥资源SYNCH1,操作系统检测到这种情况后,就把Thread3的优先级提高到一个很高的的优先级。此时处于可运行状态的线程Thread2和Thread3中,Thread3的优先级大于Thread2的优先级,Thread3被调度执行。
Thread3执行到T4时刻,释放了互斥资源SYNCH1,操作系统恢复了Thread3的优先级,Thread1获得了互斥资源SYNCH1,重新进入可执行队列。处于可运行状态的线程Thread1和Thread2中,Thread1的优先级大于Thread2的优先级,所以Thread1被调度执行。
上述机制,使优先级最高的Thread1获得执行的时机提前。
3. uC/OS-II中互斥信号量对优先级继承机制的实现
互斥信号量是一种特殊信号量,取值只能是0或1,实现对共享资源的独占式处理。为了保证系统的实时性,拒绝优先级反转,对互斥信号量的管理采用了优先级继承机制。解决优先级反转是互斥信号量管理区别于普通信号量管理的最本质的区别。
uC/OS-II中的互斥型信号量由三个部分组成:
◎ 一个标志,指示mutex是否可以使用(0或1);
◎ 一个优先级,准备一旦高优先级的任务需要这个mutex,赋予给占有mutex的任务;
◎ 一个等待该mutex的任务列表.
假设现在有三个任务分别是Task1,Task2,Task3,优先级从大到小。程序在运行过程中,Task1和Task2处于挂起(pend)的状态,等待某个事件的发生。这样优先级最低的Task3首先申请到了mutex并开始同共享资源打交道,过了一会儿,Task1等待的事件出现了,那么此时Task1就需要使用共享资源了,于是申请mutex(OSMutexPend)。在这种情况下,OSMutexPend注意到高优先级的任务要使用这个共享资源,于是将Task3的优先级升到比Task1更高的级别,并强制任务调度回到Task3。而Task1只好继续挂起,等待共享资源被释放。同时Task3继续运行,直到Task3调用OSMutexPost(),释放Mutex.在调用OSMutexPost()时,OSMutexPost()会将Task3的优先级恢复到原来的水平,同时还会注意到有个高优先级的任务Task1需要这个Mutex,于是将这个Mutex交给Task1,并做任务切换。
uC/OS-II单独为互斥信号量管理编写了C语言文件os_mutex.c,互斥信号管理的核心函数如下:
1,建立互斥型信号量:
OS_EVENT *OSMutexCreate(INT8U prio,INT8U *err)
返回值赋给信号量
参数prio:较高的空闲优先级,用于任务提权
参数err:存放出错信息
2,删除互斥型信号量:
OS_EVENT *OSMutexDel(OS_EVENT *pevent,INT8U opt,INT8U *err)
返回值为NULL,赋给信号量
参数pevent:要删除的信号量
参数opt:删除的选项
参数err:存放出错信息
3,挂起等待互斥型信号量:
void OSMutexPend(OS_EVENT *pevent,INT16U timeout,INT8U *err)
参数pevent:等待的信号量
参数timeout:等待超时的节拍数,0为永久等待
参数err:存放出错信息
4,释放互斥型信号量:
INT8U OSMutexPost(OS_EVENT *pevent)
返回释放状况
参数:要释放的信号量
5,无阻塞请求互斥型信号量:
INT8U OSMutexAccept(OS_EVENT *pevent,INT8U *err)
调用此函数会立即返回,不会等待,返回值0表示信号量不可用,返回1表示得到互斥型信号量
参数pevent:要请求的互斥型信号量
参数err:存放出错信息
6,查询互斥型信号量当前状态:
INT8U OSMutexQuery(OS_EVENT *pevent,OS_MUTEX_DATA *pdata)
返回查询出错代码
参数pevent:要查询的信号量
参数pdata:存放查询到的状态信息