【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进程已经向共享内存中写入了。这样就能实现共享内存中的同步与互斥。

五:结语

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

相关推荐
xmweisi0215 分钟前
Ansible内置模块之package
linux·ansible·rhce·rhca·红帽认证·it培训
xmweisi0216 分钟前
Ansible内置模块之service
linux·ansible·rhce·rhca·红帽认证
大神的风范21 分钟前
从0开始学linux韦东山教程第一三章问题小结(1)
linux·服务器
wgc2k24 分钟前
Java游戏服务器开发流水账(3)游戏数据的缓存简介
服务器·游戏
liuze4081 小时前
使用 docker 安装 nacos3.x
运维·docker·容器
橙色小博1 小时前
Python中的re库详细用法与代码解析
linux·python·正则表达式·php·re
10000hours2 小时前
【SGL】Scatter-Gather List内存传输技术
linux·数据结构·网络协议·list·存储·sgl
某不知名網友2 小时前
linux_进程地址空间(虚拟地址空间)
java·linux·算法
白总Server2 小时前
微软系统 红帽系统 网络故障排查:ping、traceroute、netstat
linux·运维·服务器·microsoft·中间件·架构·github
家庭云计算专家2 小时前
一键设置动态域名+ipv6内网直通访问ssh服务-家庭云计算专家
运维·docker·容器·云计算·ssh·onlyoffice