从零开始学习UCOSII操作系统7--信号量
参考博客:@ http://blog.csdn.net/gatiemehttps://blog.csdn.net/gatieme/article/details/21071379
前言:这里一定要分析清楚,因为信号量分析清楚后,后面的邮箱等其他的通信的东西都是大同小异的。
1、信号量的组成
(1)一部分是16位无符号的整型信号量计数值(0~65536)
(2)另一部分是由等待信号量的任务组成的等待任务表。
(3)信号量的6个基本函数:
OSSemAccept(), OSSemCreate(),OSSemDel()
OSSemPend(),OSSemPost()以及OSSemQuery()
其中要使用这些信号量的函数,必须在OS_CFG.h中将配置常数置为1,这样UCOSII才能支持信号量。
当OS_SEM_EN设为0的时候,所有的信号量都不能使用。
需要使用这些函数的时候,需要把这些宏定义置为1;
2、信号量使用的函数
(1)OSSemAccept(),OSSemPost()以及OSSemQuery()函数可以由任务或者中断服务子程序调用。
(2)OSSemDel()和OSSemPend()函数只能由任务程序调用。
3、信号量的两个关键问题
(1)任务怎么得到一个信号量的问题?
想要得到一个信号量的任务,必须执行等待的操作(pend)
如果信号量有效(非0),则信号量减1,任务继续运行。
如果此时信号量无效,则等待信号量的任务就被列入等待信号量的任务表中。此时可以设置超时时间,如果等待了多少时间后,仍然没有信号发生。
则此任务进入就绪态,准备运行,并且显示出错的代码---等待超时失败。
(2)任务对信号量的释放问题
任务执行发信号的POST操作来释放信号量,如果没有任务等待该信号量,那么这个信号量的值就是仅仅简单的+1.则此时信号量大于0有效。
如果有任务等待该信号量,那么就会有另一个任务进入就绪态,此时信号量就不+1了,因为刚刚的信号量就用出去了。
4、信号量的有效与无效问题
信号量有效:信号量的计算器非0,信号量有效表示任务对资源可用。
信号量无效:信号量的计算器为0,信号量无效表示任务对目前的资源不可用,需要等待其他的另一个任务或者中断服务子程序发出该信号量。
5信号量的值OSEventCnt大小表示什么?
二值信号量Mutext,表示任务可以独占共享资源
计数型的信号量Semaphore,用于某资源可同时为N个任务所用。
二值信号量与互斥型信号量的区别:
互斥型信号量必须是同一个任务申请,同一个任务释放,其他的任务释放无效。
二值信号量,一个任务申请成功后,可以由另一个任务释放,也可以由本任务释放。
6、信号量是如何实现任务之间的通信的?
(1)信号量的建立必须在任务级中建立。
(2)信号量类型为OS_EVENT,信号量值可以为1和0(二值信号量),0~65536计数型的信号量,不同的值代表不同的意义。
(3)对于互斥型信号量来说,就两个操作,请求和释放。
(4)一个任务请求信号量的时候,如果被其他的任务占用,则任务等待,同时导致任务切换。 如果没有被其他任务占用,则获得,继续执行。
(5)释放信号量的时候,如果其他高优先级任务正在请求并且等待该信号量的时候,则导致任务切换。
(6)OSSemAccept(信号量)起到查询信号量的作用,返回信号量的值。
(7)OSSemPend(Sem,timeout,&err);
timeout代表等待timeout个信号量后还没有得到信号量,恢复到就绪的状态,如果timeout=0,标志等待无限信号量。
7、信号量的三个关键函数的代码分析
(1)OSSemCreate()创建一个信号量(由任务或者启动代码操作)
创建工作必须在任务级代码中或者多任务启动之前完成。功能只要是先获取一个事件控制块ECB,写入一些参数,其中调用了OS_EventWaitListInit()函数,对事件控制块的等待任务列表进行初始化。
就是将事件控制块定义为信号量,然后把该填写的量填写。最后设置信号量的初始值
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
if (OSIntNesting > 0) { /* 中断服务不能创建信号量 */
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 得到下一个空闲的事件空闲控制块 */
if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* Get an event control block */
pevent->OSEventType = OS_EVENT_TYPE_SEM;
pevent->OSEventCnt = cnt; /* 设置信号量的初始值 */
pevent->OSEventPtr = (void *)0; /* Unlink from ECB free list */
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
OS_EventWaitListInit(pevent); /* Initialize to 'nobody waiting' on sem. */
}
return (pevent);
}
(2)删除一个信号量
1、删除一个信号量的函数OSSemDel()的源代码,当文件OS_CFG.h中的OS_SEM_DEL_EN为1的时候,该代码才被编译。
2、总之,在删除信号量之前,必须首先删除操作该信号量的所有的任务。
3、进入switch中,当OPT为OS_DEL_NO_PEND,并且没有任务在等待该信号量的时候,OSSemDel()函数将事件控制块ECB标志为未使用,并将其退回到空闲事件控制链表中,此操作允许该事件用于创建另一个信号量。
当OPT为OS_DEL_ALWAYS时候,所有等待该信号的任务都将进入就绪态,每个任务都得到了该信号量,当然这样可能会导致致命的后果,因为采用信号量就是为了防止对资源的多重访问。
OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
#if OS_ARG_CHK_EN > 0
if (err == (INT8U *)0) { /* Validate 'err' */
return (pevent);
}
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*err = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 是否是指向某一个用于信号量的一个函数 */
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0) { /* See if called from ISR ... */
*err = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0) { /* 看是否有任务在等待信号量 */
tasks_waiting = OS_TRUE; /* 是的 */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete semaphore only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the semaphore */
while (pevent->OSEventGrp != 0) { /* Ready ALL tasks waiting for semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM);
}
#if OS_EVENT_NAME_SIZE > 1
pevent->OSEventName[0] = '?'; /* Unknown name */
pevent->OSEventName[1] = OS_ASCII_NUL;
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*err = OS_NO_ERR;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
#endif
(3)等待一个信号量OSSEMPend()
等待一个信号量函数的源代码分析:
PS:设置该函数中的超时时间,真正实现的函数是在OSTimeTICK()函数中逐个的递减。这个地方要注意下,对每个任务的任务控制块TCB中的OSTCBDly做递减操作。
最后的参数:perr就是指向一个错误的标志。
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
#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;
}
if (OSIntNesting > 0u) { /* 不在中断中操作此函数 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0u) { /* If sem. is positive, resource available ... */
pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return;
}
/* Otherwise, must wait until event occurs */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* 资源没有异常的话,那么就把资源定义为信号量 */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* 设置信号量的超时时间 */
OS_EventTaskWait(pevent); /* 使一个任务进入等待某事件发生状态 */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OS_EXIT_CRITICAL();
}
(4)发出一个信号量:OSSEMPOST()
当任务发出一个等待的信号量之后,那么它必须接收到任务或者中断服务子程序给他发出相应的信号量,那么他才能回到就绪态,不然的话,就会不断的存在在事件控制等待列表中。
OS_EventTaskRdy()函数把优先级最高的任务从等待任务列表中去除,并使它进入就绪态,然后,调用OSSched()任务调用函数,因为已经获得信号量的任务,并且检车一下是否是最高的优先级的就绪态任务,如果是优先级最高的就绪态任务,这时就要进行任务切换,准备执行该就绪态任务。如果得到这个信号量的任务不是最高优先级的任务,退回到这个函数,并继续执行。
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
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(); /* Find HPT ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */
pevent->OSEventCnt++; /* Increment semaphore count to register event */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_ERR_SEM_OVF);
}
(5)无等待的请求一个信号量OSSemAccept()
有些书籍或者博主称为查询信号量。
解释:当一个任务请求一个信号量的时候,如果该信号量暂停无效,也可以让该任务简单的返回,而不是进入休眠的状态,这种情况下面的操作是由OSSEMAccept()函数完成的。
也就是别人家称为很牛逼的词语:非阻塞函数
有我就响应,没有那么我就继续执行。
这个函数十分的简单:从该信号量的事件控制块中取出当前的计数值,并检查该信号量是否有效(计数值是否为非0值)
如果信号量有效的话,则信号量的计数值减1,
如果信号量无效的话,那么就直接返回。
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (0u);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0u) { /* See if resource is available */
pevent->OSEventCnt--; /* Yes, decrement semaphore and notify caller */
}
OS_EXIT_CRITICAL();
return (cnt); /* Return semaphore count */
}
(6)查询一个信号量的当前的状态OSSemQuery()
这里面使用的,内存拷贝的方法,不断把源地址的内容,也就是源数据结构的成员,赋值到现在地址的成员中。
这段代码可能使用到了很多高级应用,但是我觉得是不好的,因为我们可以设置一个结构体,来对应事件控制块的成员。
然后一一赋值,可读性大大的提高了,做过开发的都知道,代码又丑又长,可读性没有的话,维护的成本会大大的提高。我们不是追求省那么一丁点的内存。而是后面的人维护起来比较爽。
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (p_sem_data == (OS_SEM_DATA *)0) { /* Validate 'p_sem_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_sem_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
p_sem_data->OSCnt = pevent->OSEventCnt; /* Get semaphore count */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)