进程间通信之共享内存分析

零拷贝技术:https://strikefreedom.top/linux-io-and-zero-copy

一、内存映射和共享内存的区别

1.1、内存映射之mmap函数:将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。需要注意的是在mmap之后,并没有在将文件内容加载到物理页上,只上在虚拟内存中分配了地址空间。当进程在访问这段地址时(通过mmap在写入或读取时FileA),若虚拟内存对应的page没有在物理内存中缓存,则产生"缺页",由内核的缺页异常处理程序处理,将文件对应内容,以页为单位(4096)加载到物理内存。

void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
参数:

start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

length:代表将文件中多大的部分映射到内存。

prot:映射区域的保护方式。可以为以下几种方式的组合:

  • PROT_EXEC 映射区域可被执行
  • PROT_READ 映射区域可被读取
  • PROT_WRITE 映射区域可被写入
  • PROT_NONE 映射区域不能存取

flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

  • MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
  • MAP_SHARED:对映射区域的写入数据会复制回文件内, 而且允许其他映射该文件的进程共享。 与其它所有映射这个对象的进程共享映射空间,对共享区的写入,相当于输出到文件。
  • MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
  • MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,所以映射区域无法和非亲缘进程共享。
  • MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
  • MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。

offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

1.2、IPC之共享内存

共享内存是在两个正在运行的进程间共享和传递数据的一种高效形式。不同进程之间共享的内存通常安排在同一段物理内存中。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址。IPC共享内存的实现借用了mmap机制。共享内存是最快的进程间通信方式。共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读写。所以我们通常需要同步机制控制对共享内存的访问,比如信号量 。

  • Posix接口,共享内存的POSIX接口,使用方法:shm_open()、mmap()。POSIX的共享内存实现会默认把共享内存文件放在/dev/shm/分区下,如果没有这个分区,需要手动挂载一下。POSIX共享内存一般要放在/dev/shm/目录下,这是因为/dev/shm/使用了一种特殊的文件系统tmpfs,它是虚拟的,并不是磁盘上的真实的分区,速度较快,性能较好。Linux共享内存的实现依赖于共享内存文件系统,该文件系统通常装载在/dev/shm,在调用shm_open系统函数的时候,会在/dev/shm/目录下生成内存文件。

    shm_open:创建一个新的共享区域或者附加在已有的共享区域上.区域被其名字标识,函数返回各文件的描述符.
    
    shm_unlink:类似于unlink系统调用对文件进行操作,直到所有的进程不再引用该内存区后才对其进行释放.
    
    mmap:用于将一个文件映射到某一内存区中,其中也使用了shm_open函数返回的文件描述符.
    
    munmap:用于释放mmap所映射的内存区域.
    
    msync:同步存取一个映射区域并将高速缓存的数据回写到物理内存中,以便其他进程可以监听这些改变.
    
  • System V接口,使用方法shmget()、shmmat()…

    shmget:创建一个新的共享区域或者附加在已有的共享区域上(同shm_open).
    
    shmat:用于将一个文件映射到内存区域中(同mmap).
    
    shmdt:用于释放所映射的内存区域(同munmap)
    
    shmctl:对于多个用户,断开其对共享区域的连接(同shm_unlink)
    

1.3、共享内存和直接文件内存映射(mmap)的区别?

​ 共享内存和文件内存映射的接口、用法不一样,POSIX的共享内存实现会默认把共享内存文件放在/dev/shm/分区下,如果没有这个分区,需要手动挂载一下。直接内存映射的文件无关文件的位置。然后就是共享内存和文件内存映射,在内核中的实现原理,都使用了内核的cache和swap机制,完全没有区别。

1.4、共享内存和read/write相比优缺点是什么?

直接将文件映射到虚拟内存,意味着没有数据没有缓存在内核缓存空间,而是直接读到了用户空间,回想一下系统的IO和内核缓存搭配可以使得部分的文件使用效率更高。(因为OS会根据局部性原理在一次read()系统调用的时候预读取更多的文件数据到内核空间缓冲区中,这样当下一次read()系统调用的时候发现要读取的数据已经存在于内核空间缓冲区中的时候只要直接拷贝数据到用户空间缓冲区中即可,无需再进行一次低效的磁盘I/O操作)。而且从mmap的细节我们可以看到,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。映射的文件最好是大于4k的(一个内存页的大小),并且最好是4k的倍数。也就是说两个方式都是有优缺点的,只能通过分析其场景而选择不同的方式。


二、多进程间(亲缘进程和非亲缘进程间)使用mmap的共享内存(与磁盘同步):

在这里插入图片描述

2.1、mmap匿名映射。

既然是兄弟,或者父子进程间进行通信,我们有着一样的数据,为什么一定要靠着打开一个文件,然后映射到内存,来进行通信呢?它们之间也不需要被映射到内存中的文件中的数据呀,这样做是是不是多此一举?于是就想我们可以请求操作系统在内存中,给他们开辟一块固定大小的空间,它们使用这块内存的地址,来作为桥梁进行通信,这就是匿名内存映射区

void *mptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANON,-1,offset);
//注意
mode参数必须要有一个参数是 MAP_ANON
文件描述符可以填-1
len 长度需要指定出来,4k的整数倍

2.2、mmap映射共有四种组合, 私有/共享、文件/匿名映射组合。

  • 私有文件映射:多个进程使用同样的物理页面进行初始化,但是各个进程对内存文件的修改不会共享,也不会反映到物理文件中。

比如对linux .so动态库文件就采用这种方式映射到各个进程虚拟地址空间中。

  • 私有匿名映射:mmap会创建一个新的映射,各个进程不共享,主要用于分配内存(malloc分配大内存会调用mmap)。

  • 共享文件映射:多个进程通过虚拟内存技术共享同样物理内存,对内存文件的修改会反应到实际物理内存中,也是进程间通信的一种。

  • 共享匿名映射:这种机制在进行fork时不会采用写时复制,父子进程完全共享同样的物理内存页,也就是父子进程通信。

2.3、系统调用mmap用于共享内存时有下面两种常用的方式: 对于任意的两个进程可以使用第一种方式,而对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式.此时,不必指定具体的文件,只要设定相应的标志即可

  • 使用普通文件提供的内存映射:适用于任何进程之间.此时,需要打开或创建一个文件,然后再调用mmap, 常见于非亲缘进程之间的共享内存通信。
  • 使用特殊文件提供的匿名内存映射:适用于具有亲缘关系的进程之间.由于父子进程特殊的亲缘关系,在父进程中调用mmap,然后调用fork.那么在调用fork之后,子进程会继承父进程匿名映射后的地址空间,同样也继承mmap返回的地址,这样,父子进程就可以通过映射区进行通信了。注意,mmap返回的地址,需要由父子进程共同维护。

三、pod共享内存

3.1、pod内的多容器通信

​ 同一pod下的容器使用相同的网络名称空间,这就意味着他们可以通过’localhost’来进行通信,它们共享同一个Ip和相同的端口空间

​ 同一个pod里的容器共享IPC名称空间,这就意味着他们可以通过进程间通信的手段来进行通信。kubernetes创建的Pod,其共享内存默认64MB,且不可更改。kubernetes本身是没有设置share memory的大小的,64MB其实是docker 默认的共享内存的大小。docker run 的时候,可以通过--shm-size来设置共享内存的大小。k8s可以将memory类型的emptyDir挂载到/dev/shm来解决共享内存大小的问题。

​ 可以使用一个共享的存储卷来简单高效的地在容器间共享数据.大多数情况下,使用一个共享目录在同一pod里的不同容器间共享数据就够了。在Pod的不同container之间需要共享数据,可以将同一个emptyDir挂载到两个container中来达到共享的目的。此时,emtpyDir实际是一个宿主机上的目录,本质上还是磁盘(比如通过sidecar的方式来收集日志)。kubernetes提供来一种特殊的emptyDir:medium为memory的临时存储。用户可以将memory介质的emptyDir挂到任何目录,然后将这个目录当作一个高性能的文件系统来使用。emptyDir支持Memory,可以将该emptyDir.medium字段设置"Memory" 告诉Kubernetes安装tmpfs(RAM支持的文件系统),配合deploy,可以实现在pod使用共享内存超出限制的时候驱逐pod。

spec:
      containers:
      - image: centos:7
        name: centos
        volumeMounts:
        - mountPath: /dev/shm
          name: cache-volume
      volumes:
      - emptyDir:
          medium: Memory
          sizeLimit: 128Mi
        name: cache-volume

3.2 、pod间共享内存

同一个Node上不同的Pod之间若使用共享内存通信,则需要使用主机路径的挂载卷。

     containers:
      - name: demo-agent
        image: demo_agent:1.0
        volumeMounts:
        - mountPath: /dev/shm
          name: shm
        resources:
          limits:
            cpu: 200m
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: shm
        hostPath:
          path: /dev/shm
          type: Directory

在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Unix网络编程卷2:进程间通信PDF是一本非常有用的书籍,它涵盖了关于进程间通信的所有关键知识。 进程间通信是指进程之间交换数据或信息的过程,这对于理解操作系统以及网络编程非常重要。本书不仅讲解了进程通信的基础知识,还深入解释了信号、管道、消息队列、共享内存等高级通信方法。 在本书中,作者详细介绍了如何使用各种系统调用和库函数实现不同类型的进程间通信。读者将学习如何在不同进程之间共享文件句柄,如何创建匿名和命名管道,以及如何使用信号和信号处理程序等。 此外,本书还说明了如何以面向对象的方式编写并发程序。作者展示了C++ STL标准库和Boost库的使用方法,这些工具可以帮助程序员编写更高效的并发程序。 总之,Unix网络编程卷2:进程间通信PDF是一本非常有用的书籍,可以通过实例和详细解释帮助读者更好地理解进程通信的概念和技术。 ### 回答2: 《Unix网络编程 卷2:进程间通信》是一本经典的计算机网络编程书籍,主要讲解了在UNIX环境下进程之间如何进行通信,并介绍了常用的进程间通信机制和技术。 该书包含了进程间通信的基本概念和理论知识,从分析进程地址空间、进程控制、信号处理、进程间通信等多个方面详细阐述了进程间通信的各种实现方式,并通过实际的例子和代码提供了丰富的实践经验。同时,该书还包含了大量的参考文献和附录,方便读者深入学习和进一步研究进程间通信技术。 该书涵盖的主要内容包括UNIX进程模型、基本进程管理、进程资源和限制、信号、管道、消息队列、共享内存、信号量、套接字、RPC、XSI IPC等多个进程间通信机制和技术。其中,对于常用的进程间通信方式如管道、消息队列、共享内存、信号量等都进行了详细的介绍。同时,该书还介绍了进程间通信的高级技术,如分布式进程间通信(RPC)和XSI IPC等,帮助读者更好地实现进程间的通信。 总之,《Unix网络编程 卷2:进程间通信》是一本非常重要的计算机网络编程参考书籍,对于了解UNIX进程模型、深入理解进程间通信技术以及开发UNIX网络应用程序有很大的帮助。该书不仅适合计算机专业的学生和研究人员,也适合从事UNIX网络编程工作的程序员和工程师阅读。 ### 回答3: 《UNIX网络编程 卷2:进程间通信》是由W.Richard Stevens和Stephen A. Rago合作编写的,是一本关于UNIX如何实现进程间通信的技术指南,是一本精华之作。该书的主要内容包括:管道、FIFO、消息队列、信号量、共享内存以及套接字等多种进程间通信方式。同时,书中也介绍了如何在不同的进程间进行信息交换、如何保证进程的同步性和互斥性以及如何利用各种进程间通信工具实现并发编程。 该书在介绍管道、FIFO、消息队列等进程间通信机制时,都给出了详细的代码实现,并给出了该技术的优缺点以及适用场景。同时,书中还提供了丰富的案例分析,让读者能通过实践更好地理解和掌握这些技术。此外,书中还介绍了一些进程和线程相关的基础知识,如进程的创建、精灵进程、线程的创建、线程的同步等,这些知识为读者更好地理解进程间通信技术提供了背景和基础。 总的来说,该书是一本深入浅出、全面系统的进程间通信技术指南,它通过具体的代码实现和案例分析,使得读者能在实践中更好地理解和掌握这些技术,并能够开发出高效可靠的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值