共享内存是System V系列的一种进程间通信的方式。共享内存允许多个毫不相干的进程读取和写入同一块物理内存,当某个进程往共享内存中写入数据时,其它进程就能够立马读取到共享内存中的数据,从而达到进程间通信的目的。这也是所有进程间通信方式中最快的一种。

共享内存的原理

CPU执行语句的过程大致就是先找到当前进程的PCB(进程控制块)里的进程地址空间,然后找到要执行的代码的地址,再通过页表的映射找到该代码实际物理内存的地址,然后CPU执行。

android 共享内存原理 共享内存通信的原理_linux

那么假设,如果内存中的一块地方能同时被不同页表映射入各自进程,那么就能够实现不同进程在对同一块内存进行写入和读取数据。

android 共享内存原理 共享内存通信的原理_网络_02

当进程A往shared memory里写入数据的同时,进程B就能立刻从shared memory中读取到数据,这也正是共享内存是进程间通信的方式中最快的原因。

实现共享内存要掌握的函数接口和指令

指令

  1. 查看系统中当前存在的共享内存
ipcs -m
  1. 删除系统中的某个共享内存
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的标识符。

参数:

  1. key:就是上面函数生成的密钥,当创建shared memory时用于标记。当寻找shared memory时用于寻找。
  2. size:想要申请的memory shared的大小,单位为byte,实际申请是以4KB为单位申请的,并且大小向上兼容(假设填的是5000byte,OS实际申请8KB,但用户仍只能用5000byte)。
  3. shmflg:一个标志位图,选项一般使用的有IPC_CREATIPC_EXCL文件权限‘0666’。当IPC_CREAT单独使用时,若key对应的shared memory不存在就创建并标记key,若存在就啥都不干;当IPC_CREAT和IPC_EXCL一起使用时, 若key对应的shared memory不存在就创建并标记key,若存在就返回-1报错。

返回值:

  1. 若成功,就返回shared memory的标识符(有的系统数字可能很大(七位数开始),有的系统数字可能很小(个位数开始))。
  2. 若失败,就返回-1。
void *shmat(int shmid, const void *shmaddr, int shmflg);

该函数的功能就是将进程与shmid对应的shared memory管理起来(挂接)。

参数:

  1. shmid:要挂接的shared memory的标识符。
  2. shmaddr:一般情况下传NULL。(详情可自行man)
  3. 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。

参数:

  1. shmid:对应的shared memory的标识符。
  2. cmd:控制命令,一般传IPC_RMID。(详情可自行man)
  3. buf:当cmd穿IPC_RMID的时候,buf传NULL就可以了。(详情可自行man)

演示代码

android 共享内存原理 共享内存通信的原理_网络_03

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;
}