Linux——进程通信之共享内存

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

目录

一.  回顾上文

二.共享内存

1.定义

2.特点:

3.实现步骤:

如下为成功链接共享内存使用权的完整步骤:

4.函数介绍

        4.1shmget函数

        4.1.2参数介绍       

        4.2ftok函数:

        4.2.1参数介绍

                关于ftok(); shmget();函数的代码实验:

        代码运行: 

                举个生活中的例子:

                运行结果:

                所以有两种方法解决: 

               4.3shmctl();——用于删除共享内存空间

               参数介绍:

               返回值:

              代码演示: 

    接下来就是第三步:关联各进程和共享内存空间的之间的“羁绊”!

            4.4shmat();——获取共享内存空间的地址

         参数介绍:

        代码演示: 

        运行结果:

         4.6 shmdt();——取消与共享内存空间的关联

         代码演示:

        最后就是双方的数据通信了!

 二.最终代码图:

        Comm.hpp:

        Sever.cc:

        Client.cc: 

        运行结果:

三.总结:

共享内存相比较于管道而言,两个进程在进行进程间通信时,需要经历几次数据的拷贝呢?

共享内存的缺点:


一.  回顾上文

        我所说的进程间数据通信指的是:以两个进程为例,一个进程只写,另一个进程只读的方式进行数据通信,它们的实现是单向的。

        1.而管道就是进程通信的一种方式,它是一种半双工通信方式,即数据是单向的(一个进程读,另一个进程写)。

        2.管道分为匿名管道和命名管道。

        3.匿名管道是用来让具有血缘关系的父子进程进行专门的数据通信,使用的是pipe函数创建管道,进而使用fork函数创建出子进程,便可开始数据通信。但是匿名管道需要关闭相应的文件描述符,父进程要么只读,要么只写,子进程也是一样。

        4.命名管道是在内存中对应一个缓冲区,读进程从管道读数据是一次性操作,数据一旦被读走,它就会被丢弃,释放空间以便于让写进程能够写更多的数据。而命名管道可以让没有任意的两个进程也可以进行数据通信,使用的方式是mkfifo函数创建管道文件,之后便是以文件的形式进行IO传输。

        

        除了管道之外,便是要学习另外一种数据通信的方式:System V。而System V通信中最主要常考的就是共享内存了,所以我们接下来讲述的重点是共享内存。

Linux——进程通信之共享内存,Linux,linux,网络,运维

         上图中,这两个进程都是独立的,在系统底层它们都有各自的内核数据结构(struct_task)——简称PCB,也有各自的进程地址空间(mm_struct),虽然它们的代码数据都在内存中,但互不影响干扰,所以是独立的。

        而想让两个毫不相干的进程采取通信,就必须要让两个进程看到同一份公共的资源,而今天的这份公共资源就是——共享内存。

二.共享内存

1.定义

        共享内存是存放在物理内存中的一段空间,该空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据快,同时还会创建相结构体来记录该共享内存属性,以便于管理。

        因此共享内存并不只有一份,我们可以根据需求申请多个共享内存。

2.特点:

        相较于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。

3.实现步骤:

        有了共享内存,就可以让两个进程指向这块共享空间资源了。于是进行第二步,让各个进程拥有这份公共资源的使用权限,它们才可以进行数据通信。

        但是想拥有共享内存的使用权并不简单,需要执行多个步骤才能实现。

如下为成功链接共享内存使用权的完整步骤:

Linux——进程通信之共享内存,Linux,linux,网络,运维

4.函数介绍

         4.1shmget函数

Linux——进程通信之共享内存,Linux,linux,网络,运维

        shmget函数创建共享存储空间并返回一个共享存储标识符,成功完成后,shmget () 应返回一个非负整数,即共享内存标识符:否则,它应返回-1 并设置 Errno以指示错误。

        4.1.2参数介绍
       

        如上shmget函数参数: 三个参数,第二个是设置共享内存空间的大小,第三个参数是标志位,类似于open函数的O_RDONLYO 、O_CTREAT等宏定义选项。

        下图为第三参数的默认选项,有两个:IPC_CREAT、IPC_EXCL

Linux——进程通信之共享内存,Linux,linux,网络,运维

 Linux——进程通信之共享内存,Linux,linux,网络,运维

IPC_CREAT:若没有,则创建共享内存空间;若该该共享内存空间存在,则会获取。

IPC_EXCL:   不能单独使用,必须搭配IPC_CREAT。

合起来的作用就是: 如果空间不存在,就创建;若存在该空间,就返回错误error

        而第一个参数key_t key,该参数是ftok函数的返回值,所以想要使用key,就得先获取ftok函数的返回值。

        4.2ftok函数:

Linux——进程通信之共享内存,Linux,linux,网络,运维

         4.2.1参数介绍

        ftok函数有两个参数,第一个参数与open函数的第一参数一样,都需要指定路径的文件名;第二参数id,它的取值范围是0-255之间的数值,在这个范围内随意取即可。

        使用ftok函数成功后,返回一个key_t类型的键值,若函数使用失败出错,返回-1 。

关于ftok(); shmget();函数的代码实验:

        注:在该实验中,用两个.cc文件模拟了两个进程,使用一个头文件去封装了ftok、shmget函数:

Linux——进程通信之共享内存,Linux,linux,网络,运维

代码运行: 

Linux——进程通信之共享内存,Linux,linux,网络,运维

        通过结果发现,两个进程的key地址都相等,这是因为这两个进程在调用getKey()时,ftok函数的参数是一样的,所以它们被分配的是同一个key值。

Linux——进程通信之共享内存,Linux,linux,网络,运维

        既然两个进程拥有相同的Key值,这就为它们能够看到同一份共享内存空间打下了坚实的基础,之后便是创造共享内存空间了,在这里我采用的是Sever进程去创建它,这个没有限制,哪个进程去创建它都可以。但是既然有一个进程创建了,另一个进程直接获取该共享空间即可,没必要再去创建一个,前人栽树(Sever进程),后人乘凉即可(Client进程)。 

举个生活中的例子:

        我请一个朋友去饭店吃饭,去饭店订了个空包间3号厅,准备好一切工作后,我通过微信发位置信息给朋友,让他来xx饭店3号包间吃饭。朋友收到信息后,按照约定时间来到了饭店,他需要找到这个包间,于是他将包间信息说与服务员听,服务员带着他来到了包间,找到了我,然后我们开始吃饭聊天。


        在这个例子中,我就好比是Server进程,我开辟了一块属于我俩的共享内存空间,我将位置信息发给他(Client进程),他获取(getShm())到这个饭店包间的位置信息,他拿着具体包间信息( key值 )给服务员看,服务员带着他来到了包间门前(共享内存空间),他(OS系统)会拿着Client的key和包间的key进行对比,只要一样,client就能进入到包间中。
key是共享内存空间被系统认识的唯一标识符,当一个进程A创建了共享内存空间,另一个进程B只要拿着和进程A一样的key,它们就能够进入同一个共享内存空间中去。

        key是用来表示要shmget,设置进入共享内存属性中的!

运行结果:

Linux——进程通信之共享内存,Linux,linux,网络,运维

         如上图:两个进程的shmid都为1,表明server进程成功创建了共享内存空间,而client进程也成功获取到了server创建的内存空间,那么第1步已完成!该进行第2步: 让各进程进行页表映射连接共享内存空间了。


        这里需要注意一点: 当第二次运行Server进程时,由于第一次的成功创建共享内存,所以第二次的运行会报错shmget函数的返回值问题,错误表示“文件已存在!” 让我们无法运行,这是因为shmget函数的第三参数IPC_CREAT | IPC_EXCL导致的,因为第一次创建成功的shmid的生命周期是跟随OS系统的,而不是跟随进程的——说白了就是shmid的值不会随之进程的结束而被销毁,只有当我们关闭了Linux系统,它才会消失。

        所以有两种方法解决: 

1.就是关闭Linux系统再重启便可以运行./server;

2.就是采用指令: ipcrm -m shmid(填它的数值);

ipcs指令意为显示共享内存空间、消息队列、信号量的属性面板:

Linux——进程通信之共享内存,Linux,linux,网络,运维

         从上图可知:该指令显示了共享内存空间的key的地址、shmid值、owner值(拥有者)。剩下的属性后面会谈到!

Linux——进程通信之共享内存,Linux,linux,网络,运维

        使用ipcrm -m shmid指令就可以删除掉第一次运行成功的shmid值,然后再一次运行Sever进程时,就可以运行成功。但是删除掉shmid值后,第一次创建出来的共享内存空间也会被相应的删除,因为第二次创建出的shmid值为32769,与第一次32768并不一样。

                  其实共享内存==物理内存块(共享内存空间是存储在物理内存中的)+共享内存的相关属性!!

        举人例子,当我们在学C语言的过程中,采用malloc创建堆区空间进行动态存储,比如我们采用指针指向开辟了1024字节的空间,而当我们free的时候,系统会回收掉指针指向的这块空间地址,而指针指向的只是这块地址的首地址,它是怎么知道这块地址空间有多大,需要回收多大字节的空间?具体的原理是怎么实现的?


        因为我们需要开辟的是1024字节大小的空间,而CPU会为其开辟大于1024字节的空间,可能会开1034字节,多出来的10字节空间会存放这块空间的相关属性信息,比如这块空间的起始地址,未尾地字节大小,创建时间.....·;CPU在执行free函数的时候,就是参照着这块堆区空间的相关属性进行精准释放空间的!! !

        所以我们在使用ipcrm -m shmid的指令原理就是系统通过共享内存空间的相关属性shmid去释放这块共享内存空间的。shmid的重要性相当于pid在进程中的重要性。


那么趁热打铁,既然说到了释放共享内存,那么先来学习一下在代码中释放共享内存空间的函数吧:

4.3shmctl();——用于删除共享内存空间

Linux——进程通信之共享内存,Linux,linux,网络,运维

        参数介绍:

1、shmid就是shmget函数返回的共享存储标识符;
2、cmd参数是宏定义参数,共有三个:

        IPC_RMID:常用删除共享内存;

        IPC_STAT::得到共享内存的状态,把共享内存的shmid ds结构复制到bu中;

        IPC SET: 改变共享内存的状态,把bu所指的shmid ds结构中的uid、gid、mode复制到共享内存的shmid ds结构内。(内核为每个共享存储段维护着一个结构,结构名为shmid ds,里面存放着共享内存的大小,pid,存放时间等一些参数)
3、buf就是结构体shmid ds,一般填nullptr即可。


        返回值:

                成功返回0: 失败返回错误error

代码演示: 

Linux——进程通信之共享内存,Linux,linux,网络,运维

接下来就是第三步:关联各进程和共享内存空间的之间的“羁绊”!


4.4shmat();——获取共享内存空间的地址

Linux——进程通信之共享内存,Linux,linux,网络,运维

 参数介绍:

该函数的参数有三个:
        第一个就是shmget函数的返回值shmid;

        第二个参数建议是用nullptr。 若为NULL,共享内存会被attach到一个合适的虚拟地址空间。不为NULL: 系统会根据参数及地址边界对齐等分配一个合适的地址;

        第三个参数是宏定义选项,不指定的情况下默认使用0即可

        该函数的返回值为-1,表示获取共享内存空间地址失败,也就无法关联成功。

代码演示: 

Linux——进程通信之共享内存,Linux,linux,网络,运维

        代码解析:因为start是指针,指针在Linux64位下的大小为8字节,所以想要判断指针(8字节)转换为整型int(4字节)的值是否等于-1(判断shmat函数是否成功返回),在强转的时候就不能转换为4字节,而得找同等大小整型为8字节的long long整型,所以要记住:if((int)start==-1))是错误的!

Linux——进程通信之共享内存,Linux,linux,网络,运维

运行结果:

Linux——进程通信之共享内存,Linux,linux,网络,运维    Linux——进程通信之共享内存,Linux,linux,网络,运维


4.6 shmdt();——取消与共享内存空间的关联

Linux——进程通信之共享内存,Linux,linux,网络,运维

Linux——进程通信之共享内存,Linux,linux,网络,运维

 代码演示:

Linux——进程通信之共享内存,Linux,linux,网络,运维

除了进程双方的数据写入读取外,所有的准备工作已经做好了,接下来总结一下:

1、key_t Creat_Key();——                        用于获取ftok的返回值键值key;

2、int Creat_Shm(key_t k);——               用于创建共享内存空间;

3、int Get_Shm(key_t k);——                  用于让另一个进程获取到共享内存空间;
4、void* attach_Shm(int shmid);——     用于让各个进程获取共享内存空间地址并关联起来;
5、void Dattach_Shm(void* start); —— 用于让各个进程取消与共享内存空间的关联;
6、void void Del_Shm(int shmid)——    用于让创建共享内存空间的进程A释放该空间;


最后就是双方的数据通信了!

Linux——进程通信之共享内存,Linux,linux,网络,运维

        数据通信就很简单了,就是一个往共享内存区域里写内容,一个从共享区域里读取打印内容。代码中的start指针指向的就是开辟出的共享内存空间,把start看作一个缓冲区就行。

        写内容有两种方式,一种是提前写成字符串,往里传就行,如上;另一种是即输即用,如下:

Linux——进程通信之共享内存,Linux,linux,网络,运维


 二.最终代码图:

        Comm.hpp:

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

#define NAME_ "./tmp/"
#define ID 0x62
#define SIZE 4096

key_t Creat_Key(){
    key_t k=ftok(NAME_,ID);
    if(k<0){
        std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-1);
    }
    //创建成功
    return k;
}

int Shmet(key_t k,int flags){
    int shmid=shmget(k,SIZE,flags);
    if(shmid<0){
         std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-2);
    }
    return shmid;
}


int Creat_Shm(key_t k){
    return Shmet(k,IPC_CREAT |IPC_EXCL|0600);
}

int Get_Shm(key_t k){
    return Shmet(k,IPC_CREAT);
}


//关联
void* attach_Shm(int shmid){
    void* start=shmat(shmid,nullptr,0);
    if((long long)start==-1L){
            std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-4);
    }
    //关联成功
    printf("related successily!\n");
    return start;
}

//去关联
void Dattach_Shm(void* start){
    if(shmdt(start)==-1){
        std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-5);
    }
    //去关联成功
    printf(" abondon successily !\n");
}


void Del_Shm(int shmid){
    if(shmctl(shmid,IPC_RMID,nullptr)==-1){
           std::cerr<<"errno:"<<strerror(errno)<<std::endl;
        exit(-3);
    }
    //删除成功
    printf("删除共享内存空间\n");
}

        Sever.cc:

#include "Comm.hpp"

int main(){
    key_t k=Creat_Key();
    printf("key:%u\n",k);

    int shmid=Creat_Shm(k);
    printf("shmid:%d\n",shmid);

    //关联
    char* start=(char*)attach_Shm(shmid);
    printf("start:%p\n",start);

    //数据通信
    while(true){
        printf(" Client says:%s\n",start);
        sleep(1);
    }

    Dattach_Shm(start);

    Del_Shm(shmid);
    return 0;
}

        Client.cc: 

#include "Comm.hpp"

int main(){
    int k=Creat_Key();
    printf("key:%u\n",k);

    int shmid=Get_Shm(k);
    printf("shmid:%d\n",shmid);

//关联
     sleep(1);
    char* start=(char*)shmat(shmid,nullptr,0);
    printf("start:%p\n",start);

    int cnt=0;
    const char* s="我是另一个进程,我正在给Sever发消息!";
    while(true){
       snprintf(start,SIZE,"%s:pid:[%d] cnt:[%d]",s,getpid(),cnt++); 
        sleep(1);
    }  
    
    //去关联
    Dattach_Shm(start);

    return 0;
}

 运行结果:

Linux——进程通信之共享内存,Linux,linux,网络,运维

三.总结:

共享内存的优点: 传输速度最快 !
使用共享内存进行进程传输为什么最快?

        原因就是这俩进程不需要建立自己的缓冲区。一一信息直达!

共享内存相比较于管道而言,两个进程在进行进程间通信时,需要经历几次数据的拷贝呢?

        先来看两个进程在管道下的数据拷贝: (注:下图没有考虑C/C++中的stdin流、stdout流)

Linux——进程通信之共享内存,Linux,linux,网络,运维

        从上图看:写进程从写入数据到C缓冲区到管道,再从管道中拿出到缓冲区到读进程共经历了4次的数据拷贝。

(注:下图考虑了C/C++中的stdin流、stdout流)

Linux——进程通信之共享内存,Linux,linux,网络,运维

        从上图看:加上了输入输出流后,数据共经历了4+2=6次的拷贝。


        接着,我们来看看两个进程在共享内存空间中的数据拷贝:仍是先不考虑C的stdin、stdout流

Linux——进程通信之共享内存,Linux,linux,网络,运维

  从上图看:写进程写入数据就直接到了共享内存空间,再从该空间中拿出到读进程共经历了2次的数据拷贝。 

Linux——进程通信之共享内存,Linux,linux,网络,运维

         从上图看:加上了输入输出流后,数据共经历了2+2=4次的拷贝。

       

共享内存的缺点:

        共享内存没有提供同步的机制,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段(如信号量、互斥量等)来进行进程间的同步工作。

        说白了使用共享内存可能会造成进程A正在写数据的过程中,进程B就已经读走了一些,进程A在这次还没有把数据完整的写完,进程B拿了一半就走了,导致了这种通信方式缺乏了数据安全性。


Linux——进程通信之共享内存,Linux,linux,网络,运维

 文章来源地址https://www.toymoban.com/news/detail-521090.html

Linux——进程通信之共享内存,Linux,linux,网络,运维

 

运行结果:

Linux——进程通信之共享内存,Linux,linux,网络,运维

 

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

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

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

相关文章

  • 【Linux】进程间通信之共享内存

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

    2024年02月03日
    浏览(50)
  • 进程间通信--共享内存详解【Linux】

    本文详细讲解了共享内存的原理和使用,并且通过实例代码角度来深度理解共享内存,下面就让我们开始吧。 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源。 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    等明年国庆去西藏洗涤灵魂,laozi不伺候这无聊的生活了 1. 通过之前的学习我们知道,每个进程都有自己独立的内核数据结构,例如PCB,页表,物理内存块,mm_struct,所以具有独立性的进程之间如果想要通信的话,成本一定是不低的。 2. a.数据传输:一个进程需要将它的数据

    2023年04月17日
    浏览(50)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包