Linux-IPC之信号量

这篇博客详细介绍了Linux中的System V信号量,包括三种类型的信号量:System V、Posix有名和基于内存的。重点讲解了信号量在进程同步中的作用,以及如何通过semget()、semctl()和semop()函数进行操作。文中通过示例说明了在并发环境中信号量操作可能遇到的问题,强调了初始化和正确使用信号量的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

信号量(也叫信号灯)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语(不会被其他指令中断)。信号量是进程/线程同步的一种方式,有时候我们需要保护一段代码,使它每次只能被一个执行进程/线程运行,这种工作就需要一个二进制开关;有时候需要限制一段代码可以被多少个进程/线程执行,这就需要用到关于计数信号量。信号量开关是二进制信号量的一种逻辑扩展,两者实际调用的函数都是一样。

信号量分为以下三种:

1、System V信号量,在内核中维护,可用于进程或线程间的同步,常用于进程的同步。 

2、Posix有名信号量,一种来源于POSIX技术规范的实时扩展方案(POSIX Realtime Extension),可用于进程或线程间的同步,常用于线程。

3、Posix基于内存的信号量,存放在共享内存区中,可用于进程或线程间的同步。

为了获得共享资源进程需要执行下列操作:

(1)测试控制该资源的信号量。

(2)若信号量的值为正,则进程可以使用该资源。进程信号量值减1,表示它使用了一个资源单位。此进程使用完共享资源后对应的信号量会加1。以便其他进程使用。

(3)若信号量的值为0,则进程进入休息状态,直至信号量值大于0。进程被唤醒,返回第(1)步。

    为了正确地实现信号量,信号量值的测试值的测试及减1操作应当是原子操作(原子操作是不可分割的,在执行完毕前不会被任何其它任务或事件中断)。为此信号量通常是在内核中实现的。


System V IPC机制:信号量。

函数原型:

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
int semget(key_t key,int nsems,int flag);
int semop(int semid,struct sembuf *sops,size_t num_sops);
int semctl(int semid, int semnum, int cmd, …);

一、示例:程序被打断出错


//创建、初始化共享内存
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#define PROJ_ID 1
#define TIMES 1000000
int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("error args\n");
		return -1;
	}
	key_t skey;
	skey=ftok(argv[1],PROJ_ID);
	if(-1==skey)
	{
		perror("ftok");
		return -1;
	}
	printf("the key is %d\n",skey);
	int shmid;
	shmid=shmget(skey,1<<12,0600|IPC_CREAT);//创建共享内存
	if(-1==shmid)
	{
		perror("shmget");
		return -1;
	}
	printf("the shmid is %d\n",shmid);
	int* p;
	p=shmat(shmid,NULL,0);
	if((int*)-1==p)
	{
		perror("shmat");
		return -1;
	}
	*p=0;//初始化共享内存
	return 0;
}

//add1.c&&add2.c分别将内存中的数加10000000次1
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#define PROJ_ID 1
#define TIMES 10000000
int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("error args\n");
		return -1;
	}
	key_t skey;
	skey=ftok(argv[1],PROJ_ID);
	if(-1==skey)
	{
		perror("ftok");
		return -1;
	}
//	printf("the key is %d\n",skey);
	int shmid;
	shmid=shmget(skey,1<<12,0600|IPC_CREAT);
	if(-1==shmid)
	{
		perror("shmget");
		return -1;
	}
//	printf("the shmid is %d\n",shmid);
	int* p;
	p=shmat(shmid,NULL,0);
	if((int*)-1==p)
	{
		perror("shmat");
		return -1;
	}
	int i;
	for(i=0;i<TIMES;i++)
	{
		*p=(*p)+1;
	}
	printf("the value is %d\n",*p);
	return 0;
}

两个相同的程序,功能是将共享内存中的数加10000000次1,正确的结果应该是20000000

同时运行(用脚本实现)后的结果却为:


说明在两个程序并行的时候语句被打断。


二、semget()函数


创建一个信号量集或访问一个已存在的信号量集。返回值:成功时,返回一个称为信号量标识符的整数,semop和semctl会使用它;出错时,返回-1。
参数key是唯一标识一个信号量的关键字,如果为IPC_PRIVATE(值为0,创建一个只有创建者进程才可以访问的信号量,通常用于父子进程之间;非0值的key(可以通过ftok函数获得)表示创建一个可以被多个进程共享的信号量;
参数nsems指定需要使用的信号量数目。如果是创建新集合,则必须制定nsems。如果引用一个现存的集合,则将nsems指定为0.
参数flag是一组标志,其作用与open函数的各种标志很相似。它低端的九个位是该信号量的权限,其作用相当于文件的访问权限。
此外,它们还可以与键值IPC_CREAT按位或操作,以创建一个新的信号量。即使在设置了IPC_CREAT标志后给出的是一个现有的信号量的键字,也并不是一个错误。我们也可以通过IPC_CREAT和IPC_EXCL标志的联合使用确保自己将创建出一个新的独一无二的信号量来,如果该信号量已经存在,就会返回一个错误。

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

int main()
{
	int semid;
	semid=semget((key_t)1234,10,0666|IPC_CREAT);
	printf("the semid is %d\n",semid);
	return 0;
}

运行结果:


信号量:


三、semctl()函数

函数semctl用来直接控制信号量信息。函数返回值:成功时,返回0;失败时,返回-1.
1.参数semid是由semget返回的信号量标识符。
2.参数semnum为集合中信号量的编号,当要用到成组的信号量时,从0开始。一般取值为0,表示这是第一个也是唯一的一个信号量。
3.参数cmd为执行的操作。通常为:IPC_RMID(立即删除信号集,唤醒所有被阻塞的进程)、GETVAL(根据semun返回第GETVAL-1个信号的值,从0开始,第一个信号量编号为0)、SETVAL(根据semun设定信号的值,从0开始,第一个信号量编号为0)、GETALL(获取所有信号量的值,第二个参数为0,将所有信号的值存入semun.array中)、SETALL(将所有semun.array的值设定到信号集中,第二个参数为0)等。
参数…是一个union semun(需要由程序员自己定义),它至少包含以下几个成员:

union semun{
	int val;		/* Value for SETVAL */
	struct semid_ds *buf;	/* Buffer for IPC_STAT, IPC_SET */
	unsigned short *array;	/* Array for GETALL, SETALL */
};

通常情况仅使用val,给val赋值为1

在生产者源码里,首先用函数semctl()初始化信号量集合sem_id,它包含两个信号,分别表示生产的数量和空仓库的数量,那么在消费者的进程中用相同的key值就会得到该信号量集合;实现两个进程之间的通信。

在主函数里,设定对两个信号量的PV操作,然后在各自的进程中对两个信号进行操作。

(1)如果只运行生产者进程,则生产10个之后,该进程就会因为在得不到空仓库资源而阻塞,这个时候运行消费者进程,阻塞就会被解除;

(2)如果先运行生产者进程,生产几个产品之后,关闭该进程,则运行消费者进程,当消费完生产的产品后,该进程就会因为在得不到产品资源而阻塞,这个时候运行生产者进程,阻塞就会被解除;

(3)如果同时运行两个进程,由于消费比生产快,因此消费者每次都要等待生产者生产产品之后才能消费;

在每次运行程序之前,一定要先运行生产者进程先初始化信号量。


示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

int main()
{
	int semid;
	semid=semget((key_t)1234,1,0666|IPC_CREAT);//IPC_CREAT创建信号量
	if(-1==semid)
	{
		perror("semget");
		return -1;
	}
	int ret;
	ret=semctl(semid,0,GETVAL);
	if(-1==ret)
	{
		perror("semctl");
		return -1;
	}
	printf("ret is %d\n",ret);
	return 0;
}

运行结果:


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

int main()
{
	int semid;
	semid=semget((key_t)1234,1,0600|IPC_CREAT);
	if(-1==semid)
	{
		perror("semget");
		return -1;
	}
	printf("the semid is %d\n",semid);
	int ret;
	ret=semctl(semid,0,GETVAL);
	if(-1==ret)
	{
		perror("semctl");
		return -1;
	}
	printf("ret is %d\n",ret);
	ret=semctl(semid,0,SETVAL,1);//将信号量的值置1
	if(-1==ret)
	{
		perror("semctl");
		return -1;
	}
	ret=semctl(semid,0,GETVAL);
	if(-1==ret)
	{
		perror("semctl");
		return -1;
	}
	printf("after ret is %d\n",ret);
	return 0;
}

输出结果:


//信号量集合操作
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int main()
{
	int semid;
	semid=semget((key_t)1234,3,0600|IPC_CREAT);
	if(-1==semid)
	{
		perror("semid");
		return -1;
	}
	int ret;
	unsigned short arr[3];
	ret=semctl(semid,0,GETALL,arr);
	if(-1==ret)
	{
		perror("semctl");
		return -1;
	}
	printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
	return 0;
}
<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <strings.h>

int main()
{
	int semid;
	semid=semget((key_t)1234,3,0600|IPC_CREAT);
	if(-1==semid)
	{
		perror("semid");
		return -1;
	}
	int ret;
	unsigned short arr[3];
	ret=semctl(semid,0,GETALL,arr);
	if(-1==ret)
	{
		perror("semctl_get");
		return -1;
	}
	printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
	arr[0]=1;
	arr[1]=2;
	arr[2]=3;
	ret=semctl(semid,0,SETALL,arr);
	if(-1==ret)
	{
		perror("semctl_set");
		return -1;
	}
	bzero(arr,sizeof(arr));
	ret=semctl(semid,0,GETALL,arr);
	if(-1==ret)
	{
		perror("semctl_get");
		return -1;
	}
	printf("1sem=%d,2sem=%d,3sem=%d\n",arr[0],arr[1],arr[2]);
	return 0;
}





四、semop()函数


函数semop用于改变信号量对象中各个信号量的状态。返回值:成功时,返回0;失败时,返回-1.
参数semid是由semget返回的信号量标识符。
参数sops是指向一个结构体数组的指针。每个数组元素至少包含以下几个成员:

struct sembuf{
	short sem_num; 	//操作信号量在信号量集合中的编号,第一个信号量的编号是0。
	short sem_op;	//sem_op成员的值是信号量在一次操作中需要改变的数值。通常只会用到两个值,一个是-1,也就是p操作,它等待信号量变为可用;一个是+1,也就是v操作,它发送信号通知信号量现在可用。
	short sem_flg;	//通常设为:SEM_UNDO,程序结束,信号量为semop调用前的值。
};

参数nops为sops指向的sembuf结构体数组的大小。

示例:两个进程并行,同时将内存中的数循环加1(与第一个示例对比,达到20000000)
注:必须先创建信号量,并将其值置0,在运行加法程序之前一定要先初始化
//初始化init.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PROJ_ID 1

int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("error args\n");
		return -1;
	}
	key_t skey;
	skey=ftok(argv[1],PROJ_ID);
	if(-1==skey)
	{
		perror("ftok");
		return -1;
	}
	printf("the key is %d\n",skey);
	int shmid;
	shmid=shmget(skey,1<<12,0600|IPC_CREAT);
	if(-1==shmid)
	{
		perror("shmget");
		return -1;
	}
	printf("the shmid is %d\n",shmid);
	int *p;
	p=(int*)shmat(shmid,NULL,0);
	if((int*)-1==p)
	{
		perror("shmat");
		return -1;
	}
	*p=0;
	printf("the value is %d\n",*p);
	return 0;
}

//两个相同的加法程序add1.c&add2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <strings.h>
#define TIMES 10000000
#define PROJ_ID 1

int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("error args\n");
		return -1;
	}
	key_t skey;
	skey=ftok(argv[1],PROJ_ID);
	if(-1==skey)
	{
		perror("skey");
		return -1;
	}
	printf("the skey is %d\n",skey);
	int shmid;
	shmid=shmget(skey,1<<12,0600|IPC_CREAT);
	if(-1==shmid)
	{
		perror("shmget");
		return -1;
	}
	printf("the shmid is %d\n",shmid);
	int* p;
	p=(int*)shmat(shmid,NULL,0);
	if((int*)-1==p)
	{
		perror("shmat");
		return -1;
	}
	int semid;
	semid=semget((key_t)1234,0,0600);
	if(-1==semid)
	{
		perror("semget");
		return -1;
	}
	struct sembuf sop;
	bzero(&sop,sizeof(sop));
	sop.sem_num=0;
	sop.sem_op=-1;
	sop.sem_flg=SEM_UNDO;
	struct sembuf sov;
	bzero(&sov,sizeof(sov));
	sov.sem_num=0;
	sov.sem_op=1;
	sov.sem_flg=SEM_UNDO;
	int i;
	for(i=0;i<TIMES;i++)
	{
		semop(semid,&sop,1);
		*p=(*p)+1;
		semop(semid,&sov,1);
	}
	printf("the value is %d\n",*p);
	return 0;
}

输出结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值