|
程序清单 2.8 隐含的信号量。 |
|
INT8U CommSendCmd(char *cmd, char *response, INT16U timeout) |
|
{ |
|
Acquire port's semaphore; |
|
Send command to device; |
|
Wait for response (with timeout); |
|
if (timed out) { |
|
Release semaphore; |
|
return (error code); |
|
} else { |
|
Release semaphore; |
|
return (no error); |
|
} |
|
} |
要向外设发送命令的任务得调用上述函数。设信号量初值为1,表示允许使用。初始化是在通讯口驱动程序的初始化部分完成的。第一个调用CommSendCmd()函数的任务申请并得到了信号量,开始向外设发送命令并等待响应。而另一个任务也要送命令,此时外设正“忙”,则第二个任务被挂起,直到该信号量重新被释放。第二个任务看起来同调用了一个普通函数一样,只不过这个函数在没有完成其相应功能时不返回。当第一个任务释放了那个信号量,第二个任务得到了该信号量,第二个任务才能使用RS-232口。

图2.11在任务级看不到隐含的信号量
计数式信号量用于某资源可以同时为几个任务所用。例如,用信号量管理缓冲区阵列(buffer pool),如图2.12所示。缓冲区阵列中共有10个缓冲区,任务通过调用申请缓冲区函数BufReq()向缓冲区管理方申请得到缓冲区使用权。当缓冲区使用权还不再需要时,通过调用释放缓冲区函数BufRel()将缓冲区还给管方。函数示意码如程序清单2.9所示
|
程序清单 2.9 用信号量管理缓冲区。 |
|
BUF *BufReq(void) |
|
{ |
|
BUF *ptr; |
|
|
|
Acquire a semaphore; |
|
Disable interrupts; |
|
ptr = BufFreeList; |
|
BufFreeList = ptr->BufNext; |
|
Enable interrupts; |
|
return (ptr); |
|
} |
|
|
|
|
|
void BufRel(BUF *ptr) |
|
{ |
|
Disable interrupts; |
|
ptr->BufNext = BufFreeList; |
|
BufFreeList = ptr; |
|
Enable interrupts; |
|
Release semaphore; |
|
} |

图2.12 计数式信号量的用法
缓冲区阵列管理方满足前十个申请缓冲区的任务,就好像有10把钥匙可以发给诸任务。当所有的钥匙都用完了,申请缓冲区的任务被挂起,直到信号量重新变为有效。缓冲区管理程序在处理链表指针时,为满足互斥条件,中断是关掉的(这一操作非常快)。任务使用完某一缓冲区,通过调用缓冲区释放函数BufRel()将缓冲区还给系统。系统先将该缓冲区指针插入到空闲缓冲区链表中(Linked list)然后再给信号量加1或释放该信号量。这一过程隐含在缓冲区管理程序BufReq()和BufRel()之中,调用这两个函数的任务不用管函数内部的详细过程。
信号量常被用过了头。处理简单的共享变量也使用信号量则是多余的。请求和释放信号量的过程是要花相当的时间的。有时这种额外的负荷是不必要的。用户可能只需要关中断、开中断来处理简单共享变量,以提高效率。(参见2.18.0.1 关中断和开中断)。假如两个任务共享一个32位的整数变量,一个任务给这个变量加1,另一个任务给这个变量清0。如果注意到不管哪种操作,对微处理器来说,只花极短的时间,就不会使用信号量来满足互斥条件了。每个任务只需操作这个任务前关中断,之后再开中断就可以了。然而,如果这个变量是浮点数,而相应微处理器又没有硬件的浮点协处理器,浮点运算的时间相当长,关中断时间长了会影响中断延迟时间,这种情况下就有必要使用信号量了。
2.19 死锁(或抱死)(Deadlock (or Deadly Embrace))
死锁也称作抱死,指两个任务无限期地互相等待对方控制着的资源。设任务T1正独享资源R1,任务T2在独享资源T2,而此时T1又要独享R2,T2也要独享R1,于是哪个任务都没法继续执行了,发生了死锁。最简单的防止发生死锁的方法是让每个任务都:
l 先得到全部需要的资源再做下一步的工作
l 用同样的顺序去申请多个资源
l 释放资源时使用相反的顺序
内核大多允许用户在申请信号量时定义等待超时,以此化解死锁。当等待时间超过了某一确定值,信号量还是无效状态,就会返回某种形式的出现超时错误的代码,这个出错代码告知该任务,不是得到了资源使用权,而是系统错误。死锁一般发生在大型多任务系统中,在嵌入式系统中不易出现。
可以利用信号量使某任务与中断服务同步(或者是与另一个任务同步,这两个任务间没有数据交换)。如图2.13所示。注意,图中用一面旗帜,或称作一个标志表示信号量。这个标志表示某一事件的发生(不再是一把用来保证互斥条件的钥匙)。用来实现同步机制的信号量初始化成0,信号量用于这种类型同步的称作单向同步(unilateral rendezvous)。一个任务做I/O操作,然后等信号回应。当I/O操作完成,中断服务程序(或另外一个任务)发出信号,该任务得到信号后继续往下执行。

图2.13 用信号量使任务与中断服务同步
如果内核支持计数式信号量,信号量的值表示尚未得到处理的事件数。请注意,可能会有一个以上的任务在等待同一事件的发生,则这种情况下内核会根据以下原则之一发信号给相应的任务:
l 发信号给等待事件发生的任务中优先级最高的任务,或者
l 发信号给最先开始等待事件发生的那个任务
根据不同的应用,发信号以标识事件发生的中断服务或任务也可以是多个。
两个任务可以用两个信号量同步它们的行为。如图2.14所示。这叫做双向同步(bilateral rendezvous)。双向同步同单向同步类似,只是两个任务要相互同步。
例如则程序清单2.10中,运行到某一处的第一个任务发信号给第二个任务[L22.10(1)],然后等待信号返回[L2.10(2)]。同样,当第二个任务运行到某一处时发信号给第一个任务[2.10(3)]等待返回信号[L2.10(4)]。至此,两个任务实现了互相同步。在任务与中断服务之间不能使用双向同步,因为在中断服务中不可能等一个信号量。
图2.14 两个任务用信号量同步彼此的行为
|
程序清单2.10 双向同步 |
|
Task1() |
|
{ |
|
for (;;) { |
|
Perform operation; |
|
Signal task #2; (1) |
|
Wait for signal from task #2; (2) |
|
Continue operation; |
|
} |
|
} |
|
|
|
Task2() |
|
{ |
|
for (;;) { |
|
Perform operation; |
|
Signal task #1; (3) |
|
Wait for signal from task #1; (4) |
|
Continue operation; |
|
} |
|
} |
当某任务要与多个事件同步时,要使用事件标志。若任务需要与任何事件之一发生同步,可称为独立型同步(即逻辑或关系)。任务也可以与若干事件都发生了同步,称之为关联型(逻辑与关系)。独立型及关联型同步如图2.15所示。
图2.15独立型及关联型同步
可以用多个事件的组合发信号给多个任务。如图2.16所示,典型地,8个、16个或32个事件可以组合在一起,取决于用的哪种内核。每个事件占一位(bit),以32位的情况为多。任务或中断服务可以给某一位置位或复位,当任务所需的事件都发生了,该任务继续执行,至于哪个任务该继续执行了,是在一组新的事件发生时辨定的。也就是在事件位置位时做辨断。
内核支持事件标志,提供事件标志置位、事件标志清零和等待事件标志等服务。事件标志可以是独立型或组合型。μC/OS-Ⅱ目前不支持事件标志. |