μC/OS-Ⅱ源码学习(4)---信号量
快速回顾
本文进一步解析事件模型中,信号量类型的函数源码。
先回顾一下上一节的通用事件控制块类型OS_EVENT:
//ucos_ii.h
typedef struct os_event {
INT8U OSEventType; /* 事件类型,有六种(其中一种是UNUSED) */
void *OSEventPtr; /* OSEventPtr是一个多用途的指针,当作为链表时,可以指向下一个控制块;当作为具体的事件控制块时,指向具体的事件结构,如OS_Q。信号量不使用该指针 */
INT16U OSEventCnt; /* 信号量计数器,其它事件类型不使用该成员 */
OS_PRIO OSEventGrp; /* 等待信号的任务优先级组,和OSEventTbl共同组成”事件等待表“ */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 等待信号的组内优先级 */
#if OS_EVENT_NAME_EN > 0u
INT8U *OSEventName; //事件名称
#endif
} OS_EVENT;
信号量的创建
信号量的创建函数为OSSemCreate(cnt),传入的cnt会作为初始值填入OSEventCnt成员,由于信号量没有自己的专属结构,因此OSEventPtr指向空。
//os_sem.c
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508 //IEC标准,可以忽略
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
if (OSIntNesting > 0u) { /* 不能在中断中创建信号量 */
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 从空白事件控制块链表中取一个 */
if (OSEventFreeList != (OS_EVENT *)0) { /* 如果空白事件控制块链表还有剩余,则将链表头指向下一个,以便下一次获取 */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* 只有非空的事件控制块才能填装 */
pevent->OSEventType = OS_EVENT_TYPE_SEM;
pevent->OSEventCnt = cnt; /* 填装传入的信号量初始值 */
pevent->OSEventPtr = (void *)0; /* 内部指针指向空,断开和原链表的联系,信号量也没有自己的事件结构需要链接 */
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); /* 初始化等待任务表,就是把OSEventGrp和OSEventTbl清零 */
}
return (pevent);
}
信号量的操作
信号量的操作有很多种,最常用的就是等待(Pend)和释放(Post)了,接下来依次探究各个信号量操作函数源码。
OSSemPend(OS_EVENT *pevent, INT32U timeout, INT8U *perr)
先描述该函数的作用:
①任务使用该语句时,表明需要等待信号量pevent,当信号量计数器大于0时,直接减1并返回继续执行任务;当计数器等于0时,则要等待其它任务释放信号量。
②如果给定了timeout不为0,则若timeout减至0时都没有取得信号量,直接返回执行;若传入的timeout为0,则一直等待直到获得信号量。
//os_sem.c
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL //IEC标准,可以忽略
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 传入的信号量不能为空 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 传入的是事件类型必须是信号量 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0u) { /* 不能在中断中调用 */
*perr = OS_ERR_PEND_ISR;
return;
}
if (OSLockNesting > 0u) { /* 当调度器上锁时不能调用 */
*perr = OS_ERR_PEND_LOCKED;
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0u) { /* 信号量计数器大于0 */
pevent->OSEventCnt--; /* 计数器减1,并返回继续执行任务 */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return;
}
/* 计数器为0时,则必须等待其它任务释放信号量 */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* 当前任务状态变为“等待信号量” */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; //任务Pending状态为OK(只要没出问题都是OK)
OSTCBCur->OSTCBDly = timeout;
OS_EventTaskWait(pevent); /* 将任务和事件的等待双向绑定,后面会具体展开 */
OS_EXIT_CRITICAL();
OS_Sched(); /* 找到高优先级任务并执行上下文切换,之后的代码将延期执行 */
OS_ENTER_CRITICAL(); //再次回到这,只能是由以下三种原因引起
switch (OSTCBCur->OSTCBStatPend) { /* 检查任务的Pend状态,查看任务就绪原因 */
case OS_STAT_PEND_OK: //成功获取了信号量,导致回到OK状态
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT: /* 调用了OSSemPendAbort()导致取消了Pend状态 */
*perr = OS_ERR_PEND_ABORT;
break;
case OS_STAT_PEND_TO: /* 设定的延时超时 */
default:
OS_EventTaskRemove(OSTCBCur, pevent); //将事件的等待表上的当前任务优先级移除
*perr = OS_ERR_TIMEOUT;
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* 将任务状态设置为就绪 */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Pending状态OK */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* 清除任务等待事件 */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0; //如果设置了多重事件等待,则都清除
#endif
OS_EXIT_CRITICAL();
}
当执行OS_Sched()执行上下文切换后,后面的代码就不会立即执行了,而是等待任务再次就绪(可能是信号量大于0,或调用OSSemPendAbort()取消等待,也可能是等待超时)并被别的任务执行OS_Sched()调度后才能回到该处继续执行。
这里面还涉及到两个关键函数,第一个是链接任务和事件的函数OS_EventTaskWait(),发生在任务开始等待事件时,将任务等待的事件控制块记录在TCB内,并将事件等待表中对应任务的优先级置1。在这之后,将优先级就绪表中的任务对应优先级清除(个人觉得这最后一步可以另外做个函数调用,因为和上面的操作关联性不高)。
//os_core.c
void OS_EventTaskWait (OS_EVENT *pevent)
{
INT8U y;
OSTCBCur->OSTCBEventPtr = pevent; /* 在任务TCB中存储等待的事件控制块指针 */
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; /* 将任务优先级记录到事件等待表中,包括优先级组和组内优先级 */
pevent->OSEventGrp |= OSTCBCur->OSTCBBitY;
/* 将优先级就绪表中的对应任务优先级清除,即取消就绪状态 */
y = OSTCBCur->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
}
另一个是取消链接函数OS_EventTaskRemove():发生在等待事件超时,将事件等待表中对应任务的优先级清除。这里只有事件对任务的单向解绑定,没有任务对事件的解绑,和上面的双向绑定有出入,同时也没有后面优先级就绪表的操作。这是因为很多操作都在系统Tick中断中进行了,显得和绑定的函数不成对。
//os_core.c
void OS_EventTaskRemove (OS_TCB *ptcb,
OS_EVENT *pevent)
{
INT8U y;
y = ptcb->OSTCBY;
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* 将事件等代表中的对应任务优先级清除 */
if (pevent->OSEventTbl[y] == 0u) {
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
}
OSSemAccept(OS_EVENT *pevent)
功能描述:获取信号量并执行,无论信号量是否大于0,都立即返回(返回-1后的信号量计数器),不会进入等待。当发生错误时(如事件为空或非信号量类型),返回0。
//os_sem.c
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_CRITICAL_METHOD == 3u /* 初始化临界值变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 检查信号量不为空 */
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 检查事件类型必须为信号量 */
return (0u);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0u) { /* 检查信号量计数器是否大于0,大于0就减1 */
pevent->OSEventCnt--;
}
OS_EXIT_CRITICAL();
return (cnt); /*返回减1后的信号量计数器 */
}
OSSemPendAbort(OS_EVENT *pevent, INT8U opt, INT8U *perr)
功能描述:取消任务对pevent事件的等待,可选项opt有两种:
//ucos_ii.h
#define OS_PEND_OPT_NONE 0u /* 就绪一个最高优先级的等待任务 */
#define OS_PEND_OPT_BROADCAST 1u /* 就绪所有等待该事件的任务 */
函数最终返回恢复的任务数量。
//os_sem.c
INT8U OSSemPendAbort (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
INT8U nbr_tasks;
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件不能为空 */
*perr = OS_ERR_PEVENT_NULL;
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件类型必须是信号量 */
*perr = OS_ERR_EVENT_TYPE;
return (0u);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* 事件等待表非空才需要操作,否则直接返回 */
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: /* 取消所有正在等待的任务 */
while (pevent->OSEventGrp != 0u) { /* 将所有等待信号量的任务调整为就绪状态 */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE: /* 就绪一个最高优先级的等待任务 */
default:
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (0u); /* No tasks waiting on semaphore */
}
这里面涉及一个重要操作OS_EventTaskRdy(),该函数的作用是从等待pevent事件的所有任务中,就绪一个最高优先级的任务(即HPT,Highest Priority Task),并返回该任务的优先级。
//os_core.c
INT8U OS_EventTaskRdy (OS_EVENT *pevent,
void *pmsg,
INT8U msk,
INT8U pend_stat)
{
OS_TCB *ptcb;
INT8U y;
INT8U x;
INT8U prio;
#if OS_LOWEST_PRIO > 63u
OS_PRIO *ptbl;
#endif
#if OS_LOWEST_PRIO <= 63u //任务数量小于64个,使用8位OS_PRIO
y = OSUnMapTbl[pevent->OSEventGrp]; /* 找到等待事件的最高优先级任务 */
x = OSUnMapTbl[pevent->OSEventTbl[y]];
prio = (INT8U)((y << 3u) + x); /* 还原任务的原始优先级(0-254) */
#else //任务数量大于63个,使用16位OS_PRIO
if ((pevent->OSEventGrp & 0xFFu) != 0u) {
y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &pevent->OSEventTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
x = OSUnMapTbl[*ptbl & 0xFFu];
} else {
x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
}
prio = (INT8U)((y << 4u) + x);
#endif
ptcb = OSTCBPrioTbl[prio]; /* 找到该优先级对应的TCB */
ptcb->OSTCBDly = 0u; /* 等待延时归零,防止OSTimeTick()恢复任务 */
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
ptcb->OSTCBMsg = pmsg; /* 如果使用了队列或邮箱,则将传入的消息存到该任务TCB中 */
#else
pmsg = pmsg; /* 防止没使用到pmsg导致编译告警 */
#endif
ptcb->OSTCBStat &= (INT8U)~msk; /* 清除任务状态,msk在这里是OS_STAT_SEM */
ptcb->OSTCBStatPend = pend_stat; /* 设置pend状态为OS_STAT_PEND_ABORT */
/* See if task is ready (could be susp'd) */
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY; /* 任务没有挂起,则进入就绪,将优先级就绪表相应位置1 */
OSRdyTbl[y] |= ptcb->OSTCBBitX;
}
OS_EventTaskRemove(ptcb, pevent); /* 将事件等待表中的对应任务优先级清零(解除绑定) */
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) { /* 移除多重等待表中的任务优先级 */
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
ptcb->OSTCBEventPtr = (OS_EVENT *)pevent;
}
#endif
return (prio);
}
OSSemPost(OS_EVENT *pevent)
功能描述:释放一个传入的信号量(pevent计数器加1),如果有任务在等待,则执行上下文切换。
//os_sem.c
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件不能为空 */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件类型必须是信号量 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* 检查是否有任务在等待该信号量 */
/* 在等待该信号量的任务中就绪一个最高优先级的 */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* 尝试执行上下文切换 */
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) { /* 信号量上溢出判断 */
pevent->OSEventCnt++; /* 信号量计数器加1 */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_ERR_SEM_OVF);
}
有一个和OSSemPend()对应的细节:
①在pend信号量时,如果信号量计数器大于0,则计数器直接减1,并继续执行。而有任务post时,如果没有任务在等待,则计数器直接加1,同样继续执行。
①在pend信号量的时候,如果信号量计数器为0,则直接阻塞等待,不会对计数器进行操作。当有任务post信号量时,如果有任务在等待,则直接切换上下文,同样不会对计数器进行操作。
OSSemSet(OS_EVENT *pevent, INT16U cnt, INT8U *perr)
功能描述:手动设置信号量的计数器值。这一操作不是一定能成功的,只有当当前计数器值大于0或没有任务等待该信号量时,才能成功设置(这也是合理的,如果有任务正在苦苦等待该信号量,手动设置一个新值会让该任务立即就绪,从而影响了系统的执行时序。μC/OSⅡ不推荐强制修改,这个功能默认也不会开启)。
//os_sem.c
void OSSemSet (OS_EVENT *pevent,
INT16U cnt,
INT8U *perr)
{
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件不能为空 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件类型必须是信号量 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
OS_ENTER_CRITICAL();
*perr = OS_ERR_NONE;
if (pevent->OSEventCnt > 0u) { /* 如果信号量计数器大于0,直接设为传入值 */
pevent->OSEventCnt = cnt;
} else {
if (pevent->OSEventGrp == 0u) { /* 没有任务等待该信号量,也可以直接设为传入值 */
pevent->OSEventCnt = cnt;
} else { //有任务在等待该信号量,则暂时无法设置
*perr = OS_ERR_TASK_WAITING;
}
}
OS_EXIT_CRITICAL();
}
#endif
信号量的删除
信号量创建后一般是没必要删除的,μC/OSⅡ默认也不会开启删除功能,但为了逻辑的连贯性,这里也分析一下删除的源码OSSemDel()。
该函数支持传入选项:
//ucos_ii.h
#define OS_DEL_NO_PEND 0u //没有任务等待才删除,有任务等待则相当于什么都不做
#define OS_DEL_ALWAYS 1u //无论有没有任务等待,都立即删除,并将等待的任务重新就绪
接下来看源码:
//os_sem.c
OS_EVENT *OSSemDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#if OS_CRITICAL_METHOD == 3u /* 初始化临界区变量 */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件不能为空 */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件必须是信号量类型 */
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 不能在中断内调用 */
*perr = OS_ERR_DEL_ISR;
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* 检查是否有任务等待该信号量 */
tasks_waiting = OS_TRUE;
} else {
tasks_waiting = OS_FALSE;
}
switch (opt) {
case OS_DEL_NO_PEND: /* 只有在没有任务等待该信号量时,才进行删除 */
if (tasks_waiting == OS_FALSE) { //没有任务等待,则复位事件控制块数据
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* 将事件控制块归还空白事件控制块链表 */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 将链表头重新指向该归还的事件控制块 */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* 返回空事件 */
} else { //有任务在等待,无法删除,返回信号本身
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* 强制删除 */
while (pevent->OSEventGrp != 0u) { /* 将所有等待的任务重新就绪 */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* 归还事件控制块,同上面操作 */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* 如果有事件等待该信号量,则尝试执行上下文切换 */
OS_Sched(); /* 找到最高优先级的就绪任务并执行 */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* 返回空事件 */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT; //非法选项
pevent_return = pevent; //返回事件本身
break;
}
return (pevent_return);
}
原文地址:https://blog.csdn.net/sherlock_cp/article/details/144429603
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!