解读信号量

本文介绍了铁路上使用Semaphore机制来确保单线铁路的安全通行。详细解释了Semaphore的概念、由来及计算机版本中的实现方式,并通过RTOS示例展示了二元信号量的工作流程。

铁路上的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值。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值