【Linux进程间通信】共享内存的使用
背景
最近需要开发一个测试程序,接受Tester端的测试指令,执行一条条外设的测试用例,执行完成后将测试数据的结果上报,上报方式未定,考虑到耦合和配套问题,决定采用共享内存机制,设计共享内存块,分为接受指令和数据上报两部分,主程序运行后就会一直轮询共享内存去等待指令,获取指令后执行对应的测试用例,执行完成后将数据结果写入数据上报区,Tester端什么时间获取不需要关注,只做好自己的数据上报即可。
共享内存
共享内存为什么能作为进程间通信的机制?
共享内存是一种进程间的通信方式,它允许两个不相关的进程访问同一逻辑内存。不同进程之间共享的内存通常为同一段物理内存,进程可以将同一段物理内存连接到他们自己的地址空间中,连接到这段共享内存的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
在Linux环境中,每个进程都有属于自己的进程控制块(PCB)和虚拟地址空间(Addr Space),并且都有与之对应的页表,负责将进程的虚拟地址和物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,所指向的区域就是共享内存块。
共享内存块通信原理示意图:
当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。一般情况下,要确保一个进程在写的时候不能被读,就需要使用信号量来实现同步与互斥,视实际需求而定。
由于共享内存在读写操作时是直接在内存上操作,所以相比于其他进程间通信的方式,共享内存的效率是最高的。
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创建好的共享内存块,并向内存块持续写入数据。
参考文章:
https://blog.csdn.net/sunxiaopengsun/article/details/79817688
https://cloud.tencent.com/developer/article/1551288
https://blog.csdn.net/yanghaoran321/article/details/7872722