如果使用信号量,则完全可以避免上述的情况。UART1和UART2可以作为资源,即系统中有两个资源,那么与之相关的信号量初始值及为2。当这两个资源都被占用时,信号量的值变为0,现在哪怕任务3的优先级再高,也只能加入该信号量的等待列表,等待UART1或UART2释放资源,而不能直接剥夺资源使用权。
uC/OS II中的信号量由两部分构成:一部分是16位的无符号整型信号量的计数值(计数范围0~65535);另一部分是等待该信号量的任务组成的等待任务。uC/OS II中队信号量的操作是通过6个函数进行的:OSSemCreate(),OSSemPend(),OSSemPost(),OSSemAccept()、OSSemQuery()和OSSemDel()。需要注意的是OSSemCreate(),OSSemPend()和OSSemPost()这三个函数是信号量所必须的,只要OS需要支持信号量,这三个函数不能被屏蔽,而OSSemAccept()、OSSemQuery()和OSSemDel()则可以通过配置常数#define OS_SEM_ACCEPT_EN、#define OS_SEM_QUERY_EN和#define OS_SEM_DEL_EN裁剪。
1. OS_EVENT *OSSemCreate (INT16U cnt)
创建信号量,创建工作必须在任务级代码中或者多任务启动之前完成。功能只要是先获取一个事件控制块ECB,写入一些参数。其中调用了OS_EeventWaitListInt()函数,对事件控制块的等待任务列表进行初始化。完成初始化工作后,返回一个该ECB类型的句柄(Handle)。源码如下:
OS_EVENT *OSSemCreate (INT16U cnt)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
OS_EVENT *pevent;
if (OSIntNesting > 0) { /* ISR中,不允许创建信号量 */
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 获得空链表指针 */
if (OSEventFreeList != (OS_EVENT *)0) { /* 检查ECB是否被全部使用,如有空余,调整指针位置*/
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* ECB可用 */
pevent->OSEventType = OS_EVENT_TYPE_SEM; /* 类型设置为OS_EVENT_TYPE_SEM
pevent->OSEventCnt = cnt; /* 信号量设置为传递参数 */
pevent->OSEventPtr = (void *)0; /* 指向空,被删除时重新链接空闲ECB链 */
OS_EventWaitListInit(pevent); /* 初始化ECB的等待任务列表,全为0 */
}
return (pevent); /* 返回创建ECB型的句柄(指针) */
}
2. OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
删除一个信号量,此段代码可以被屏蔽。需要注意的是删除信号量之前,必须首先删除操作该信号量的所有任务。可以通过参数OS_DEL_ALWAYS强制删除该信号量,不过结果往往是灾难型的。使用该函数有一定的危险性,慎用!源码如下:
#if OS_SEM_DEL_EN > 0 /* 条件编译 */
OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
BOOLEAN tasks_waiting;
if (OSIntNesting > 0) { /* ISR中,不允许删除信号量 */
*err = OS_ERR_DEL_ISR;
return (pevent);
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* 参数验证 */
*err = OS_ERR_PEVENT_NULL;
return (pevent);
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 参数验证 */
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { /* 检查是否有任务在等待该信号量 */
tasks_waiting = TRUE; /* Yes */
} else {
tasks_waiting = FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* 根据参数决定,无等待任务时,删除信号量 */
if (tasks_waiting == FALSE) {
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return ((OS_EVENT *)0); /* Semaphore has been deleted */
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING; /* 返回参数,说明有任务在等待此信号量 */
return (pevent);
}
case OS_DEL_ALWAYS: /* 根据参数决定,总是删除信号量 */
while (pevent->OSEventGrp != 0x00) { /*将所有等待态的任务加入就绪表,后果难以预料*/
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
}
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == TRUE) { /* 当有任务等待该信号量时需要任务调度 */
OS_Sched(); /* Find highest priority task ready to run */
}
*err = OS_NO_ERR;
return ((OS_EVENT *)0); /* Semaphore has been deleted */
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
}
#endif
3. void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
几乎所有的翻译都把这个函数称为等待一个信号量,这可能是受《嵌入式操作系统uC/OS II》的影响,不过这种翻译不利于理解这个函数的具体作用。我也是阅读了这个函数的源码后,才理解了这个函数的作用。等待一个信号量只是这个函数功能的一部分,当信号量的值为0时,这个函数确实是把当前任务从任务优先级列表中删除,加入等待该信号量的优先级列表。但是,当信号量不为0时,这个函数只是执行一些变量判断,然后将信号量减1,通过return语句跳出了函数,继续执行函数接下来的程序。源码如下:
void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
if (OSIntNesting > 0) { /* ISR中,不允许此操作 */
*err = OS_ERR_PEND_ISR;
return;
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* 参数验证 */
*err = OS_ERR_PEVENT_NULL;
return;
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 参数验证 */
*err = OS_ERR_EVENT_TYPE;
return;
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0) { /* 如果信号量代表的资源至少有一个可以使用 */
pevent->OSEventCnt--; /* 信号量减1,从函数返回,继续执行下面的程序 */
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return;
}
/* 信号量为0,则必须等待该信号量被别的任务释放 */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* 信号量类型 */
OSTCBCur->OSTCBDly = timeout; /* 等待超时时间 */
OS_EventTaskWait(pevent); /* 任务挂起,详见《与ECB操作相关的四个函数》 */
OS_EXIT_CRITICAL();
OS_Sched(); /* 该优先级的任务不可用,寻找下一优先级任务 */
OS_ENTER_CRITICAL();
if (OSTCBCur->OSTCBStat & OS_STAT_SEM) { /* 任务等待超时 */
OS_EventTO(pevent);
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT; /* 返回超时标志 */
return;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
图1. OSSemPend流程图
4. INT8U OSSemPost (OS_EVENT *pevent)
《嵌入式操作系统uC/OS II》一书将此函数注释为发出一个信号量,原文注释为“This function signals a semaphore”,其实,我觉得翻译为释放信号量更为贴切。占用相关资源的任务执行完毕了,说明这个资源现在又有效了,可以被别的任务所使用,也就是说代表相关资源的信号量被释放了。源码入下:
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { /* 确定是否存在等待该信号量的任务 */
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM); /* 如果有,将高优先级的任务放入就绪表 */
OS_EXIT_CRITICAL();
OS_Sched(); /* 任务调度 */
return (OS_NO_ERR);
}
if (pevent->OSEventCnt < 65535) { /* 溢出判断 */
pevent->OSEventCnt++; /* 资源被释放,信号量加1 */
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_SEM_OVF);
}
图2. OSSemPost流程图
5. INT16U OSSemAccept (OS_EVENT *pevent)
这个函数的出现是由于ISR中不允许通过PEND功能等待,此函数只会检查count是否大于0,是的话信号量减1,否则直接返回。需要注意的是,如果信号量大于1,这个函数的最终返回值是信号量的值,而不是减1后的值,然后通过判断这个返回值,做适当的处理。典型的处理代码如下:
OS_EVENT *DispSem;
void Task (void *pdata)
{
INT16U value;
pdata = pdata;
for (;;) {
value = OSSemAccept(DispSem); /*查看设备是否就绪或事件是否发生 */
if (value > 0) {
. /* 就绪,执行处理代码 */
}
}
}
源码如下:
#if OS_SEM_ACCEPT_EN > 0 /* 条件编译 */
INT16U OSSemAccept (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT16U cnt;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* 参数验证 */
return (0);
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (0);
}
#endif
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0) { /* 检查资源是否可用 */
pevent->OSEventCnt--; /* 可用的话,资源值减1 */
}
OS_EXIT_CRITICAL();
return (cnt); /* 返回实际资源值(减1之前的值) */
}
#endif
6. INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
OSSemQuery()函数用于获取某个信号量的信息。使用OSSemQuery()之前,应用程序需要先创立类型为OS_SEM_DATA 的数据结构,用来保存从信号量的事件控制块中取得的数据。使用OSSemQuery()可以得知是否有,以及有多少任务位于信号量的任务等待队列中(通过查询.OSEventTbl [ ]域),还可以获取信号量的标识号码。这个函数比较简单,唯一需要注意的就是OS_SEM_DATA数据结构的定义,在uCOS_II.H中。源码如下:
#if OS_SEM_QUERY_EN > 0
INT8U OSSemQuery (OS_EVENT *pevent, OS_SEM_DATA *pdata)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr;
#endif
INT8U *psrc;
INT8U *pdest;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
pdata->OSEventGrp = pevent->OSEventGrp; /* 复制等待列表 */
psrc = &pevent->OSEventTbl[0];
pdest = &pdata->OSEventTbl[0];
#if OS_EVENT_TBL_SIZE > 0
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 1
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 2
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 3
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 4
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 5
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 6
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 7
*pdest = *psrc;
#endif
pdata->OSCnt = pevent->OSEventCnt; /* Get semaphore count */
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
#endif /* OS_SEM_QUERY_EN */
文章评论(0条评论)
登录后参与讨论