进程间通信之共享内存_进程间的通信共享内存-程序员宅基地

技术标签: 进程间通信  Linux学习笔记  linux  


1.进程间通信的分类:
(1)管道:1、匿名管道pipe;2、命名管道mkfifo
(2)System V IPC:1、System V 消息队列;2、System V 共享内存;3、System V 信号量。
(3)POSIX IPC:1、消息队列;2、共享内存;3、信号量;4、互斥量;5、条件变量;6、读写锁。
前面已经了解了进程间管道通信,那么共享内存又是什么原理?

1.共享内存的概念

什么是共享内存?
共享内存通信是一种进程间通信的方式,它允许两个或更多进程访问同一块内存,就如同 malloc () 函数向不同进程返回了指向同一个物理内存区域的指针。共享内存是 Unix/Linux下的多进程之间的通信方法,这种方法通常用于一个程序的多进程间通信,实际上多个程序间也可以通过共享内存来传递信息。而且共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存和管道的区别:

管道通信和共享内存都是进程间通信的方式,但是它们的实现方式不同。管道通信需要在内核和用户空间进行四次的数据拷贝:由用户空间的buffer中将数据拷贝到内核中 -> 内核将数据拷贝到内存中 -> 内存到内核 -> 内核到用户空间的buffer。而共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。多个进程可以同时操作,所以需要进行同步。

共享内存示意图:
在这里插入图片描述

共享内存数据结构:
用man shmctl指令可以查看共享内存的数据结构。

struct shmid_ds {
    
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};

而其中ipc_perm是一个内核为每个IPC对象所维护的一个数据结构,如下:

struct ipc_perm {
    
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

2.共享内存函数

2.1 shmget函数

shmget函数功能:用来创建共享内存。

NAME
       shmget - allocates a System V shared memory segment
	   //分配System V共享内存段
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

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

DESCRIPTION
       shmget() returns the identifier of the System V shared memory segment associated with the value of the argument key.  A new shared memory seg?
       ment, with size equal to the value of size rounded up to a multiple of PAGE_SIZE, is created if key has the value  IPC_PRIVATE  or  key  isn't
       IPC_PRIVATE, no shared memory segment corresponding to key exists, and IPC_CREAT is specified in shmflg.

       If shmflg specifies both IPC_CREAT and IPC_EXCL and a shared memory segment already exists for key, then shmget() fails with errno set to EEX?
       IST.  (This is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)

       The value shmflg is composed of:

       IPC_CREAT   to create a new segment.  If this flag is not used, then shmget() will find the segment associated with key and check  to  see  if
                   the user has permission to access the segment.

       IPC_EXCL    used with IPC_CREAT to ensure failure if the segment already exists.

RETURN VALUE
       On success, a valid shared memory identifier is returned.  On errir, -1 is returned, and errno is set to indicate the error.
       //成功返回有效的共享内存标识符,失败返回-1,并且错误码被设置。

int shmget(key_t key, size_t size, int shmflg);
key_t key:这个值用ftok生成,ftok会经过算法生成出一个冲突概率低的值,这个值保证唯一;目的是申请的共享内存块是尽可能不同的;
size_t size:申请共享内存块的大小;
int shmflg:这个参数常用两个选项,分别是IPC_CREAT and IPC_EXCL;
单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回
IPC_EXCL不能单独使用,一般都要配合IPC_CREAT;
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1。

ftok函数:

NAME
       ftok - convert a pathname and a project identifier to a System V IPC key

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);
       
RETURN VALUE
       On success, the generated key_t value is returned.  On failure -1 is returned, with errno indicating the error as for the stat(2) system call.

ftok会将这个路径pathname和proj_id(可以随便写)经过算法生成出一个冲突概率低的值。

2.2 shmat函数

shmat函数功能:将共享内存段连接到进程地址空间。

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid: 共享内存标识;
shmaddr:指定连接的地址;
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY;
返回值:成功返回一个指针,指向共享内存;失败返回-1;
shmaddr为NULL,核心自动选择一个地址;
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址;
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍;(所以一般直接设为nullptr就可)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存。

返回值为一个指针,并且指针指向共享内存,所以使用这个指针进行数据的写入或读出。

2.3 shmdt函数

shmdt函数功能:将共享内存段与当前进程脱离。

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

2.4 shmctl函数

shmctl函数功能:用于控制共享内存。

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

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

3. 共享内存的使用

使用之前,先认识下面的IPC指令,共享内存,消息队列,信号量等指令基本相似,所以在使用共享内存,消息队列,信号量进行通信时,其都有一批函数,总的说是大同小异,但是原理是不同的。

查看命令 删除命令
ipcs -m : 查看共享内存 ipcrm -m shmid : 删除共享内存
ipcs -q : 查看共享内存 ipcrm -q msqid : 删除消息队列
ipcs -s : 查看共享内存 ipcrm -s semid : 删除信号量

comm.hpp代码如下:

#pragma once
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <cassert>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/stat.h>
using namespace std;

//  IPC_CREAT and IPC_EXCL
// 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回
// IPC_EXCL不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的!

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096; // 共享内存的大小

key_t getKey()
{
    
    key_t k = ftok(PATHNAME, PROJID); //key_t ftok(const char *pathname, int proj_id);
    if(k == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

server.cc代码如下:

#include "comm.hpp"

int main()
{
    
    //1.创建共享内存先要创建一个key_t k(ftok)
    key_t k = getKey();

    //2.创建一个共享内存(shmget)
    umask(0); //默认权限
    int shmid = shmget(k, gsize, IPC_CREAT | IPC_EXCL | 0666); //int shmget(key_t key, size_t size, int shmflg)
    // 因为server创建共享内存,所以第三个参数为IPC_CREAT | IPC_EXCL,这个共享内存一定是最新的
    if(shmid == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(2);
    }

    //3.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.写入信息"i am process server"
    char buffer[64] = "i am process server";
    int i = 0;
    while (buffer[i])
    {
    
        start[i] = buffer[i];
        ++i;
    }
    //start = buffer; 错误写法,因为这样写直接就将start指针修改,start就不是指向共享内存的地址
    sleep(10);

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    //4.删除共享内存(shmctl)
    int m = shmctl(shmid, IPC_RMID, nullptr); // IPC_RMID | 删除共享内存段
    assert(m != -1);
    (void)m;

    return 0;
}

client.cc代码如下:

#include "comm.hpp"

int main()
{
    
    //1.获取已经存在的共享内存
    key_t k = getKey();

    int shmid = shmget(k, gsize, IPC_CREAT); //int shmget(key_t key, size_t size, int shmflg)
    // 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
    // 这里获取的是已经存在的


    //2.将共享内存段连接到进程地址空间(shmat)
    char* start = (char*)shmat(shmid, nullptr, 0);
    if(*start == -1)
    {
    
        cerr << "error: " << errno << " : " << strerror(errno) << endl;
        exit(3);
    }

    //3.从共享内存中读取数据
    int m = 3;
    while (m--)
    {
    
        cout << "i am client,i read: " << start << endl;
        sleep(3);
    }

    //将共享内存段与当前进程脱离(shmdt)
    int n = shmdt(start);
    assert(n != -1);
    (void)n;

    return 0;
}

makefile代码如下:

.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11
client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f server client

运行结果如下:
在这里插入图片描述

如果在自写代码中有如下错误,File exists,这是因为执行server.cc程序,程序并不是完整退出,而是程序进行一半时退出,例如:程序进行一半时按ctrl+c强制退出,程序没有执行到最后,也就是共享内存没有被删除,这时,就可以用ipcs -m查看共享内存;
在这里插入图片描述
然后ipcrm -m shmid(4)如下shmid为4,进行删除,重新运行程序即可。
在这里插入图片描述
如果是别的问题,那一定是代码错着,仔细检查代码吧。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_68278653/article/details/131791521

智能推荐

关于hadoop-2.6.0-cdh5.7.0 、 hbase-1.2.0-cdh5.7.0开启snappy压缩,直接导入lib库,免编译版_hadoop 哪里版本开始支持snappy-程序员宅基地

文章浏览阅读926次。支持hadoop-2.6.0-cdh* 版本的snappy压缩功能的 lib 库_hadoop 哪里版本开始支持snappy

flutter之玩转蓝牙插件flutter_blue 0.6.0+1-第一篇-程序员宅基地

文章浏览阅读6.4k次。介绍FlutterBlue是一款flutter对蓝牙插件,旨在提供来自两个平台(iOS和Android)的最大功能。使用FlutterBlue实例,您可以扫描并连接到附近的设备(BluetoothDevice)。一旦连接到设备,BluetoothDevice对象就可以发现服务(BluetoothService),特征(BluetoothCharacteristic)和描述符(Bluetooth..._flutter blue

yolov5算法_为什么使用yolov5不用最新的-程序员宅基地

文章浏览阅读661次。yolov5/7_为什么使用yolov5不用最新的

详解HTTP1.0、1.1、2.0版本区别/优化-程序员宅基地

文章浏览阅读4.2k次,点赞3次,收藏19次。HTTP/1.1并不支持 HTTP 首部压缩,为此 SPDY 和 HTTP/2 应运而生, SPDY 使用的是通用的DEFLATE 算法,而 HTTP/2 则使用了专门为首部压缩而设计的 HPACK 算法。本质上是为了减少请求,通过多个js或css合并成一个文件,多张小图片拼合成Sprite图,可以让多个HTTP请求减少为一个,减少额外的协议开销,而提升性能。连接和并行发送请求,这样每次一个请求都需要三次握手,而且其实建立连接和释放连接的这个过程是最耗时的,传输数据相反却不那么耗时。的缓存机制失效的问题。_http1.0

tinyxml2简单的创建、增、删、改、查_tinyxml2 删除-程序员宅基地

文章浏览阅读5.6k次,点赞5次,收藏19次。最近涉及到配置文件的问题,之前一直使用tinyxml,不过后来再上网看了看,发现tinyxml2有较之于tinyxml的优点,这里简单的介绍下tinyxml2的操作(xml类似于小型数据库,既然是数据库,必然有增删改查)这里给出我准备齐全的tinyxml2压缩包(里面有很多东西)tinyxml2压缩包下载这里是我的代码(我的代码基础源于这篇博客https://blog.csdn.net..._tinyxml2 删除

ObjectMapper:Json与对象间进行转换_java 如何objectmapper将json对象写入到json文件-程序员宅基地

文章浏览阅读207次。2.读了一篇文章,觉得JSON与列表的转换没什么意义,具体可以看看这个大佬的。3.read,read->读的过程,俩个参数,前JSON,后要转化的对象;write,写,对象转为JSON。1.添加依赖,毕竟有依赖才能调用。_java 如何objectmapper将json对象写入到json文件

随便推点

BM3406-VB一款N—Channel沟道SOT23的MOSFET晶体管参数介绍与应用说明-程序员宅基地

文章浏览阅读210次。BM3406-VB是一款SOT23封装的N—Channel沟道场效应晶体管,具有低电阻、高电流特性,适用于高性能开关电子应用。4. **汽车电子:** 在汽车电子系统中,用于控制电动窗、座椅等模块。2. **驱动模块:** 在驱动电路中,实现快速、可靠的开关操作。1. **电源模块:** 用于电源开关和稳压模块,确保高效能耗。3. **电动工具:** 用于电动工具中的电源和驱动控制。- 产品型号:BM3406-VB。- 沟道类型:N—Channel。- 品牌:VBsemi。- 封装:SOT23。

深圳NPDP认证|如何做好一个B端产品经理?-程序员宅基地

文章浏览阅读248次。Business,就是生意,要长期了解企业的商业模式,思考问题不再是需求方说什么,而是企业的业务模式要求系统如何支持,不是承接需求,而是自发地升级系统、升级生态。在不同层次对产品的理解,和项目的预备、前、中、后期、归档阶段,避免重复性工作,方便修改和协作。企业经营中,会遇到需要提升公司收益、销售收入、管理能力等等痛点,作为一个B端产品经理,要具备什么样的能力,才能规划、引领大家打造出一个优秀的产品呢?产品经理要对自身的看待方式和工作过程有自觉的审美,做好的、美妙的产品,在过程中享受审美和创造美感的过程。

vscode cannot find package in D\src_package command-line-arguments: cannot find packag-程序员宅基地

文章浏览阅读761次。> go help gopath文档中有一段:On Windows, the value is a semicolon-separated string.意思是以 " : "作为分隔符如果你配置的 GOPATH = D:/gopath, 会被解析成 gopath = D 和 gopath = /gopath两个目录!!!_package command-line-arguments: cannot find package

android图片资源加密解密,Android下资源图片的加密和解密-程序员宅基地

文章浏览阅读2.4k次。apk文件使用解压工具就能看到drawable等资源,但是有些游戏中的图片资源却是无法看到的。这个问题探索了许久……【1】图片资源不放置在drawable文件下,放在assets中(但是解压apk,同样能看到图片资源),以下说说使用方法。分析:Ⅰ)当图片资源放在drawable中的时候,能有相应的Id去解析:BitmapFactory.decodeResource(res, id)如果放置在as..._安卓png图片解密工具

Java基础——HashSet和TreeSet类_java treeset排序比较的是hash值吗-程序员宅基地

文章浏览阅读113次。文章目录HashSetTreeSetHashSet源码:添加元素的过程:先调用该元素所属类的hashCode()方法计算哈希值,再处理得到底层数组的索引位置。如果得到的索引位置上已经有元素,二者的哈希值不一定是一样。这时,比较哈希值,如果相同,再用equals()方法比较,如果返回为true,添加失败。如果哈希值相同而且用equals()方法比较返回为false,用链表的方式添加到这个元素的后面,即原来的元素指向新添加的元素。*哈希值计算方法:哈希值相当于是一个随机数。乘数一般选31,_java treeset排序比较的是hash值吗

oracle学习之表管理,约束以及视图索引同义词的操作-程序员宅基地

文章浏览阅读117次。在oracle中,常见的数据库对象有:表,视图,索引,序列,同义词存储过程,存储函数,触发器,包,包体,数据库链路(datalink),快照表是数据库的基本存储单元,以行列组成,命名规则:1,必须以字母开头2,长度在1~30之间3,oracle默认存储都存为大写创建表必须具备:权限 和 表空间常见的数据类型如下://创建表语法:crea..._oracle给3个表建立关系及约束同义词