【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

相关文章
|
9天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
22天前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
69 34
|
5天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
26 7
|
4天前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
28 5
|
4天前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
16 3
|
10天前
|
缓存 NoSQL Linux
Linux系统内存使用优化技巧
交换空间(Swap)的优化 禁用 Swap sudo swapoff -a 作用:这个命令会禁用系统中所有的 Swap 空间。swapoff 命令用于关闭 Swap 空间,-a 参数表示关闭 /etc/fstab 文件中配置的所有 Swap 空间。 使用场景:在高性能应用场景下,比如数据库服务器或高性能计算服务器,禁用 Swap 可以减少磁盘 I/O,提高系统性能。
28 3
|
10天前
|
缓存 Linux
Linux查看内存命令
1. free free命令是最常用的查看内存使用情况的命令。它显示系统的总内存、已使用内存、空闲内存和交换内存的总量。 free -h • -h 选项:以易读的格式(如GB、MB)显示内存大小。 输出示例: total used free shared buff/cache available Mem: 15Gi 4.7Gi 4.1Gi 288Mi 6.6Gi 9.9Gi Swap: 2.0Gi 0B 2.0Gi • to
25 2
|
26天前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
61 16
|
4月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
739 1
|
15天前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
19 3

热门文章

最新文章