共享内存是System V系列的一种进程间通信的方式。共享内存允许多个毫不相干的进程读取和写入同一块物理内存,当某个进程往共享内存中写入数据时,其它进程就能够立马读取到共享内存中的数据,从而达到进程间通信的目的。这也是所有进程间通信方式中最快的一种。
共享内存的原理
CPU执行语句的过程大致就是先找到当前进程的PCB(进程控制块)里的进程地址空间,然后找到要执行的代码的地址,再通过页表的映射找到该代码实际物理内存的地址,然后CPU执行。
那么假设,如果内存中的一块地方能同时被不同页表映射入各自进程,那么就能够实现不同进程在对同一块内存进行写入和读取数据。
当进程A往shared memory里写入数据的同时,进程B就能立刻从shared memory中读取到数据,这也正是共享内存是进程间通信的方式中最快的原因。
实现共享内存要掌握的函数接口和指令
指令
- 查看系统中当前存在的共享内存
ipcs -m
- 删除系统中的某个共享内存
ipcrm -m [shmid]
函数接口
key_t ftok(const char *pathname, int proj_id);
该函数通过传入一个有效路径(任意)和一个非0整数来生成一个System V IPC密钥,用于标记shared memory。
int shmget(key_t key, size_t size, int shmflg);
该函数的功能就是创建/寻找shared memory,然后返回shared memory的标识符。
参数:
- key:就是上面函数生成的密钥,当创建shared memory时用于标记。当寻找shared memory时用于寻找。
- size:想要申请的memory shared的大小,单位为byte,实际申请是以4KB为单位申请的,并且大小向上兼容(假设填的是5000byte,OS实际申请8KB,但用户仍只能用5000byte)。
- shmflg:一个标志位图,选项一般使用的有IPC_CREAT、IPC_EXCL和文件权限‘0666’。当IPC_CREAT单独使用时,若key对应的shared memory不存在就创建并标记key,若存在就啥都不干;当IPC_CREAT和IPC_EXCL一起使用时, 若key对应的shared memory不存在就创建并标记key,若存在就返回-1报错。
返回值:
- 若成功,就返回shared memory的标识符(有的系统数字可能很大(七位数开始),有的系统数字可能很小(个位数开始))。
- 若失败,就返回-1。
void *shmat(int shmid, const void *shmaddr, int shmflg);
该函数的功能就是将进程与shmid对应的shared memory管理起来(挂接)。
参数:
- shmid:要挂接的shared memory的标识符。
- shmaddr:一般情况下传NULL。(详情可自行man)
- shmflg:一般情况下传0。(详情可自行man)
返回值:
返回shared memory的起始地址,就像malloc一样。
int shmdt(const void *shmaddr);
该函数的功能就是将进程与shmaddr地址起始的shared memory之间的联系切断(去关联)。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
该函数的功能是控制shmid对应的shared memory。
参数:
- shmid:对应的shared memory的标识符。
- cmd:控制命令,一般传IPC_RMID。(详情可自行man)
- buf:当cmd穿IPC_RMID的时候,buf传NULL就可以了。(详情可自行man)
演示代码
comon.hpp
#ifndef _COMMON_HPP_
#define _COMMON_HPP_
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 66
#define MAX_SIZE 4096
key_t getKey() {
key_t k = ftok(PATHNAME, PROJ_ID);
if (k == -1) {
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
int getShm(key_t k, int shmflg) {
int id = shmget(k, MAX_SIZE, shmflg);
if (id == -1) {
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return id;
}
int creatShm(key_t k) {
return getShm(k, IPC_CREAT | IPC_EXCL | 0666);
}
int seekShm(key_t k) {
return getShm(k, IPC_CREAT);
}
void *attachShm(int shmid) {
void *start = shmat(shmid, NULL, 0);
if ((long long)start == -1l) {
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return start;
}
void detachShm(const void *start) {
if (shmdt(start) == -1) {
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(4);
}
}
void deleteShm(int shmid) {
if (shmctl(shmid, IPC_RMID, NULL) == -1) {
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(5);
}
}
#endif
server.cc
#include "common.hpp"
#include <unistd.h>
int main() {
key_t k = getKey();
std::cout << "server get key" << std::endl;
int shmid = creatShm(k);
std::cout << "server get shmid" << std::endl;
char *start = (char*)attachShm(shmid);
std::cout << "server attachShm success" << std::endl;
while (true) {
std::cout << "server get message## " << start << std::endl;
sleep(1);
}
detachShm(start);
std::cout << "server detachShm success" << std::endl;
deleteShm(shmid);
std::cout << "server deleteShm success" << std::endl;
return 0;
}
client.cc
#include "common.hpp"
#include <unistd.h>
#include <cstdio>
int main() {
key_t k = getKey();
std::cout << "client get key" << std::endl;
int shmid = seekShm(k);
std::cout << "client get shmid" << std::endl;
char *start = (char*)attachShm(shmid);
std::cout << "client attachShm success" << std::endl;
int cnt = 0;
while (true) {
snprintf(start, MAX_SIZE, "I am client, pid[%d], times[%d]", getpid(), ++cnt);
sleep(1);
}
detachShm(start);
std::cout << "client detachShm success" << std::endl;
return 0;
}