信号量的操作及原理
1.OSSemCreate创建信号量semaphore
在使用信号量之前,要先用OSSemCreate创建一个信号量,并通过返回的合法事件结构体指针使用信号量。
OS_EVENT *OSSemCreate(INT16U cnt)
{
#if OS_CRITICAL_METHOD == 3 /* 原理请查看http://blog.csdn.net/liuhui_8989/article/details/8783323 */
OS_CPU_SR cpu_sr;
#endif
OS_EVENT *pevent;
if(OSIntNesting>0){/* 不能在中断内创建信号量 */
return((OS_EVENT *)0);/* 直接返回0 */
}
OS_ENTER_CRITICAL();
pevent =OSEventFreeList;/* 获取空闲的事件控制块 */
if(OSEventFreeList!=(OS_EVENT *)0){/* 将OSEventFreeList指向下一个事件控制块 */
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 */
OS_EventWaitListInit(pevent);/* 初始化事件控制块中任务等待表为0 */
}
return(pevent);
}
简而言之,如果没有了空闲的事件控制块或者是在中断内创建信号里,则返回无效的事件控制块0;否则返回类型为信号量,任务等待表OSEventGrp和OSEventTbl[]为0,且已设置了计数器初值的事件控制块指针。这样便成功地创建了一个信号量。这里要注意,使用OSSemCreate函数返回的指针前,要检验是否为有效的指针。
cnt的值至少为0。
创建了信号量之后,便可以对信号量进行如下操作了,申请、释放、删除、查询信号量。
2. 信号量的申请和释放
申请:voidOSSemPend(OS_EVENT *pevent, INT16U timeout, INT8U *err)
释放:INT8U OSSemPost(OS_EVENT *pevent)
OSSemPend函数有三个参数,
第一个是一个指向事件控制块的指针,该值为OSSemCreate返回值。
第二个是一个等待时间值(至少为0),如果信号量目前被占用,则无法立即申请到信号量,调用该函数的任务将被挂起,如果等待时间值为0,则一直被挂起直到信号量被释放为止(OSSemPost能够在释放信号量的同时,恢复等待信号量的任务),如果等待时间值大于0,则在超时时间过后,由OSTineTick恢复为就绪状态的任务。
第三个为一个指向错误代码的指针,该值作为函数返回值使用。
OS_NO_ERR 函数调用成功,获得了信号量。
OS_TIMEOUT 在规定的时间内没有申请到信号量
OS_ERR_EVENT_TYPE 事件类型错误,不是信号量
OS_ERR_PEND_ISR 不能在中断内申请信号量
OS_ERR_PEVENT_NULL pevent指针无效
下面讲解一下函数内部原理:
如果信号量的计数器值大于0,则将其减1,表示又有一个任务占用,并直接返回。
如果信号量的计数器值为0,表示信号量已被其他任务占用,此时任务控制块中的状态标志是等待信号状态以及就绪的,因为使用的是按位或操作,保留了原有的就绪状态标志。
之后调用了OS_EventTaskWait(pevent); 此时作了3件事:
(1)OSTCBCur->OSTCBEventPtr = pevent;将事件控制块指针保存于任务控制块中
(2)去除任务在任务就绪表的就绪状态,注意没有包括任务控制块中的状态标志
(3)设置事件控制块中的任务等待表
至此任务被挂起!通过OS_Sched运行其他任务去了。
接下来的结果取决于,信号量是否在规定的等待时间内被释放。
在当前任务被挂起,而运行其他任务的同时,每个时钟节拍都会运行OSTimeTick中断函数,此函数会遍历所有任务,如果任务控制块中的状态标志为就绪的,且Dly等待值不为0,则将Dly减1,如果减1后刚好为0,则在任务就绪表中恢复该任务的就绪状态!
如果该就绪状态的任务恢复运行,此时任务控制块的状态标志仍为OS_STAT_SEM,运行OS_EventTO,做的事刚好和OS_EventTaskWait相反。
(1)OSTCBCur->OSTCBEventPtr = 0;
(2)设置任务控制块中的状态标志为就绪状态(去除OS_STAT_SEM状态)
(3)去除事件控制块中的任务等待表
此时返回OS_TIMEOUT。
但是如果在等待时间未过去,其他任务释放了信号量,OSSemPost能够在释放信号量的同时,恢复等待信号量的任务。等待信号量的任务恢复运行,此时任务控制块的状态标志不包含OS_STAT_SEM了,所以函数直接跳过第二个if语句,返回OS_NO_ERR。
释放信号量过程:
函数OSSemPost在对信号量的计数器操作之前,首先检查任务等待表中是否还有其他等待该信号的任务,如果没有,就把计数器加1,如果有,则调用OS_EventTaskRdy将任务等待表中最高优先级的任务设为就绪状态,并调用OSSched调度任务。
3. 应用
3.1申请函数和释放函数在同一任务中成对出现
main:
pevent = OSSemCreate(1);
task1:
OSSemPend(pevent, 0, err);
....
OSSemPost(pevent);
task2:
OSSemPend(pevent, 0, err);
....
OSSemPost(pevent);
当一个任务没有释放信号量,另一个任务在申请信号量时只能挂起直到信号量释放。
3.2 应用程序中有一个函数Fun(),如果想使任务M必须经过Y任务允许才能调用函数一次,可以使用信号量
main:
pevent = OSSemCreate(0);
task1:
OSSemPend(pevent, 0, err);
Fun();