进程间通信--共享内存详解【Linux】

这篇具有很好参考价值的文章主要介绍了进程间通信--共享内存详解【Linux】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文详细讲解了共享内存的原理和使用,并且通过实例代码角度来深度理解共享内存,下面就让我们开始吧。

进程间通信目的

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

通信背景

1.由于进程是具有独立性的,进程想交互数据,成本会非常高。但是有些情况下需要多进程处理一件事情。
2.进程独立并不是彻底独立,有时候我们需要双方能够进行一定程度的信息交互。

我们要学的进程间通信,不是告诉我们如何通信,是他们两个如何先看到同一份资源。(文件,内存块…等方式)

进程间通信 共享内存,Linux,linux,服务器,网络
两个进程同时访问磁盘上的一个文件进行读写

但由于进程在磁盘上读写太慢,所以进程间通信一般读写内存中的文件。

进程间通信 共享内存,Linux,linux,服务器,网络
两个进程同时访问内存上的一个文件进行读写

共享内存

一. 原理

共享内存是指多个进程可以同时访问同一块内存区域的机制。在共享内存中,多个进程可以把同一块内存映射到它们自己的地址空间中,并且可以直接读写这块内存,就好像它们都拥有这块内存一样。这样可以实现进程间高效的数据共享,而不需要通过进程间通信机制进行数据传输,从而提高程序的性能。

那么内存中是如何实现共享内存的呢?
首先我们要明白,进程间通信的前提是:先让不同的进程,看到同一份资源。而共享内存就是通过进程可以看到同一块内存而实现的。

进程间通信 共享内存,Linux,linux,服务器,网络

共享内存原理图

  • 每个进程都有一个虚拟地址空间,在地址空间的栈区和堆区中间有一块很大的空内存,名叫共享区。
  • 在用户需要申请共享内存时,操作系统在物理内存中申请一块空间,然后映射到页表中,页表建立连接之后,再映射到进程各自的共享区中。两个进程共同映射的物理内存的操作就叫做共享内存。

二. 基本编写

要实现共享内存,我们需要四个接口:
操作系统角度:

①创建共享内存
②删除共享内存

进程角度:

③关联共享内存
④去关联共享内存

2.1 创建共享内存

int shmget(key_t key, size_t size, int shmflg);

allocates a System V shared memory segment
分配System V标准的共享内存段。

参数列表
size: 要申请的共享内存大小。
由于操作系统对内存管理的最小单位是页(4KB),所以size建议设置成为页的整数倍。

shmflg: 创建共享内存的选项。

为了解决两个问题

1.如果共享内存存在怎么办
2.如果共享内存不存在怎么办

常见的有两个选项

  • IPC_CREAT:创建共享内存,如果已经存在,就获取之。不存在,就创建之
  • IPC_EXCL:不单独使用,必须和IPC_CREAT配合使用。如果不存在指定的共享内存,创建之。如果存在了,出错返回
  • 并且可以按位或文件权限,设置共享内存权限。
int shmid = shmget(key, SHM_SIZE, flags | 0666);

IPC_EXCL可以保证,如果shmget函数调用成功,一定是一个全新的share memory。如果调用失败,则报错。

这里会有两个问题:

1. 共享内存存在哪里?
内核中 – 内核会给我们维护共享内存的结构,共享内存也要被管理起来,所以一定会有管理共享内存的结构体。

2. 我怎么知道,这个共享内存属于存在还是不存在?
这个问题稍后回答。

进程间通信 共享内存,Linux,linux,服务器,网络
共享内存结构体shmid_ds

可以看到在shmid_ds中,存在ipc_perm结构体,保存的是共享内存的权限。
而在ipc_perm中,存在一个key值,由shmget提供。

由此可以引出最后一个参数:

key:共享内存的唯一值,这个参数需要由用户提供。
共享内存要被管理 -> struct shmid_ds -> struct ipc_perm -> key(shmget)(共享内存唯一值)

1. 为什么key值需要由用户提供?
进程间通信的前提是,先让不同的进程,看到同一份资源。如果由操作系统提供,创建共享内存的进程可以知道key值,但是使用共享内存的进程无法获取。所以key值必须由用户获取,然后在使用时标定key值,则能让使用共享内存的进程获取到。

共享内存,在内核中,让不同的进程看到同一份共享内存,做法是:让他们拥有同一个key即可。

匿名管道 --> 约定好使用同一个文件
共享内存 --> 约定好使用同一个唯一key

2.为什么key值要有唯一性?
操作系统中可能有很多个共享内存在被使用,所以我们就需要用一个唯一值来标识每一个共享内存。

3. 那么如何保证key值唯一性呢?
生成唯一key值函数:ftok函数。

key_t ftok(const char *pathname, int proj_id);

将文件路径和一个项目标识符,转化为唯一key值。

返回值:一个整数,创建成功,返回一个合法的共享内存标识符。失败,返回 -1。

进程间通信 共享内存,Linux,linux,服务器,网络

生成唯一key值并且让服务端和客户端可以获取

通过唯一key值的标识,我们就可以知道共享内存是否存在。

2.2 删除共享内存

当我们运行完毕创建全新的共享内存代码后(进程退出),创建成功,但是第二(n
)次的时候,该代码无法运行,告诉我们文件存在(共享内存存在)。

进程间通信 共享内存,Linux,linux,服务器,网络
创建共享内存

所以system V下的共享内存,生命周期是随内核的。如果不显示的删除,只能通过操作系统重启来解决。

这里有两个问题:
1. 我怎么知道有哪些IPC资源?

ipcs -m

进程间通信 共享内存,Linux,linux,服务器,网络
查看共享内存列表

key:shmget传入的key值
shmid:shmget的返回值,共享内存id
owner:所有者
perms:共享内存权限
bytes:共享内存大小
nattch:共享内存的进程链接数
status:共享内存状态

可以看到刚刚创建的shmid为1的共享内存确实存在。

2. 如何显示删除?

指令实现

ipcrm -m shmid

进程间通信 共享内存,Linux,linux,服务器,网络
删除共享内存

可以看到shmid为1的共享内存被删除了。

代码实现
用指令可以删除共享内存,但是我们需要编写代码,代码中有没有删除共享内存的函数呢?操作系统为我们提供了接口。

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

System V shared memory control
控制System V标准的共享内存段。当前我们只用它来实现删除。

参数列表:

shmid:创建的共享内存id。
cmd:如何控制共享内存。我们只用其中的一个选项IPC_RMID,删除共享内存。
buf:当我们要删除时,只要指定了shmid,这个参数设置为NULL即可。

返回值:-1为失败。

shmctl(shmid, IPC_RMID, nullptr);

下面写了一个服务器代码,功能是生成key值开始创建共享内存,等待五秒。创建共享内存,等待五秒,删除共享内存。

右侧监控脚本

while :; do ipcs -m; sleep 1; echo "####################"; done

当我们运行服务端代码时,先生成key值并开始创建,此时共享内存还未被创建。服务端创建成功时,右侧可以看到共享内存被创建了。服务端删除成功时,右侧可以看到共享内存被删除了。

进程间通信 共享内存,Linux,linux,服务器,网络

2.3 关联共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg);

System V shared memory attach
关联System V标准的共享内存段

参数列表

shmid:共享内存id
shmaddr:要关联到共享内存的哪个地址上。当前无法操作,把它设置为NULL即可
shmflg:读或者写。设置为默认0,即为读。

返回值:void* 类型,如果成功,返回要关联的共享内存起始地址,失败为-1。类似于molloc,使用时需要强转为需要的指针类型。

char* str = (char*)shmat(shmid, nullptr, 0);

可以看到,当加入shmat之后,共享内存的nattch变为1,表示进程与共享内存产生了关联。

进程间通信 共享内存,Linux,linux,服务器,网络
服务端加入shmat代码

2.4 去关联共享内存

int shmdt(const void *shmaddr);

System V shared memory detach
去关联System V标准的共享内存段。

参数列表

shmaddr:共享内存起始地址,即为shmat的返回值

可以看到,当加入shmdt之后,nattch从1变0,再从0变1,代表进程与共享内存先关联,再去关联。

进程间通信 共享内存,Linux,linux,服务器,网络
服务端加入shmdt代码

加入客户端,让服务端创建共享内存并关联,然后运行客户端,也让它关联共享内存,然后都去关联,可以看到如下现象。
nattch从1变2,再变成1,再变成0。代表服务端关联,客户端关联,去关联。

进程间通信 共享内存,Linux,linux,服务器,网络加入客户端

三、实例代码

3.1 头文件

Comm.hpp

包含需要的库文件,并且封装CreateKey函数,用来生成key值。

#pragma once

#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define PATH_NAME "/home/ydp/blog"
#define PROJ_ID 0x14
#define SHM_SIZE 4096

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
        std::cerr << "ftok: " << strerror(errno) << std::endl;
        exit(0);
    }
}

Log.hpp

封装Log函数,用来打印Debug信息

#pragma once 

#include <iostream>
#include <ctime>

std::ostream &Log()
{
    std::cout << "For Debug | " << " timestamp: " 
    	 	  << (uint64_t)time(nullptr) << " | ";
    return std::cout;
}

3.2 服务端

服务端实现以下操作:

①创建key
②创建共享内存
③将共享内存和进程关联
④持续循环打印共享内存中的内容
⑤解除共享内存和进程之间的关联
⑥删除共享内存

#include "Comm.hpp"
#include "Log.hpp"
using namespace std;

// 我想创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;
// 充当创建共享内存的角色
int main()
{
    // 1.创建key
    key_t key = CreateKey();
    Log() << "key: " << key << endl;

    // 2.创建共享内存
    Log() << "create share memory begin" << endl;
    int shmid = shmget(key, SHM_SIZE, flags | 0666);
    if(shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << endl;
        return 2;
    }
    Log() << "create shm success, shmid: " << shmid << endl;

    // 用它
    // 3.将共享内存和进程关联
    char* str = (char*)shmat(shmid, nullptr, 0);
    Log() << "attach shm: " << shmid << " success" << endl;
    // 4.持续循环打印共享内存中的内容
    while(true)
    {
        printf(".%s\n", str);
        sleep(1);
    }

    // 5.解除共享内存和进程之间的关联
    shmdt(str);
    Log() << "detach shm: " << shmid << " success" << endl;

    // 6.删除共享内存
    shmctl(shmid, IPC_RMID, nullptr);
    Log() << "delete shm: " << shmid << " success" << endl;

    return 0;
}

3.3 客户端

客户端实现以下操作:

①创建相同的key
②获取共享内存
③将共享内存和进程关联
④将26个字母写入共享内存
⑤解除共享内存和进程之间的关联

#include "Comm.hpp"
#include "Log.hpp"
using namespace std;

// 充当使用共享内存的角色

int main()
{
    // 1.创建相同的key
    key_t key = CreateKey();
    Log() << "key: " << key << endl;

    // 2.获取共享内存
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT);
    if(shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << endl;
        return 2;
    }

    // 3.将共享内存和进程关联
    char *str = (char*)shmat(shmid, nullptr, 0);

    // 用它
    // 竟然没有使用任何的系统调用接口!
    // 4.将26个字母每隔一秒写入共享内存
    int cnt = 0;
    while(cnt <= 26)
    {
        str[cnt] = 'A' + cnt;
        ++cnt;
        // 保证str为字符串
        str[cnt] = '\0';
        sleep(1);
    }
    
    // 5.解除共享内存和进程之间的关联
    shmdt(str);

    return 0;
}

在客户端与服务端中可以发现,输入输出不需要调用系统接口。

由此我们可以得出三个结论:

1.我们把共享内存实际上是映射到了我们进程地址空间的用户空间了(堆->栈之间),对每一个进程而言,挂接到自己的上下文中的共享内存,属于自己的空间,类似于堆空间或者栈空间,可以被用户直接使用,不需要调用系统接口。

2.共享内存,因为它自身的特性,它没有任何访问控制。共享内存被双方直接看到,属于双方的用户空间,可以直接通信,但是不安全。

3.共享内存是所有进程间通信,速度最快的。

对比管道:
管道从用户到操作系统需要四次拷贝。
进程间通信 共享内存,Linux,linux,服务器,网络
管道拷贝次数

共享内存由于客户端和服务端看到的是同一份内容,不需要拷贝,所以从用户到操作系统只需要两次拷贝。
进程间通信 共享内存,Linux,linux,服务器,网络
共享内存拷贝次数

3.4 输出样例

进程间通信 共享内存,Linux,linux,服务器,网络

可以看到,当客户端每隔一秒写入字母,服务器也每隔一秒读取到了字母

四、特征总结

共享内存特征

  1. 共享内存可以被用户直接使用,不需要调用系统接口。
  2. 共享内存没有任何访问控制。共享内存被双方直接看到,属于双方的用户空间,可以直接通信,但是不安全。
  3. 共享内存是所有进程间通信,速度最快的。

总结

本文从原理和代码编写角度详细介绍了进程间通信的一种方式 – 共享内存。大家也可以尝试去使用一下共享内存,这样可以更深入地了解其中的细节。喜欢的话,欢迎点赞支持和关注~文章来源地址https://www.toymoban.com/news/detail-781885.html

到了这里,关于进程间通信--共享内存详解【Linux】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】进程间通信——管道/共享内存

    进程间通信( Inter-Process Communication,简称IPC )是指不同进程之间进行数据交换和共享信息的机制和技术。在操作系统中,每个进程都是独立运行的,有自己的地址空间和数据,因此进程之间需要一种机制来进行通信,以便彼此协调工作、共享数据或者进行同步操作。 进程间

    2024年02月16日
    浏览(30)
  • 【Linux】进程间通信之共享内存

    共享内存比管道快哦~ 文章目录 前言 一、共享内存的实现原理 二、实现共享内存的代码 总结 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的

    2024年02月03日
    浏览(35)
  • (26)Linux 进程通信之共享内存(共享储存空间)

    共享内存是System V版本的最后一个进程间通信方式。 共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内

    2024年01月16日
    浏览(31)
  • Linux--进程间的通信-共享内存

    前文: Linux–进程间的通信-匿名管道 Linux–进程间的通信–进程池 Linux–进程间的通信-命名管道 对于两个进程,通过在内存开辟一块空间(操作系统开辟的),进程的虚拟地址通过页表映射到对应的共享内存空间中,进而实现通信 ; 特点和作用: 高效性: 共享内存是一种

    2024年04月26日
    浏览(27)
  • 【hello Linux】进程间通信——共享内存

    目录 前言: 1. System V共享内存 1. 共享内存的理解 2. 共享内存的使用步骤 3. 共享内存的使用         1. 共享内存的创建         查看共享内存         2. 共享内存的释放         3. 共享内存的挂接         4. 共享内存的去挂接 4. 共享内存的使用示例 1. 两进

    2024年02月01日
    浏览(66)
  • 【Linux】进程间的通信之共享内存

    利用 内存共享 进行进程间的通信的原理其实分为以下几个步骤: 在物理内存中创建一块共享内存。 将共享内存链接到要通信的进程的页表中,并通过页表进行进程地址空间的映射。 进程地址空间映射完毕以后返回首个虚拟地址,以便于进程之间进行通信。 根据共享内存的

    2024年02月09日
    浏览(37)
  • 【Linux】进程间通信 -- system V共享内存

    共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据 理解: 进程间通信,是专门设计的,用来IPC 共享内存是一种通信方式,所有想通信的进程

    2024年02月16日
    浏览(33)
  • 【Linux】进程间通信——system V共享内存

    目录  写在前面的话 System V共享内存原理 System V共享内存的建立 代码实现System V共享内存 创建共享内存shmget() ftok() 删除共享内存shmctl() 挂接共享内存shmat() 取消挂接共享内存shmdt() 整体通信流程的实现          上一章我们讲了进程间通信的第一种方式 --- 管道,这一章我

    2024年02月14日
    浏览(29)
  • Linux学习记录——이십 进程间通信(2)共享内存

    system是一套标准,独立于文件系统之外,是系统专门为通信而设计出来的内核模块,称之为system V的IPC通信机制。 共享内存的主要做法也是让两个毫不相关的进程看到同一份资源。 进程的地址空间内,栈和堆区之间有个共享区,堆是向上增长,栈是向下增长,重合的那个地方

    2023年04月24日
    浏览(28)
  • 【Linux】进程间通信之共享内存/消息队列/信号量

    共享内存是通过让不同的进程看到同一个内存块的方式。 我们知道,每一个进程都会有对应的PCB-task_struct ,独立的进程地址空间,然后通过页表将地址映射到物理内存中。此时我们就可以让OS在内存中申请一块空间,然后将创建好的内存空间映射到进程的地址空间中,两个需

    2024年02月05日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包