【Linux】共享内存

news2024/12/23 16:09:54

目录

一、共享内存

1.1 申请共享内存块

1.2 释放共享内存块

1.3 挂接共享内存

二、共享内存的使用

2.1 Server端与Client端

2.2 挂接与运行

三、共享内存总结

3.1 共享内存的特点

3.2 共享内存实现访问控制


一、共享内存

共享内存是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据,减少了拷贝的次数。

共享内存是操作系统提供的。

共享内存=共享内存块+对应的共享内存的内核数据结构。

接下来我们来看看其系统调用接口——shmget。

其作用是返回一个申请的System V的共享内存块。

1.1 申请共享内存块

返回值:

  • 成功则返回有效共享内存的用户层标识符(类似于文件的fd),失败则返回-1并设置错误码。

参数:

1.key:

  • server端根据key值创建共享内存,client端可以通过该唯一的key值找到对应的共享内存块。

2. size:

  • 想创建多大的共享内存

3.shmflg:

  • 有两个参数(IPC_CREAT、IPC_EXCL)
  • IPC_CREAT 表示创建共享内存,如果该共享内存已经存在,则获取该共享内存;如果该共享内存不存在,则创建共享内存并返回。
  • IPC_EXCL单独使用是无意义的,当配合IPC_CREAT使用时,如果该共享内存存在,则出错返回;如果不存在,则创建并返回。所以IPC_CREAT和IPC_EXCL一起使用一定能创建出一个共享内存。
  • 并可以传入内存的访问权限。

关于形成唯一的key值,我们还可以使用一个系统接口——ftok。

该接口的功能是将 pathname 和 proj_id 结合起来,通过算法计算出一个 key 值

返回值:

        通过pathname和proj_id计算出一个System的 key 值。失败返回-1。

参数:

1.pathname:

        传入一个路径,传入路径的的本质就是拿到该路径下的这个文件的inode值。

2.proj_id

        项目id,可以传入0-255之间的整数即可,但是超过了,也会自动进行截断。

所以,接下来就是我们的第一步:创建公共的key值

接下来我们来使用一个ftok生成key值。

#define PATH_NAME "."   //.表示当前文件所在路径,该路径下一定要有权限
#define PROJ_ID 0x66
#define SHM_SIZE 4096    //共享内存的大小,最好是页(4096)大小的整数倍

//Server端代码:
int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);   //key_t 其实就是 int 类型
    Log("create key done", Debug) << "Server key:" << k << endl;
    return 0;
}

//Client端代码:
int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);
    Log("create key done", Debug) << "Client key:" << k << endl;
    return 0;
}

结果如下:两个文件生成的key值相同,这样我们就可以让两个进程访问一个共享内存。

 有了key值的准备,接下来我们使用一下 shmget

    // 2.创建共享内存
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL);
    if (shmid == -1)
    {
        perror("shmget error");
        exit(1);
    }

1.2 释放共享内存块

然后我们来观察结果,发现,在第一次创建时,共享内存确实开辟成功了,可是当我们再次运行时,因为上一次申请的共享内存没有释放,所以导致开辟空间出错。

所以,接下来我们学习一下查看ipc的命令,使用命令 ipcs-m 可以看到开辟出的共享内存块。

所以我们可以使用 ipcrm -m +shmid 来删除开辟的共享内存。因此我们可以知道,Ststem V IPC资源的生命周期是随内核的!

在程序中,我们便可以使用 shmctl 来释放共享内存

使用如下:

 实现一个将十进制转化为十六进制的写法,可以让key值以十六进制进行显示。

1.3 挂接共享内存

 接下来就是将我们的Client端挂接到Server端

shmat:

返回值:

        返回共享内存的起始地址(类似于malloc),失败的话返回-1,并设置错误码。

参数:

1.shmid:

        表示要挂接的共享内的标识符id。

2.shmaddr:

        指定共享内存的起始地址,传入该参数可以对共享内存进行更多的操作;如果只是使用挂接,传入nullptr即可。

3.shmflg:

        表示挂接方式,直接传入0,就表示以读写方式进行挂接。

shmdt:

返回值:

        成功返回0,失败返回-1,并设置错误码。

参数:

        传入共享内存的起始地址即可释放。

首先我们将创建共享内存时的权限设置一下,这样进程才能挂接至共享内存中。

接下来是 shmat 与 shmdt 的使用,如下:

 接下来我们编写一个脚本 while :;do ipcs -m; sleep 1;done 来实时监测共享内存的挂接情况(从0至1),这样程序就挂接上共享内存了。

二、共享内存的使用

2.1 Server端与Client端

我们接下来的目的是让Server端创建一块共享内存,然后Server端和Client同时挂接上共享内存,然后Client不断发送数据,Server端接收数据。

Server端代码(创建、释放、挂接、解挂接)

#define PATH_NAME "/home/wzh" /// 该路径下一定要有权限
#define PROJ_ID 0x66
#define SHM_SIZE 4096 // 共享内存的大小,最好是页(4096)大小的整数倍
#include "comm.hpp"

string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof(buffer), "0x%x", k);
    return buffer;
}

int main()
{
    key_t k = ftok(PATH_NAME, PROJ_ID);
    Log("create key done", Debug) << "Server key:" << TransToHex(k) << endl;
    // 2.创建共享内存
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid == -1)
    {
        perror("shmget error");
        exit(1);
    }
    // 3.将指定的共享内存,链接到自己的地址空间
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    Log("attcah shm done ", Debug) << "shmid : " << shmid << endl;
    printf("shmaddr:%p\n", shmaddr);

    //进行通信:
    //服务端进行读取
    sleep(3);
    for (;;)
    {
        if (*(shmaddr + 55) == 'd')
            break;
        printf("%s\n", shmaddr);
        sleep(1);
    }

    // 4.将指定的共享内存,从自己的地址空间中去关联
    int n = shmdt(shmaddr);
    Log("detach shm done", Debug) << "shmid : " << shmid << endl;
    cout << "n:" << n << endl;

    // 删除共享内存
    shmctl(shmid, IPC_RMID, nullptr); // 选项IPC_RMID 功能是删除共享内存
    Log("rm shm done", Debug) << "shmid : " << shmid << endl;

    return 0;
}

 然后我们编写Client端的代码

#include "comm.hpp"

int main()
{
    // 1.创建公共的key值
    key_t k = ftok(PATH_NAME, PROJ_ID);
    Log("create key done", Debug) << "Client key:" << k << endl;
    //2.寻找共享内存(IPC_CREAT)
    int shmid = shmget(k, SHM_SIZE, IPC_CREAT);
    if (shmid < 0)
    {
        Log("create shm done", Error) << "Client key:" << k << endl;
        exit(2);
    }
    //3.挂接
    char *shmaddr = (char *)shmat(shmid, nullptr, 0);
    printf("shmaddr:%p\n", shmaddr);

    // 4.使用
    // 将共享内存看作一个char形的buffer
    for (char a = 'a'; a <= 'z';a++)
    {
        // 每一次都向shmaddr[共享内存的起始地址进行写入]
        snprintf(shmaddr, SHM_SIZE - 1,
                 "hello serve , 我是Client进程 我的pid:%d,inc:%c\n", getpid(), a);
        sleep(1);
    }

    // 5.去关联
    int n = shmdt(shmaddr);
    assert(n != -1);

    Log("detach shm success", Debug) << "Client key:" << k << endl;
    return 0;
}

2.2 挂接与运行

先运行Server端创建共享内存,然后让client去关联申请的共享内存,然后我们来观察现象:

 现象如下:Server端和Client端成功挂接上共享内存:

接下来将代码运行起来,测试两个进程间能否通信成功:

三、共享内存总结

3.1 共享内存的特点

堆栈相对而生,其中这块区域就是可以存放共享内存、内存映射和共享库,如果双方进程想进行通信,可以直接进行访问,也就是内存级的读和写。

之前在文件操作中,我们使用系统调用read、write这些操作,这些属于内核级操作。而共享内存的使用是直接在进程内部进行操作的,所以效率比管道高,其不用经过内核处理。

共享内存被创建,默认申请为全零。

结论1:只要是通信双方使用shm,一方直接向共享内存中写入数据,另一方面就可以立马看到数据。

共享内存是所有进程间通信(IPC)中速度最快的,不需要过多的拷贝!

共享内存是速度最快的拷贝方式,因为直接访问内存本质是减少了拷贝的次数。

结论2:共享内存缺乏访问控制!会带来并发问题。比如写方还没有将数据写完,读端就将数据读取了。

鉴于共享内存无法实现访问控制,我们可以额外创建管道来实现共享内存的访问控制。

3.2 共享内存实现访问控制

首先我们使用 Init类,当创建对象时,就创建了fifo管道,当进程结束时fifo自动删除。

然后我们定义以下4个接口,OpenFIFO、Wait、Signal、Closefifo本质对应的是open打开fifo文件;Wait对应read等待数据的写入,无数据则阻塞;Signal对应Client端进行write写入数据;Closefifo调用close关闭文件。

class Init
{
public:
    Init()
    {
        umask(0);
        int n = mkfifo("./fifo", 0666);
        assert(n == 0);
        (void)n;
        Log("create fifo success", Notice) << endl;
    }
    ~Init()
    {
        unlink("./fifo");
        Log("create fifo success", Notice) << endl;
    }
};
//创建管道
Init init;

#define READ O_RDONLY
#define WRITE O_WRONLY
int OpenFIFO(string pathname, int flags)
{
    Log("等待中……", Notice) << endl;
    int fd = open(pathname.c_str(), flags);
    assert(fd >= 0);
    return fd;
}

void Wait(int fd)
{
    uint32_t temp = 0;
    ssize_t s = read(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    Log("唤醒中……", Notice) << endl;
}
void Signal(int fd)
{
    uint32_t temp = 1;
    ssize_t s = write(fd, &temp, sizeof(uint32_t));
    assert(s == sizeof(uint32_t));
    (void)s;
}
void Closefifo(int fd)
{
    close(fd);
}
//Server端改动:
  int fd = OpenFIFO("./fifo", READ);
    for (;;)
    {
        Wait(fd);
        printf("%s\n", shmaddr);
        sleep(1);
        if (strcmp(shmaddr, "quit") == 0)
        {
            printf("quit\n");
            break;
        }
    }
    Closefifo(fd);

//Client端改动:
    int fd = OpenFIFO("./fifo", WRITE);
    while (true)
    {
        ssize_t s = read(0, shmaddr, SHM_SIZE - 1);
        Signal(fd);
        if (strcmp(shmaddr, "quit") == 0)
            break;
    }
    Closefifo(fd);

这样,server端就具有了访问控制,如果没有数据,则阻塞等待数据。

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

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

相关文章

xxl-job原理(版本2.3.1)

一、xxl-job架构图 1、调度中心 ​ 负责管理调度信息&#xff0c;按照调度配置发出调度请求&#xff0c;自身不承担业务代码。调度系统与任务解耦&#xff0c;提高了系统可用性和稳定性&#xff0c;同时调度系统性能不再受限于任务模块。 2、执行器 负责接收调度请求并执行任…

CSS注入 2.0

看过CSS注入1.0的朋友&#xff0c;不相信对CSS注入有了一个概念性的理解&#xff0c;在上一篇文章中我只是简单复现了一下波兰老哥的CSS注入过程&#xff0c;阐述了其大致原理。对于其中很大一部分技术细节&#xff0c;代码细节并未做深入的理解(当时我也看不懂&#xff0c;哈哈…

什么是分布式事务

上一篇文章已经讲完分布式了&#xff0c;那暖男说要讲分布式事务那就一定会讲&#xff0c;只是我估计大家没料到暖男这么快就肝好了吧&#xff1f; 事务想必大家并不陌生&#xff0c;至于什么是 ACID&#xff0c;也是老生常谈了。不过暖男为了保证文章的完整性确保所有人都听得…

uniapp-微信小程序分包操作步骤详解

1、在原来的pages的同级下新建一个目录&#xff1a;package1 &#xff08;名字自己随便起。想分几个包就建几个新目录。比如package2、package3……&#xff09; 结构是并列的&#xff1a; 2、把想要放进分包里去的模块都剪切在新目录里面去。 &#xff08;就是以前全都放在…

移动端 - 搜索组件(suggest篇)

这一篇博客是和 search-input篇 衔接的, 需要的可以看上文 移动端 - 搜索组件(search-list篇) 这里我们需要去封装这么一个组件 先说一下大致的方向: 1. 根据父组件传入的关键字数据发送请求获取后端数据, 进行模板渲染 2. 处理一些边界情况(后端返回数据为空, 初次加载数据…

《小猫猫大课堂》三轮3——字符函数和字符串函数及其模拟实现

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重…

C语言——自定义类型详解(结构体,联合体,枚举,位段)

专栏&#xff1a;C语言 个人主页&#xff1a;HaiFan. 专栏简介&#xff1a;本专栏主要更新一些C语言的基础知识&#xff0c;也会实现一些小游戏和通讯录&#xff0c;学时管理系统之类的&#xff0c;有兴趣的朋友可以关注一下。 结构体前言一、结构体1.结构体类型的声明2.结构体…

黑马学ElasticSearch(十)

目录&#xff1a; &#xff08;1&#xff09;自动补全-安装品分词器 &#xff08;2&#xff09;自动补全-自定义分词器 &#xff08;3&#xff09;自动补全-DSL实现自动补全查询 &#xff08;4&#xff09; 自动补全-修改酒店索引库结构 &#xff08;5&#xff09;自动补全…

测试开发——用例篇(如何设计一个测试用例,设计测试用例的一些具体方法)

目录 一、测试用例的基本要素 二、设计测试用例的万能公式 (在没有需求文档的情况下&#xff09; 1、水杯的测试用例 2、一个网站的登录测试用例 三、基于需求进行测试用例的设计 四、测试用例的具体设计方法 1、等价类 2、边界问题 3、判定表&#xff08;因果图&#…

协程和线程的区别、协程原理与优缺点分析、在Java中使用协程

文章目录什么是协程协程的优点与缺点协程实现原理.协程与线程在不同编程语言的实现在Java中使用协程Kilim介绍Kilim整合Java,使用举例小总结什么是协程 相对于协程&#xff0c;你可能对进程和线程更为熟悉。进程一般代表一个应用服务&#xff0c;在一个应用服务中可以创建多个…

源码看CAF的线程调度框架

序 本篇文章带着大家来看下CAF&#xff08;C Actor Framwwork&#xff09;的调度框架&#xff0c;也是算现阶段C比较成熟的调度框架&#xff0c;大家如果自己完成一个比较大型的项目&#xff0c;任务调度也可以参照CAF。 鉴于篇幅较长&#xff0c;大家如果学习使用如何使用CAF…

修改jupyter notebook默认路径

修改jupyter notebook默认路径jupyter notebook默认打开C:\Users\你的用户名&#xff0c;用户名是你的电脑用户名&#xff0c;upload文件又会在C盘生成一堆文件&#xff0c;很乱&#xff0c;用notebook打开文件还要跳转到目录&#xff0c;很麻烦&#xff0c;那有没有办法呢&…

【PYTHON】如何配置集成开发环境Geany

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计学专业大二本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后…

【笔记】大话设计模式17-20

【笔记】大话设计模式17-20 文章目录【笔记】大话设计模式17-2017 适配器模式17.1 Example17.2 定义17.3 Show me the code17.4 总结18 备忘录模式18.1 Example18.2 定义18.3 Show me the code18.4 总结19 组合模式19.1 Example19.2 定义19.3 Show me the code19.4 总结20 迭代…

基于python的人工智能数据处理常用算法

文章目录二分法求解最小二乘法曲线拟合最小二乘法的来历最小二乘法与曲线拟合多项式曲线拟合SciPy内置最小二乘法应用泰勒级数背景引入泰勒公式泰勒级数展开与多项式近似二分法求解 机器学习过程中往往会用到很多变量&#xff0c;而这些变量之间的复杂关系一般用非线性方程来&…

VS系列知识-VS Code的安装+Vue环境的搭建+Vue指令

一、VS Code下载地址 Visual Studio Code - Code Editing. Redefined 二、VS Code初始化设置 1.安装插件 在安装好的VSCode软件的扩展菜单中查找安装如下4个插件 2、创建项目 vscode本身没有新建项目的选项&#xff0c;所以要先创建一个空的文件夹&#xff0c;如project_xx…

【用三大件写出的开门烟花特效】

又到了一年一度的春节时期啦&#xff01;昨天呢是北方的小年&#xff0c;今天是南方的小年&#xff0c;看到大家可以愉快的放烟花&#xff0c;过大年很是羡慕呀&#xff01;辞旧岁&#xff0c;贺新春&#xff0c;今年我呀要放烟花&#xff0c;过春节&#xff01;&#x1f9e8;。…

云原生|kubernetes|2022年底cks真题解析(1-10)

前言&#xff1a; cka和cks认证真的比较恶心&#xff0c;他们的那个PSI Bridge Secure Browser真的非常卡。 吐槽完毕&#xff0c;不废话&#xff0c;直接上真题解析。 CKS总共是16道题&#xff0c;题目顺序是打乱的&#xff0c;由于认证系统非常卡&#xff0c;因此&#xf…

通讯录最终版——动态存储+文件处理

最终版通讯录即从上一个版本修改过来先看总体代码&#xff0c;我们再看看差异ps&#xff1a;里面涉及到很多函数的使用&#xff0c;后续我会出专栏来书写这些函数的使用和实例&#xff0c;与常见错误大家可以通过https://cplusplus.com查找test.c#define _CRT_SECURE_NO_WARNIN…

Spring入门-IOC/DI入门与使用文件配置管理(1)

文章目录Spring入门1&#xff0c;Spring介绍1.1 为什么要学?1.2 学什么?1.3 怎么学?2&#xff0c;Spring相关概念2.1 初识Spring2.1.1 Spring家族2.1.2 了解Spring发展史2.2 Spring系统架构2.2.1 系统架构图2.2.2 课程学习路线2.3 Spring核心概念2.3.1 目前项目中的问题2.3.…