| 网站首页 | 业界新闻 | 技术文章 | 视频教程 | 下载频道 | 程序源码 | 个人空间 | 编程论坛 |
 
| 技术教程首页 | 开发语言 | WEB开发 | .NET技术 | 数据库 | 操作系统 | 网页制作 |
 
 
您现在的位置: 编程中国 >> 技术教程 >> 开发语言 >> VC++ >> VC技术资料 >> 正文
  ►  Linux环境进程间通信(四):信号灯
Linux环境进程间通信(四):信号灯
作者:郑彦兴    阅读人次:……    文章来源:developerWorks    发布时间:2007-8-29    网友评论()条
 

 

六、竞争问题

第一个创建信号灯的进程同时也初始化信号灯,这样,系统调用semget包含了两个步骤:创建信号灯;初始化信号灯。由此可能导致一种竞争状态:第一个创建信号灯的进程在初始化信号灯时,第二个进程又调用semget,并且发现信号灯已经存在,此时,第二个进程必须具有判断是否有进程正在对信号灯进行初始化的能力。在参考文献[1]中,给出了绕过这种竞争状态的方法:当semget创建一个新的信号灯时,信号灯结构semid_ds的sem_otime成员初始化后的值为0。因此,第二个进程在成功调用semget后,可再次以IPC_STAT命令调用semctl,等待sem_otime变为非0值,此时可判断该信号灯已经初始化完毕。下图描述了竞争状态产生及解决方法:


实际上,这种解决方法也是基于这样一个假定:第一个创建信号灯的进程必须调用semop,这样sem_otime才能变为非零值。另外,因为第一个进程可能不调用semop,或者semop操作需要很长时间,第二个进程可能无限期等待下去,或者等待很长时间。

七、信号灯应用实例

本实例有两个目的:1、获取各种信号灯信息;2、利用信号灯实现共享资源的申请和释放。并在程序中给出了详细注释。


#include <linux/sem.h>
#include <stdio.h>
#include <errno.h>
#define SEM_PATH "/unix/my_sem"
#define max_tries 3 

int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg; 			//union semun: 请参考附录2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL|00666;
flag2=IPC_CREAT|00666;
key=ftok(SEM_PATH,'a');
//error handling for ftok here;
init_ok=0;
semid=semget(key,1,flag1);//create a semaphore set that only includes one semphore.
if(semid<0)
{
	tmperrno=errno;
	perror("semget");
if(tmperrno==EEXIST)
//errno is undefined after a successful library call( including perror call) so it is saved //in tmperrno.
		{
		semid=semget(key,1,flag2);
//flag2 只包含了IPC_CREAT标志, 参数nsems(这里为1)必须与原来的信号灯数目一致
		arg.buf=&sem_info;
		for(i=0; i<max_tries; i++)
		{
			if(semctl(semid, 0, IPC_STAT, arg)==-1)
			{	perror("semctl error"); i=max_tries;}
			else
			{ 
				if(arg.buf->sem_otime!=0){ i=max_tries;  init_ok=1;}
				else	 sleep(1);	
			}
		}
		if(!init_ok)
  // do some initializing, here we assume that the first process that creates the sem will 
  // finish initialize the sem and run semop in max_tries*1 seconds. else it will not run 
  // semop any more.
		{
			arg.val=1;
			if(semctl(semid,0,SETVAL,arg)==-1) perror("semctl setval error");
		} 
	}
	else
	{perror("semget error, process exit");	exit();	}
}
else //semid>=0; do some initializing 	
{
	arg.val=1;
	if(semctl(semid,0,SETVAL,arg)==-1)
		perror("semctl setval error");
}
//get some information about the semaphore and the limit of semaphore in redhat8.0
	arg.buf=&sem_info;
	if(semctl(semid, 0, IPC_STAT, arg)==-1)
		perror("semctl IPC STAT");		
	printf("owner's uid is %d\n", 	arg.buf->sem_perm.uid);
	printf("owner's gid is %d\n", 	arg.buf->sem_perm.gid);
	printf("creater's uid is %d\n", 	arg.buf->sem_perm.cuid);
	printf("creater's gid is %d\n", 	arg.buf->sem_perm.cgid);

	arg.__buf=&sem_info2;
	if(semctl(semid,0,IPC_INFO,arg)==-1)
		perror("semctl IPC_INFO");
	printf("the number of entries in semaphore map is %d \n",	 		arg.__buf->semmap);
	printf("max number of semaphore identifiers is %d \n", 		 	arg.__buf->semmni);
	printf("mas number of semaphores in system is %d \n",		 		arg.__buf->semmns);
	printf("the number of undo structures system wide is %d \n",	 	arg.__buf->semmnu);
	printf("max number of semaphores per semid is %d \n",		 		arg.__buf->semmsl);
	printf("max number of ops per semop call is %d \n",		 		arg.__buf->semopm);
	printf("max number of undo entries per process is %d \n", 	 		arg.__buf->semume);
	printf("the sizeof of struct sem_undo is %d \n", 	  	 			arg.__buf->semusz);
	printf("the maximum semaphore value is %d \n", 					arg.__buf->semvmx);
	
//now ask for available resource:	
	askfor_res.sem_num=0;
	askfor_res.sem_op=-1;
	askfor_res.sem_flg=SEM_UNDO;		
		
		if(semop(semid,&askfor_res,1)==-1)//ask for resource
			perror("semop error");
	
	sleep(3); //do some handling on the sharing resource here, just sleep on it 3 seconds
	printf("now free the resource\n");	
	
//now free resource	
	free_res.sem_num=0;
	free_res.sem_op=1;
	free_res.sem_flg=SEM_UNDO;

	if(semop(semid,&free_res,1)==-1)//free the resource.
		if(errno==EIDRM)
			printf("the semaphore set was removed\n");
//you can comment out the codes below to compile a different version:			
	if(semctl(semid, 0, IPC_RMID)==-1)
		perror("semctl IPC_RMID");
	else printf("remove sem ok\n");
}

注:读者可以尝试一下注释掉初始化步骤,进程在运行时会出现何种情况(进程在申请资源时会睡眠),同时可以像程序结尾给出的注释那样,把该程序编译成两个不同版本。下面是本程序的运行结果(操作系统redhat8.0):


owner's uid is 0
owner's gid is 0
creater's uid is 0
creater's gid is 0
the number of entries in semaphore map is 32000 
max number of semaphore identifiers is 128 
mas number of semaphores in system is 32000 
the number of undo structures system wide is 32000 
max number of semaphores per semid is 250 
max number of ops per semop call is 32 
max number of undo entries per process is 32 
the sizeof of struct sem_undo is 20 
the maximum semaphore value is 32767 
now free the resource
remove sem ok

Summary:信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合,可用于多种共享资源的进程间同步。每个信号灯都有一个值,可以用来表示当前该信号灯代表的共享资源可用(available)数量,如果一个进程要申请共享资源,那么就从信号灯值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。当进程要申请多种共享资源时,linux可以保证操作的原子性,即要么申请到所有的共享资源,要么放弃所有资源,这样能够保证多个进程不会造成互锁。Linux对信号灯有各种各样的限制,程序中给出了输出结果。另外,如果读者想对信号灯作进一步的理解,建议阅读sem.h源代码,该文件不长,但给出了信号灯相关的重要数据结构。

附录1: struct sem_array如下:


/*系统中的每个信号灯集对应一个sem_array 结构 */
struct sem_array {
	struct kern_ipc_perm	sem_perm;		/* permissions .. see ipc.h */
	time_t			sem_otime;			/* last semop time */
	time_t			sem_ctime;			/* last change time */
	struct sem		*sem_base;			/* ptr to first semaphore in array */
	struct sem_queue	*sem_pending;		/* pending operations to be processed */
	struct sem_queue	**sem_pending_last; 	/* last pending operation */
	struct sem_undo		*undo;			/* undo requests on this array */
	unsigned long		sem_nsems;		/* no. of semaphores in array */
};

其中,sem_queue结构如下:


/* 系统中每个因为信号灯而睡眠的进程,都对应一个sem_queue结构*/
 struct sem_queue {
	struct sem_queue *	next;	 	/* next entry in the queue */
	struct sem_queue **	prev;	 	/* previous entry in the queue, *(q->prev) == q */
	struct task_struct*	sleeper; 	/* this process */
	struct sem_undo *	undo;	 	/* undo structure */
	int   pid;	 					/* process id of requesting process */
	int   status;	 				/* completion status of operation */
	struct sem_array *	sma;	 		/* semaphore array for operations */
	int	id;	 						/* internal sem id */
	struct sembuf *	sops;		 	/* array of pending operations */
	int	nsops;					 	/* number of operations */
	int	alter;	 					/* operation will alter semaphore */
};

附录2:union semun是系统调用semctl中的重要参数:


union semun {
	int val;					/* value for SETVAL */
	struct semid_ds *buf;		/* buffer for IPC_STAT & IPC_SET */
	unsigned short *array;		/* array for GETALL & SETALL */
	struct seminfo *__buf;		/* buffer for IPC_INFO */   //test!!
	void *__pad;
};
struct  seminfo {
	int semmap;
	int semmni;
	int semmns;
	int semmnu;
	int semmsl;
	int semopm;
	int semume;
	int semusz;
	int semvmx;
	int semaem;
};

参考文献:

[1] UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。对POSIX以及系统V信号灯都有阐述,对Linux环境下的程序开发有极大的启发意义。

[2] linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,给出了系统V信号灯相关的源代码分析,尤其在阐述保证操作原子性方面,以及阐述undo标志位时,讨论的很深刻。

[3]GNU/Linux编程指南,第二版,Kurt Wall等著,张辉译

[4]semget、semop、semctl手册

上一页  [1] [2] 

 

 
文章录入:编辑01    责任编辑:编辑01 
  • 上一篇文章:

  • 下一篇文章:

  •  
    相关文章
    原创地带
    24小时热门帖子