【Linux】详解如何利用共享内存实现进程间通信

简介: 【Linux】详解如何利用共享内存实现进程间通信

一、共享内存(Shared Memory)的认识

       共享内存(Shared Memory)是多进程间共享的一部分物理内存。它允许多个进程访问同一块内存空间,从而在不同进程之间共享和传递数据。这种方式常常用于加速进程间的通信,因为数据不需要在不同的进程间进行拷贝。

       在操作系统中,共享内存通常是通过映射一段能被其他进程所访问的内存实现的。一个进程可以创建一个共享内存段,并将该段连接到其地址空间中。其他进程也可以将这段共享内存连接到它们的地址空间中。这样,所有进程都可以访问同一段内存,实现数据的共享。

       在内核中共享内存可以存在很多个,操作系统必须先创建描述共享内存的结构体,再把这些结构体组织起来管理。为了保证两个或者是多个进程看到同一个共享内存,就要给每一个共享内存提供唯一性的标识

二、创建共享内存的方法

       创建共享内存的方法为shmget,其中第一个参数为key,key就是共享内存在内核中的唯一标识。size是要设置的共享内存的大小(在内核中,共享内存是以4kb为基本单位的,我们在给共享内存分配大小的时候最好也是分配4kb的整数倍的大小。)。还有一个参数shmflg,shmflg可以有很多选项,但最常见的有两个:

  1. IPC_CREAT:如果共享内存不存在, 就创建之, 如果共享内存已经存在, 直接获取它。
  2. IPC_EXCL:不能单独使用, 没意义。
  3. IPC_CREAT | IPC_EXCL:如果共享内存不存在, 就创建之, 如果共享内存已经存在,就出错返回!!如果共享内存创建是成功的, 则一定是一个新的共享内存!

       如果shmget成功获取或创建了共享内存段,它会返回一个非负整数,这个整数是共享内存段的标识符(也称为共享内存段的ID)。这个标识符在后续的共享内存操作中(如shmat和shmdt)会被使用。

2.1、key的获取

       这里的pathname是一串文件路径,proj_id是一个整数,这两个参数由用户随意指定,操作系统底层通过特定的算法帮我们形成一个key值,如果形成失败-1被返回。如果成功这个key值就会被设置进描述共享内存的结构体中用来标识这块共享内存的唯一性。通过给两个进程或者是多个进程传入同样的pathname和proj_id就能让它们看到同一块共享内存。

三、查看共享内存的方法

采用ipcs指令可以查看系统中指定用户创建的共享内存,消息队列和信号量。

ipcs -m:查看系统中指定用户创建的共享内存

ipcs -q:查看系统中指定用户创建的消息队列

 ipcs -s:查看系统中指定用户创建的信号量

四、指令删除共享内存的方法

ipcrm -m shmid(共享内存id):删除用户指定的共享内存。

五、代码实现共享内存通信

5.1、获取key值

其实获取key可以封装成函数也可以不封装,这里我是将其封装成函数了。

key_t get_key(const char* pathname, int proj_id)
{
    key_t key = ftok(pathname, proj_id);
 
    //成功返回key值,失败返回-1
    if(key == -1)
    {
        cout << "获取key值失败,原因是:" << strerror(errno) << endl;
        exit(1);
    }
 
    return key;
}

5.2、创建共享内存

       共享内存是为了实现两方或是多方通信的,这里我就设置成为两方通信。所以一定是一方创建共享内存,另一方获取共享内存。要注意的是,共享内存也是有权限的,所以创建的一方需要指明创建的共享内存的权限。

int get_or_create_shared_memory(key_t key, int size, int flag)
{
    int shmid = shmget(key, size, flag);
 
    //成功返回共享内存标识符,失败返回-1
    if(shmid == -1)
    {
        cout << "共享内存创建失败,原因是:" << strerror(errno) << endl;
        exit(2);
    }
 
    return shmid;
}
 
int create_shared_memory(key_t key, int size)
{
    return get_or_create_shared_memory(key, size, IPC_CREAT | IPC_EXCL | 0666);
}
 
int get_shared_memory(key_t key, int size)
{
    return get_or_create_shared_memory(key, size, IPC_CREAT);
}

 5.3、挂接共享内存/去挂接共享内存

       shmid表示要挂接的共享内存的shmid,shmaddr表示要将该共享内存挂接到进程地址空间的什么位置,其实这个我们不用管,操作系统会自行帮我们挂接,可以直接设置为nullptr,shmflg表示可以对该共享内存做什么操作,设置为0默认是可读可写。 如果挂接成功,返回挂接到进程地址空间的地址,如果挂接失败,返回-1。

5.4、同步操作

       如果读写共享内存的进程间没有进行同步操作,可能就会发生脏读,即写入的数据和读到的数据不一致。所以要进行进程同步操作。这里我借助了管道来进行同步操作,即写方写完了再唤醒读方来读。

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstdio>
 
using namespace std;
 
#define MODE 0666 //权限
#define NAME "./fifo.txt"
 
//定义命名管道结构体
class Fifo
{
private:
    string _name; // 文件路径加文件名
public:
    Fifo(const string &name)
        : _name(name)
    {
        int n = mkfifo(_name.c_str(), MODE);
 
        if (n == 0)
            cout << "创建管道成功!" << endl;
        else
            cout << "创建管道失败!原因是:" << strerror(errno) << endl;
    };
    ~Fifo()
    {
        int n = unlink(_name.c_str());
 
        if (n == 0)
            cout << "删除管道成功!" << endl;
        else
            cout << "删除管道失败!原因是:" << strerror(errno) << endl;
    };
};
 
//同步结构体
class Sync
{
private:
    int rfd;
    int wfd;
public:
    void open_read()
    {
        rfd = open(NAME, O_RDONLY);
        if (rfd == -1)
        {
            cout << "读打开管道失败!" << endl;
            exit(1);
        }
    }
 
    void open_write()
    {
        wfd = open(NAME, O_WRONLY);
        if (wfd == -1)
        {
            cout << "写打开管道失败!" << endl;
            exit(1);
        }
    }
 
    int wait()
    {
        int ret = 0;
        int n = read(rfd, &ret, sizeof(int));
        return n;
    }
 
    void wake_up()
    {
        int ret = 0;
        int n = write(wfd, &ret, sizeof(int));
    }
};

读写方分别创立一个sync对象,在读写的时候分别调用wait和wake_up方法进行同步。

5.5、删除共享内存

        进程创建的共享内存如果在进程结束时没有释放,则共享内存会一直存在。也就是说,共享内存的声明周期是随内核的,如果我们没有主动去释放共享内存,除非重启系统,否则共享内存一直存在。所以在写端当你已经不写了时要将共享内存删掉。

shmctl系统调用加上IPC_RMID选项可以删除共享内存。

void shm_del(int shmid)
{
    int ret = shmctl(shmid, IPC_RMID, nullptr);
    if (ret == -1)
        cerr << "删除共享内存失败" << endl;
    else
        cout << "删除共享内存成功" << endl;
}

        shmctl的第三个选项可以传入一个描述共享内存的对象的地址来获取该共享内存的属性,如果只是删除共享内存,直接设置为nullptr即可。

六、总结

        共享内存不提供进程间协同的任何机制。但是共享内存是所有进程间通信机制中速度最快的。因为共享内存是通过页表直接与进程地址空间中的地址产生关联的,写方只需要将数据拷贝到共享内存中,读方直接通过地址就能访问内容,无需进行数据的拷贝,直接就提高了访问数据的速度。也就是说共享内存进行进程间通信只需要一次数据的拷贝,而我们之前提到的管道通信,都是读方调用write函数将数据写入内存(进行了一次拷贝),读方再调用read函数将数据拷贝到用户层,要进行两次数据的拷贝。

七、说明

       因为实现共享内存的文件数较多,所以以上并不是全部代码,如果想获取全部实现代码,请移步到本人码云C++代码: C++代码保存的地方 - Gitee.com

相关文章
|
22天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
49 1
|
10天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
65 13
|
11天前
|
运维 监控 Ubuntu
【运维】如何在Ubuntu中设置一个内存守护进程来确保内存不会溢出
通过设置内存守护进程,可以有效监控和管理系统内存使用情况,防止内存溢出带来的系统崩溃和服务中断。本文介绍了如何在Ubuntu中编写和配置内存守护脚本,并将其设置为systemd服务。通过这种方式,可以在内存使用超过设定阈值时自动采取措施,确保系统稳定运行。
29 4
|
17天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
24天前
|
C语言 开发者 内存技术
探索操作系统核心:从进程管理到内存分配
本文将深入探讨操作系统的两大核心功能——进程管理和内存分配。通过直观的代码示例,我们将了解如何在操作系统中实现这些基本功能,以及它们如何影响系统性能和稳定性。文章旨在为读者提供一个清晰的操作系统内部工作机制视角,同时强调理解和掌握这些概念对于任何软件开发人员的重要性。
|
22天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
23天前
|
Linux 调度 C语言
深入理解操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅,从进程管理的基本概念出发,逐步探索到内存管理的高级技巧。我们将通过实际代码示例,揭示操作系统如何高效地调度和优化资源,确保系统稳定运行。无论你是初学者还是有一定基础的开发者,这篇文章都将为你打开一扇了解操作系统深层工作原理的大门。
|
25天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
1月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 算法 安全
深入理解Linux内核的内存管理机制
本文旨在深入探讨Linux操作系统内核的内存管理机制,包括其设计理念、实现方式以及优化策略。通过详细分析Linux内核如何处理物理内存和虚拟内存,揭示了其在高效利用系统资源方面的卓越性能。文章还讨论了内存管理中的关键概念如分页、交换空间和内存映射等,并解释了这些机制如何协同工作以提供稳定可靠的内存服务。此外,本文也探讨了最新的Linux版本中引入的一些内存管理改进,以及它们对系统性能的影响。