信号量用于控制对共享资源的保护,但是现在基本用来做任务同步用(不太清楚的可以参考链接:【UCOSIII】UCOSIII的信号量)。
优先级反转
优先级反转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果,下图就是一个优先级反转的例子:
关于这个优先级反转的例子,先来进行分析一下:
- 任务H和任务M起初处于挂起状态,等待某一事件的发生,而任务L正在运行;
- 某一时刻任务L想要访问共享资源,在此之前它必须先获得对应该资源的信号量;
- 任务L获得信号量并开始使用该共享资源;
- 由于任务H优先级高,它等待的事件发生后便剥夺了任务L的CPU使用权(或者说,任务L调用了延时函数,发生任务调度,任务H此时是最高优先级);
- 任务H开始运行;
- 任务H运行过程中也要使用任务L正在使用着的资源,由于该资源的信号量还被任务L占用着,任务H只能进入挂起状态,等待任务L释放该信号量;
- 任务L继续运行;
- 由于任务M的优先级高于任务L,当任务M等待的事件发生后,任务M剥夺了任务L的CPU使用权(或者说,任务L又调用了延时函数,发生任务调度,任务M此时是最高优先级);
- 任务M处理该处理的事;
- 任务M执行完毕后,将CPU使用权归还给任务L;
- 任务L继续运行;
- 最终任务L完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高优先级的任务在等待这个信号量,故内核做任务切换;
- 任务H得到该信号量并接着运行。
在这种情况下,任务H的优先级实际上降到了任务L的优先级水平。因为任务H要一直等待直到任务L释放其占用的那个共享资源。由于任务M剥夺了任务L的CPU使用权,使得任务H的情况更加恶化,这样就相当于任务M的优先级高于任务H,导致优先级反转。
解决这个问题大办法就是:提升优先级。当由于信号量被任务L占用而导致任务H被挂起之后,将任务L的优先级暂时提升至任务H的优先级,直到任务L释放信号量。也就是说,将拥有信号量的任务优先级暂时提升至目前正在等待该信号量的任务中的最高优先级!这样,就不会被比等待该信号量的任务的优先级低的任务,强行剥夺拥有信号量的任务,导致优先级反转。
互斥信号量
遵循解决优先级反转的一般解决思路,UCOSIII支持一种特殊的二进制信号量:互斥信号量,用它可以解决优先级反转问题,如下图所示:
同样,对于这个流程也进行分析一下:
- 任务H和任务M起初处于挂起状态,等待某一事件的发生,而任务L正在运行;
- 某一时刻任务L想要访问共享资源,在此之前它必须先获得对应该资源的信号量;
- 任务L获得信号量并开始使用该共享资源;
- 由于任务H优先级高,它等待的事件发生后便剥夺了任务L的CPU使用权(或者说,任务L调用了延时函数,发生任务调度,任务H此时是最高优先级);
- 任务H开始运行;
- 任务H运行过程中也要使用任务L在使用的资源,考虑到任务L正在占用着资源,UCOSIII会将任务L的优先级升至同任务H一样,使得任务L能继续执行而不被其他中等优先级的任务打断;
- 任务L以任务H的优先级继续运行,注意此时任务H并没有运行,因为任务H在等待任务L释放掉互斥信号量;
- 任务L完成所有的任务,并释放掉互斥型信号量,UCOSIII会自动将任务L的优先级恢复到提升之前的值,然后UCOSIII会将互斥型信号量给正在等待着的任务H;
- 任务H获得互斥信号量开始执行;
- 任务H不再需要访问共享资源,于是释放掉互斥型信号量。
- 由于没有更高优先级的任务需要执行,所以任务H继续执行;
- 任务H完成所有工作,并等待某一事件发生,此时UCOSIII开始运行在任务H或者任务L运行过程中已经就绪的任务M;
- 任务M继续执行。
注意!只有任务才能使用互斥信号量(中断服务程序则不可以),UCOSIII允许用户嵌套使用互斥型信号量,一旦一个任务获得了一个互斥型信号量,则该任务最多可以对该互斥型信号量嵌套使用250次,当然该任务只有释放相同的次数才能真正释放这个互斥型信号量。
至于互斥信号量的思想:将拥有信号量的任务优先级暂时提升至目前正在等待该信号量的任务中的最高优先级!
互斥信号量API函数
函数 | 说明 |
OSMutexCreate() | 创建一个互斥信号量 |
OSMutexDel() | 删除一个互斥型信号量 |
OSMutexPend() | 等待一个互斥型信号量 |
OSMutexPendAbort() | 取消等待 |
OSMutexPost() | 释放一个互斥型信号量 |
创建互斥型信号量
创建互斥信号量使用函数OSMutexCreate(),函数原型如下:
void OSMutexCreate (OS_MUTEX *p_mutex, //指向互斥型信号量控制块
CPU_CHAR *p_name, //互斥信号量的名字
OS_ERR *p_err)
{
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();
p_mutex->Type = OS_OBJ_TYPE_MUTEX; /* Mark the data structure as a mutex */
p_mutex->NamePtr = p_name;
p_mutex->OwnerTCBPtr = (OS_TCB *)0;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; /* Mutex is available */
p_mutex->TS = (CPU_TS )0;
p_mutex->OwnerOriginalPrio = OS_CFG_PRIO_MAX;
OS_PendListInit(&p_mutex->PendList); /* Initialize the waiting list */
OSMutexQty++;
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}
互斥信号量控制块是什么结构呢?
struct os_mutex { /* Mutual Exclusion Semaphore */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_MUTEX */
CPU_CHAR *NamePtr; /* Pointer to Mutex Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* List of tasks waiting on mutex */
#if OS_CFG_DBG_EN > 0u
OS_MUTEX *DbgPrevPtr;
OS_MUTEX *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_TCB *OwnerTCBPtr;
OS_PRIO OwnerOriginalPrio;
OS_NESTING_CTR OwnerNestingCtr; /* Mutex is available when the counter is 0 */
CPU_TS TS;
};
可以看出,互斥信号量是一个二进制信号量,并没有用于记录当前信号量的值的成员变量Ctr。
请求互斥型信号量
当一个任务需要对资源进行独占式访问的时候就可以使用函数OSMutexPend(),如果该互斥信号量正在被其他的任务使用,那么UCOSIII就会将请求这个互斥信号量的任务放置在这个互斥信号量的等待表中。任务会一直等待,直到这个互斥信号量被释放掉,或者设定的超时时间到达为止。如果在设定的超时时间到达之前信号量被释放,UCOSIII将会恢复所有等待这个信号量的任务中优先级最高的任务。
注意!如果占用该互斥信号量的任务比当前申请该互斥信号量的任务优先级低的话,OSMutexPend()函数会将占用该互斥信号量的任务的优先级提升到和当前申请任务的优先级一样。当占用该互斥信号量的任务释放掉该互斥信号量以后,恢复到之前的优先级。OSMutexPend()函数原型如下:
void OSMutexPend (OS_MUTEX *p_mutex, //指向互斥信号量
OS_TICK timeout, //指定等待互斥信号量的超时时间(时钟节拍数)
OS_OPT opt, //用于选择是否使用阻塞模式
CPU_TS *p_ts, //指向一个时间戳
OS_ERR *p_err)
{
OS_PEND_DATA pend_data;
OS_TCB *p_tcb;
CPU_SR_ALLOC();
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0; /* Initialize the returned timestamp */
}
CPU_CRITICAL_ENTER();
if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) { /* Resource available? */
p_mutex->OwnerTCBPtr = OSTCBCurPtr; /* Yes, caller may proceed */
p_mutex->OwnerOriginalPrio = OSTCBCurPtr->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}
if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) { /* See if current task is already the owner of the mutex */
p_mutex->OwnerNestingCtr++;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_OWNER; /* Indicate that current task already owns the mutex */
return;
}
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK; /* No */
return;
} else {
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(); /* Lock the scheduler/re-enable interrupts */
p_tcb = p_mutex->OwnerTCBPtr; /* Point to the TCB of the Mutex owner */
if (p_tcb->Prio > OSTCBCurPtr->Prio) { /* See if mutex owner has a lower priority than current */
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
OS_RdyListRemove(p_tcb); /* Remove from ready list at current priority */
p_tcb->Prio = OSTCBCurPtr->Prio; /* Raise owner's priority */
OS_PrioInsert(p_tcb->Prio);
OS_RdyListInsertHead(p_tcb); /* Insert in ready list at new priority */
break;
case OS_TASK_STATE_DLY:
case OS_TASK_STATE_DLY_SUSPENDED:
case OS_TASK_STATE_SUSPENDED:
p_tcb->Prio = OSTCBCurPtr->Prio; /* Only need to raise the owner's priority */
break;
case OS_TASK_STATE_PEND: /* Change the position of the task in the wait list */
case OS_TASK_STATE_PEND_TIMEOUT:
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
OS_PendListChangePrio(p_tcb,
OSTCBCurPtr->Prio);
break;
default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
return;
}
}
OS_Pend(&pend_data, /* Block task pending on Mutex */
(OS_PEND_OBJ *)((void *)p_mutex),
OS_TASK_PEND_ON_MUTEX,
timeout);
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find the next highest priority task ready to run */
CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* We got the mutex */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;
case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;
case OS_STATUS_PEND_TIMEOUT: /* Indicate that we didn't get mutex within timeout */
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;
case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_OBJ_DEL;
break;
default:
*p_err = OS_ERR_STATUS_INVALID;
break;
}
CPU_CRITICAL_EXIT();
}
timeout:指定等待互斥信号量的超时时间(时钟节拍数),如果在指定的时间内互斥信号量没有释放,则允许任务恢复执行。该值设置为0的话,表示任务将会一直等待下去,直到信号量被释放掉。
opt:用于选择是否使用阻塞模式,有下面两个选项。OS_OPT_PEND_BLOCKING:指定互斥信号量被占用时,任务挂起等待该互斥信号量;OS_OPT_PEND_NON_BLOCKING:指定当互斥信号量被占用时,直接返回任务。
注意!当设置为OS_OPT_PEND_NON_BLOCKING,是timeout参数就没有意义了,应该设置为0。
发送互斥信号量
我们可以通过调用函数OSMutexPost()来释放互斥型信号量,只有之前调用过函数OSMutexPend()获取互斥信号量,才需要调用OSMutexPost()函数来释放这个互斥信号量,函数原型如下:
void OSMutexPost (OS_MUTEX *p_mutex, //指向互斥信号量
OS_OPT opt, //用来指定是否进行任务调度操作
OS_ERR *p_err)
{
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
CPU_TS ts;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
if (OSTCBCurPtr != p_mutex->OwnerTCBPtr) { /* Make sure the mutex owner is releasing the mutex */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_NOT_OWNER;
return;
}
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
ts = OS_TS_GET(); /* Get timestamp */
p_mutex->TS = ts;
p_mutex->OwnerNestingCtr--; /* Decrement owner's nesting counter */
if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) { /* Are we done with all nestings? */
OS_CRITICAL_EXIT(); /* No */
*p_err = OS_ERR_MUTEX_NESTING;
return;
}
p_pend_list = &p_mutex->PendList;
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { /* Any task waiting on mutex? */
p_mutex->OwnerTCBPtr = (OS_TCB *)0; /* No */
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0;
OS_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}
/* Yes */
if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio) {
OS_RdyListRemove(OSTCBCurPtr);
OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio; /* Lower owner's priority back to its original one */
OS_PrioInsert(OSTCBCurPtr->Prio);
OS_RdyListInsertTail(OSTCBCurPtr); /* Insert owner in ready list at new priority */
OSPrioCur = OSTCBCurPtr->Prio;
}
/* Get TCB from head of pend list */
p_tcb = p_pend_list->HeadPtr->TCBPtr;
p_mutex->OwnerTCBPtr = p_tcb; /* Give mutex to new owner */
p_mutex->OwnerOriginalPrio = p_tcb->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
/* Post to mutex */
OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
(OS_TCB *)p_tcb,
(void *)0,
(OS_MSG_SIZE )0,
(CPU_TS )ts);
OS_CRITICAL_EXIT_NO_SCHED();
if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) {
OSSched(); /* Run the scheduler */
}
*p_err = OS_ERR_NONE;
}
opt:用来指定是否进行任务调度操作,有以下两个选项。OS_OPT_POST_NONE:不指定特定的选项;OS_OPT_POST_NO_SCHED:禁止在本函数内执行任务调度操作。
UCOSIII实际例程
在互斥信号量实验之前,首先先来一个优先级反转的实例。
优先级反转实验
例程要求:创建4个任务,任务A用于创建B、C和D这三个任务,A还创建了一个初始值为1的信号量TEST_SEM,任务B和D都请求信号量TEST_SEM,其中任务优先级从高到底分别为:B、C、D。
例子:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "includes.h"
//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//任务优先级
#define START_TASK_PRIO 10
//任务堆栈大小
#define START_STK_SIZE 128
//任务控制块
OS_TCB StartTaskTCB;
//任务堆栈
CPU_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *p_arg);
//任务优先级
#define HIGH_TASK_PRIO 7
//任务堆栈大小
#define HIGH_STK_SIZE 128
//任务控制块
OS_TCB High_TaskTCB;
//任务堆栈
CPU_STK HIGH_TASK_STK[HIGH_STK_SIZE];
void high_task(void *p_arg);
//任务优先级
#define MIDDLE_TASK_PRIO 8
//任务堆栈大小
#define MIDDLE_STK_SIZE 128
//任务控制块
OS_TCB Middle_TaskTCB;
//任务堆栈
CPU_STK MIDDLE_TASK_STK[MIDDLE_STK_SIZE];
void middle_task(void *p_arg);
//任务优先级
#define LOW_TASK_PRIO 9
//任务堆栈大小
#define LOW_STK_SIZE 128
//任务控制块
OS_TCB Low_TaskTCB;
//任务堆栈
CPU_STK LOW_TASK_STK[LOW_STK_SIZE];
void low_task(void *p_arg);
//LCD刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
OS_SEM TEST_SEM; //定义一个信号量
int main(void) //主函数
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
LCD_Init(); //LCD初始化
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ALIENTEK STM32F1");
LCD_ShowString(30,30,200,16,16,"UCOSIII Examp 10-4");
LCD_ShowString(30,50,200,16,16,"Prio Inversion");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2015/5/28");
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务寸vs:v
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度,
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
void start_task(void *p_arg) //开始任务函数
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //统计任务
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER(); //进入临界区
OSSemCreate ((OS_SEM* )&TEST_SEM, //创建一个信号量
(CPU_CHAR* )"TEST_SEM",
(OS_SEM_CTR)1, //信号量初始值为1
(OS_ERR* )&err);
OSTaskCreate((OS_TCB * )&High_TaskTCB, //创建HIGH任务
(CPU_CHAR * )"High task",
(OS_TASK_PTR )high_task,
(void * )0,
(OS_PRIO )HIGH_TASK_PRIO,
(CPU_STK * )&HIGH_TASK_STK[0],
(CPU_STK_SIZE)HIGH_STK_SIZE/10,
(CPU_STK_SIZE)HIGH_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&Middle_TaskTCB, //创建MIDDLE任务
(CPU_CHAR * )"Middle task",
(OS_TASK_PTR )middle_task,
(void * )0,
(OS_PRIO )MIDDLE_TASK_PRIO,
(CPU_STK * )&MIDDLE_TASK_STK[0],
(CPU_STK_SIZE)MIDDLE_STK_SIZE/10,
(CPU_STK_SIZE)MIDDLE_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&Low_TaskTCB, //创建LOW任务
(CPU_CHAR * )"Low task",
(OS_TASK_PTR )low_task,
(void * )0,
(OS_PRIO )LOW_TASK_PRIO,
(CPU_STK * )&LOW_TASK_STK[0],
(CPU_STK_SIZE)LOW_STK_SIZE/10,
(CPU_STK_SIZE)LOW_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
}
void high_task(void *p_arg) //高优先级任务的任务函数
{
u8 num;
OS_ERR err;
CPU_SR_ALLOC();
POINT_COLOR = BLACK;
OS_CRITICAL_ENTER();
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"High Task");
OS_CRITICAL_EXIT();
while(1)
{
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时500ms
num++;
printf("high task Pend Sem\r\n");
OSSemPend(&TEST_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量
printf("high task Running!\r\n");
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1 = ~LED1;
OSSemPost(&TEST_SEM,OS_OPT_POST_1,&err); //释放信号量
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时500ms
}
}
void middle_task(void *p_arg) //中等优先级任务的任务函数
{
u8 num;
OS_ERR err;
CPU_SR_ALLOC();
POINT_COLOR = BLACK;
OS_CRITICAL_ENTER();
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Middle Task");
OS_CRITICAL_EXIT();
while(1)
{
num++;
printf("middle task Running!\r\n");
LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]); //填充区域
LED0 = ~LED0;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
void low_task(void *p_arg) //低优先级任务的任务函数
{
static u32 times;
OS_ERR err;
while(1)
{
OSSemPend(&TEST_SEM,0,OS_OPT_PEND_BLOCKING,0,&err); //请求信号量
printf("low task Running!\r\n");
for(times=0;times<10000000;times++)
{
OSSched(); //发起任务调度
}
OSSemPost(&TEST_SEM,OS_OPT_POST_1,&err);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
这个实验的步骤是:
- low_task任务获得下信号量TEST_SEM开始运行;
- high_task请求信号量TEST_SEM,但是此时信号量TEST_SEM被任务low_task占用着,因此high_task就要一直等待,直到low_task任务释放信号量TEST_SEM;
- 由于high_task没有请求到信号量TEST_SEM,只能一直等待,而middle_task一直在运行,给人的感觉就是middle_task的任务优先级高于high_task。但是事实上high_task任务的任务优先级是高于middle_task的,这个就是优先级反转!
high_task任务因为获得信号量TEST_SEM而运行从上例中可以看出,当一个低优先级任务和一个高优先级任务同时使用同一个信号量,而系统中还有其他中等优先级任务时。如果低优先级任务获得了信号量,那么高优先级的任务就会处于等待状态,但是,中等优先级的任务可以打断低优先级任务而先于高优先级任务运行(此时高优先级的任务在等待信号量,所以不能运行),这是就出现了优先级反转的现象。
互斥信号量实验
在上例中由于使用了信号量导致了优先级反转发生,下例中我们将信号量换成互斥信号量。
例子:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "includes.h"
//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//任务优先级
#define START_TASK_PRIO 10
//任务堆栈大小
#define START_STK_SIZE 128
//任务控制块
OS_TCB StartTaskTCB;
//任务堆栈
CPU_STK START_TASK_STK[START_STK_SIZE];
//任务函数
void start_task(void *p_arg);
//任务优先级
#define HIGH_TASK_PRIO 7
//任务堆栈大小
#define HIGH_STK_SIZE 128
//任务控制块
OS_TCB High_TaskTCB;
//任务堆栈
CPU_STK HIGH_TASK_STK[HIGH_STK_SIZE];
void high_task(void *p_arg);
//任务优先级
#define MIDDLE_TASK_PRIO 8
//任务堆栈大小
#define MIDDLE_STK_SIZE 128
//任务控制块
OS_TCB Middle_TaskTCB;
//任务堆栈
CPU_STK MIDDLE_TASK_STK[MIDDLE_STK_SIZE];
void middle_task(void *p_arg);
//任务优先级
#define LOW_TASK_PRIO 9
//任务堆栈大小
#define LOW_STK_SIZE 128
//任务控制块
OS_TCB Low_TaskTCB;
//任务堆栈
CPU_STK LOW_TASK_STK[LOW_STK_SIZE];
void low_task(void *p_arg);
//LCD刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
OS_MUTEX TEST_MUTEX; //定义一个互斥信号量
int main(void) //主函数
{
OS_ERR err;
CPU_SR_ALLOC();
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始
LCD_Init(); //LCD初始化
POINT_COLOR = RED;
LCD_ShowString(30,10,200,16,16,"ALIENTEK STM32F1");
LCD_ShowString(30,30,200,16,16,"UCOSIII Examp 10-5");
LCD_ShowString(30,50,200,16,16,"Mutex test");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2015/5/28");
OSInit(&err); //初始化UCOSIII
OS_CRITICAL_ENTER(); //进入临界区
//创建开始任务
OSTaskCreate((OS_TCB * )&StartTaskTCB, //任务控制块
(CPU_CHAR * )"start task", //任务名字
(OS_TASK_PTR )start_task, //任务函数
(void * )0, //传递给任务函数的参数
(OS_PRIO )START_TASK_PRIO, //任务优先级
(CPU_STK * )&START_TASK_STK[0], //任务堆栈基地址
(CPU_STK_SIZE)START_STK_SIZE/10, //任务堆栈深度限位
(CPU_STK_SIZE)START_STK_SIZE, //任务堆栈大小
(OS_MSG_QTY )0, //任务内部消息队列能够接收的最大消息数目,为0时禁止接收消息
(OS_TICK )0, //当使能时间片轮转时的时间片长度,为0时为默认长度,
(void * )0, //用户补充的存储区
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //任务选项
(OS_ERR * )&err); //存放该函数错误时的返回值
OS_CRITICAL_EXIT(); //退出临界区
OSStart(&err); //开启UCOSIII
}
void start_task(void *p_arg) //开始任务函数
{
OS_ERR err;
CPU_SR_ALLOC();
p_arg = p_arg;
CPU_Init();
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCPUUsageInit(&err); //统计任务
#endif
#ifdef CPU_CFG_INT_DIS_MEAS_EN //如果使能了测量中断关闭时间
CPU_IntDisMeasMaxCurReset();
#endif
#if OS_CFG_SCHED_ROUND_ROBIN_EN //当使用时间片轮转的时候
//使能时间片轮转调度功能,时间片长度为1个系统时钟节拍,既1*5=5ms
OSSchedRoundRobinCfg(DEF_ENABLED,1,&err);
#endif
OS_CRITICAL_ENTER(); //进入临界区
OSMutexCreate((OS_MUTEX* )&TEST_MUTEX, //创建一个互斥信号量
(CPU_CHAR* )"TEST_MUTEX",
(OS_ERR* )&err);
OSTaskCreate((OS_TCB * )&High_TaskTCB, //创建HIGH任务
(CPU_CHAR * )"High task",
(OS_TASK_PTR )high_task,
(void * )0,
(OS_PRIO )HIGH_TASK_PRIO,
(CPU_STK * )&HIGH_TASK_STK[0],
(CPU_STK_SIZE)HIGH_STK_SIZE/10,
(CPU_STK_SIZE)HIGH_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&Middle_TaskTCB, //创建MIDDLE任务
(CPU_CHAR * )"Middle task",
(OS_TASK_PTR )middle_task,
(void * )0,
(OS_PRIO )MIDDLE_TASK_PRIO,
(CPU_STK * )&MIDDLE_TASK_STK[0],
(CPU_STK_SIZE)MIDDLE_STK_SIZE/10,
(CPU_STK_SIZE)MIDDLE_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OSTaskCreate((OS_TCB * )&Low_TaskTCB, //创建LOW任务
(CPU_CHAR * )"Low task",
(OS_TASK_PTR )low_task,
(void * )0,
(OS_PRIO )LOW_TASK_PRIO,
(CPU_STK * )&LOW_TASK_STK[0],
(CPU_STK_SIZE)LOW_STK_SIZE/10,
(CPU_STK_SIZE)LOW_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT(); //退出临界区
OSTaskDel((OS_TCB*)0,&err); //删除start_task任务自身
}
void high_task(void *p_arg) //高优先级任务的任务函数
{
u8 num;
OS_ERR err;
CPU_SR_ALLOC();
POINT_COLOR = BLACK;
OS_CRITICAL_ENTER();
LCD_DrawRectangle(5,110,115,314); //画一个矩形
LCD_DrawLine(5,130,115,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(6,111,110,16,16,"High Task");
OS_CRITICAL_EXIT();
while(1)
{
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时500ms
num++;
printf("high task Pend Mutex\r\n");
OSMutexPend (&TEST_MUTEX,0,OS_OPT_PEND_BLOCKING,0,&err); //请求互斥信号量
printf("high task Running!\r\n");
LCD_Fill(6,131,114,313,lcd_discolor[num%14]); //填充区域
LED1 = ~LED1;
OSMutexPost(&TEST_MUTEX,OS_OPT_POST_NONE,&err); //释放互斥信号量
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err); //延时500ms
}
}
void middle_task(void *p_arg) //中等优先级任务的任务函数
{
u8 num;
OS_ERR err;
CPU_SR_ALLOC();
POINT_COLOR = BLACK;
OS_CRITICAL_ENTER();
LCD_DrawRectangle(125,110,234,314); //画一个矩形
LCD_DrawLine(125,130,234,130); //画线
POINT_COLOR = BLUE;
LCD_ShowString(126,111,110,16,16,"Middle Task");
OS_CRITICAL_EXIT();
while(1)
{
num++;
printf("middle task Running!\r\n");
LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]); //填充区域
LED0 = ~LED0;
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}
void low_task(void *p_arg) //低优先级任务的任务函数
{
static u32 times;
OS_ERR err;
while(1)
{
OSMutexPend (&TEST_MUTEX,0,OS_OPT_PEND_BLOCKING,0,&err);//请求互斥信号量
printf("low task Running!\r\n");
for(times=0;times<10000000;times++)
{
OSSched(); //发起任务调度
}
OSMutexPost(&TEST_MUTEX,OS_OPT_POST_NONE,&err); //释放互斥信号量
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err); //延时1s
}
}