【原创】ucos信号量的操作及原理

时间:2024-03-27 17:35:44

信号量的操作及原理

1.OSSemCreate创建信号量semaphore
    在使用信号量之前,要先用OSSemCreate创建一个信号量,并通过返回的合法事件结构体指针使用信号量。
  1. OS_EVENT *OSSemCreate(INT16U cnt)
  2. {
  3. #if OS_CRITICAL_METHOD == 3 /* 原理请查看http://blog.csdn.net/liuhui_8989/article/details/8783323 */
  4. OS_CPU_SR cpu_sr;
  5. #endif
  6. OS_EVENT *pevent;
  7. if(OSIntNesting>0){/* 不能在中断内创建信号量 */
  8. return((OS_EVENT *)0);/* 直接返回0 */
  9. }
  10. OS_ENTER_CRITICAL();
  11. pevent =OSEventFreeList;/* 获取空闲的事件控制块 */
  12. if(OSEventFreeList!=(OS_EVENT *)0){/* 将OSEventFreeList指向下一个事件控制块 */
  13. OSEventFreeList=(OS_EVENT *)OSEventFreeList->OSEventPtr;
  14. }
  15. OS_EXIT_CRITICAL();
  16. if(pevent !=(OS_EVENT *)0){/* Get an event control block */
  17. pevent->OSEventType= OS_EVENT_TYPE_SEM;
  18. pevent->OSEventCnt= cnt;/* 设置计数器的初值 */
  19. pevent->OSEventPtr=(void*)0;/* Unlink from ECB free list */
  20. OS_EventWaitListInit(pevent);/* 初始化事件控制块中任务等待表为0 */
  21. }
  22. return(pevent);
  23. }
    简而言之,如果没有了空闲的事件控制块或者是在中断内创建信号里,则返回无效的事件控制块0;否则返回类型为信号量,任务等待表OSEventGrp和OSEventTbl[]为0,且已设置了计数器初值的事件控制块指针。这样便成功地创建了一个信号量。这里要注意,使用OSSemCreate函数返回的指针前,要检验是否为有效的指针。
    cnt的值至少为0。
    创建了信号量之后,便可以对信号量进行如下操作了,申请、释放、删除、查询信号量。
2. 信号量的申请和释放
  1. 申请:voidOSSemPend(OS_EVENT *pevent, INT16U timeout, INT8U *err)
  2. 释放: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指针无效
下面讲解一下函数内部原理:
【原创】ucos信号量的操作及原理
    如果信号量的计数器值大于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();
task2:
OSSemPost(pevent);