写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2024.04.06
- 四十八、UCOSIII:互斥量函数接口讲解
- 1、创建互斥量函数OSMutexCreate()
- 2、删除互斥量函数OSMutexDel()
- 3、获取互斥量函数OSMutexPend()
- 4、释放互斥量函数OSMutexPost()
四十八、UCOSIII:互斥量函数接口讲解
1、创建互斥量函数OSMutexCreate()
在定义完互斥量结构体变量后就可以调用 OSMutexCreate()函数进行创建一个互斥量,跟信号量的创建差不多。
这里的“创建互斥量”指的就是对内核对象(互斥量)的一些初始化。
要特别注意的是内核对象使用之前一定要先创建, 这个创建过程必须要保证在所有可能使用内核对象的任务之前,所以一般我们都是在创建任务之前就创建好系统需要的内核对象(如互斥量等)。
创建互斥量函数OSMutexCreate()源码具体如下:
void OSMutexCreate (OS_MUTEX *p_mutex, (1) //互斥量指针
CPU_CHAR *p_name, (2) //取互斥量的名称
OS_ERR *p_err) (3) //返回错误类型
{
CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
// SR(临界段关中断只需保存SR),开中断时将该值还原。
#ifdef OS_SAFETY_CRITICAL(4)//如果启用(默认禁用)了安全检测
if (p_err == (OS_ERR *)0) //如果错误类型实参为空
{
OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
return; //返回,不继续执行
}
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508(5)//如果启用(默认禁用)了安全关键
//如果是在调用 OSSafetyCriticalStart()后创建
if (OSSafetyCriticalStartFlag == DEF_TRUE)
{
*p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为“非法创建内核对象”
return; //返回,不继续执行
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u (6)
//如果启用(默认启用)了中断中非法调用检测
if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用
{
*p_err = OS_ERR_CREATE_ISR; //错误类型为“在中断函数中定时”
return; //返回,不继续执行
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u(7)//如果启用(默认启用)了参数检测
if (p_mutex == (OS_MUTEX *)0) //如果参数 p_mutex 为空
{
*p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为“创建对象为空”
return; //返回,不继续执行
}
#endif
OS_CRITICAL_ENTER(); //进入临界段,初始化互斥量指标
//标记创建对象数据结构为互斥量
p_mutex->Type = OS_OBJ_TYPE_MUTEX; (8)
p_mutex->NamePtr = p_name; (9)
p_mutex->OwnerTCBPtr = (OS_TCB *)0; (10)
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; (11)
p_mutex->TS = (CPU_TS )0; (12)
p_mutex->OwnerOriginalPrio = OS_CFG_PRIO_MAX;
OS_PendListInit(&p_mutex->PendList); //初始化该互斥量的等待列表
#if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量
OS_MutexDbgListAdd(p_mutex); //将该互斥量添加到互斥量双向调试链表
#endif
OSMutexQty++; (13)//互斥量个数加1
OS_CRITICAL_EXIT_NO_SCHED(); (14) //退出临界段(无调度)
*p_err = OS_ERR_NONE; //错误类型为“无错误”
}
- (1):互斥量控制块指针, 指向我们定义的互斥量控制块结构体变量,所以在创建之前我们需要先定义一个互斥量控制块变量。
- (2):互斥量名称,字符串形式。
- (3):用于保存返回的错误类型。
- (4):如果启用了安全检测(默认禁用), 在编译时则会包含安全检测相关的代码,如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行创建互斥量操作。
- (5):如果启用(默认禁用)了安全关键检测, 在编译时则会包含安全关键检测相关的代码,如果是在调用OSSafetyCriticalStart()后创建该互斥量,则是非法的, 返回错误类型为“非法创建内核对象”错误代码,并且退出,不执行创建互斥量操作。
- (6):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的,返回错误类型为“在中断中创建对象”的错误代码, 并且退出,不执行创建互斥量操作。
- (7):如果启用了参数检测(默认启用), 在编译时则会包含参数检测相关的代码,如果p_mutex参数为空,返回错误类型为“创建对象为空”的错误代码,并且退出,不执行创建互斥量操作。
- (8):标记创建对象数据结构为互斥量。
- (9):初始化互斥量的名称。
- (10):初始化互斥量结构体中的OwnerTCBPtr成员变量,目前系统中尚无任务持有互斥量。
- (11):初始化互斥量结构体中的OwnerNestingCtr成员变量为0,表示互斥量可用。
- (12):记录时间戳的变量TS初始化为0。 初始化互斥量结构体中的OwnerOriginalPrio成员变量为OS_CFG_PRIO_MAX(最低优先级)。初始化该互斥量的等待列表等。
- (13):系统中互斥量个数加1。
- (14):退出临界段(无调度),创建互斥量成功。
如果我们创建一个互斥量,那么互斥量创建成功的示意图具体见图
互斥量创建函数的使用实例具体如下:
OS_MUTEX mutex; //声明互斥量
/* 创建互斥量 mutex */
OSMutexCreate ((OS_MUTEX *)&mutex, //指向互斥量变量的指针
(CPU_CHAR *)"Mutex For Test", //互斥量的名字
(OS_ERR *)&err); //错误类型
2、删除互斥量函数OSMutexDel()
OSSemDel()用于删除一个互斥量,互斥量删除函数是根据互斥量结构(互斥量句柄)直接删除的,删除之后这个互斥量的所有信息都会被系统清空, 而且不能再次使用这个互斥量了。
需要注意的是,如果某个互斥量没有被定义,那也是无法被删除的,如果有任务阻塞在该互斥量上, 那么尽量不要删除该互斥量。想要使用互斥量删除函数就必须将OS_CFG_MUTEX_DEL_EN宏定义配置为1,其函数源码具体如下
#if OS_CFG_MUTEX_DEL_EN > 0u //如果启用了 OSMutexDel()
OS_OBJ_QTY OSMutexDel (OS_MUTEX *p_mutex, (1) //互斥量指针
OS_OPT opt, (2) //选项
OS_ERR *p_err) (3) //返回错误类型
{
OS_OBJ_QTY cnt;
OS_OBJ_QTY nbr_tasks;
OS_PEND_DATA *p_pend_data;
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
OS_TCB *p_tcb_owner;
CPU_TS ts;
CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
// SR(临界段关中断只需保存SR),开中断时将该值还原。
#ifdef OS_SAFETY_CRITICAL(4)//如果启用(默认禁用)了安全检测
if (p_err == (OS_ERR *)0) //如果错误类型实参为空
{
OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
return ((OS_OBJ_QTY)0); //返回0(有错误),停止执行
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(5)//如果启用了中断中非法调用检测
if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用
{
*p_err = OS_ERR_DEL_ISR; //错误类型为“在中断中中止等待”
return ((OS_OBJ_QTY)0); //返回0(有错误),停止执行
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u(6)//如果启用了参数检测
if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空
{
*p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为“对象为空”
return ((OS_OBJ_QTY)0); //返回0(有错误),停止执行
}
switch (opt) (7)//根据选项分类处理
{
case OS_OPT_DEL_NO_PEND: //如果选项在预期内
case OS_OPT_DEL_ALWAYS:
break; //直接跳出
default: (8)//如果选项超出预期
*p_err = OS_ERR_OPT_INVALID; //错误类型为“选项非法”
return ((OS_OBJ_QTY)0); //返回0(有错误),停止执行
}
#endif
#if OS_CFG_OBJ_TYPE_CHK_EN > 0u(9)//如果启用了对象类型检测
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 非互斥量类型
{
*p_err = OS_ERR_OBJ_TYPE; //错误类型为“对象类型错误”
return ((OS_OBJ_QTY)0); //返回0(有错误),停止执行
}
#endif
OS_CRITICAL_ENTER(); //进入临界段
p_pend_list = &p_mutex->PendList; (10)//获取互斥量的等待列表
cnt = p_pend_list->NbrEntries; (11)//获取等待该互斥量的任务数
nbr_tasks = cnt;
switch (opt) (12)//根据选项分类处理
{
case OS_OPT_DEL_NO_PEND: (13)//如果只在没任务等待时删除互斥量
if (nbr_tasks == (OS_OBJ_QTY)0) //如果没有任务在等待该互斥量
{
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
OS_MutexDbgListRemove(p_mutex);//将该互斥量从互斥量调试列表移除
#endif
OSMutexQty--; (14)//互斥量数目减1
OS_MutexClr(p_mutex); (15)//清除互斥量内容
OS_CRITICAL_EXIT(); //退出临界段
*p_err = OS_ERR_NONE; (16)//错误类型为“无错误”
}
else(17)//如果有任务在等待该互斥量
{
OS_CRITICAL_EXIT(); //退出临界段
*p_err = OS_ERR_TASK_WAITING; //错误类型为“有任务正在等待”
}
break; //跳出
case OS_OPT_DEL_ALWAYS: (18)//如果必须删除互斥量
p_tcb_owner = p_mutex->OwnerTCBPtr; (19)//获取互斥量持有任务
if ((p_tcb_owner != (OS_TCB *)0) &&//如果持有任务存在,
(p_tcb_owner->Prio != p_mutex->OwnerOriginalPrio))
//而且优先级被提升过。 (20)
{
switch (p_tcb_owner->TaskState) (21)//根据其任务状态处理
{
case OS_TASK_STATE_RDY: (22)//如果是就绪状态
OS_RdyListRemove(p_tcb_owner); //将任务从就绪列表移除
p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;(23)//还原任务的优先级
OS_PrioInsert(p_tcb_owner->Prio); (24)
//将该优先级插入优先级表格
OS_RdyListInsertTail(p_tcb_owner); (25)//将任务重插入就绪列表
break; //跳出
case OS_TASK_STATE_DLY: (26)//如果是延时状态
case OS_TASK_STATE_SUSPENDED: //如果是被挂起状态
case OS_TASK_STATE_DLY_SUSPENDED: //如果是延时中被挂起状态
p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio;//还原任务的优先级
break;
case OS_TASK_STATE_PEND: (27)//如果是无期限等待状态
case OS_TASK_STATE_PEND_TIMEOUT: //如果是有期限等待状态
case OS_TASK_STATE_PEND_SUSPENDED:
//如果是无期等待中被挂状态
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
//如果是有期等待中被挂状态
OS_PendListChangePrio(p_tcb_owner,
//改变任务在等待列表的位置
p_mutex->OwnerOriginalPrio);
break;
default: (28)//如果状态超出预期
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
//错误类型为“任务状态非法”
return ((OS_OBJ_QTY)0);
//返回0(有错误),停止执行
}
}
ts = OS_TS_GET(); (29)//获取时间戳
while(cnt > 0u) (30)
//移除该互斥量等待列表中的所有任务。
{
p_pend_data = p_pend_list->HeadPtr;
p_tcb = p_pend_data->TCBPtr;
OS_PendObjDel((OS_PEND_OBJ *)((void *)p_mutex),
p_tcb,
ts); (31)
cnt--;
}
#if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量
OS_MutexDbgListRemove(p_mutex); //将互斥量从互斥量调试列表移除
#endif
OSMutexQty--; (32)//互斥量数目减1
OS_MutexClr(p_mutex); (33)//清除互斥量内容
OS_CRITICAL_EXIT_NO_SCHED(); (34)//退出临界段,但不调度
OSSched(); (35)//调度最高优先级任务运行
*p_err = OS_ERR_NONE;(36)//错误类型为“无错误”
break; //跳出
default: (37)//如果选项超出预期
OS_CRITICAL_EXIT(); //退出临界段
*p_err = OS_ERR_OPT_INVALID; //错误类型为“选项非法”
break; //跳出
}
return (nbr_tasks); (38)
//返回删除前互斥量等待列表中的任务数
}
#endif
- (1):互斥量控制块指针,指向我们定义的互斥量控制块结构体变量, 所以在删除之前我们需要先定义一个互斥量控制块变量,并且成功创建互斥量后再进行删除操作。
- (2):互斥量删除的选项。
- (3):用于保存返回的错误类型。
- (4):如果启用了安全检测(默认), 在编译时则会包含安全检测相关的代码,如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,不执行删除互斥量操作。
- (5):如果启用了中断中非法调用检测(默认启用), 在编译时则会包含中断非法调用检测相关的代码,如果该函数是在中断中被调用,则是非法的,返回错误类型为“在中断中删除对象”的错误代码,并且退出,不执行删除互斥量操作。
- (6):如果启用了参数检测(默认启用), 在编译时则会包含参数检测相关的代码,如果p_mutex参数为空,返回错误类型为“内核对象为空”的错误代码,并且退出,不执行删除互斥量操作。
- (7):判断opt选项是否合理,该选项有两个, OS_OPT_DEL_ALWAYS与OS_OPT_DEL_NO_PEND,在os.h文件中定义。此处是判断一下选项是否在预期之内,如果在则跳出switch语句。
- (8):如果选项超出预期,则返回错误类型为“选项非法”的错误代码,退出,不继续执行。
- (9):如果启用了对象类型检测,在编译时则会包含对象类型检测相关的代码, 如果p_mutex不是互斥量类型,返回错误类型为“内核对象类型错误”的错误代码,并且退出,不执行删除互斥量操作。
- (10):程序执行到这里,表示可以删除互斥量了,系统首先获取互斥量的等待列表保存到p_pend_list变量中。
- (11):然后再获取等待该互斥量的任务数。
- (12):根据选项分类处理。
- (13):如果opt是OS_OPT_DEL_NO_PEND, 则表示只在没有任务等待的情况下删除互斥量,如果当前系统中有任务阻塞在该互斥量上,则不能删除,反之,则可以删除互斥量。
- (14):如果没有任务在等待该互斥量,互斥量数目减1。
- (15):清除互斥量内容
- (16):删除成功,返回错误类型为“无错误”的错误代码。
- (17):如果有任务在等待该互斥量,则返回错误类型为“有任务在等待该互斥量”错误代码。
- (18):如果opt是OS_OPT_DEL_ALWAYS,则表示无论如何都必须删除互斥量, 那么在删除之前,系统会把所有阻塞在该互斥量上的任务恢复。
- (19):首先获取一下持有互斥量的任务。
- (20):如果该互斥量被任务持有了,并且优先级也被提升了(发生优先级继承)。
- (21):根据持有互斥量任务的状态进行分类处理。
- (22):如果任务处于就绪状态。
- (23):那么就将任务从就绪列表移除,然后还原任务的优先级, 互斥量控制块中的OwnerOriginalPrio成员变量保存的就是持有互斥量任务的原本优先级。
- (24):调用OS_PrioInsert()函数将任务按照其原本的优先级插入优先级列表中。
- (25):将任务重新插入就绪列表。
- (26):如果任务处于延时状态、被挂起状态或者是延时中被挂起状态, 就直接将任务的优先级恢复即可,并不用进行任务列表相关的操作。
- (27):如果任务处于无期限等待状态、有期限等待状态、 无期等待中被挂状态或者是有期等待中被挂状态,那么就调用OS_PendListChangePrio()函数改变任务在等待列表的位置,根据任务的优先级进行修改即可。
- (28):如果状态超出预期,则返回错误类型为“任务状态非法”的错误代码。
- (29):获取时间戳,记录一下删除的时间。
- (30):然后根据前面cnt记录阻塞在该互斥量上的任务个数,逐个移除该互斥量等待列表中的任务。
- (31):调用OS_PendObjDel()函数将阻塞在内核对象(如互斥量)上的任务从阻塞态恢复, 此时系统在删除内核对象,删除之后,这些等待事件的任务需要被恢复。
- (32):系统中互斥量数目减1。
- (33):清除互斥量中的内容。
- (34):退出临界段,但不调度。
- (35):调度最高优先级任务运行。
- (36):删除互斥量完成,返回错误类型为“无错误”的错误代码。
- (37):如果选项超出预期则返回错误类型为“任务状态非法”的错误代码。
- (38):返回删除前互斥量等待列表中的任务数。
互斥量删除函数OSMutexDel()的使用也是很简单的,只需要传入要删除的互斥量的句柄与选项还有保存返回的错误类型即可,调用函数时,系统将删除这个互斥量。
需要注意的是在调用删除互斥量函数前,系统应存在已创建的互斥量。
如果删除互斥量时,系统中有任务正在等待该互斥量,则不应该进行删除操作, 因为删除之后的互斥量就不可用了。
删除互斥量函数OSMutexDel()的使用实例具体如下:
OS_SEM mutex;; //声明互斥量
OS_ERR err;
/* 删除互斥量mutex*/
OSMutexDel ((OS_MUTEX *)&mutex, //指向互斥量的指针
OS_OPT_DEL_NO_PEND,
(OS_ERR *)&err); //返回错误类型
3、获取互斥量函数OSMutexPend()
我们知道,当互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其他任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后, 其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。
任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态, 那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;
如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前, 会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。
互斥量的获取函数就是OSMutexPend(),其源码具体如下:
void OSMutexPend (OS_MUTEX *p_mutex, (1) //互斥量指针
OS_TICK timeout, (2) //超时时间(节拍)
OS_OPT opt, (3) //选项
CPU_TS *p_ts, (4) //时间戳
OS_ERR *p_err) (5) //返回错误类型
{
OS_PEND_DATA pend_data;
OS_TCB *p_tcb;
CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和
//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
// SR(临界段关中断只需保存SR),开中断时将该值还原。
#ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测
if (p_err == (OS_ERR *)0) //如果错误类型实参为空
{
OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
return; //返回,不继续执行
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测
if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用
{
*p_err = OS_ERR_PEND_ISR; //错误类型为“在中断中等待”
return; //返回,不继续执行
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测
if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空
{
*p_err = OS_ERR_OBJ_PTR_NULL; //返回错误类型为“内核对象为空”
return; //返回,不继续执行
}
switch (opt) //根据选项分类处理
{
case OS_OPT_PEND_BLOCKING: //如果选项在预期内
case OS_OPT_PEND_NON_BLOCKING:
break;
default: //如果选项超出预期
*p_err = OS_ERR_OPT_INVALID; //错误类型为“选项非法”
return; //返回,不继续执行
}
#endif
#if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 非互斥量类型
{
*p_err = OS_ERR_OBJ_TYPE; //错误类型为“内核对象类型错误”
return; //返回,不继续执行
}
#endif
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = (CPU_TS )0; //初始化(清零)p_ts,待用于返回时间戳
}
CPU_CRITICAL_ENTER(); //关中断
if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0)(6)//如果互斥量可用
{
p_mutex->OwnerTCBPtr = OSTCBCurPtr; (7)//让当前任务持有互斥量
p_mutex->OwnerOriginalPrio = OSTCBCurPtr->Prio; (8)//保存持有任务的优先级
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1; (9)//开始嵌套
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = p_mutex->TS; (10)//返回互斥量的时间戳记录
}
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_NONE; //错误类型为“无错误”
return; //返回,不继续执行
}
/* 如果互斥量不可用 */ (11)
if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) //如果当前任务已经持有该互斥量
{
p_mutex->OwnerNestingCtr++; (12)//互斥量嵌套数加1
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = p_mutex->TS; //返回互斥量的时间戳记录
}
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_MUTEX_OWNER; (13)//错误类型为“任务已持有互斥量”
return; //返回,不继续执行
}
/* 如果当前任务非持有该互斥量 */ (14)
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务
{
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为“渴求阻塞”
return; //返回,不继续执行
}
else(15)//如果选择了阻塞任务
{
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁
{
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_SCHED_LOCKED; //错误类型为“调度器被锁”
return; //返回,不继续执行
}
}
/* 如果调度器未被锁 */ (16)
OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,并重开中断
p_tcb = p_mutex->OwnerTCBPtr; //获取互斥量持有任务
if (p_tcb->Prio > OSTCBCurPtr->Prio) (17)
//如果持有任务优先级低于当前任务
{
switch (p_tcb->TaskState) (18)
//根据持有任务的任务状态分类处理
{
case OS_TASK_STATE_RDY: //如果是就绪状态
OS_RdyListRemove(p_tcb); //从就绪列表移除持有任务
p_tcb->Prio = OSTCBCurPtr->Prio; (19)
//提升持有任务的优先级到当前任务
OS_PrioInsert(p_tcb->Prio); (20)//将该优先级插入优先级表格
OS_RdyListInsertHead(p_tcb); (21)//将持有任务插入就绪列表
break; //跳出
case OS_TASK_STATE_DLY: //如果是延时状态
case OS_TASK_STATE_DLY_SUSPENDED: //如果是延时中被挂起状态
case OS_TASK_STATE_SUSPENDED: //如果是被挂起状态
p_tcb->Prio = OSTCBCurPtr->Prio; (22)
//提升持有任务的优先级到当前任务
break; //跳出
case OS_TASK_STATE_PEND: //如果是无期限等待状态
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);(23)
break; //跳出
default: (24)//如果任务状态超出预期
OS_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_STATE_INVALID; //错误类型为“任务状态非法”
return; //返回,不继续执行
}
}
/*
阻塞任务,将当前任务脱离就绪列表,并插入节拍列表和等待列表。*/
OS_Pend(&pend_data,
(OS_PEND_OBJ *)((void *)p_mutex),
OS_TASK_PEND_ON_MUTEX,
timeout); (25)
OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,但不进行调度
OSSched(); (26)//调度最高优先级任务运行
CPU_CRITICAL_ENTER(); //开中断
switch (OSTCBCurPtr->PendStatus)(27)//根据当前运行任务的等待状态分类处理
{
case OS_STATUS_PEND_OK: (28)//如果等待正常(获得互斥量)
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = OSTCBCurPtr->TS; //返回互斥量最后一次被释放的时间戳
}
*p_err = OS_ERR_NONE; (29)//错误类型为“无错误”
break; //跳出
case OS_STATUS_PEND_ABORT: (30)//如果等待被中止
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = OSTCBCurPtr->TS; //返回等待被中止时的时间戳
}
*p_err = OS_ERR_PEND_ABORT; //错误类型为“等待被中止”
break; //跳出
case OS_STATUS_PEND_TIMEOUT: (31)//如果超时内为获得互斥量
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = (CPU_TS )0; //清零 p_ts
}
*p_err = OS_ERR_TIMEOUT; //错误类型为“超时”
break; //跳出
case OS_STATUS_PEND_DEL: (32)//如果互斥量已被删除
if (p_ts != (CPU_TS *)0) //如果 p_ts 非空
{
*p_ts = OSTCBCurPtr->TS; //返回互斥量被删除时的时间戳
}
*p_err = OS_ERR_OBJ_DEL; //错误类型为“对象被删除”
break; //跳出
default: (33)//根据等待状态超出预期
*p_err = OS_ERR_STATUS_INVALID;//错误类型为“状态非法”
break; //跳出
}
CPU_CRITICAL_EXIT(); //开中断
}
- (1):互斥量指针。
- (2):用户自定义的阻塞超时时间,单位为系统时钟节拍。
- (3):获取互斥量的选项,当互斥量不可用的时候,用户可以选择阻塞或者不阻塞。
- (4):用于保存返回等到互斥量时的时间戳。
- (5):用于保存返回的错误类型,用户可以根据此变量得知错误的原因。
- (6):如果互斥量可用, 互斥量控制块中的OwnerNestingCtr变量为0则表示互斥量处于开锁状态,互斥量可用被任务获取。
- (7):让当前任务持有互斥量。
- (8):保存一下持有互斥量任务的优先级。如果发生了优先级继承,就会用到这个变量。
- (9):开始嵌套,这其实是将互斥量变为闭锁状态, 而其他任务就不能获取互斥量,但是本身持有互斥量的任务就拥有该互斥量的所有权,能递归获取该互斥量,每获取一次已经持有的互斥量, OwnerNestingCtr的值就会加一,以表示互斥量嵌套,任务获取了多少次互斥量就需要释放多少次互斥量。
- (10):保存并且返回互斥量的时间戳记录,记录错误类型为“无错误”,退出,不继续执行。
- (11):而如果任务想要获取的斥量处于闭锁状态(OwnerNestingCtr变量不为0), 那么就判断一下当前任务是否已经持有该互斥量。
- (12):如果当前任务已经持有该互斥量,那么任务就拥有互斥量的所有权,能递归获取互斥量,那么互斥量嵌套数就加1。
- (13):返回互斥量的时间戳记录与错误类型为“任务已持有互斥量”的错误代码,然后退出。
- (14):如果当前任务并没有持有该互斥量,那肯定是不能获取到的, 就看看用户有没有选择阻塞任务,如果选择了不阻塞任务,那么就返回错误类型为“渴求阻塞”的错误代码,退出,不继续执行。
- (15):而用户如果选择了阻塞任务, 就判断一下调度器是否被锁,如果调度器被锁了,就返回错误类型为“调度器被锁”的错误代码。
- (16):如果调度器未被锁,就锁调度器,并重开中断, 至于为什么,在前面的章节就讲解过了,此处就不再重复赘述。
- (17):获取持有互斥量的任务,判断一下当前任务与持有互斥量的任务优先级情况, 如果持有互斥量的任务优先级低于当前任务,就会临时将持有互斥量任务的优先级提升,提升到与当前任务优先级一致,这就是优先级继承。
- (18):根据持有互斥量任务的任务状态分类处理。
- (19):如果该任务处于就绪状态,那么从就绪列表中移除该任务,然后将该任务的优先级到与当前任务优先级一致。
- (20):将该优先级插入优先级表格。
- (21):再将该任务按照优先级顺序插入就绪列表。
- (22):如果持有互斥量任务处于延时状态、延时中被挂起状态或者是被挂起状态, 仅仅是提升持有互斥量任务的优先级与当前任务优先级一致即可,不需要操作就绪列表。
- (23):如果持有互斥量任务无期限等待状态、有期限等待状态、 无期限等待中被挂起状态或者是有期限等待中被挂起状态,那么就直接根据任务的优先级来改变持有互斥量任务在等待列表的位置即可。
- (24):如果任务状态超出预期,返回错误类型为“任务状态非法”的错误代码,不继续执行。
- (25):程序执行到这里,就表示如果需要优先级继承的就已经处理完毕了, 否则就不用优先级继承,那么可以直接调用OS_Pend()函数阻塞任务,将当前任务脱离就绪列表,并插入节拍列表和等待列表中。
- (26):进行一次任务调度,以运行处于最高优先级的就绪任务。
- (27):程序能执行到这里, 表示任务已经从阻塞中恢复了,但是恢复的原因有多种,需要根据当前运行任务的等待状态分类处理。
- (28):如果任务等待正常(获得了互斥量),这是最好的结果了,任务等到了互斥量。
- (29):保存一下获取的时间戳与错误类型为“无错误”的错误代码,就跳出switch语句继续执行。
- (30):如果等待被中止,返回等待被中止时的时间戳与错误类型为“等待被中止”的错误代码,跳出switch语句。
- (31):如果超时时间内未获得互斥量,就返回错误类型为“阻塞超时”的错误代码,然后跳出switch语句。
- (32):如果互斥量已被删除, 返回互斥量被删除时的时间戳与错误类型为“对象被删除”的错误代码,跳出switch语句。
- (33):根据等待状态超出预期,返回错误类型为“状态非法”的错误代码,退出。
至此,获取互斥量的操作就完成了,如果任务获取互斥量成功,那么在使用完毕需要立即释放,否则很容易造成其他任务无法获取互斥量, 因为互斥量的优先级继承机制是只能将优先级危害降低,而不能完全消除。
同时还需注意的是,互斥量是不允许在中断中操作的,因为互斥量特有的优先级继承机制在中断是毫无意义的, 互斥量获取函数的使用实例具体如下:
OS_MUTEX mutex; //声明互斥量
OS_ERR err;
OSMutexPend ((OS_MUTEX *)&mutex, //申请互斥量 mutex
(OS_TICK )0, //无期限等待
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果不能申请到互斥量就阻塞任务
(CPU_TS *)0, //不想获得时间戳
(OS_ERR *)&err); //返回错误类
4、释放互斥量函数OSMutexPost()
任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。
在前面的讲解中,我们知道,当互斥量有效的时候,任务才能获取互斥量,那么,是什么函数使得互斥量变得有效呢?
μC/OS给我们提供了互斥量释放函数OSMutexPost(), 任务可以调用该函数进行释放互斥量,表示我已经用完了,别人可以申请使用,但是要注意的是,互斥量的释放只能在任务中,不允许在中断中释放互斥量。
使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用OSMutexPost()函数时会释放一次互斥量,当互斥量的成员变量OwnerNestingCtr为0的时候, 互斥量状态才会成为开锁状态,等待获取该互斥量的任务将被唤醒。
如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被完全释放后, 任务的优先级将恢复为原本设定的优先级,其源码具体如下:
void OSMutexPost (OS_MUTEX *p_mutex, (1) //互斥量指针
OS_OPT opt, (2) //选项
OS_ERR *p_err) (3) //返回错误类型
{
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
CPU_TS ts;
CPU_SR_ALLOC();
//使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变
//量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR)
//,开中断时将该值还原。
#ifdef OS_SAFETY_CRITICAL(4)//如果启用(默认禁用)了安全检测
if (p_err == (OS_ERR *)0) //如果错误类型实参为空
{
OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数
return; //返回,不继续执行
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(5)//如果启用了中断中非法调用检测
if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用
{
*p_err = OS_ERR_POST_ISR; //错误类型为“在中断中等待”
return; //返回,不继续执行
}
#endif
#if OS_CFG_ARG_CHK_EN > 0u(6)//如果启用了参数检测
if (p_mutex == (OS_MUTEX *)0) //如果 p_mutex 为空
{
*p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为“内核对象为空”
return; //返回,不继续执行
}
switch (opt) //根据选项分类处理
{
case OS_OPT_POST_NONE: //如果选项在预期内,不处理
case OS_OPT_POST_NO_SCHED:
break;
default: //如果选项超出预期
*p_err = OS_ERR_OPT_INVALID; //错误类型为“选项非法”
return; //返回,不继续执行
}
#endif
#if OS_CFG_OBJ_TYPE_CHK_EN > 0u(7)//如果启用了对象类型检测
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) //如果 p_mutex 的类型不是互斥量类型
{
*p_err = OS_ERR_OBJ_TYPE; //返回,不继续执行
return;
}
#endif
CPU_CRITICAL_ENTER(); //关中断
if(OSTCBCurPtr != p_mutex->OwnerTCBPtr)(8)//如果当前运行任务不持有该互斥量
{
CPU_CRITICAL_EXIT(); //开中断
*p_err = OS_ERR_MUTEX_NOT_OWNER; (9)//错误类型为“任务不持有该互斥量”
return; //返回,不继续执行
}
OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,开中断
ts = OS_TS_GET(); (10)//获取时间戳
p_mutex->TS = ts;
//存储互斥量最后一次被释放的时间戳
p_mutex->OwnerNestingCtr--; (11)//互斥量的嵌套数减1
if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) //如果互斥量仍被嵌套
{
OS_CRITICAL_EXIT(); //解锁调度器
*p_err = OS_ERR_MUTEX_NESTING; (12)//错误类型为“互斥量被嵌套”
return; //返回,不继续执行
}
/* 如果互斥量未被嵌套,已可用 */
p_pend_list = &p_mutex->PendList; (13)//获取互斥量的等待列表
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) //如果没有任务在等待该互斥量
{
p_mutex->OwnerTCBPtr = (OS_TCB *)0;(14)//清空互斥量持有者信息
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0;(15)
OS_CRITICAL_EXIT(); //解锁调度器
*p_err = OS_ERR_NONE; (16)//错误类型为“无错误”
return; //返回,不继续执行
}
/* 如果有任务在等待该互斥量 */
if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio)(17)//如果当前任务的优先级被改过
{
OS_RdyListRemove(OSTCBCurPtr); (18)//从就绪列表移除当前任务
OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio;(19)//还原当前任务的优先级
OS_PrioInsert(OSTCBCurPtr->Prio); (20)//在优先级表格插入这个优先级
OS_RdyListInsertTail(OSTCBCurPtr); (21)//将当前任务插入就绪列表尾端
OSPrioCur = OSTCBCurPtr->Prio; (22)//更改当前任务优先级变量的值
}
p_tcb = p_pend_list->HeadPtr->TCBPtr; (23) //获取等待列表的首端任务
p_mutex->OwnerTCBPtr = p_tcb; (24)//将互斥量交给该任务
p_mutex->OwnerOriginalPrio = p_tcb->Prio;(25)
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1; (26)//开始嵌套
/* 释放互斥量给该任务 */
OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
(OS_TCB *)p_tcb,
(void *)0,
(OS_MSG_SIZE )0,
(CPU_TS )ts); (27)
OS_CRITICAL_EXIT_NO_SCHED(); //减锁调度器,但不执行任务调度
if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) //如果 opt没选择“发布时不调度任务”
{
OSSched(); (28) //任务调度
}
*p_err = OS_ERR_NONE; //错误类型为“无错误”
}
- (1):互斥量指针。
- (2):释放互斥量的选项。
- (3):用于保存返回的错误类型,用户可以根据此变量得知错误的原因。
- (4):如果启用(默认禁用)了安全检测,在编译时则会包含安全检测相关的代码, 如果错误类型实参为空,系统会执行安全检测异常函数,然后返回,停止执行。
- (5):如果启用了中断中非法调用检测,并且如果该函数在中断中被调用, 则返回错误类型为“在中断中释放”的错误代码,然后退出不继续执行。消息、信号量等内核对象可以在中断中释放, 但是唯独互斥量是不可以的,因为其具备的优先级继承特性在中断的上下文环境中毫无意义。
- (6):如果启用了参数检测,在编译时则会包含参数检测相关的代码, 如果p_mutex参数为空,返回错误类型为“内核对象为空”的错误代码,并且退出,不执行释放互斥量操作。
- (7):如果启用了对象类型检测,在编译时则会包含对象类型检测相关代码, 如果 p_mutex不是互斥量类型,那么返回错误类型为“对象类型有误”的错误代码,并且退出,不执行释放互斥量操作。
- (8):程序能运行到这里,说明传递进来的参数是正确的,此时, 系统会判断一下调用互斥量释放函数的任务是否持有该互斥量,如果是则进行互斥量的释放,否则就返回错误。
- (9):如果当前运行任务不持有该互斥量,返回错误类型为“任务不持有该互斥量”的错误代码,然后退出,不继续执行。
- (10):获取时间戳,保存一下互斥量最后一次被释放的时间戳。
- (11):互斥量控制块中的OwnerNestingCtr成员变量减一, 也就是互斥量的嵌套数减1,当该变量为0的时候,互斥量才变为开锁状态。
- (12):如果互斥量仍被嵌套,也就是OwnerNestingCtr不为0, 那还是表明当前任务还是持有互斥量的,并未完全释放,返回错误类型为“互斥量仍被嵌套”的错误代码,然后退出,不继续执行。
- (13):如果互斥量未被嵌套,已可用(OwnerNestingCtr为0), 那么就获取互斥量的等待列表保存在p_pend_list变量中,通过该变量访问互斥量等待列表。
- (14):如果没有任务在等待该互斥量, 那么就清空互斥量持有者信息,互斥量中的OwnerTCBPtr成员变量重置为0。
- (15):互斥量中的OwnerNestingCtr成员变量重置为0,表示互斥量处于开锁状态。
- (16):执行到这里,表示当前任务已经完全释放互斥量了,返回错误类型为“无错误”的错误代码。
- (17):如果有任务在等待该互斥量,那么就很有可能发生了优先级继承, 先看看当前任务的优先级是否被修改过,如果有则说明发生了优先级继承,就需要重新恢复任务原本的优先级。
- (18):从就绪列表移除当前任务。
- (19):还原当前任务的优先级。
- (20):在优先级表格插入这个优先级。
- (21):将当前任务插入就绪列表尾端。
- (22):更改当前任务优先级变量的值。
- (23):获取等待列表的首端任务。
- (24):将互斥量交给该任务。
- (25):保存一下该任务的优先级。
- (26):互斥量的OwnerNestingCtr成员变量设置为1,表示互斥量处于闭锁状态。
- (27):调用OS_Post()函数释放互斥量给该任务。
- (28):进行一次任务调度。
已经获取到互斥量的任务拥有互斥量的所有权,能重复获取同一个互斥量,但是任务获取了多少次互斥量就要释放多少次互斥量才能彻底释放掉互斥量, 互斥量的状态才会变成开锁状态,否则在此之前互斥量都处于无效状态,别的任务就无法获取该互斥量。
使用该函数接口时,只有已持有互斥量所有权的任务才能释放它, 每释放一次该互斥量,它的OwnerNestingCtr成员变量就减1。
当该互斥量的OwnerNestingCtr成员变量为0时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态, 等待在该互斥量上的任务将被唤醒。
如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级。
下面看看互斥量释放函数是如何使用的,具体如下:
OS_MUTEX mutex; //声明互斥互斥量
OS_ERR err;
OSMutexPost ((OS_MUTEX *)&mutex, //释放互斥互斥量 mutex
(OS_OPT )OS_OPT_POST_NONE, //进行任务调度
(OS_ERR *)&err); //返回错误类型