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

五:结语

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

相关推荐
雯宝2 小时前
mnt_init 函数
linux
律宏阔8 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔8 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴8 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千8 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei378 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei378 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei378 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh8 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW8 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux