进程通信之共享内存,信号量

文章详细介绍了Linux中共享内存的概念、使用步骤、函数接口及示例代码,并探讨了使用共享内存可能遇到的数据竞争问题。接着,文章讲解了信号量的原理,作为进程间同步的工具,以及信号量的使用步骤、函数接口和示例代码,展示了如何用信号量解决数据践踏问题。

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

目录

1、共享内存

2、共享内存使用步骤

3、共享内存函数接口

4、共享内存示例代码

5、使用共享内存的问题

6、信号量 

7、信号量使用步骤

8、信号量函数接口

9、信号量示例代码如下

10、信号量总结

1、共享内存

共享内存,顾名思义,就是通过不同进程共享一段相同的内存来达到通信的目的,由于SHM对象不再交由内核托管,因此共享内存SHM对象是众多IPC方式最高效的一种方式,但也因为这个原因,SHM一般不能单独使用,而需要配合诸如互斥锁、信号量等协同机制使用。

 2、共享内存使用步骤

1)先申请key值 key_t key = ftok(".",10);

2)根据申请到的key值去申请共享内存的ID号。 -> shmget() -> man 2 shmget (share memory get)

3)根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域---映射 -> man 2 shmmat

4)当不再使用时,解除映射关系 -> man 2 shmmdt

5)当没有进程再需要使用这块共享内存时,删除它-> shmctl() -> man 2 shmctl

3、共享内存函数接口

根据申请到的key值去申请共享内存的ID号
shmget()  --->  man 2 shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg); //shm --->share memory
参数:key: key值
     size: 共享内存的总字节数,必须是PAGE_SIZE的倍数   #define PAGE_SIZE 1024
     shmflg:IPC_CREAT  -> 不存在则创建
        IPC_CREAT|0666
返回值:成功:共享内存ID号
    失败:-1
 
 根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域---映射
 shmat()  --->  man 3 shmat
 #include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:shmid: 共享内存ID号
    shmaddr:NULL  -> 系统自动分配地址给你  99.999%
            不为NULL -> 用户自己选择地址  0.001%
    shmflg:普通属性,填0
返回值:成功:共享内存的起始地址
        失败:(void *)-1
通常用法:shmat(shmid,NULL,0)

当不再使用时,解除映射关系
shmdt() --->  man 2 shmdt
int shmdt(const void *shmaddr);
参数:shmaddr :你需要解除内存映射的地址
返回值:成功:0
    失败:-1
 
 当没有进程再需要使用这块共享内存时,删除它
 shmctl()  ---> man 2 shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:shmid:共享内存ID号
    cmd:操作命令
        IPC_STAT --->获取共享内存的属性 --》必须填第三个参数
        IPC_RMID --->删除共享内存  --》填 NULL
    buf:看cmd
返回值:成功:0
    失败:-1

通常用法:
shmctl(id,IPC_RMID,NULL);//删除共享内存 

说明:
#include <string.h>
char *strcpy(char *dest, const char *src); //字符串拷贝-->使用于字符串操作
void *memcpy(void *dest, const void *src, size_t n); //内存拷贝-->适用于内存操作

4、共享内存示例代码

include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<string.h>

//共享内存_wr.c
int main()
{
	key_t key = ftok(".",10);
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	if(shmid < 0)
	{
		printf("shmget fail\n");
		return -1;
	}
	char* shm = shmat(shmid,NULL,0);
	if(shm == (void*) - 1)
	{
		printf("shmad fail\n");
		return -1;
	}
	printf("%d %d\n",key,shmid);

	char sendbuf[1024] = "hello world";
	memcpy(shm,sendbuf, strlen(sendbuf)); 
	
	while(1);
	
	
	shmdt(shm);
	shmctl(shmid,IPC_RMID,NULL);
	
	
	return 0;
}

//共享内存_rd.c
int main()
{
	key_t key = ftok(".",10);
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	if(shmid < 0)
	{
		printf("shmget fail\n");
		return -1;
	}
	printf("%d %d\n",key,shmid);
	
	char* shm = shmat(shmid,NULL,0);
	if(shm == (void*) - 1)
	{
		printf("shmad fail\n");
		return -1;
	}
	
	printf("%s\n",shm);
	while(1);
		printf("%s\n",shm); 
	
	shmdt(shm);
	shmctl(shmid,IPC_RMID,NULL);
	
	return 0;
}

5、使用共享内存的问题

出现数据践踏,我们写了一次数据进去,那么就无限打印。

我们想要的是,更新一次数据,就打印一次就行。-----(用信号量来解决数据践踏)

6、信号量 

信号量SEM全称Semaphore,中文也翻译为信号灯。信号量跟MSG和SHM有极大的不同,SEM不是用来传输数据的,而是作为“旗语”,用来协调各进程或者线程工作的。

信号量本质上是一个数字,用来表征一种资源的数量,当多个进程或者线程争夺这些稀缺资源的时候,信号量用来保证他们合理地、秩序地使用这些资源,而不会陷入逻辑谬误之中。

在Unix/Linux系统中常用的信号量有三种:

  • IPC信号量组
  • POSIX具名信号量
  • POSIX匿名信号量

实际上在其内部实现中,IPC信号量组是一个数组,里面包含N个信号量元素,每个元素相当于一个POSIX信号量。这种机制可以一次性在其内部设置多个信号量。

  • 临界资源(critical resources)
    • 多个进程或线程有可能同时访问的资源(变量、链表、文件等等)
  • 临界区(critical zone)
    • 访问这些资源的代码称为临界代码,这些代码区域称为临界区
  • P操作
    • 程序进入临界区之前必须要对资源进行申请,这个动作被称为P操作,这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样,P操作就是申请资源,如果申请成功,资源数将会减少。如果申请失败,要不在门口等,要不走人。
  • V操作
    • 程序离开临界区之后必须要释放相应的资源,这个动作被称为V操作,这就像你把车开出停车场之后,要将停车卡归还给保安一样,V操作就是释放资源,释放资源就是让资源数增加。


信号量组非常类似于停车场的卡牌,想象一个有N个车位的停车场,每个车位是立体的可升降的,能停n辆车,那么我们可以用一个拥有N个信号量元素,每个信号量元素的初始值等于n的信号量来代表这个停车场的车位资源——某位车主要把他的m辆车开进停车场,如果需要1个车位,那么必须对代表这个车位的信号量元素申请资源,如果n大于等于m,则申请成功,否则不能把车开进去。

 7、信号量使用步骤

1)先申请共享内存的key值和id值,映射

2)由于信号量属于IPC对象,所以要申请key值 key = ftok(".",10);

3)根据key值申请信号量ID号。 -> semget() -> man 2 semget

4)控制/设置信号量值参数。 -> semctl() -> man 2 semctl

5)如何实现信号量的P/V操作? (P操作: 1->0 V操作: 0->1)

 8、信号量函数接口

根据key值申请信号量ID号
semget()  -> man 2 semget
#include <sys/types
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
参数:key:信号量的key值
    nsems:  信号量元素的个数。例如: 空间+数据  -> 2
    semflg:  IPC_CREAT|0666  -> 不存在则创建
 返回值:成功: 信号量ID
    失败: -1
 
控制/设置信号量值参数
semctl()  -> man 2 semctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
参数:semid:信号量ID
    semnum:需要操作的成员的下标   空间:0  数据:1
    cmd:    SETVAL  -> 用于设置信号量的起始值
            IPC_RMID -> 删除信号量的ID
    ...:    空间/数据的起始值
    例如: 想设置空间的起始值为1,数据的起始值为0
    semctl(semid,0,SETVAL,1);//设置空间的起始值为1 (有车位)
    semctl(semid,1,SETVAL,0);//数据的起始值为0 (没车)
返回值:成功:0
    失败:-1
 
实现信号量的P/V操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, size_t nsops);
函数参数:semid:  信号量ID号
        sops:进行P/V操作结构体
        nsops: 信号量操作结构体的个数 -> 1
        struct sembuf
        {
            unsigned short sem_num;   //需要操作的成员的下标  空间:0  数据:1
            short          sem_op;    //P操作/V操作           P: -1  V: 1
            short          sem_flg;   //普通属性,填0.
        }
返回值:成功:0
        失败:-1

9、信号量示例代码如下

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

//信号量_rd.c
int main()
{
	//1、获取key值
	key_t key = ftok(".",10);
	//2、根据key值 获取共享内存的ID号
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
	char*shm_p = shmat(shmid,NULL,0);
	if(shm_p == (void*)-1)
	{
		perror("shmat error");
		return -1;
	}

	//4、获取信号量的key值
	key_t key1 = ftok(".",20);
	//5、根据key值申请信号量ID号
	int semid = semget(key1,2,IPC_CREAT|0666);
	//6、初始化信号量起始值 有空间没数据
	semctl(semid,0,SETVAL,1);//设置空间的起始值为1
	semctl(semid,1,SETVAL,0);//设置数据的起始值为0

	//空间结构体(只是初始化,没有操作)
	struct sembuf space;
	space.sem_num = 0;
	space.sem_op = 1; //V操作 这个变量决定是p还是V
	space.sem_flg = 0;

	//数据结构体
	struct sembuf data;
	data.sem_num = 1;
	data.sem_op = -1;//P操作
	data.sem_flg = 0;

	//此时映射出来的shm_p 就是两个进程的共享内存
	while(1)
	{
		//空间:0  数据:1
		//把车开出来之前,请问数据能不能-1?
		semop(semid, &data, 1);

		//能  -> 里面有车  -> 函数返回
		//不能 -> 里面没车 -> 函数阻塞

		//从车库里面把车开出来
		//从共享内存中读取数据
		printf("recv:%s\n",shm_p);
		//sleep(1);

		//把车开出来之后,空间+1
		semop(semid, &space, 1);

		//空间:1  数据:0

		//退出条件,这里要注意 应该使用strncmp 指定字节数
		if(strncmp(shm_p,"exit",4) == 0)
			break;
	}

	return 0;
}

//信号量_wr.c
int main()
{
	//1、获取key值
	key_t key = ftok(".",10);
	//2、根据key值 获取共享内存的ID号
	int shmid = shmget(key,1024,IPC_CREAT|0666);
	//3、根据ID号 将共享内存映射至本进程虚拟内存空间的某个区域
	char*shm_p = shmat(shmid,NULL,0);
	if(shm_p == (void*)-1)
	{
		perror("shmat error");
		return -1;
	}

	//4、获取信号量的key值
	key_t key1 = ftok(".",20);
	//5、根据key值申请信号量ID号
	int semid = semget(key1,2,IPC_CREAT|0666);
	//6、初始化信号量起始值--->有空间没数据
	//semctl第二个参数0--表示空间 1--表示数据
	semctl(semid,0,SETVAL,1);//设置空间的起始值为1 //有空间
	semctl(semid,1,SETVAL,0);//设置数据的起始值为0 //无数据


	//空间结构体(这里只是初始化,没有操作)
	struct sembuf space;
	space.sem_num = 0;//空间  (说明:通过struct sembuf里面的sem_num变量来区分结构体到底是空间还是数据)
	space.sem_op = -1;//P操作
	space.sem_flg = 0;//普通属性

	//数据结构体(这里只是初始化,没有操作)
	struct sembuf data;
	data.sem_num = 1;//数据
	data.sem_op = 1;//V操作
	data.sem_flg = 0;//普通属性

	//此时映射出来的shm_p 就是两个进程的共享内存
	while(1)
	{
		//初始化空间:1  数据:0
		//开车进去之前,空间 -1 --P操作
		semop(semid, &space, 1);//请问空间能不能P操作?semop第三个参数1描述的是结构体的个数

		//能   -> 有车位  -> 函数返回
		//不能 -> 没车位  -> 函数阻塞


		//开车进去
		//从键盘上获取数据,存储到共享内存shm_p
		scanf("%s",shm_p);

		//开车进去之后,数据+1 --V操作
		semop(semid, &data, 1); //数据自动+1 这句话操作的是数据

		//退出条件,这里要注意 应该使用strncmp 指定字节数
		if(strncmp(shm_p,"exit",4) == 0)
			break;
	}

	//4、当不再使用时,解除映射关系
	shmdt(shm_p);
	//5、当没有进程再需要使用这块共享内存时,删除它
	shmctl(shmid,  IPC_RMID, NULL);

	semctl(semid,0,IPC_RMID);

	return 0;
}

 10、信号量总结

进程1发送端

1)共享内存的初始化

先申请共享内存的key值,根据key值获取共享内存的id号,进行共享内存的映射

2)信号量的初始化

先申请信号量的key值,根据key值获取信号量的id号,初始化信号量,空间为1,数据为0

初始化空间的结构体,初始化数据的结构体

//semctl第二个参数0--表示空间 1--表示数据

semctl(semid,0,SETVAL,1);//设置空间的起始值为1 //有空间

semctl(semid,1,SETVAL,0);//设置数据的起始值为0 //无数据

//空间结构体(这里只是初始化,没有操作)

struct sembuf space;

space.sem_num = 0;//空间 (说明:通过struct sembuf里面的sem_num变量来区分结构体到底是空间还是数据)

space.sem_op = -1;//P操作

space.sem_flg = 0;//普通属性

//数据结构体(这里只是初始化,没有操作)

struct sembuf data;

data.sem_num = 1;//数据

data.sem_op = 1;//V操作

data.sem_flg = 0;//普通属性

无论读写,空间的sem_num为0,数据的sem_num为1

3)写数据 对空间进行p操作,往共享内存写入数据,对数据进行v操作

semop(semid, &space, 1)

semop(semid, &data, 1);

semop()函数的第三个参数为结构体的数量 --> 1

无论 读写 空间数据 结构体,都先执行P操作再执行V操作

4)删除共享内存

解除共享内存的映射,删除共享内存

5)删除信号量

删除信号量

进程1接收端

1)共享内存的初始化

先申请共享内存的key值,根据key值获取共享内存的id号,进行共享内存的映射

2)信号量的初始化

先申请信号量的key值,根据key值获取信号量的id号,初始化信号量,空间为1,数据为0

初始化空间的结构体,初始化数据的结构体

//初始化信号量起始值 有空间没数据

semctl(semid,0,SETVAL,1);//设置空间的起始值为1

semctl(semid,1,SETVAL,0);//设置数据的起始值为0

//空间结构体(只是初始化,没有操作)

struct sembuf space;

space.sem_num = 0;

space.sem_op = 1; //V操作 这个变量决定是p还是V

space.sem_flg = 0;

//数据结构体

struct sembuf data;

data.sem_num = 1;

data.sem_op = -1;//P操作

data.sem_flg = 0;

无论读写,空间的sem_num为0,数据的sem_num为1

3)读数据

对数据进行p操作,在共享内存读取数据,对空间进行v操作

semop(semid, &space, 1)

semop(semid, &data, 1);

semop()函数的第三个参数为结构体的数量 --> 1

无论 读写 空间数据 结构体,都先执行P操作再执行V操作

4)删除共享内存

解除共享内存的映射,删除共享内存

5)删除信号量

删除信号量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值