【Linux进程间通信】共享内存的使用

背景


最近需要开发一个测试程序,接受Tester端的测试指令,执行一条条外设的测试用例,执行完成后将测试数据的结果上报,上报方式未定,考虑到耦合和配套问题,决定采用共享内存机制,设计共享内存块,分为接受指令和数据上报两部分,主程序运行后就会一直轮询共享内存去等待指令,获取指令后执行对应的测试用例,执行完成后将数据结果写入数据上报区,Tester端什么时间获取不需要关注,只做好自己的数据上报即可。

image

共享内存

共享内存为什么能作为进程间通信的机制?

共享内存是一种进程间的通信方式,它允许两个不相关的进程访问同一逻辑内存。不同进程之间共享的内存通常为同一段物理内存,进程可以将同一段物理内存连接到他们自己的地址空间中,连接到这段共享内存的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。

在Linux环境中,每个进程都有属于自己的进程控制块(PCB)和虚拟地址空间(Addr Space),并且都有与之对应的页表,负责将进程的虚拟地址和物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,所指向的区域就是共享内存块。

共享内存块通信原理示意图:

image

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。一般情况下,要确保一个进程在写的时候不能被读,就需要使用信号量来实现同步与互斥,视实际需求而定。

由于共享内存在读写操作时是直接在内存上操作,所以相比于其他进程间通信的方式,共享内存的效率是最高的。

Linux下共享内存相关接口

共享内存的创建与打开:

进程可以通过调用函数shmget()来打开或创建一个共享内存区

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

key:为ipc键值,使用IPC_PRIVATE,这样,操作系统将忽略键,建立一个新的共享内存,但是如果向让其他进程访问这块共享内存区域,则需要创建共享内存块后,就记录下key值,以提供给其他进程访问。

size:申请的共享存储段的长度,一般为内存页(4k)大小的整数倍
Flag: 如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
返回值:成功时返回一个新建或已经存在的的共享内存标识符,取决于shmflg的参数。失败返回-1并设置错误码。


共享内存与进程的连接:

如果一个进程已创建或打开一个共享内存,需要调用函数shmat()把该共享内存连接到进程上,即把待使用的共享内存映射到进程空间,

void *shmat(int shmid,char __user *shmaddr,int shmflg);

Shmid : 共享内存标志(句柄)
shmaddr:shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用)
Shmflg: 若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段
返回值: 成功返回共享存储段的指针(虚拟地址),并且内核将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1(类似于引用计数);出错返回-1


断开共享内存与进程的连接:

调用函数shmdt可以断开共享内存和进程的连接

int shmdt(const void *addr);

addr:共享存储段的地址,调用shmat的返回值
返回值:shmdt将使相关shmid_ds结构中shm_nattch计数器值减1;出错返回-1。


共享内存的控制:

调用函数shmctl()可以对共享内存进行一些控制

int shmctl(int shmid,int cmd,struct shmid_ds * buf);

shmid:共享存储段的ID
cmd:控制命令,IPC_STAT(赋值)、IPC_SET(赋值)、IPC_RMID(删除)、SHM_LOCK(上锁)、SHM_UNLOCK(解锁)
buf:出错返回-1。
返回值:成功返回0,失败返回-1

共享内存不会随着程序的结束而自动消除,要么调用shmctl()删除,要么手动使用命令ipcrm -m shmid去删除,否则一直保留在系统中,直至系统掉电


共享内存常用shell指令

1.查询当前所有的ipc通信资源情况

ipcs

2.查询当前系统的共享内存资源情况

ipcs -m

3.查询当前系统的信号量资源情况

ipcs -s

4.删除系统中某个共享内存段

ipcrm -m [shmid]

5.删除系统中某个信号量

ipcrm -s [semid]

代码实现

ipc.c

#include"ipc.h"
 
static int CommShm(int size,int flags)
{
	key_t key = ftok(PATHNAME,PROJ_ID);
	if(key < 0)
	{
		perror("ftok");
		return -1;
	}
	int shmid = 0;
	if((shmid = shmget(key,size,flags)) < 0)
	{
		perror("shmget");
		return -2;
	}
	return shmid;
}

int DestroyShm(int shmid)
{
	if(shmctl(shmid,IPC_RMID,NULL) < 0)
	{
		perror("shmctl");
		return -1;
	}
	return 0;
}

int CreateShm(int size)
{
	return CommShm(size,IPC_CREAT | IPC_EXCL | 0666);
}

int GetShm(int size)
{
	return CommShm(size,IPC_CREAT);
}

ipc.h

#ifndef _IPC_H__
#define _IPC_H__
 
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>
 
#define PATHNAME "."
#define PROJ_ID 0x6666
 
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);




#endif

client.c

#include "ipc.h"
 
int main()
{
	// 获取共享内存块的key值
	int shmid = GetShm(4096);
	usleep(1*1000*1000);
	// 连接共享内存块
	char *addr = shmat(shmid,NULL,0);
	usleep(2*1000*1000);
	int i = 0;
	while(i < 26)
	{
		// 持续写入共享内存块数据
		addr[i] = 'A' + i;
		i++;
		addr[i] = 0;
		usleep(1*1000*1000);
	}
	shmdt(addr);
	usleep(2*1000*1000);
	return 0;
}

server.c

#include"ipc.h"
 
int main()
{
	// 创建共享内存区域
	int shmid = CreateShm(4096);
	// 连接共享内存
	char *addr = shmat(shmid,NULL,0);
	usleep(2*1000*1000);
	int i = 0;
	while(i++ < 26)
	{
		// 持续读取共享内存块中的数据
		printf("client# %s\n",addr);
		usleep(1*1000*1000);
	}
	shmdt(addr);
	usleep(2*1000*1000);
	DestroyShm(shmid);
	return 0;
}

makefile

.PHONE:all
all:server client

client:client.c ipc.c
	gcc -o $@ $^
server:server.c ipc.c
	gcc -o $@ $^

.PHONE:clean
clean:
	rm -f client server

运行结果

在命令行中输入make all,编译生成server和client,先./server执行server,这时server会创建共享内存,再运行client,连接server创建好的共享内存块,并向内存块持续写入数据。

image









参考文章:
https://blog.csdn.net/sunxiaopengsun/article/details/79817688
https://cloud.tencent.com/developer/article/1551288
https://blog.csdn.net/yanghaoran321/article/details/7872722

posted @ 2022-08-10 22:30  Emma1111  阅读(401)  评论(0编辑  收藏  举报