共享内存(Shared Memory):映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问
- 优点:无须复制,快捷,信息量大
- 缺点:
-
- 通信是通过将共享空间缓冲区直接附加到进程的虚拟地址空间中来实现的,因此进程间的读写操作的同步问题
- 利用内存缓冲区直接交换信息,内存的实体存在于计算机中,只能同一个计算机系统中的诸多进程共享,不方便网络通信
共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。
不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
共享内存的通信原理
在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
共享内存的通信原理示意图:
共享内存并未提供同步机制,进程A向共享内存中写入数据,进程B从共享内存中读取数据,需要考虑到同步问题,可以使用信号量来处理同步问题。
对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
共享内存的接口函数以及指令
- 查看系统中共享内存存储段
- ipcs -m
- 删除系统中共享存储段
- ipcrm -m [shmid]
创建共享内存
shmget()
int shmget(key_t key, size_t size, int shmflg);
参数1:由ftok生成的key标识,标识系统的唯一IPC资源
参数2:需要申请共享内存的大小。操作系统中,申请内存的最小单位为页,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
参数3:IPC_CREAT,IPC_EXCL。
shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666);
返回值:成功时返回共享内存标识ID,失败返回-1.
IPC操作时IPC_CREAT和IPC_EXCL的选项说明:
当只有IPC_CREAT时,无论是否存在都返回共享内存ID,不存在则创建。
当只有IPC_EXCL时,不管有没有该共享内存,shmget()都返回-1
当IPC_CREAT|IPC_EXCL时,如果不存在,则创建,已存在共享内存ID,则shmget返回-1。
挂载共享内存
shmat()
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数1:共享内存标识ID
参数2:设置成NULL,则存储段连接到由内核选择的第一个合适的地址。
参数3:若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。
char *addr = shmat(shmid, NULL, 0);
返回值:成功返回共享内存存储段的指针(虚拟地址),并且内存将使其与该共享存储段相关的shmid_ds结构中的shm_nattch计数器加1;出错返回-1.
断开共享内存连接
shmdt()
int shmdt(const void *shmaddr);
参数1:shmat()返回的地址
返回:成功返回0,并将shmid_ds结构体中的shm_nattch计数器减1;出错返回-1.
shmdt(addr);
销毁共享内存
shmctl()
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数1:共享内存标识ID
参数2:操作指令,设置位IPC_RMID,表示删除
参数3:设置为NULL
shmctl(shmid, IPC_RMID, NULL);
返回值:成功返回0, 失败返回-1
demo:客户端写入数据,服务端进行读取
//comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define PATHNAME "."
#define PROJ_ID 0x6666
int CreateShm(int size);
int DestroyShm(int shmid);
int GetShm(int size);
#endif
//comm.c
#include "comm.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 | 0666);
}
//client.c
#include "comm.h"
int main()
{
int shmid = GetShm(4096);
printf("shmid:%d\n", shmid);
sleep(1);
char *addr = shmat(shmid, NULL, 0);
int i = 0;
while(i < 20)
{
strcat(addr, "hello");
i++;
sleep(1);
}
shmdt(addr);
sleep(2);
return 0;
}
//server.c
#include "comm.h"
int main()
{
//int shmid = CreateShm(4096);
int shmid = GetShm(4096);
printf("shmid:%d\n", shmid);
char *addr = shmat(shmid, NULL, 0);
int i = 0;
while(i++ < 20)
{
printf("client# %s\n", addr);
sleep(1);
}
shmdt(addr);
sleep(2);
DestroyShm(shmid);
return 0;
}
Makefile:
#.PHONY:all
all:server client
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
#.PHONY:clean
clean:
rm -f client server
客户端:
服务端:
客户端一直在往共享内存中写,服务端读取时会一直增多。需要注意的是,这里并未考虑进程同步问题,可以通过信号量来处理这样的问题。
参考:https://blog.csdn.net/ypt523/article/details/79958188