铁路上的Semaphore
一段单线铁路在一个时刻只允许一列火车通过。用一个信号灯来维护这段铁路。一列火车在进入单线铁路之前必须等待信号灯的许可。如果一列火车进入这段轨道,信号灯改变状态,以防止其他火车进入。在火车离开这段轨道时,必须将信号灯复原,使得其他火车得以进入。

图一:信号灯直指下面表示“可以通行",当这段铁路上有火车后,将切换为Stop状态

图二:信号灯斜向表示"请准备停止并等待下一个信号"

图三:信号灯指向右端表示"请停止",不允许进入该段铁路。

图四:实际的一段使用Semaphore控制的铁路上行车过程
Semaphore的由来
信号灯是E.W.Dijkstra在60年代晚期定义的程序结构。Dijkstra的模型就是基于一段单轨铁路上的操作。
在信号灯的计算机版本中,一个信号灯一般是一个整数,称之为信号量。一个 线程在被允许进行后对信号量做一个p操作。
P操作的字面意思是线程必须等到信号量的值为正(positive)才能继续进行,进行前先给信号量减1。当做完相关的操作时(相当于离开铁轨),线程执行一个 v操作,即给信号量加1。这两个操作必须具有不可中断性,也叫不可分性,英文字面为原子性(atomic),即他们不能被分成两个子操作,在子操作之间还可以插入其它线程的其他操作,这些操作可能改变信号量。在P操作中,信号量的值在被减之 前一定要为正(使得信号量在被减1之后不会为负)。
在P操作或V操作当中,操作不会互相干扰。如果两个V操作要同时执行,则信号量的新值比原来大2。
P/V
P代表prolagen,一个由proberen de verlagen演变来的合成词,它的意思是"试图减"。V代表verhogen,它的意思是"增加"。这些在 Dijkstra的技术笔记EWD 74中提到过。
一个典型的RTOS二元信号量的工作过程:
任务能调用两个RTOS函数,即TakeSemaphore和ReleaseSemaphore,如果一个任务已经调用了TakeSemaphore来获得信号量,而没有调用ReleaseSemaphore来释放它,则任何其他调用TakeSemaphore的任务将阻塞,直到先前的任务调用ReleaseSemaphore。在某一时刻只能有一个任务拥有信号量。
struct
{
long lTankLevel;
long lTimeUpdated;
} tankdata[MAX_TANKS];
/* Button Tasks */
void vRespondToButton(void) /* High priority*/
{
int i;
while(TRUE)
{
//Blocked, until user press button
i = // Get the button ID;
TakeSemaphore();
printf("\nTIME: %08ld LEVEL: %08ld",
tankdata[i].lTimeUpdated,
tankdata[i].lTankLevel);
ReleaseSemaphore();
}
}
/* Level Task*/
void vCalculateTankLevels (void)
{
int i = 0;
while(TRUE)
{
//...
TakeSemaphore();
//setup the tankdata[i].lTimeUpdated
//setup the tankdata[i].lTankLevel
ReleaseSemaphore();
//...
}
}
可以这样理解上面的程序:共享资源为tankdata[]数组,相当于一段单轨铁路。同一时刻,如果使用了信号量的话,又且只有一个任务可以访问它。
一个实际的RTOS(uC/OS)中对信号量的调用:
#define TASK_PRIORITY_READ 11
#define TASK_PRIORITY_CONTROL 12
#define STK_SIZE 1024
static unsigned int ReadStk[STK_SIZE];
static unsigned int ControlStk[STK_SIZE];
static int iTemperatures[2];
OS_EVENT *p_semTemp;
void main(void)
{
/* initiate(But not startup) the RTOS */
OSInit();
/* Tell RTOS informations on tasks */
OSTaskCreate(vReadTemperatureTask, NULLP,
(void *)&ReadStk[STK_SIZE], TASK_PRIORITY_READ);
OSTaskCreate(vControlTask, NULLP,
(void *)&ControlStk[STK_SIZE], TASK_PRIORITY_CONTROL);
/* Startup the RTOS(it will never return) */
OSStart();
}
void vReadTemperatureTask(void)
{
while(TRUE)
{
OSTimeDly(5); /* Delay about 1/4 second */
OSSemPend(p_semTemp, WAIT_FOREVER);
//READ iTemperatures[0];
//READ iTemperatures[1];
OSSemPost(p_semTemp);
}
}
void vControlTask(void)
{
p_semTemp = OSSemInit(1);
while(TRUE)
{
OSSemPend(p_semTemp, WAIT_FOREVER);
if(iTemperatures[0] != iTemperatures[1])
//Close Alarm;
OSSemPost(p_semTemp);
//do other things;
}
}
初始化问题:必须在vReadTemperatureTask调用OSSemPend前就完成对OSSemCreate的调用。但实际发生了什么事?没有人知道。
解决方案:把信号量初始化的OSSemCreate放在保证能够首先运行的启动代码中,例如主函数调用OSStart之前的位置是调用OSSemInit的好地方。
信号量的分类:
整型信号量(integer semaphore):信号量是整数。这样的信号量可以被多次获取:获取的时候对整数递减,释放的时候对整数递增。如果一个任务想获取整数已经等于0的信号量时,这个任务阻塞。这是一种最原始的信号量。这里的整数应该是正整数的意思。
记录型信号量(record semaphore):每个信号量s除一个整数值s.value(计数)外,还有一个进程等待队列s.L,其中是阻塞在该信号量的各个进程的标识。s.value>=0时,s.queue为空。s.value<0时,s.value的绝对值为s.queue中等待进程的个数。
二进制信号量(binary semaphore):只允许信号量取0或1值。
本文介绍了铁路上使用Semaphore机制来确保单线铁路的安全通行。详细解释了Semaphore的概念、由来及计算机版本中的实现方式,并通过RTOS示例展示了二元信号量的工作流程。

723

被折叠的 条评论
为什么被折叠?



