进程间通信之共享内存及其shm函数的使用【Linux】

2024-01-07 16:44

本文主要是介绍进程间通信之共享内存及其shm函数的使用【Linux】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

进程间通信之共享内存及其shm函数的使用

  • 什么是共享内存
    • 共享内存的内核数据结构
  • 如何实现共享内存
    • 共享内存函数
      • shmget函数
        • ftok函数
      • shmat函数
      • shmdt函数
      • shmctl函数
  • 代码实现

什么是共享内存

  共享内存区是最快的IPC(Inter-Process Communication,进程间通信)形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.
在这里插入图片描述
  匿名管道和命名管道都需要通过系统调用接口来实现进程间通信,因为管道属于文件,属于内核中的一种特殊数据结构,由OSweihu,所以只能使用系统调用。而共享内存存在堆栈之间,可以直接读写。双方进程若要进行通信,直接进行内存级的读写即可。

从下图可以更加直观的看到在内核照中的运行方式:

在这里插入图片描述

  • 共享内存的提供者是操作系统。
  • 共享内存缺乏访问控制,用户(进程)双方都不会影响对方,甚至不知道对方的存在,容易造成接受的数据是不完整的。
  • 共享内存 = 共享内存块 + 对应的共享内存的内核数据结构

共享内存的内核数据结构

在这里插入图片描述

如何实现共享内存

共享内存函数

shmget函数

功能:用来创建共享内存
原型: int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

  参数key要保证在系统中的唯一性,key值相同,就是同一块共享内存,保证key的唯一性可以使用ftok函数实现:

ftok函数

所需要的头文件:
#include <sys/types.h>
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id);
参数:
pathname:指定的文件,此文件必须存在且可存取
proj_id:计划代号(project ID),可以在[0, 255]中随机取一个数字。
函数返回值

  • 成功:返回key_t值(即IPC 键值)
  • 出错:-1,错误原因存于error中
    ftok函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键(也就是shmget函数中需要的key值)。

  注意:如果在使用ftok的这段时间里,pathname指向的文件或者目录被删除而且又重新创建,ftok是能够成功返回的,但是由于inode可能不同了,返回值也就不同于所希望的值,由于key值相同,使用同一块共享内存,这时候就可能会导致key值不同,双方无法共享一块内存,所以这个pathname一定要是一个不会被修改的文件路径。


  对于shmget的第二个参数size,代表这个共享内存的大小,最好是页(PAGE:4096byte)的整数倍,(OS和磁盘进行I/O操作的基本单位也是4096byte),如果设置为4097byte,虽然只是多了一个字节,但是系统底层可能会创建4096 * 2的空间,即使这样,也只能使用自己申请的大小(4097byte),不能使用4096*2的空间。

第三个参数shmflg:

  • 该参数用于确定共享内存属性。
  • 使用上为:标志位 | 内存权限
  • 标志位参数有两种:IPC_CREAT、IPC_EXCL
  • 使用TPC_CREAT | 0666(内存权限):创建共享内存若底层存在,就获取,并返回,若不存在,就新建,并返回。
  • 使用TPC_CREAT | IPC_EXCL | 0666(内存权限):创建共享内存若底层存在,则出错返回。若不存在,则新建,并返回。所以返回的一定是一个全新的shm.

shmat函数

功能:将共享内存段连接到进程地址空间
原型: void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

使用这个函数建立物理内存和虚拟内存的映射。
shmaddr设置为nullptr表示让内核自己确定位置。
shmflg设置为0为默认方式。
使用shmat函数可以简单的这样:
char* shmaddr = (char*)shmat(shmid, nullptr, 0);

  • shmaddrNULL,核心自动选择一个地址
  • shmaddr不为NULLshmflgSHM_RND标记,则以shmaddr为连接地址。
  • shmaddr不为NULLshmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)
  • shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);
参数: shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段功能

shmctl函数

功能:用于控制共享内存
原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

 cmd的三个命令:

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

shmctl常用IPC_RMID,用于删除共享内存空间。

代码实现

//server.cpp
// 由server再共享内存里面读取信息#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <string>
#include <cstdlib>
const int SIZE = 4094;
int main()
{key_t key = ftok(".", 113);if (key < 0){perror("ftok");exit(1);}int shmid = shmget(key, SIZE, IPC_CREAT | 0666);if (shmid < 0){perror("shmget");exit(2);}char *addr = (char *)shmat(shmid, NULL, 0);int i = 0;while (i < 20){i++;printf("client# %s\n", addr);fflush(stdout);memset(addr, 0, sizeof(addr)); // 每次写入都清空,保证共享内存中没有上一次的数据残留。sleep(1);}shmdt(addr);shmctl(shmid, IPC_RMID, NULL);return 0;
}
// client.cpp
// 由client再共享内存里面发送信息#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <error.h>
#include <string.h>
#include <unistd.h>
#include <cstdlib>
const int SIZE = 4094;
int main()
{key_t key = ftok(".", 113);if (key < 0){perror("ftok");exit(1);}int shmid = shmget(key, SIZE, IPC_CREAT | 0666);if (shmid < 0){perror("shmget");exit(2);}char *addr = (char *)shmat(shmid, NULL, 0);int i = 0;while (i < 15){fgets(addr, 100, stdin);fflush(stdin);sleep(1);i++;}shmdt(addr);return 0;
}
// makeflie
.PHONY:all
all:server clientserver:server.cppg++ -o $@ $^client:client.cppg++ -o $@ $^.PHONY:clean
clean:rm -f server client

  上面的代码是实现一个由客户端进行在终端上进行写入信息到共享内存,服务端从共享内存读出到终端打印到屏幕上的一个过程。


    😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

  • List item

这篇关于进程间通信之共享内存及其shm函数的使用【Linux】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/580589

相关文章

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

使用C#代码在PDF文档中添加、删除和替换图片

《使用C#代码在PDF文档中添加、删除和替换图片》在当今数字化文档处理场景中,动态操作PDF文档中的图像已成为企业级应用开发的核心需求之一,本文将介绍如何在.NET平台使用C#代码在PDF文档中添加、... 目录引言用C#添加图片到PDF文档用C#删除PDF文档中的图片用C#替换PDF文档中的图片引言在当

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定