IPC之信号量详解

一、信号量介绍

作用:实现进程间的同步和互斥。
信号量,确切地说是信号量集,集合里有≥1个信号量成员。可以把信号量集比作数组,信号量当做数组用的元素。
但是,常用的信号量集都只有一个信号量成员。所以,平常说的信号量一般都指只有1个信号量成员的信号量集。
个人理解:信号量类似标志位。平常的标志位是这样的,初始flag =1. 如果flag=0则去执行某些操作,在程序的某些地方使flag置0。
不同的是,标志位只能用来实现当前进程的同步和互斥,而信号量是用来实现不同进程间的同步和互斥
什么是PV操作?
p操作(wait):申请一个单位资源,进程进入
v操作(signal):释放一个单位资源,进程出来
个人理解:进程的信号量有点像线程的互斥锁,
P操作相当于上锁,V操作相当于解锁。
(详细解释,自行百度...)


二、信号量函数由semget、semop、semctl三个函数组成。

1. semget函数
函数原型:
int semget(key_t key, int nsems, int semflg);

作用:创建一个新的信号量集或是获得一个已存在的信号量集的标识符

第一个参数key: 由ftok函数返回得到的IPC键值(常用);或者0(IPC_PRIVATE)—— 创建一个只有创建进程可以访问的信号量

第二个参数nsems:创建的信号量集合中信号量的个数。(通常为1,表示单信号量)

第三个参数semflg:0表示获取已存在的信号量标识符;

                                IPC_CREAT 表示key对应的信号量不存在则创建,存在则直接返回该信号量的标识符;

                                IPC_CREAT|IPC_EXCL 表示key对应的信号量不存在则创建,存在则报错。

分析: semflg是一个32位的整数,像IPC_CREAT和IPC_EXCL其实只用到了高24位表示,

低8位是权限位。一般用0666(6的二进制是110,表示可读,可写,不可执行,三个6分别对应当前用户,group组用户,其他用户)

返回值:成功返回信号量集的标识符,失败返回-1。


2. semop函数
函数原型:

int semop(int semid, struct sembuf *sops, unsigned nsops);

作用: 改变信号量的值

第一个参数semid:由semget返回得到的信号量标识符

第二个参数sops:

	struct sembuf {
		unsigned short sem_num;		//通常为0
		short sem_op;			//通常为±1,-1表示P操作,+1表示V操作
		short sem_flg;			//通常为SEM_UNDO
	}
	sem_num:信号量集数组的下标,表示某一个信号量。0表示第1个信号量(通常为0,一般只用单信号量)
	sem_op:信号量的变化量值。
(可以任意改变信号量值,而不只是1)但通常情况下中使用两个值,-1是P操作,用来等待一个信号量变得可用,而+1是V操作,用来通知一个信号量可用。
	sem_flg:通常为SEM_UNDO,表示调用该信号量的进程退出时,恢复相应信号量的计数值。
		例如信号量初始值是20,进程a以SEM_UNDO方式操作信号量加1;在进程未退出时,信号量变成21;在进程退出时,信号量的值恢复20。
第三个参数nsops:进行操作信号量的个数,即sops结构变量的个数。通常设为1,只完成对一个信号量的操作。

返回值:成功返回0,失败返回-1。


3. semctl函数
函数原型:

int semctl(int semid, int semnum, int cmd, ...);

作用:执行在信号量集上的控制操作

第一个参数semid:由semget返回得到的信号量标识符

第二个参数sem_num:信号量集数组的下标,表示某一个信号量。(通常为0)

第三个参数cmd:对信号量所执行的命令,常用命令有:SETVAL / GETVAL :设置/获取信号量的值;IPC_RMID 从内核中删除信号量

第四个不定参数:有些cmd命令需要额外的参数,一般都是union semun arg 形式

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 */
};
返回值:成功返回一个≥0的整数,具体哪个数值与执行的cmd有关; 失败返回-1。

(比如:cmd为GETVAL,返回值就是get得到的信号量的值;而cmd为SETVAL时,返回值为0表示指令执行成功)


三、通常的执行过程如下:
进程1(sem):

①调用semget创建信号量;

②调用semctl的SETVAL给信号量设置一个初始值;

③调用semop,执行P操作和V操作

进程2(sem2):

①调用semget获取已存在的信号量的标识符semid;

②调用semop,执行P操作和V操作。

(注意:如果有另外一进程也使用了该信号量,并且执行了P操作使信号量的值-1,那么此时本进程执行P操作时会阻塞等待直到另一进城执行V操作+1释放资源)

   分析:P操作相当于上锁,V操作相当于解锁。


四、应用举例

1. sem.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <time.h>	

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 */
	void *__pad;
};

int sem_p(int semid, int semnum);		// P操作
int sem_v(int semid, int semnum);		// V操作

int main(int argc, char **argv)
{
	int semid,ret;
	union semun arg;
	key_t key = ftok("/tmp", 0x04);
	if ( key < 0 )
	{
		perror("ftok key error");
		return -1;
	}
	// 1.创建信号量 (创建了三个信号量,实际只用0号信号量)
	semid = semget(key,3,IPC_CREAT|0600);
	if (semid == -1)
	{
		perror("create semget error");
		return -1;
	}
	// 2.对0号信号量设置初始值
	arg.val = 1;
	ret =semctl(semid,0,SETVAL,arg);
	if (ret < 0)
	{
		perror("semctl error");
		semctl(semid,0,IPC_RMID,arg);
		return -1 ;
	}	
	// 3.打印当前0号信号量的值
	ret =semctl(semid,0,GETVAL,arg);
	printf("after semctl setval  sem[0].val = %d\n",ret);
	// 4.开始P操作
	printf("P operate begin\n");
	if (sem_p(semid,0) < 0)
	{
		perror("P operate error");
		return -1;
	}
	printf("P operate end\n");
	ret =semctl(semid,0,GETVAL,arg);
	printf("after P sem[0].val= %d\n",ret);
	// 5.延时60s		(这个时间段内去执行另一个进程sem2,会在它的P操作那阻塞等待sem执行完V操作)
	sleep(60);
	time_t tNow = time(NULL);
	printf("delay 60S,now time is:%s\n",ctime(&tNow));
	// 6.开始V操作
	printf("V operate begin\n");
	if (sem_v(semid, 0) < 0)
	{
		perror("V operate error");
		return -1 ;
	}
	printf("V operate end\n");
	ret =semctl(semid,0,GETVAL,arg);
	printf("after V sem[0].val= %d\n",ret);
	// 7.移除信号量
	sleep(3);
	semctl(semid,0,IPC_RMID,arg);
	return 0;
}

//对信号量数组semnum编号的信号量做P操作
int sem_p(int semid, int semnum)
{
	struct sembuf op;  
    op.sem_num = 0;  
    op.sem_op = -1;  
    op.sem_flg = SEM_UNDO;  
	return (semop(semid,&op,1));
}

//对信号量数组semnum编号的信号量做V操作
int sem_v(int semid, int semnum)
{
	struct sembuf op;  
    op.sem_num = 0;  
    op.sem_op = +1;  
    op.sem_flg = SEM_UNDO;  
	return (semop(semid,&op,1));
}

2. sem2.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <time.h>

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 */
	void *__pad;
};

int sem_p(int semid, int semnum);		// P操作
int sem_v(int semid, int semnum);		// V操作

int main(int argc, char **argv)
{
	int semid,ret;
	union semun arg;
	key_t key = ftok("/tmp", 0x04) ;
	if ( key < 0 )
	{
		perror("ftok key error") ;
		return -1 ;
	}
	// 1.获取信号量的semid
	semid = semget(key,3,0);
	if (semid == -1)
	{
		perror("create semget error");
		return -1;
	}
	// 2.打印当前0号信号量的值
	ret =semctl(semid,0,GETVAL,arg);
	printf("origin: sem.val = %d\n",ret);
	// 3.开始P操作
	printf("P operate begin\n");
	if (sem_p(semid,0) < 0)
	{
		perror("P operate error");
		return -1;
	}
	time_t tNow = time(NULL);
	printf("P operate end...........%s\n",ctime(&tNow));
	ret =semctl(semid,0,GETVAL,arg);
	printf("after P sem.val= %d\n",ret);
	// 4.开始V操作
	printf("V operate begin\n");
	if (sem_v(semid, 0) < 0)
	{
		perror("V operate error");
		return -1;
	}
	printf("V operate end\n");
	ret =semctl(semid,0,GETVAL,arg);
	printf("after V sem.val= %d\n",ret);
	return 0;
}

//对信号量数组semnum编号的信号量做P操作
int sem_p(int semid, int semnum)
{
	struct sembuf op;  
    op.sem_num = 0;  
    op.sem_op = -1;  
    op.sem_flg = SEM_UNDO;  
	return (semop(semid,&op,1));
}

//对信号量数组semnum编号的信号量做V操作
int sem_v(int semid, int semnum)
{
	struct sembuf op;  
    op.sem_num = 0;  
    op.sem_op = +1;  
    op.sem_flg = SEM_UNDO;  
	return (semop(semid,&op,1));
}
编译:gcc sem.c -o sem    和   gcc sem2.c -o sem2

运行:先执行sem,在执行sem2

过程:运行sem时会在执行P操作后延时60s,在此期间运行sem2会阻塞在P操作这,

          直到60S后sem执行V操作恢复信号量的值,sem2才能执行下去。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值