目录
共享内存示意图(理解共享内存的关键)
shmget函数
第一个参数
ftok函数
使用ftok打印key值
第二个参数
第三个参数
返回值
打印shmget
再谈key值
举例理解key值
共享内存的过程
创建共享内存
关联共享内存
去除关联
编辑
使用共享内存通信
删除共享内存
完整共享内存通信代码
comm.hpp
server.cc
client.cc
makefile
共享内存示意图(理解共享内存的关键)
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
需要通信时:
OS在物理内存上申请一块共享内存空间,让两个进程看到这同一份空间(也就是使两个进程看到同一块资源)。然后将创建好的内存分别通过页表映射进两个进程的地址空间,也称为挂接。
不通信的时候:
1、取消进程与内存的映射关系,也就是去除关联。
2、释放共享内存空间。
System V 共享内存是专门设计出来进程IPC(进程间通信)的。
shmget函数
首先通过man手册来看一下shmget的基本信息。
int shmget(key_t key,size_t size, int shmflg);
第一个参数
首先我们要明白,key能唯一标识我们创建的共享内存!
ftok函数
我们使用ftok函数来决定key的值。
key_t ftok(char *pathname,char proj_id);
pathname就是我们设置的一个路径,proj_id是任意设置的(作用也是为了生成能唯一标识的key)。
ftok可以通过这两个参数来生成一个key值,当我们创建key失败的时候该函数会返回-1。
使用ftok打印key值
comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x66 // 随便设置的
// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if (k < 0)
{
// cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2
// 使用strerror来打印错误码描述
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
#endif
shm_client.cc
#include"comm.hpp"
int main()
{
key_t k = getKey();
printf("0x%x\n", k);
return 0;
}
shm_server.cc
#include"comm.hpp"
int main()
{
key_t k = getKey();
printf("0x%x\n", k);
return 0;
}
makefile
.PHONY:all
all : shm_client shm_server
shm_client : shm_client.cc
g++ - o $@ $ ^ -std = c++11
shm_server:shm_server.cc
g++ - o $@ $ ^ -std = c++11
.PHONY:clean
clean :
rm - f shm_client shm_server
第二个参数
size:也就是共享内存的大小。
第三个参数
有两种设置方式:
1、IPC_CREAT
作用:如果不存在,创建,如果存在,就获取一个新的共享内存。
2、IPC_EXCL | IPC_CREAT
如果不存在,就创建共享内存,如果存在,就出错返回。
用户层面:如果创建成功,一定是一个新的shm(共享内存)。
转定义我们能看到他们底层就用一个二进制位来标定的
返回值
成功会返回一个共享内存标识符,失败就返回-1,未来对该共享内存做操作就可以使用该标识符。
打印shmget
附有详细注释
comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x66 // 随便设置的
#define MAX_SIZE 4096// 设置的内存空间大小
// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if (k < 0)
{
// cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2
// 使用strerror来打印错误码描述
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k, MAX_SIZE, flags);
if (shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
}
// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作 ———— 通过传入不同的flags值
int getShm(key_t k)
{
return getShmHelper(k, IPC_CREAT);
}
// 用于shm_server.cc来进行创建共享内存操作 ———— 通过传入不同的flags值
int creatShm(key_t k)
{
return getShmHelper(k, IPC_CREAT | IPC_EXCL);
}
#endif
shm_client.cc
#include"comm.hpp"
int main()
{
// 使用comm.hpp中的getkey函数来获取k值
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid=getShm(k);
printf("shmid: %d\n",shmid);
return 0;
}
shm_server.cc
#include "comm.hpp"
int main()
{
// 使用comm.hpp中的getkey函数来获取k值
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid=creatShm(k);
printf("shmid: %d\n",shmid);
return 0;
}
makefile
.PHONY:all
all : shm_client shm_server
shm_client : shm_client.cc
g++ - o $@ $ ^ -std = c++11
shm_server:shm_server.cc
g++ - o $@ $ ^ -std = c++11
.PHONY:clean
clean :
rm - f shm_client shm_server
再谈key值
举例理解key值
我们在使用malloc(1024)在堆空间开辟空间的时候,只需要在free(p)中传入一个地址就可以准确的对我们开辟的空间进行释放,并不用传入我们需要开辟空间的大小。
那么OS是如何知道我们开辟了多大的空间呢?
其实,我们向OS要1024的空间,OS并非就给我们只开辟1024的空间。OS为了管理我们,还要给我们额外开辟一些空间!!!
因此,我们的共享内存也要被管理起来。
共享内存=物理内存块+共享内存相关属性
我们申请共享内存的时候也会申请一个数据结构对象,操作系统需要将共享内存管理起来,本质管理的就是共享对应的特定数据结构对象形成的数据结构进行操作的。有点儿类似我们进程中的PCB。
创建共享内存的时候,如何保证共享内存在OS中是唯一的?
key就能保证!
只要另一个进程也看到同一个key就能进行通信!
key在哪儿?
在共享内存的相关属性中。
key是要通过shmget,设置进入共享内存属性中的!用来表示该共享内存,在内核中是唯一的!
shmid vs key
fd vs inode
一个是内核用的id,一个是用户用的id。
至于为什么内核和用户不同用一个id,就类似与我们的身份证号与名字一样。
为什么身份证号已经可以唯一的标识我们了,我们还要用姓名进行标识?
这是一种解耦的体现。
共享内存的过程
共享内存从创建到使用需要经过几个过程。
创建共享内存 --> 关联共享内存 --> 去关联 --> 使用共享内存(通信) --> 删除共享内存
创建共享内存
使用shmget函数进行创建,服务端用来创建,客户端只需要获取我们创建出来的共享内存。
// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if (k < 0)
{
// cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2
// 使用strerror来打印错误码描述
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k, MAX_SIZE, flags);
if (shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
}
// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作 ———— 通过传入不同的flags值
int getShm(key_t k)
{
return getShmHelper(k, IPC_CREAT);
}
// 用于shm_server.cc来进行创建共享内存操作 ———— 通过传入不同的flags值
int creatShm(key_t k)
{
return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
关联共享内存
现在只做了共享内存的创建和删除,还没有让共享内存与对应的内存关联起来!!!!(是通过页表关联的)
// 关联共享内存
void *attachShm(int shmid)
{
void *mem = shmat(shmid, nullptr, 0);
if ((long long)mem == -1L)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
shmid函数
第一个参数
是我们创建共享内存时返回的共享内存地址。
第二个参数
shmaddr:是我们指定一个虚拟地址,让我们的共享内存去映射到该虚拟地址处
第三个参数
shmflg:读写权限问题
关联成功返回值:对应共享内存空间(进程地址空间里面的)的起始地址。类似我们之前使用的malloc,返回值是我们开辟空间的起始地址。把共享内存贴到对应的进程地址空间。
去除关联
// 去除关联
void detachShm(void *start)
{
if (shmdt(start) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
返回值
成功就是0,失败就是-1.
使用共享内存通信
// server.cc
// 使用
while(true)
{
printf("client say : %s\n",start);
sleep(1);
}
// client.cc
// 使用
const char *message = "hello server, 我是另一个进程,正在和你通信";
pid_t id = getpid();
int cnt = 1;
// char buffer[1024];
while (true)
{
sleep(1);
snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
// memcpy(start,buffer,strlen(buffer)+1);
}
客户端向共享内存中发送数据,服务端接受后并打印出来!
删除共享内存
// 删除指定共享内存
void delShm(int shmid)
{
if (shmctl(shmid, IPC_RMID, nullptr) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
我们使用ipcs -m 可以看到我们当前创建的共享内存
ipcrm -m +我们需要删除的shmid就可以删除对应的共享内存
也可以使用shmctl函数进行删除,shmctl本意就是控制共享内存,在这里我们只需要使用删除操作即可!
根据操作不同返回值不同,当我们使用删除操作时,操作失败返回-1
完整共享内存通信代码
comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x66 // 随便设置的
#define MAX_SIZE 4096 // 设置的内存空间大小
// 通过这个函数可以让服务端和客户端生成同一个key
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID);
if (k < 0)
{
// cin, cout,cerr -> stdin, stdout ,sterr -> 0, 1, 2
// 使用strerror来打印错误码描述
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
// 该接口被getShm和creatShm调用
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k, MAX_SIZE, flags);
if (shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
}
// 用于shm_client.cc来进行获取shm_server.cc创建的共享内存操作 ———— 通过传入不同的flags值
int getShm(key_t k)
{
return getShmHelper(k, IPC_CREAT);
}
// 用于shm_server.cc来进行创建共享内存操作 ———— 通过传入不同的flags值
int creatShm(key_t k)
{
return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
// 关联共享内存
void *attachShm(int shmid)
{
void *mem = shmat(shmid, nullptr, 0);
if ((long long)mem == -1L)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
// 去除关联
void detachShm(void *start)
{
if (shmdt(start) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
// 删除指定共享内存
void delShm(int shmid)
{
if (shmctl(shmid, IPC_RMID, nullptr) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
#endif
server.cc
#include "comm.hpp"
// #include<unistd.h>
int main()
{
// 使用comm.hpp中的getkey函数来获取k值
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = creatShm(k);
printf("shmid: %d\n", shmid);
// 五秒后共享内存将通过页表与对应进程地址空间进行关联
sleep(5);
char *start = (char *)attachShm(shmid);
printf("attach success, address start: %p\n", start);
// 使用
while(true)
{
printf("client say : %s\n",start);
sleep(1);
}
// std::cout<<"正在使用"<<std::endl;
// 去关联
detachShm(start);
sleep(5);
// 五秒后共享内存将被删除
delShm(shmid);
return 0;
}
client.cc
#include "comm.hpp"
int main()
{
// 使用comm.hpp中的getkey函数来获取k值
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = getShm(k);
printf("shmid: %d\n", shmid);
sleep(5);
char *start = (char *)attachShm(shmid);
printf("attach success, address start: %p\n", start);
// 使用
const char *message = "hello server, 我是另一个进程,正在和你通信";
pid_t id = getpid();
int cnt = 1;
// char buffer[1024];
while (true)
{
sleep(1);
snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
// snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
// memcpy(start,buffer,strlen(buffer)+1);
}
// 去关联
detachShm(start);
sleep(10);
// done
return 0;
}
makefile
.PHONY:all
all:shm_client shm_server
shm_client:shm_client.cc
g++ -o $@ $^ -std=c++11
shm_server:shm_server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f shm_client shm_server