『Linux从入门到精通』第 ㉕ 期 - System V 共享内存

news2024/9/24 21:27:27

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧共享内存原理
  • 🐧共享内存相关函数
    • 🐦key 与 shmid 区别
  • 🐧代码实例

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

共享内存是一种进程间通信的机制,允许多个进程访问同一块物理内存,以实现数据的共享。通过共享内存,进程可以直接读写共享的内存区域,而无需通过中间的数据传输机制(例如管道或消息队列)进行通信,因此共享内存是最快的IPC形式。

共享内存示意图
在这里插入图片描述

🐧共享内存原理

  1. 创建共享内存: 在一个进程中调用系统调用(例如 shmget),请求创建一块共享内存。这个调用需要指定内存的大小以及一些标志,以控制共享内存的权限和行为。

  2. 关联共享内存: 其他进程通过调用系统调用(例如 shmat)将共享内存附加到它们的地址空间中。这个调用返回指向共享内存区域的指针,使得进程可以直接读写这块内存。

  3. 读写共享内存: 一旦多个进程都关联了同一块共享内存,它们就可以直接对这块内存进行读写操作,就像操作普通的内存一样。因为它们共享同一块物理内存,一个进程对共享内存的修改会立即反映到其他进程的视图中。

  4. 分离共享内存: 当进程不再需要访问共享内存时,它可以调用系统调用(例如 shmdt)将共享内存从它的地址空间中分离。这并不会导致共享内存的删除,只是使得该进程无法再访问这块内存。

  5. 删除共享内存: 当不再需要使用共享内存时,一个进程可以调用系统调用(例如 shmctl)请求删除共享内存。这会导致释放共享内存所占用的系统资源。

特点和注意事项:

  • 共享内存提供了高效的进程间通信方式,因为数据直接存储在物理内存中,无需复制或转移。
  • 进程需要谨慎地协调对共享内存的访问,以避免数据一致性问题。例如,可以使用互斥锁等同步机制。
  • 共享内存的使用需要确保不同进程使用相同的数据结构和协议,以便正确地进行数据交换和共享。
  • 在使用共享内存时,应注意防范竞态条件和死锁等并发编程的问题。

🐧共享内存相关函数

  1. 创建共享内存区域: 使用 shmget 函数创建一个共享内存区域。该函数的原型为:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int shmget(key_t key, size_t size, int shmflg);
    
    • key 是一个用于标识共享内存的键值。
    • size 是要分配的共享内存的大小(字节数)。
    • shmflg 是一组标志,通常使用 IPC_CREAT 表示如果内存不存在则创建。
  2. 连接到共享内存区域: 使用 shmat 函数将进程连接到已经存在的共享内存区域。该函数的原型为:

    #include <sys/types.h>
    #include <sys/shm.h>
    
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    • shmid 是共享内存区域的标识符,由 shmget 返回。
    • shmaddr 通常设置为 NULL,让系统自动选择合适的地址。
    • shmflg 可以为 0。
  3. 使用共享内存: 一旦连接到共享内存,进程就可以直接在这块内存中读写数据。

  4. 分离共享内存: 使用 shmdt 函数将进程与共享内存脱离。该函数的原型为:

    #include <sys/types.h>
    #include <sys/shm.h>
    
    int shmdt(const void *shmaddr);
    
    • shmaddr 是连接到共享内存区域的地址。
  5. 删除共享内存区域: 使用 shmctl 函数可以删除或控制共享内存区域的属性。如果不再需要共享内存,可以使用 shmctl 函数的 IPC_RMID 命令删除它。函数原型为:

    #include <sys/ipc.h>
    #include <sys/shm.h>
    
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
    • shmid 是共享内存区域的标识符。
    • cmd 为控制命令,可以使用 IPC_RMID 表示删除共享内存。

这些函数提供了对共享内存的创建、连接、使用和删除的基本操作。

🐦key 与 shmid 区别

在上面所示的接口中,shmget 函数需要一个 key_t 类型的参数 key。而其他的函数多数用到 shmid 而不会用到 key,那么这两个参数分别是什么,有什么区别呢?

  1. key(键值):

    • key 是一个整数,用于在共享内存创建过程中唯一标识一个共享内存段。
    • 它并不是由系统自动生成的,而是由应用程序提供的,通常以某种方式与程序的逻辑相关。
    • 可以使用 ftok 函数将路径名和一个整数标识符转换为 key,以便在创建共享内存时使用。
    • key 通常用于在不同的进程之间共享相同的内存块,因此它是创建共享内存的关键参数之一。
  2. shmid(共享内存标识符):

    • shmid 是一个由系统生成的标识符,用于标识已经创建的共享内存段。
    • 在使用 shmget 函数创建共享内存时,它通过返回值返回给调用者,用于后续的操作。
    • shmid 是由系统内核分配的,通常是一个唯一的整数。
    • 通过 shmat 函数将进程连接到共享内存时,需要使用 shmid 作为参数。

总的来说,key 是在共享内存创建时由应用程序指定的用户定义的标识符,而 shmid 是由系统内核在共享内存创建时自动生成的系统级标识符。key 用于唯一标识共享内存的名字,而 shmid 用于在程序运行时标识特定的共享内存实例。

shm可以用于多个进程之间通信,在同一时刻,可能有多个共享内存被用来进行通信。所以系统中一定会有很多个共享内存同时存在,那么系统就会采取一定措施来管理这些共享内存。所以共享内存并不是单单的一块内存空间,系统会为它构建一个结构体对象来描述它。

  • 所以,共享内存 = 内核数据结构 + 真正开辟的内存空间;

共享内存数据结构

struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};

两个进程使用共享内存进行通信的前提是,如何让两个进程使用同一块共享内存。内存中有许许多多的共享内存,我们如何让两个进程使用一个共享内存呢?

这就要提到另一个函数 ftok 了。

ftok 函数是一个用于生成System V IPC(Inter-Process Communication,进程间通信)的键值的函数。它的主要用途是在创建System V IPC对象(如消息队列、信号量、共享内存)时,为这些对象生成唯一的键值。

函数原型如下:

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

key_t ftok(const char *pathname, int proj_id);
  • pathname 是一个与文件相关的路径名,用于生成键值。通常是指向一个存在的文件的路径。
  • proj_id 是一个用户定义的整数,用于区分不同的IPC对象。在不同的IPC对象中,如果 pathname 相同,而 proj_id 不同,生成的键值也会不同。

ftok 函数通过将 pathname 转换为一个唯一的键值,以确保在不同的进程中使用相同的 pathnameproj_id 参数生成的键值是一致的。

一般来说,ftok 函数的使用场景是在创建System V IPC对象之前,通过调用 ftok 来生成一个唯一的键值。这个键值将被传递给诸如 msggetsemgetshmget 等函数,用于创建具体的消息队列、信号量或共享内存段。

需要注意的是,ftok 存在一些限制和注意事项,比如需要确保 pathname 指向的文件是存在的,否则 ftok 会返回 -1。此外,由于 proj_id 是一个整数,因此其范围应在0到255之间,以保证生成的键值在合理的范围内。

所以,key 是在内核中使用的,类比文件的 inode 编号。而 shmid 是给用户使用的,类比文件的文件描述符 fd。

🐧代码实例

接下来,我们就通过一个简单的代码设计来熟悉共享内存的使用。该设计的内容是,通过共享内存让两个进程(Server 与 Client)进行通信。

communicate.hpp

该头文件内提供shm所用到的函数方法。

/* communicate.hpp */

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <unistd.h>

using namespace std;

#define PATHNAME "." // 文件路径(用于key生成)
#define PROJID 0x12138 // 项目ID(用于key生成)

const int gsize = 4096;

key_t getKey() // 获取键值
{
    key_t key = ftok(PATHNAME, PROJID);
    if (key == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return key;
}

static int createShmHelper(key_t key, int size, int flag)
{
    int shmid = shmget(key, gsize, flag);
    if (shmid == -1)
    {
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(2);
    }

    return shmid;
}

int createShm(key_t key, int size) // 创建共享内存
{
    umask(0);
    return createShmHelper(key, size, IPC_CREAT | IPC_EXCL | 0666);
}

int getShm(key_t key, int size) // 获取共享内存
{
    umask(0);
    return createShmHelper(key, size, IPC_CREAT);
}

char *attachShm(int shmid) // 关联共享内存
{
    char *start = (char *)shmat(shmid, nullptr, 0); // 将共享内存段连接到进程地址空间
    return start;
}

void detachShm(char *start) // 去关联
{
    int n = shmdt(start); // 将共享内存段与当前进程脱离
    assert(n != -1);
    (void)n;
}

void delShm(int shmid) // 释放共享内存
{
    int n = shmctl(shmid, IPC_RMID, nullptr); // 释放共享内存
    assert(n != -1);
    (void)n;
}

#define SERVER 1
#define CLIENT 0

class Init
{
public:
    Init(int type)
        : type(type)
    {
        key_t key = getKey();
        if (type == SERVER)
            shmid = createShm(key, gsize);
        else
            shmid = getShm(key, gsize);

        start = attachShm(shmid);
    }

    char *getStart() { return start; }

    ~Init()
    {
        detachShm(start);
        if (type == SERVER)
            delShm(shmid); // 只有共享内存创建者才负责释放
    }

private:
    char *start; // 起始地址
    int type;    // server or client
    int shmid;
};

#endif

Server

/* Server.cc */
#include "communicate.hpp"
#include <unistd.h>

using namespace std;

int main()
{   
    // 建立连接
    Init init(SERVER);
    char *start = init.getStart();
    
    // 开始通信
    int n = 0;
    while (n <= 30)
    {
        cout << "client -> server# " << start << endl;
        sleep(1);
        n++;
    }

    return 0;
}

Client

/* Client.cc */
#include "communicate.hpp"
using namespace std;

int main()
{
    // 建立连接
    Init init(CLIENT);
    char *start = init.getStart();
    
    // 开始通信
    char c = 'A';
    
    while (c <= 'Z')
    {
        start[c - 'A'] = c;
        c++;
        start[c - 'A'] = 0;

        sleep(1);
    }
    return 0;
}

效果展示

在这里插入图片描述
注意

当我们运行完一次程序后,再次运行程序会发生错误:

$ ./server 
error: 17 : File exists

原因是,上次程序运行时创建的共享内存仍然存在,可以使用 ipcs -m 指令来查看已经有的共享内存:

$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x3801203f 0          hxy        666        4096       0                       

由此可见,共享内存的生命周期是随系统的,不随进程。

我们可以使用 ipcrm -m 指令删除指定的共享内存:

$ ipcrm -m shmid

本章的内容到这里就结束了!如果觉得对你有所帮助的话,欢迎三连~

在这里插入图片描述

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

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

相关文章

智能电网监控:图像分类技术在能源电力领域的创新应用

一、引言 在当今这个对能源效率要求日益增长的时代&#xff0c;电力行业正面临着前所未有的挑战。为了满足日益增长的电力需求&#xff0c;同时确保电网的稳定性和可靠性&#xff0c;我们采用了一种革命性的方法&#xff1a;通过智能算法和自动化技术来优化电网的运行。这一项…

最短路径Floyd算法

第一题&#xff1a;[USACO08OPEN] Clear And Present Danger S #include<bits/stdc.h> using namespace std; int n,m; int g[105][105]; int arr[100005]; long long sum; int main() {scanf("%d%d",&n,&m);for(int i1;i<m;i){scanf("%d"…

Python不换行print在终端中不显示

问题描述 当使用不换行 print 即 print(‘test, end) 后立即关闭标准输出 sys.stdout open(os.devnull, w)则 print 的内容不会显示在正常的终端上&#xff08;例外是 PyCharm 中的终端能够正常显示&#xff09;。 复现问题 复现该问题的简易代码&#xff1a; import sys,…

带你全方位体验 Amazon Connect

1.前言 授权说明&#xff1a;本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在亚马逊云科技开发者社区、 知乎、自媒体平台、第三方开发者媒体等亚马逊云科技官方渠道。 近日亚马逊云科技在拉斯维加斯拉开了年度客户大会-亚马逊云科技 re:Invent 的序…

关于淘宝的nodejs镜像网址更新后,前端项目出现的一系列问题解决方案。

问题 npm install&#xff0c;之前是成功的&#xff0c;最近不成功。 原因 之前的npm.taobao.org镜像源已经停用 解决方法 把所有的npm.taobao.org替换成npmmirror.com&#xff0c;这个新的淘宝镜像地址 如果使用nvm(没有忽略)需要修改如下&#xff1a; nvm node_mirror…

vulhub中Wordpress 4.6 任意命令执行漏洞复现

由于Mysql初始化需要一段时间&#xff0c;所以请等待。成功运行后&#xff0c;访问http://your-ip:8080/打开站点&#xff0c;初始化管理员用户名和密码后即可使用&#xff08;数据库等已经配置好&#xff0c;且不会自动更新&#xff09;。 发送如下数据包&#xff0c;可见/tmp…

基于pytorch的手写体识别

一、环境搭建 链接: python与深度学习——基础环境搭建 二、数据集准备 本次实验用的是MINIST数据集&#xff0c;利用MINIST数据集进行卷积神经网络的学习&#xff0c;就类似于学习单片机的点灯实验&#xff0c;学习一门机器语言输出hello world。MINIST数据集&#xff0c;可以…

03-grafana的下拉列表选项制作-grafana的变量

一、准备环境 为了实现下拉列表筛选的样例&#xff0c;我们监控两个linux节点&#xff1b; 目前&#xff0c;我们已经有了一个节点了&#xff0c;再添加一个&#xff1b; 二、grafana的仪表盘变量 如果想给仪表盘自定义下拉列表&#xff0c;那么&#xff0c;需要设置变量&#…

Full-RNS CKKS

参考文献&#xff1a; [HS13] Halevi S, Shoup V. Design and implementation of a homomorphic-encryption library[J]. IBM Research (Manuscript), 2013, 6(12-15): 8-36.[BEHZ16] Bajard J C, Eynard J, Hasan M A, et al. A full RNS variant of FV like somewhat homomo…

如何用可调电源给 GSX-250R 摩托车电瓶充电

查看如何用可调电源充电瓶的教程请直接下滑至“总结”栏目。 天气很好&#xff0c;想骑车去玩一下&#xff0c;发现摩托车两个月之后摩托车电瓶只有10V左右了&#xff0c;不足以启动摩托车&#xff0c;貌似是这次电瓶电压确实过低了&#xff0c;推了好几次没推起来&#xff08…

蓝桥杯前端Web赛道-新鲜的蔬菜

蓝桥杯前端Web赛道-新鲜的蔬菜 题目链接&#xff1a;1.新鲜的蔬菜 - 蓝桥云课 (lanqiao.cn) 题目要求如下&#xff1a; 其实很容易联想到使用flex布局&#xff0c;这是flex布局一种非常经典的骰子布局&#xff0c;推荐Flex 布局教程&#xff1a;实例篇 - 阮一峰的网络日志 (r…

如何利用Flutter来写后端 服务端应用

前言 Flutter是谷歌推出的一款跨平台开发框架&#xff0c;现在属于此领域star最多的框架&#xff0c;其被广泛应用于构建前台界面&#xff0c;但或许很少人知道&#xff0c;他也可以写后端应用。 本文主角 flutter非常著名的getx库推出的get server jonataslaw/get_server:…

如何在 Windows 11/10 中合并分区而不丢失数据

在本文中&#xff0c;我们将了解如何在 Window 11/10 中合并分区而不丢失个人数据。每个人都会觉得需要扩大驱动器/分区的容量&#xff0c;但是在计算机中重新安装Windows对他们来说很麻烦。在 Windows PC 中合并分区的方法有很多种。我们将在下面逐步讨论一些工作方法&#xf…

docker 常用命令大全(基础、镜像、容器、数据卷)

文章目录 1.docker基础命令2.docker镜像命令2.1 镜像名称2.2 镜像命令2.3 案例1--拉取、查看镜像2.4 案例2--保存、导入镜像 3.docker容器命令3.1 容器命令3.2 案例--创建并运行一个容器3.3 案例--进入容器&#xff0c;修改文件3.4 小结 4.数据卷4.1 什么是数据卷4.2 数据卷操作…

学习python的地11天,昨天偶遇她失败了

昨天是目标偶遇她的第二次&#xff0c;我猜测是由于没算准时间&#xff0c;故而今日计划8:15那班车。 如果今日没有偶遇上&#xff0c;那就明天呗&#xff0c;后天&#xff0c;大后天&#xff0c;就像随机数一样&#xff0c;总有命中那一刻 昨天&#xff0c;我尝试给我的linu…

适用于 Windows 的 5 款最佳免费数据恢复软件榜单

每个计算机用户都曾经历过数据丢失的情况。很容易错误地删除重要的文件和文件夹&#xff0c;当发生这种情况时&#xff0c;可能会导致不必要的心痛和压力。值得庆幸的是&#xff0c;可以恢复 Windows PC 上丢失的数据。在本文中&#xff0c;我们将分享您可以使用的五种最佳 Win…

【Android 内存优化】怎么理解Android PLT hook?

文章目录 前言什么是hook?PLT hook作用基本原理PLT hook 总体步骤 代码案例分析方案预研面临的问题怎么做&#xff1f;ELFELF 文件头SHT&#xff08;section header table&#xff09; 链接视图&#xff08;Linking View&#xff09;和执行视图&#xff08;Execution View&…

Linux系统宝塔面板搭建Typecho博客并实现公网访问本地网站【内网穿透】

文章目录 前言1. 安装环境2. 下载Typecho3. 创建站点4. 访问Typecho5. 安装cpolar6. 远程访问Typecho7. 固定远程访问地址8. 配置typecho 前言 Typecho是由type和echo两个词合成的&#xff0c;来自于开发团队的头脑风暴。Typecho基于PHP5开发&#xff0c;支持多种数据库&#…

【MGR】MySQL Group Replication 背景

目录 17.1 Group Replication Background 17.1.1 Replication Technologies 17.1.1.1 Primary-Secondary Replication 17.1.1.2 Group Replication 17.1.2 Group Replication Use Cases 17.1.2.1 Examples of Use Case Scenarios 17.1.3 Group Replication Details 17.1…

初学arp欺骗

首先准备一台靶机这里用虚拟机的win10 已知网关与ip地址&#xff08;怕误伤&#xff09; 现在返回kali从头开始 首先探测自己的网关 然后扫内网存活的ip 发现有3台 用nmap扫一下是哪几台 成功发现我们虚拟机的ip 现在虚拟机可以正常访问网络 接下来直接开梭 ip网关 返回虚拟机…