【Linux通信篇】进程间通信——system V共享内存

--------------------------------------------------------------------------------------------------------------------------------

每日鸡汤:一旦相爱下去,就应共有一株向日葵。不管谁是太阳,都要时时辐射温暖;不管谁是向日葵,都要不渝地仰望。

--------------------------------------------------------------------------------------------------------------------------------

目录

一:直接原理

1.1、申请共享内存

1.2、释放共享内存

二:直接代码展示

2.1、创建共享内存---shmget

2.2、共享内存挂载---shmat

2.3、共享内存去挂载(关联)---shmdt

2.4、控制共享内存---shmctl

2.5、通信代码

三:共享内存的特性

四:扩展内容代码

[4.1 获取共享内存中的属性](#4.1 获取共享内存中的属性)

[4.2 使用管道对共享内存实现同步机制](#4.2 使用管道对共享内存实现同步机制)

五:结语


一:直接原理

进程间通信的本质是:先让不同的进程,看到同一份资源。

1.1、申请共享内存

三步申请法:

  1. 操作系统在内存上去申请一段空间
  2. 将申请到的内存挂接到进程地址空间的共享区
  3. 返回起始虚拟地址

1.2、释放共享内存

释放共享内存:去关联,释放内存。

这些一系列操作(申请、挂载、去关联、释放)并不是进程自己做的,而是由操作系统完成的。即我们用户(进程)通过调用一些系统调用接口让操作系统来做这些事情。因为OS中肯定有很多进程都在相互交互、通信的,那么操作系统内肯定存在多个共享内存。所以操作系统就要把这些共享内存管理起来(先描述,再组织)。故在操作系统内核中肯定有结构体来描述共享内存。

二:直接代码展示

2.1、创建共享内存---shmget

cpp 复制代码
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
  • **参数 key:**决定保证让不同的进程看到同一个共享内存;让我们知道这个共享内存是否存在。
  • **参数 size:**要创建的共享内存的大小,单位是字节。
  • 参数 shmflg:一个标记位。选项 IPC_CREAT:创建一个共享内存,若不存在直接创建,存在直接获取并返回,一般单独使用;选项 IPC_EXCL:若申请的共享内存存在,错误返回,一般不单独使用。选项 IPC_CREAT | IPC_EXCL:创建共享内存,若不存在则创建,存在,出错返回,确保我们申请成功的共享内存一定是一个新的。
  • 返回值:创建共享内存成功,返回共享内存标识符;失败返回-1

再谈参数 key:

  • key是一个数字,数字是几不重要,关键在于它必须在内核中具有唯一性,能够让不同进程进行唯一性标识。
  • 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了。
  • 对于一个已经创建好的共享内存,key哎共享内存中的描述对象中。
  • 第一个创建共享内存的时候,必须已经有一个 key 了。
  • "key 就类似于路径,一定要具有唯一性"

使用 ftok 函数调用来创建一个 key

key VS shmid(共享内存标识符)

  • key:在操作系统内标定唯一性
  • shmid:只在你的进程内,用来表示资源的唯一性

查看共享内存:ipcs -m

共享内存的生命周期是随内核的,只要用户不主动关闭,共享内存会一直存在,除非内核重启或者用户关闭释放。
删除共享内存:ipcrm -m shmid

共享内存的大小,一般建议是 4096 的整数倍,而假设申请 4097 字节的共享内存,实际上操作系统给你的是 4096*2的大小,就会造成资源的浪费。

2.2、共享内存挂载---shmat

cpp 复制代码
       #include <sys/types.h>
       #include <sys/shm.h>

       void *shmat(int shmid, const void *shmaddr, int shmflg);
  • **参数 shmid:**申请共享内存返回的共享内存标识符
  • 参数 shmaddr:由OS决定,一般设为 nullptr
  • **参数 shmflg:**一般设为0即可
  • **返回值:**返回共享内存挂接在地址空间上虚拟的起始地址
cpp 复制代码
// comm.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstring>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include "log.hpp"

Log lg;
const std::string pathname = "/home/alin";
const int proj_id = 0x8888;
const int size = 4096;

// 获取key值
key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key < 0)
    {
        lg(Fatal, "ftok error: %d, strerror: %s", errno, strerror(errno));
        exit(1);
    }
    lg(Info, "ftok success, key is : 0x%x", key);
    return key;
}

// 由flag 来标记怎样申请共享内存
int GetShareMemHelper(int flag)
{
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if(shmid < 0)
    {
        lg(Fatal, "create share memory error: %d, strerror", errno, strerror(errno));
        exit(2);
    }
    lg(Info, "create share memory success, shmid is : %d", shmid);

    return shmid;
}

// 创建共享内存
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

// 获取共享内存
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT);
}
cpp 复制代码
// prcessa.cc
#include "comm.hpp"

int main()
{
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    
    return 0;
}

2.3、共享内存去挂载(关联)---shmdt

  • 参数 shmaddr:挂接时获取的起始虚拟地址
  • 返回值:成功返回0,失败返回-1
cpp 复制代码
#include "comm.hpp"

int main()
{
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    // ipc code

    // 共享内存去挂载
    shmdt(shmaddr);
    
    return 0;
}

2.4、控制共享内存---shmctl

  • 参数 cmd:决定做什么操作。IPC_STAT:将内核中共享内存中的属性拷贝到 buff 中;IPC_RMID:删除共享内存,将该共享内存标记为0。
  • 参数 struct shmid_ds:一个共享内存的结构体,内部保存了共享内存的属性
cpp 复制代码
// processa.cc
#include "comm.hpp"

int main()
{
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    // ipc code

    // 共享内存去挂载
    shmdt(shmaddr);
    // 释放共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}

2.5、通信代码

cpp 复制代码
// processa.cc
#include "comm.hpp"

int main()
{
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    // ipc code
    while (true)
    {
        std::cout << "client say# " << shmaddr << std::endl;
        sleep(1);
    }

    // 共享内存去挂载
    shmdt(shmaddr);
    // 释放共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}
cpp 复制代码
// processb.cc
#include "comm.hpp"

int main()
{
    // 获取共享内存
    int shmid = GetShm();
    // 挂载
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    // ipc code
    while(true)
    {
        std::cout << "Please Enter@ ";
        fgets(shmaddr, 4094, stdin);
    }
    shmdt(shmaddr);
    
    return 0;
}

一旦有了共享内存,并且已经挂接到自己的地址空间中了,直接把它当成你的内存空间来使用即可,不需要调用用系统调用接口了,一旦有人把数据写到共享内存,其实我们立马就能看见了,不需要经过系统调用直接就能看到数据了。

三:共享内存的特性

  • 共享内存没有同步互斥之类的保护机制
  • 共享内存是所有的进程间通信中,速度最快的。(数据拷贝次数少)
  • 共享内存中内部的数据,由用户自己维护

四:扩展内容代码

4.1 获取共享内存中的属性

通过 shmctl 函数来获取共享内存中属性。

cpp 复制代码
#include "comm.hpp"

int main()
{
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    // ipc code
    struct shmid_ds shmds;
    while (true)
    {
        std::cout << "client say# " << shmaddr << std::endl;
        sleep(1);

        shmctl(shmid,IPC_STAT, &shmds); // shmds输出型参数
        std::cout << "shm nattch: " << shmds.shm_nattch << std::endl;
        std::cout << "shm size: " << shmds.shm_segsz << std::endl;
        std::cout << "shm mode: " << shmds.shm_perm.mode << std::endl;
        printf("shm key: 0x%x\n", shmds.shm_perm.__key);
        sleep(5);
    }

    // 共享内存去挂载
    shmdt(shmaddr);
    // 释放共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}

4.2 使用管道对共享内存实现同步机制

cpp 复制代码
// comm.hpp
#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <stdlib.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <sys/types.h>
#include <sys/stat.h>

#include "log.hpp"

Log lg;
const std::string pathname = "/home/alin";
const int proj_id = 0x6666;
// const int proj_id = 1024 ^ getpid();
const int size = 4096;

// 获取key值
key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if (key < 0)
    {
        lg(Fatal, "ftok error: %d, strerror: %s", errno, strerror(errno));
        exit(1);
    }
    lg(Info, "ftok success, key is : 0x%x", key);
    return key;
}

// 由flag 来标记怎样申请共享内存
int GetShareMemHelper(int flag)
{
    key_t k = GetKey();
    int shmid = shmget(k, size, flag);
    if (shmid < 0)
    {
        lg(Fatal, "create share memory error: %d, strerror: %s", errno, strerror(errno));
        exit(2);
    }
    lg(Info, "create share memory success, shmid is : %d", shmid);

    return shmid;
}

// 创建共享内存
int CreateShm()
{
    return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}

// 获取共享内存
int GetShm()
{
    return GetShareMemHelper(IPC_CREAT);
}

// 使用有名管道来实现同步机制
#define FIFO_FILE "./myfifo"
#define MODE 0666

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_DELETE_ERR,
    FIFO_OPEN_ERR
};

class Init
{
public:
    Init()
    {
        // 创建管道
        int n = mkfifo(FIFO_FILE, MODE);
        if (n < -1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {

        int m = unlink(FIFO_FILE);
        if (m < -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};
cpp 复制代码
// processa.cc
#include "comm.hpp"

int main()
{
    Init init;
    // 创建共享内存
    int shmid = CreateShm();
    // 挂接共享内存
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    int fd = open(FIFO_FILE, O_RDONLY);
    if(fd < 0)
    {
        lg(Fatal, "err code: %d, error string: %s", errno, strerror(errno));
        exit(FIFO_OPEN_ERR);
    }
    // ipc code
    struct shmid_ds shmds;
    while (true)
    {
        char c;
        ssize_t s = read(fd, &c, 1);
        if(s <= 0) break;

        std::cout << "client say# " << shmaddr << std::endl;
        // sleep(1);

        // shmctl(shmid,IPC_STAT, &shmds); // shmds输出型参数
        // std::cout << "shm nattch: " << shmds.shm_nattch << std::endl;
        // std::cout << "shm size: " << shmds.shm_segsz << std::endl;
        // std::cout << "shm mode: " << shmds.shm_perm.mode << std::endl;
        // printf("shm key: 0x%x\n", shmds.shm_perm.__key);
        // sleep(5);
    }

    // 共享内存去挂载
    shmdt(shmaddr);
    // 释放共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}
cpp 复制代码
// processb.cc
#include "comm.hpp"

int main()
{
    // 获取共享内存
    int shmid = GetShm();
    // 挂载
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);

    int fd = open(FIFO_FILE, O_WRONLY);
    if(fd < 00)
    {
        lg(Fatal, "err code: %d, error string: %s", errno, strerror(errno));
        exit(FIFO_OPEN_ERR);
    }
    // ipc code
    while(true)
    {
        std::cout << "Please Enter@ ";
        fgets(shmaddr, 4094, stdin);

        write(fd, "c", 1);
    }
    shmdt(shmaddr);

    return 0;
}

原理:没有管道时,a进程会一直读取共享内存中的数据,有了管道后,a进程在读取共享内存中的数据之前,会先从管道中读取,这个数据是随便的,只是为了读到特定信号,读取信号之后才可从共享内存中读取,否则一直阻塞在管道那里。当b进程向共享内存写入数据,写入数据之后,会给管道发送一个写入信号,而a进程阻塞在管道那里就是为了等待这一信号,当等待成功该信号中,a进程会立即读取共享内存中的数据,此时b进程已经向共享内存中写入了。这样就能实现共享内存中的同步与互斥。

五:结语

今天的分享到这里就结束了,如果觉得文章还可以的话,就一键三连支持一下欧。各位的支持就是捣蛋鬼前进的动力。

相关推荐
Hello-Mr.Wang29 分钟前
nginx与openSSL版本不兼容问题
linux·服务器·nginx
故事与他64532 分钟前
upload-labs-master通关攻略(13~16)
java·服务器·前端·安全·网络安全
他不爱吃香菜39 分钟前
HTTPS工作原理与安全机制详解(仅供参考)
运维·网络·信息与通信
cdut_suye1 小时前
全面剖析 Linux 进程管理与 PCB 机制
java·linux·运维·服务器·c++·人工智能·python
Chenyu_3101 小时前
04.基于C++实现多线程TCP服务器与客户端通信
linux·服务器·网络·c++·tcp/ip·算法·visualstudio
程序媛刘刘1 小时前
uniappx 使用体验
java·服务器·前端
程序员的世界你不懂1 小时前
移动Android和IOS自动化中常见问题
android·运维·自动化
zzyh1234561 小时前
springcloudalibaba负载均衡组件
运维·负载均衡
奔波霸的伶俐虫2 小时前
liunx磁盘挂载和jar启动命令
linux·运维·服务器
都市前线2 小时前
格雷希尔: G80P系列在制动卡钳行业自动化应用
运维·自动化