进程的IPC操作 之 共享内存

目录

一 共享内存的作用

二 共享内存的实现

2.1 shm系统调用

2.1.1 共享内存的创建

2.1.2 共享内存的关联与分离

2.1.3 共享内存的设置

2.1.4 测试案例

2.2 mmap内存映射

2.2.1 函数的使用

2.2.2 测试案例


一 共享内存的作用

共享内存主要是用来实现进程间通讯的一种机制,只不过,需要进程间互斥的访问这段内存,因此需要使用信号量等锁机制来对共享内存进行访问。

二 共享内存的实现

共享内存的实现主要由两种方式:一种是shm系统调用;一种是mmap实现共享(因为mmap也可以实现私有映射)的页面映射。

2.1 shm系统调用

shm系统调用主要是在sys/shm.h头文件中。

2.1.1 共享内存的创建

int shmget(key_t key, size_t size, int shmflg);

函数功能:用于创建或获取用键值为key的一段大小为size的共享内存。

函数参数:

1.key:IPC内核资源(信号量,消息队列,共享内存)的键值,用于在全局唯一的表示该资源。

2.size:申请的共享内存大小。

3.shmflg:主要讲解几个参数:

        a.IPC_CREAT和IPC_EXCL与信号量的含义相同;

        b.SHM_HUGETLB:用于申请大页面作为共享内存。

        c.SHM_NORESERVE:不为该共享内存创建swap交换分区。会导致如果物理内存不够用了,要对该共享内存读写的话会出错并且返回SIGSGV信号量。

创建成功的话,会在内核创建一个shmid_ds类型的结构体变量与该共享内存关联:

struct shmid_ds{
    IPC_PERM shm_perm;//用于表示该共享内存的键值和权限
    size_t   shm_segsz;//用于表示该共享内存的大小
    _time_t  shm_atime;//用于表示最后一次调用shmat()函数的时间
    _time_t  shm_dtime;//用于表示最后一次调用shmdt()函数的时间
    _time_t  shm_ctime;//用于表示最后以此调用shmclt()函数的时间
    _pid_t   shm_cpid;//用于表示创建共享内存的进程PID
    _pid_t   shm_lpid;//用于表示最后以此调用shmat()函数和shmdt()函数的进程PID
    shmatt_t shm_nattach;//用于表示关联到该共享内存的进程个数
};

2.1.2 共享内存的关联与分离

void* shmat(int shm_id, const void* shm_addr, int shmflg);//将共享内存shm_id绑定到进程地址shm_addr
int shmdt(const void* shm_addr);//将进程地址shm_addr与共享内存分离

函数功能:

1.shmat():主要是将调用进程的内存地址shm_addr与shm_id标识的共享内存关联:

2.shmdt():主要是讲调用进程的内存地址shm_addr与共享内存分离。

函数参数:

1.shm_id:是shmget()函数返回的用于标识该共享内存的id号;

2.shm_addr:是进程的指针,主要是用于关联共享内存的起始地址:当不设置该参数时,系统自动分配地址(推荐选择);当设置该参数时,会讲共享内存关联到该内存地址。

3.shmflg:主要参数有如下几个:

        a.SHM_RND:当不设置设置该参数时,直接将共享内存关联到shm_addr标识的内存地址(前提是设置了要映射的内存地址shm_addr);当设置该参数时,会将内存地址向下圆整到SHMLBA的整数倍地址处(SHMLBA是内存页面大小的整数倍);

        b.SHM_RDONLY:该参数主要是设置该进程对共享内存的访问权限为只读;

        c.SHM_REMAP:如果该内存地址shm_addr映射了其他共享内存,重新映射该共享内存。

2.1.3 共享内存的设置

int shmctl(int shm_id, int command, shmid_ds* buf);

函数功能:主要是给该共享内存进行相关设置。

具体使用(常用功能):

1.IPC_STAT:主要是将shm_id标识的共享内存的内核shmid_ds结构体存入函数的参数buf所指向的shmid_ds结构体中;

2.IPC_SET:主要是将buf所指向的shmid_ds结构体赋值给shm_id所标识的共享内存的内核shmid_ds结构体中;

3.IPC_RMID:给该共享内存打上删除的标记,当最后一个与该共享内存关联的进程调用shmdt()函数时,自动删除该共享内存以及与之关联的shmid_ds结构体变量。

2.1.4 测试案例

测试代码:

#include<sys/sem.h>
#include<sys/shm.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>

union semun{
	int val;
	struct semid_ds* buf;
	unsigned short int* array;
	struct seminfo* _buf;
};

void pv(int sem_id, int sem_op)
{
	struct sembuf sem_ops;
	sem_ops.sem_num = 0;
	sem_ops.sem_op = sem_op;
	sem_ops.sem_flg = SEM_UNDO;
	semop(sem_id, &sem_ops, 1);
}

int main()
{
	char* message1 = "a";
	char* message2 = "b";
	printf("开始:\n");
	//创建信号量
	int sem_id = semget(IPC_PRIVATE, 1, 0666);//创建信号量集(其中只有一个信号量)
	union semun sem_un;
	sem_un.val = 1;//信号量初始值设为1,这里只测试二元信号量
	semctl(sem_id, 0, SETVAL, sem_un);
	//创建共享内存
	int shm_id = shmget(IPC_PRIVATE, 10, 0666);
	pid_t pid = fork();
	if(pid < 0)
		return -1;
	else if(pid == 0)
	{
		char* shmaddr = shmat(shm_id, NULL, 0);
		pv(sem_id, -1);//进入临界区
		for(int i = 0; i < 5; i++)
		{
			strcat(shmaddr, message1);
			sleep(1);
			printf("%s\n", shmaddr);
		}
		pv(sem_id, 1);//退出临界区
		shmdt(shmaddr);
		exit(0);//终止子进程
	}
	else
	{
		char* shmaddr = shmat(shm_id, NULL, 0);
		pv(sem_id, -1);//进入临界区
		for(int i = 0; i < 5; i++)
		{
			strcat(shmaddr, message2);
			sleep(1);
			printf("%s\n", shmaddr);
		}
		pv(sem_id, 1);//退出临界区
		shmdt(shmaddr);
	}
	waitpid(pid, NULL, 0);//回收子进程
	semctl(sem_id, 0, IPC_RMID, sem_un);//删除信号量
	struct shmid_ds *buf;
	shmctl(shm_id, IPC_RMID, buf);//最后一个进程分离,删除共享内存及其相应的shmid_ds变量
	return 0;
}

加锁对共享内存进行读写:

不加锁对共享内存进行读写:

2.2 mmap内存映射

mmap函数主要是用于申请一段内存空间:1.作为共享内存;2.作为私有内存;3.文件映射;4.匿名映射。(1,2和3,4排列组合可以达到不同的效果)。

2.2.1 函数的使用

void* mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void*start,size_t length);

函数功能:

1.mmap():内存映射。

2.munmap():解除起始地址为start,长度为length的内存映射。

函数参数:

1.start:主要是映射在内存的起始位置,如果为空则系统自动分配(推荐设置方法);

2.length:映射的长度(文件长度与内存长度对应);

3.prot:用来设置该段内存的访问权限:

        a.PROT_READ:可读;

        b.PROT_WRITE:可写;

        c.PROT_EXEC:可执行;

        d.PROT_NONE:不能被访问。

4.flag:常用参数:

        a.MAP_SHARED:作为共享内存进行映射,并且对于该内存的修改会回写到对应的文件中(如果时文件映射的话);

        b.MAP_PRIVATE:作为私有内存进行映射,被调用进程所私有,并且不会回写到对应的文件中;

        c.MAP_ANONYMOUS:作为匿名内存进行映射,该段内存不是来自于文件的,所以mmap()函数的后两个参数可以忽略;

        d.MAP_HUGETLB:按照大内存页面进行分配。

2.2.2 测试案例

这里我采用的是匿名映射的方式,为了展示的方便,不去映射具体的文件,具体代码如下:

#include<sys/sem.h>
#include<sys/mman.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>

union semun{
	int val;
	struct semid_ds* buf;
	unsigned short int* array;
	struct seminfo* _buf;
};

void pv(int sem_id, int sem_op)
{
	struct sembuf sem_ops;
	sem_ops.sem_num = 0;
	sem_ops.sem_op = sem_op;
	sem_ops.sem_flg = SEM_UNDO;
	semop(sem_id, &sem_ops, 1);
}

int main()
{
	char* message1 = "a";
	char* message2 = "b";
	printf("开始:\n");
	//创建信号量
	int sem_id = semget(IPC_PRIVATE, 1, 0666);//创建信号量集(其中只有一个信号量)
	union semun sem_un;
	sem_un.val = 1;//信号量初始值设为1,这里只测试二元信号量
	semctl(sem_id, 0, SETVAL, sem_un);
	//创建共享内存
	char* mmap_addr = mmap(NULL, 10, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, -1, 0);//MAP_ANONYMOUS:匿名映射
	pid_t pid = fork();
	if(pid < 0)
		return -1;
	else if(pid == 0)
	{
		pv(sem_id, -1);//进入临界区
		for(int i = 0; i < 5; i++)
		{
			strcat(mmap_addr, message1);
			sleep(1);
			printf("%s\n", mmap_addr);
		}
		pv(sem_id, 1);//退出临界区
		exit(0);//终止子进程
	}
	else
	{
		pv(sem_id, -1);//进入临界区
		for(int i = 0; i < 5; i++)
		{
			strcat(mmap_addr, message2);
			sleep(1);
			printf("%s\n", mmap_addr);
		}
		pv(sem_id, 1);//退出临界区
	}
	waitpid(pid, NULL, 0);//回收子进程
	semctl(sem_id, 0, IPC_RMID, sem_un);//删除信号量
	munmap(mmap_addr, 10);
	return 0;
}

加锁对共享内存进行读写:

不加锁对共享内存进行读写:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值