【Linux】进程间通信 -- 共享内存

news2024/11/20 4:57:13

共享内存

共享内存是SystemV标准进程间通信的一种,该标准还有消息队列和信号量,但下文主要介绍共享内存,然后在谈一下信号量的内容。SystemV标准的进程间通信可以看做单机版的进程间通信。

// 1. log.hpp
#pragma once

#include <iostream>

enum ErrLevel
{
    lev_0,
    lev_1,
    lev_2,
    lev_3,
    lev_4
};

const std::string error[] = {
    "err_0",
    "err_1",
    "err_2",
    "err_3",
    "err_4"
};

std::ostream& Log(const std::string& msg, int level)
{
    std::cout << " | " << (unsigned int)time(0) << " | " << error[level] << " | " << msg << " |";
    return std::cout;
}
// 2. comm.hpp
#pragma once

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

using namespace std;

#include "log.hpp"

// key_t ftok(const char *pathname, int proj_id);
#define PATH_NAME "/home/zs/linux/testcpp"
#define PROJ_ID 0x20231118

// 共享内存的大小,最好是页(PAGE:4096)的整数倍
#define SHM_SIZE 4096

#define MODE 0666
#define FIFO_PATH "./fifo"

class FIFO
{
public:
    FIFO()
    {
        if(0 != mkfifo(FIFO_PATH, MODE))
        {
            perror("mkfifo");
            exit(1);
        }
        Log("create fifo success", lev_1) << endl;
    }

    ~FIFO()
    {
        if(0 != unlink(FIFO_PATH))
        {
            perror("unlink");
            exit(2);
        }
        Log("unlink fifo success", lev_2) << endl;
    }
};

int openFIFO(const string& pathname, int flags)
{
    int fd = open(pathname.c_str(), flags);
    if(fd == -1)
    {
        perror("open");
        exit(3);
    }

    return fd;
}

void Wait(int fd)
{
    uint32_t i = 0;
    ssize_t size = read(fd, &i, sizeof(i));
    if(size == -1)
    {
        perror("Wait::read");
        exit(4);
    }
}

void Signal(int fd)
{
    uint32_t i = 1;
    ssize_t size = write(fd, &i, sizeof(i));
    if(size == -1)
    {
        perror("Signal::write");
        exit(5);
    }
}

void closeFIFO(int fd)
{
    close(fd);
}

创建共享内存使用shmget函数。
key:只有创建的时候用到key(key是调用ftok用算法形成的,标识了系统层上的唯一性);大部分情况下用户访问共享内存用的还是shmid(shmget的返回值,它的使用类似文件描述符fd,标识了用户层上的唯一性)。
size:设置创建的共享内存的大小。
shmflg

  • IPC_CREAT:单独使用,如果共享内存不存在,创建并返回;如果已经存在,获取并返回。
  • IPC_EXCL:单独使用,没有意义。
  • IPC_CREAT | IPC_EXCL:共同使用,如果共享内存不存在,创建并返回;如果已经存在,出错返回(如果返回成功,一定是一个全新的共享内存)。
    在这里插入图片描述
    创建共享内存之前key的生成由ftok接口完成。
    ftok可以确保接收到相同的pathnameproj_id会使用算法生成相同的key
    而不同进程通过拿到相同的key值来看到同一份资源。
    在这里插入图片描述
// 3. server.hpp
#include "comm.hpp"

// 程序在加载时,自动构建全局变量,自动调用fifo的构造函数,创建管道文件
// 程序退出时,全局变量会被析构,自动调用fifo的析构函数,删除管道文件
FIFO fifo;

void test()
{
    // 通信的前置工作,让不同进程看到同一份资源(内存)
    // 1.创建公共的key值
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key == -1)
    {
        perror("ftok");
        exit(1);
    }
    Log("server create key success", lev_1) << " server key: " << key << endl;

    // 2.创建共享内存 -- 建议创建一个全新的共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | MODE);
    if(shmid == -1)
    {
        perror("shmget");
        exit(2);
    }
    Log("server create shm success", lev_2) << " shmid: " << shmid << endl;

    // 3.将共享内存挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if((void*)shmaddr == (void*)-1)
    {
        perror("shmaddr");
        exit(3);
    }
    Log("server attach shm success", lev_3) << " shmaddr: " << (void*)shmaddr << endl;

    // 4.通信 -- 将共享内存看做字符串存储空间
    // 关于共享内存的通信有两个结论:
    // a.通信双方一方可以直接向共享内存写数据,另一方可以立刻看到写的数据
    //   共享内存是所有进程间通信(IPC)速度最快的。因为它不需要过多的拷贝(表现就是不需要将数据给到操作系统)
    //   共享内存映射进进程地址空间的共享区(在用户空间内),所以不需要经过系统调用,可以直接访问,双方进程通信属于内存级的读和写。
    // b.共享内存缺乏访问控制,会引起并发问题
    // 此处采用管道的访问控制功能,来对共享内存进行一定的访问控制
    int fd = openFIFO(FIFO_PATH, O_RDONLY);
    while(true)
    {
        Log("server wait...", lev_2) << endl;
        Wait(fd);

        cout << "server output: " << shmaddr << endl;
        if(strcmp(shmaddr, "quit") == 0)
        {
            cout << "client quit, then server quit" << endl;
            break;
        }
        // sleep(1);
    }
    closeFIFO(fd);
    // sleep(10);

    // 5.将共享内存从地址空间中祛关联
    int dt = shmdt(shmaddr);
    if(dt == -1)
    {
        perror("shmdt");
        exit(4);
    }
    Log("server detach shm success", lev_4) << endl;

    // 6.删除共享内存 -- IPC_RMID: 即使当前还有进程和shm挂接,依旧会删除shm
    int ipcrm = shmctl(shmid, IPC_RMID, nullptr);
    if(ipcrm == -1)
    {
        perror("shmctl");
        exit(5);
    }
    Log("server rm shm success", lev_1) << endl;
}

共享内存的提供者是操作系统,当进程运行结束时,共享内存是还存在着的,其生命周期是随操作系统的。共享内存有两种删除方式,一是手动删除,二是利用代码调用接口删除。
在这里插入图片描述
可以调用shmctl接口进行删除。
cmd:填写删除指令IPC_RMID
buf:是用于描述共享内存结构的结构体指针,使用buf可以对共享内存的结构进行更改。
这里可以引出对共享内存的重新理解:共享内存 = 共享内存块 + 对应的共享内存的内核数据结构。
操作系统为了管理大量的共享内存,需要先描述再组织,进行管理。
在这里插入图片描述
buf所指向的共享内存的结构体。
在这里插入图片描述
共享进程创建好后,进程要访问共享进程还需要对其进行挂接(将共享内存映射进进程所在地址空间中)。
shmaddr:除非自己特别清楚要将共享内存挂接在什么位置,否则nullptr让系统自己处理。
在这里插入图片描述

// 4. client.cpp
#include "comm.hpp"

void test()
{
    // 1.获取key
    Log("client pid is:", lev_0) << " " << getpid() << endl;
    key_t key = ftok(PATH_NAME, PROJ_ID); // key_t -- int
    if(key == -1)
    {
        perror("ftok");
        exit(1);
    }
    Log("client create key success", lev_1) << " client key: " << key << endl;

    // 2.获取共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT);
    if(shmid == -1)
    {
        perror("shmget");
        exit(2);
    }
    Log("client get shm success", lev_2) << " shmid: " << shmid << endl;

    // 3.将共享内存挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid, nullptr, 0);
    if((void*)shmaddr == (void*)-1)
    {
        perror("shmaddr");
        exit(3);
    }
    Log("client attach shm success", lev_3) << " shmaddr: " << (void*)shmaddr << endl;

    // 4.通信
    int fd = openFIFO(FIFO_PATH, O_WRONLY);
    while(true)
    {
        write(1, "client input: ", strlen("client input: "));
        ssize_t size = read(0, shmaddr, SHM_SIZE);
        if(size == -1)
        {
            perror("read");
            exit(5);
        }

        shmaddr[size - 1] = '\0';
        Signal(fd);
        if(strcmp(shmaddr, "quit") == 0) break;
    }
    closeFIFO(fd);

    // 5.祛关联
    int dt = shmdt(shmaddr);
    if(dt == -1)
    {
        perror("shmdt");
        exit(4);
    }
    Log("client detach shm success", lev_4) << endl;
}

在这里插入图片描述

信号量

这里不谈消息队列的问题,但消息队列的接口使用和共享内存都是相似的。
在这里插入图片描述
临界资源:多个进程(执行流)看到的一份公共的资源。
临界区:进程里,访问临界资源的代码部分。
由临界资源和临界区的概念可以知道,多个进程(执行流),同时运行时会互相干扰,主要是不加保护地访问了同一份资源(临界资源)。而再分临界区,多个进程(执行流)是互不影响的。
互斥:为了更好地进行临界资源的保护,可以让多个进程(执行流)在任何时刻,只能有一个进入临界区。
原子性:要么不做,要么做完,没有中间状态。

信号量的出现让任何一个进程要访问临界资源时,不能直接访问,而是要先申请信号量(这里可以看出信号量也属于临界资源,可以被多个进程看到)。
信号量本质是一个计数器,申请信号量的本质就是让计数器减一。
只要申请信号量成功,在临界资源内部一定会预留有该进程要访问的资源。
而申请信号量的本质,是对临界资源的一种预定机制。
申请信号量(P操作)和释放信号量(V操作)必须是原子的。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1225673.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

100张照片带你了解真实的日本人

欢迎关注「苏南下」 在这里分享我的旅行和影像创作心得 今年三个月内去了两次日本旅行&#xff0c;到了东京、横滨、大阪、京都、奈良、富士山、神户、富士山等城市&#xff0c;途中一共拍下了10000张照片。 最近整理照片的过程中&#xff0c;发现也拍了许多有意思的人像照&…

〖大前端 - 基础入门三大核心之JS篇㊲〗- DOM改变元素节点的css样式、HTML属性

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;不渴望力量的哈士奇(哈哥)&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xf…

应用场景丨迭代市政综合管廊监测系统建设

市政综合管廊是指在城市地下建造的隧道空间&#xff0c;将市政、电力、通讯、燃气、给排水等各种管线集于一体&#xff0c;实施统一规划、设计、建设和管理。综合管廊有利于解决反复开挖路面、架空线网密集、管线事故频发等问题&#xff0c;是保障城市运行的重要基础设施和“生…

Ubuntu 22.04安装Rust编译环境并且测试

我参考的博客是《Rust使用国内Crates 源、 rustup源 |字节跳动新的 Rust 镜像源以及安装rust》 lsb_release -r看到操作系统版本是22.04,uname -r看到内核版本是uname -r。 sudo apt install -y gcc先安装gcc&#xff0c;要是结果给我的一样的话&#xff0c;那么就是安装好了…

ElasticSearch快速入门

一、全文检索 1、什么是全文检索 全文索引是一种通过对文本内容进行全面索引和搜索的技术。它可以快速的在大量文本数据中查找包含特定关键词或短语的文档&#xff0c;并返回相关的搜索结果。 全文检索广泛应用于各种信息管理系统和应用中&#xff0c;如搜索引擎、文档管理系…

项目踩坑之面试遇到的问题及解决

第一点&#xff1a; 问题 遇到的问题之&#xff1a;在实现后台管理端-账户操作的时候&#xff0c;添加员工的时候如果重复添加同一个员工&#xff0c;会触发一个数据库唯一约束异常&#xff0c;但客户端无法清晰的理解这个错误&#xff0c;所以我们就对新增员工的代码进行try…

【数据结构】树与二叉树(二十):树获取大儿子、大兄弟结点的算法(GFC、GNB)

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语 5.2 二叉树5.3 树5.3.1 树的存储结构1. 理论基础2. 典型实例3. Father链接结构4. 儿子链表链接结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法1. 获取大儿子结点的算法&#xff08;GFC&#xff09;…

Linux系统yum安装

目录 一.yum配置文件 1.yum主配置文件 2.yum仓库文件 3.yum日志文件 二.yum命令 1.yum查询命令 ①. yum list [软件名] ②. yum info [软件名] ③. yum search <关键词> ④. yum provides <关键词> ⑤. yum grouplist [包组名] ⑥. yum groupinfo <包…

python基础练习题库实验2

题目1 编写一个程序&#xff0c;要求用户输入产品代码、产品名称、产品尺寸和产品价格。 然后使用字符串格式来显示产品信息&#xff0c;就像下面的示例一样。 请注意&#xff0c;价格必须使用两位十进制数字显示。 代码 product_code input("Enter product code: &q…

智能售货柜:小本投资的不二之选

智能售货柜&#xff1a;小本投资的不二之选 智能售货柜的运营优势在于&#xff1a;一是降低运营成本&#xff0c;不需要大量员工&#xff1b;二是具备自动识别和智能结算功能&#xff0c;提高运营效率&#xff1b;三是提供数据分析&#xff0c;优化产品和服务。相比传统零售店&…

趣学python编程 (一、计算机发展历史)

未来是高度科技化和智能化的时代。过去不识字的叫“文盲”&#xff0c;如今不懂点计算机知识&#xff0c;则可能是新时代的“文盲”。不论从事什么行业&#xff0c;了解下计算机和编程都是有益的。Python 连续多年占据最受欢迎的编程语言榜首&#xff0c;未来Python有机会成为像…

Docker 可视化面板 ——Portainer

Portainer 是一个非常好用的 Docker 可视化面板&#xff0c;可以让你轻松地管理你的 Docker 容器。 官网&#xff1a;Portainer: Container Management Software for Kubernetes and Docker 【Docker系列】超级好用的Docker可视化工具——Portainer_哔哩哔哩_bilibili 环境 …

读像火箭科学家一样思考笔记02_与不确定性共舞(下)

1. 万有理论 1.1. 相对论 1.1.1. 适用于体积非常大的物体 1.2. 量子力学 1.2.1. 适用于非常小的物体 1.2.2. 在量子力学诞生之前&#xff0c;物理学一直强调的是因果关系&#xff0c;即做这件事&#xff0c;就会得到那个结果 1.2.3. 量子力学讲的似乎是&#xff1a;当我们…

ChinaSoft 论坛巡礼 | 新兴系统软件论坛

2023年CCF中国软件大会&#xff08;CCF ChinaSoft 2023&#xff09;由CCF主办&#xff0c;CCF系统软件专委会、形式化方法专委会、软件工程专委会以及复旦大学联合承办&#xff0c;将于2023年12月1-3日在上海国际会议中心举行。 本次大会主题是“智能化软件创新推动数字经济与社…

golang学习笔记——接口

文章目录 Go 语言接口例子空接口空接口的定义空接口的应用空接口作为函数的参数空接口作为map的值 类型断言接口值 类型断言例子001类型断言例子002 Go 语言接口 接口&#xff08;interface&#xff09;定义了一个对象的行为规范&#xff0c;只定义规范不实现&#xff0c;由具…

如何在3DMax中使用超过16个材质ID通道?

3DMAX效果通道扩展插件EffectsChannelEx教程 3DMax的材质ID通道允许我们生成渲染元素&#xff0c;这些元素可用于在合成或其他软件中产生处理或特殊效果。如对渲染或动画进行颜色校正。你可以在Photoshop中为你的静态3D渲染图像做这件事。或者使用After Effects、Blackmagic Fu…

gRPC 四模式之 服务器端流RPC模式

服务器端流RPC模式 在一元 RPC 模式中&#xff0c;gRPC 服务器端和 gRPC 客户端在通信时始终只有一个请求和一个响应。在服务器端流 RPC 模式中&#xff0c;服务器端在接收到客户端的请求消息后&#xff0c;会发回一个响应的序列。这种多个响应所组成的序列也被称为“流”。在…

遗传算法GA-算法原理与算法流程图

本站原创文章&#xff0c;转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一、遗传算法流程图 1.1. 遗传算法流程图 二、遗传算法的思想与机制 2.1 遗传算法的思想 2.2 遗传算法的机制介绍 三、 遗传算法的算法流程 3.1 遗传算法的算法…

深度学习——(生成模型)DDPM

前置数学知识 1、先验概率和后验概率 先验概率&#xff1a;根据以往经验和分析得到的概率,它往往作为“由因求果”问题中的“因”出现&#xff0c;如 q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt​∣xt−1​) 后验概率&#xff1a;指在得到“结果”的信息后重新修正的概率,是…

【数据库】数据库连接池导致系统吞吐量上不去-复盘

在实际的开发中&#xff0c;我们会使用数据库连接池&#xff0c;但是如果不能很好的理解其中的含义&#xff0c;那么就可以出现生产事故。 HikariPool-1 - Connection is not available, request timed out after 30001ms.当系统的调用量上去&#xff0c;就出现大量这样的连接…