Linux_共享内存通信

目录

1、共享内存原理

2、申请共享内存

2.1 ftok 

2.2 测试shmget、ftok 

2.3 查看系统下的共享内存 

3、关联共享内存

3.1 测试shmat

4、释放共享内存 

4.1 测试shmctl 

5、实现共享内存通信 

6、共享内存的特性

结语 


前言:

        在Linux下,有一种进程间通信方式(IPC)名为共享内存,他是IPC中通信最快的方式(通信方式为全双工),因为他直接在物理内存上创建一块区域并且映射在进程的地址空间中,使得进程使用共享内存就如同直接使用动态申请的空间,因此通信过程少了内核的系统调用步骤,以至于相比于其他IPC模式速度更快,不过也正是因为在通信时不受内核管辖,导致共享内存不具备同步互斥机制,因此需要用户手动处理同步互斥问题。

        但是需要注意的是共享内存虽然使用起来如同动态空间,但是他的底层和动态空间不一样,动态空间具有独立性,只限于单个进程内部的访问,而共享内存允许多个无亲缘进程进行通信,因此他和动态空间是有区别的。

1、共享内存原理

        共享内存的目的就是为了进程间通信,而进程间通信的核心观念是让不同的进程看到同一份资源,所以共享内存必须在物理内存上开辟一块空间,并且映射到进程地址空间中的共享区,具体示意图如下:

        但是共享内存的申请和malloc申请是不一样的,因为共享内存要面向所有进程,要做到这一点就必须调用系统接口,所以要进行共享内存通信必须调用系统接口。

2、申请共享内存

        在物理内存上申请共享内存的接口是shmget,该接口介绍如下:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
//key是用户给这段共享内存设置的名字,一个key对应一个共享内存
//size表示申请共享内存的大小
//shmflg表示权限设置,常用的有IPC_CREAT和IPC_EXCL

//调用成功返回非负整数表示共享内存的标识码(给系统看的),失败返回-1

        着重介绍shmflg:

        1、当传递的是IPC_CREAT|IPC_EXCL,表示若以当前key值申请的共享内存不存在,则创建一个并返回新共享内存的标识码。若以当前key值申请的共享内存存在则返回-1,表示申请失败。

        2、当传递的是IPC_CREAT,表示若以当前key值申请的共享内存不存在,则创建一个并返回新共享内存的标识码。若以当前key值申请的共享内存存在则返回该共享内存的标识码。

        所以使用IPC_CREAT|IPC_EXCL可以判断一个key对应的共享内存是否存在,即key值是否被用过,当我们想用一段新的共享内存则可以使用IPC_CREAT|IPC_EXCL。

        key值的作用是判断两个进程的共享内存是否为同一个,两个进程所用的key一样说明他们共用同一个共享内存,反之则否,因此可以理解为key值是用户给一段共享内存起的名字,而shmget返回值是系统给这段共享内存起的名字。

2.1 ftok 

        shmget需要用到key值,key的类型虽然是key_t,但是也可以传一个int类型的值给到key,只不过这么做会导致潜在的重名风险,并且key的值需要程序员自己维护,于是系统提供了一个接口ftok,他像是一个算法,可以计算并返回一个key_t类型的值,该接口介绍如下:

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

key_t ftok(const char *pathname, int proj_id);
//接收一个路径和一个整形
//成功返回一个key_t类型的值,失败返回-1

         所以两个进程调用ftok时传参是一样的,那么这两个进程就会获得相同的key值,这样两个进程就能看到同一份资源了,也就完成了通信的前提。

2.2 测试shmget、ftok 

         先用代码测试上述接口,测试代码如下:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <iostream>

using namespace std;

int main()
{
    const char *pathname = "/home/zh";
    int proj_id = 12;
    int size = 4096;

    key_t key = ftok(pathname, proj_id);
    if (key < 0)
    {
        perror("ftok");
        return -1;
    }
    cout << "key值被成功创建,key:" << key << endl;

    int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);
    if (shmid < 0)
    {
        perror("shmget");
        return -1;
    }
    cout<<"共享内存标识码被成功创建,shmid:"<<shmid<<endl;
    return 0;
}

        运行结果:

2.3 查看系统下的共享内存 

        共享内存不同于动态申请空间,动态空间的生命周期随进程。但是对于共享内存而言,若用户不主动释放共享内存,则共享内存会一直存活在系统中,他的生命周期随内核,即内核重启才会清理这些共享内存,在Linux下用指令ipcs -m查看当下系统的共享内存,测试如下:

        并且可以通过指令ipcrm -m shmid删除对应的shmid,测试如下:

3、关联共享内存

         上述接口shmget可以申请一块共享内存,但是申请到了不意味着就可以直接使用共享内存进行通信,要进行通信还要关联共享内存,关联共享内存的接口介绍如下:

#include <sys/types.h>
#include <sys/shm.h>

//关联共享内存
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid表示要关联的共享内存标识码
//shmaddr若不为NULL且shmflg不为SHM_RND,表示将共享内存的地址附加到shmaddr处
//shmaddr若为NULL,则该函数的返回值作为共享内存的地址(通常都设为NULL)
//shmflg表示权限设置,通常设为0表示对共享内存可读可写
//调用成功返回指向共享内存的指针,失败返回值(void*)-1

//去关联
int shmdt(const void *shmaddr);
//让调用该函数的进程不再关联该共享内存
//shmaddr表示共享内存的地址

        总的来说,调用shmat关联共享内存后,会拿到一个执行该共享内存的指针,通过该指针就可以对共享内存进行读写操作。

3.1 测试shmat

        因为申请共享内存的代码后续会被重复使用,为了后续更好的测试,所以对申请共享内存的接口进行再一层封装,封装成sharemem.hpp文件,代码如下:

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

using namespace std;

const char *pathname = "/home/zh";
int proj_id = 12;
int size = 4096;

key_t getkey()//封装ftok
{
    key_t key = ftok(pathname, proj_id);
    if (key < 0)
    {
        perror("ftok");
        exit(-1);
    }
    cout << "key值被成功创建,key:" << key << endl;
    return key;
}

int getshm(int shmflg)//封装shmget
{
    key_t key = getkey();
    int shmid = shmget(key, size, shmflg);
    if (shmid < 0)
    {
        perror("shmget");
        exit(-1);
    }
    cout << "共享内存标识码被成功创建,shmid:" << shmid << endl;
    return shmid;
}

int creatnewshm()//只想用最新的共享内存来进程通信
{
    return getshm(IPC_CREAT|IPC_EXCL|0666);//为了能够观察到变化,所以要保证共享内存的权限
}

int getoldshm()//获取一个已经存在的共享内存进行通信
{
    return getshm(IPC_CREAT);
}

         后续的测试只需要包含该文件即可,测试shmat代码如下:

#include "sharemem.hpp"

int main()
{
    int shmid = creatnewshm();
    cout<<"申请共享内存成功"<<endl;
    sleep(2);//观察nattch的值

    char* poi = (char*)shmat(shmid,NULL,0);
    cout<<"关联共享内存成功"<<endl;
    sleep(2);//观察nattch的值

    return 0;
}

        运行结果:

        其中,右侧nattch表示当前有多少个进程在关联该共享内存,当一个进程关联某个共享内存后,该共享内存的nattch+1,并且当该进程结束后,对应的nattch会-1。当然也可以使用shmdt手动去关联。

4、释放共享内存 

        手动释放共享内存的接口是shmctl,该接口本质的功能是控制共享内存,只不过也有删除选项,具体介绍如下:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid表示要释放的共享内存的标识码
//cmd表示该函数执行的具体任务,比如IPC_RMID表示删除任务
//buf表示指向共享内存数据结构的指针,若使用删除任务则该指针置为NULL即可

//调用成功返回0,失败返回-1

4.1 测试shmctl 

         测试shmctl的代码如下:

#include "sharemem.hpp"

int main()
{
    int shmid = creatnewshm();
    cout<<"申请共享内存成功"<<endl;
    sleep(2);//观察nattch的值

    char* poi = (char*)shmat(shmid,NULL,0);
    cout<<"关联共享内存成功"<<endl;
    sleep(2);//观察nattch的值

    shmdt(poi);
    cout<<"成功去关联共享内存"<<endl;
    sleep(2);//观察nattch的值

    shmctl(shmid,IPC_RMID,nullptr);
    cout<<"成功删除共享内存"<<endl;

    return 0;
}

        运行结果:

        从结果可以看到,无论是去关联测试还是删除共享内存,在右边的监控中都会显示对应的效果。 

5、实现共享内存通信 

         有了上述的接口以及sharemem.hpp文件,就可以实现两个进程的通信了,所以需要写一个客户端进程和一个服务器进程,其中服务器进程创建共享内存,让他们两都关联该共享内存,并且由客户端向服务器发送消息,服务器代码如下:

#include "sharemem.hpp"

int main()
{
    int shmid = creatnewshm();
    char *poi = (char *)shmat(shmid, nullptr, 0);
    cout << "关联共享内存成功" << endl;

    while (true)
    {
        cout<<"服务器接收:"<<poi<<endl;
        sleep(1);
    }
    
    shmdt(poi);
    shmctl(shmid,IPC_RMID,nullptr);

    return 0;
}

        客户端代码如下:

#include "sharemem.hpp"

int main()
{
    int shmid = getoldshm();
    char* poi = (char*)shmat(shmid,nullptr,0);
    cout<<"关联共享内存成功"<<endl;
    
    while (true)
    {
        string message;
        cout<<"客户端发送:";
        cin>>message;
        strcpy(poi,message.c_str());
    }
    shmdt(poi);
    return 0;
}

        测试结果:

        从结果可以发现,共享内存的通信本质就是对一个空间进行内存式的访问,无需调用read、write这些系统接口,直接用内存函数写入数据至内存对方就能够读取到内存里的数据。

6、共享内存的特性

        1、共享内存不需要调用系统接口实现进程间通信,只需要调用内存函数对内存进行读写即可实现进程间通信。

        2、共享内存本身没有同步互斥的概念,体现在上面的运行结果中读端会一直读内容(说明没有同步),并不会因为写端还未写而阻塞住。并且读端和写端可以同时访问共享内存(说明没有互斥)。

        3、共享内存在读写效率上更为高效,因为少了write和read这些步骤,即少了一层拷贝。 

结语 

        以上就是关于共享内存通信的讲解,共享内存作为IPC的其中一种方式,相对于其他通信方式,他有利有弊,在实际应用里先熟悉他的接口以及使用共享内存的步骤:申请共享内存(包括创建key值)、关联共享内存(shmat)、去关联(shmdt)、释放共享内存(shmctl) ,通过以上步骤可以实现完整的共享内存通信。

        最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
嵌入式Linux中的共享内存通信是一种高效的进程间通信方式,它允许多个进程在内存中共享数据,从而实现数据的快速传输和共享。下面是一种基本的共享内存通信的实现方法: 1. 创建共享内存区域: 在Linux中,可以使用shmget系统调用来创建共享内存区域。该系统调用需要指定共享内存的大小和权限等参数。创建成功后,会返回一个唯一的标识符。 2. 连接共享内存区域: 使用shmat系统调用将共享内存区域连接到进程的地址空间中。该系统调用需要指定共享内存的标识符,连接成功后,返回共享内存区域的起始地址。 3. 使用共享内存区域: 连接成功后,进程可以直接通过该内存区域的起始地址访问共享数据。可以使用指针或偏移量来访问不同的数据。 4. 分离共享内存区域: 当进程不再需要使用共享内存区域时,可以使用shmdt系统调用将其从进程的地址空间中分离。该系统调用需要指定共享内存的起始地址。 5. 删除共享内存区域: 当所有进程都不再使用该共享内存区域时,可以使用shmctl系统调用来删除共享内存区域。该系统调用需要指定共享内存的标识符。 需要注意的是,共享内存通信需要进程之间进行同步和互斥,以避免数据的不一致性和冲突。可以使用信号量、互斥锁等机制来实现进程间的同步和互斥操作。 以上是嵌入式Linux中实现共享内存通信的基本步骤,具体的实现方式可以根据需要进行调整和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安权_code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值