【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

相关文章
|
18天前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
本文详细介绍了进程间通信(IPC)的六种主要方式:管道、信号、消息队列、共享内存、信号量和套接字。每种方式都有其特点和适用场景,如管道适用于父子进程间的通信,消息队列能传递结构化数据,共享内存提供高速数据交换,信号量用于同步控制,套接字支持跨网络通信。通过对比和分析,帮助读者理解并选择合适的IPC机制,以提高系统性能和可靠性。
77 14
|
22天前
|
缓存 Linux
linux 手动释放内存
在 Linux 系统中,内存管理通常自动处理,但业务繁忙时缓存占用过多可能导致内存不足,影响性能。此时可在业务闲时手动释放内存。
81 17
|
24天前
|
消息中间件 Linux
Linux:进程间通信(共享内存详细讲解以及小项目使用和相关指令、消息队列、信号量)
通过上述讲解和代码示例,您可以理解和实现Linux系统中的进程间通信机制,包括共享内存、消息队列和信号量。这些机制在实际开发中非常重要,能够提高系统的并发处理能力和数据通信效率。希望本文能为您的学习和开发提供实用的指导和帮助。
91 20
|
2月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
110 13
|
2月前
|
运维 监控 Ubuntu
【运维】如何在Ubuntu中设置一个内存守护进程来确保内存不会溢出
通过设置内存守护进程,可以有效监控和管理系统内存使用情况,防止内存溢出带来的系统崩溃和服务中断。本文介绍了如何在Ubuntu中编写和配置内存守护脚本,并将其设置为systemd服务。通过这种方式,可以在内存使用超过设定阈值时自动采取措施,确保系统稳定运行。
88 4
|
2月前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
2月前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
9月前
|
缓存 监控 Linux
linux 内存监控
linux 内存监控
74 1
|
监控 Linux
linux性能监控:内存监控命令之free命令
linux性能监控:内存监控命令之free命令
251 1
linux性能监控:内存监控命令之free命令
|
存储 监控 Shell
Linux 性能监控之CPU&内存&I/O监控Shell脚本2
Linux 性能监控之CPU&内存&I/O监控Shell脚本2
532 0