• 【Linux详解】——共享内存


    📖 前言:本期介绍共享内存


    🕒 1. 共享内存的原理

    在之前学过的进程地址空间的基础上,我们知道,进程之间具有独立性,因为每个进程的内核数据结构的数据以及页表的映射都是独立的。而对于共享内存,我们同样了解,这是为了让进程之间能够进行通信的公共空间,接下来就通过进程地址空间的结构去了解共享空间的位置及原理:
    在这里插入图片描述

    OS为了让两个毫不相关的进程之间进行通信,进行了三个工作:

    1. 在对应的内存当中让用户帮OS申请一块空间(通过指定的调用接口)
    2. 将创建好的内存映射进进程的地址空间(用户就可以通过访问起始地址的方式来进行对申请的这块内存空间的访问)
    3. 未来不想通信:
      • 取消进程和内存的映射关系
      • 释放内存

    因此,我们把申请的这块空间称之为共享内存,将映射关系称之为进程和共享内存进行挂接。将取消进程和内存的映射关系称之为去关联,释放内存释放的就是共享内存。

    理解

    • 进程间通信,是专门设计的,用来IPC的,和malloc/new不是一个东西。
    • 共享内存是一种通信方式,所有想通信的进程,都可以用。
    • OS中一定会存在着很多共享内存。

    🕒 2. 共享内存的概念

    通过让不同的进程,看到同一个内存块的方式,叫做共享内存。

    🕘 2.1 接口认识

    #include
    #include
    
    int shmget(key_t key, size_t size, int shmflg);// size:共享内存的大小
    
    • 1
    • 2
    • 3
    • 4

    对于shmflg,常见的有两种选择:

    • IPC_CREAT:如果不存在共享文件则创建,存在则获取
    • IPC_EXCL
      • 无法单独使用,单独使用没有意义,需要结合IPC_CREAT
      • IPC_CREAT|IPC_EXCL:如果不存在,就创建,如果已存在,就出错返回。即在用户的角度,如果创建成功,一定是一个新的shm

    shmget返回值: 记住他是一个标识符就够用了,得到的是共享内存的标识符。(和文件fd没有任何关系)

    key: 是什么不重要,最重要的是其具备的唯一性。

    而获取key值,则通过一个新的接口:ftokftok通过指定的字符串数据*pathname以及char类型的proj_id数据进行一系列的算法整合返回了具有唯一性的Key:

    key_t ftok(char *pathname, char proj_id);
    
    • 1

    由于创建的key值有可能已经被别人使用了,因此有失败的可能性。创建Key值如果失败,则返回-1。

    🕘 2.2 演示生成key的唯一性

    # Makefile
    .PHONY:all
    all:shm_client shm_server
    
    shm_client:shm_client.cc
    	g++ -o $@ $^ -std=c++11
    shm_server:shm_server.cc
    	g++ -o $@ $^ -std=c++11
    
    .PHONY:clean
    clean:
    	rm -f shm_client shm_server
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    // comm.hpp
    #ifndef _COMM_HPP_
    #define _COMM_HPP_
    
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    
    #define PATHNAME "."//当前路径
    #define PROJ_ID 0x66
    
    key_t getKey()
    {
        key_t k = ftok(PATHNAME, PROJ_ID);
        if(k < 0)
        {
            // cin, cout, cerr ->stdin, stdout, stderr->0, 1, 2;标准错误stderr向2打印。
            std::cerr << errno << ":" << strerror(errno) << std::endl;
            exit(1);//终止进程
        }
        return k;
    }
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    // shm_server.cc
    #include"comm.hpp"
    
    int main()
    {
        key_t k = getKey();
    
        printf("0x%x\n", k);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    // shm_client.cc
    #include"comm.hpp"
    
    int main()
    {
        key_t k = getKey();
    
        printf("0x%x\n", k);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    [hins@VM-12-13-centos shm]$ ./shm_server
    0x6601062a
    [hins@VM-12-13-centos shm]$ ./shm_client
    0x6601062a
    
    • 1
    • 2
    • 3
    • 4

    通过make后执行发现,两个程序的k值是一样的,这就证明了ftok指定参数的返回值是唯一的。(k实际上就是32位的一个整数)

    🕘 2.3 再谈key

    OS中一定存在多个共享内存,因为彼此之间可能都需要通信,因此也就都需要申请一块空间。而OS申请的共享空间,也一定和进程一样需要被管理,既然需要管理,那么一定也是先描述再组织的方式,即共享内存 = 物理内存块+共享内存的相关属性 。

    之前谈到过,key是什么不重要,能进行唯一性的标识最重要,因此创建共享内存的时候,是如何保证共享内存在系统中是唯一的呢?当然是通过key来确定的,只要一个进程也看到了同一个key,就能够访问这个共享内存。那么key在哪里,实际上这就和PCB一样,key就在内核中的属性集合里,即:

    struct shm{
        key_t key;
        //...
    }
    
    • 1
    • 2
    • 3
    • 4

    即:key是通过shmget这样的系统调用,设置进入共享内存属性中,用来表示该共享内存在内核中的唯一性!

    shmid和key就好比fd和inode。为什么有了key还需要shmid呢?通过key和shmid的区分,能够面向系统层面和用户层面,这样能够更好的进行解耦,以免内核中的变化影响到用户级。

    🕒 3. 共享内存相关命令

    通过让不同的进程,看到同一个内存块的方式,叫做共享内存。

    查看共享内存ipcs -m/-q/-s (共享内存/消息队列/信号量数组)

    删除共享内存ipcrm -m shmnid

    将共享内存与虚拟内存进行关联:

    #include 
    #include 
    void *shmat(int shmid, const void *shmaddr, int shmflg);// 共享内存id,被映射的进程地址空间(给nullptr),给0默认可以读写。成功时,将返回共享内存的虚拟地址。失败返回-1(失败错误码errno被设置) 
    
    • 1
    • 2
    • 3

    将共享内存与虚拟内存去关联

    #include 
    #include 
    int shmdt(const void *shmaddr);//参数:shmat的返回值。成功时,将返回0。失败返回-1(失败错误码errno被设置)
    
    • 1
    • 2
    • 3

    控制(主要用移除)共享内存(shmctl)

    #include 
    #include 
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);//shmid(类似fd),传入系统设定的宏,shmid_ds数据结构。传入IPC_RMID移除共享内存成功时,将返回0。失败返回-1(失败错误码errno被设置)
    
    • 1
    • 2
    • 3

    🕒 4. 利用共享内存进行进程间通信

    // comm.hpp
    #pragma once
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
     
     
    #define PATHNAME "."//当前路径(路径都行)
    #define PROJ_ID 0X55//项目id也无要求
    #define MAX_SIZE 4096
    key_t getKey()
    {
        key_t k=ftok(PATHNAME, PROJ_ID);
        if(k==-1)
        {
            std::cout<<"ftok"<<errno<<":"<<strerror(errno)<<std::endl;
            exit(1);
        }
        return k;
    }
    int getShmHelper(key_t key,int flags)
    {
        int shmid=shmget(key,MAX_SIZE,flags);
        if(shmid==-1)//创建共享内存失败
        {
            std::cerr<<"shmget"<<errno<<":"<<strerror(errno)<<std::endl;
            exit(2);
        }
        return shmid;//返回共享内存标识符
    }
    int getShm(key_t key)//创建||获取共享内存
    {
        return getShmHelper(key,IPC_CREAT);//传0也行
    }
    int createShm(key_t key)//必定创建共享内存
    {
        return getShmHelper(key,IPC_CREAT|IPC_EXCL|0600);//生成一个全新的共享内存
    }
    void* attachShm(int shmid)//让共享内存与虚拟内存建立联系
    {
        void* memstart=shmat(shmid,nullptr,0);
        if((long long)memstart==-1L)
        {
            std::cerr<<"shmat"<<errno<<":"<<strerror<<std::endl;
            exit(3);
        }
        return memstart;
    } 
    void detchShm(void* memStart)//去关联
    {
        if(shmdt(memStart)==-1)
        {
            std::cerr<<"shmdt"<<errno<<":"<<strerror<<std::endl;
            exit(4);
        }
    }   
    void delShm(int shmid)//删除共享内存
    {
        if(shmctl(shmid,IPC_RMID,nullptr)==-1)
        {
            std::cerr<<"shmctl"<<errno<<":"<<strerror<<std::endl;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    // shm_server.cc
    #include "comm.hpp"
    int main()
    {
        key_t k=getKey();
        printf("0X%x\n",k);
        int shmid=createShm(k);
     
        char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系
        printf("memStart address:%p\n",memStart); 
     
        //通信接收代码
        while(true)
        {
            printf("client say:%s\n",memStart); 
            sleep(1);
     
            //调用用户级结构体
            struct shmid_ds ds;//创建结构体对象ds
            shmctl(shmid,IPC_STAT,&ds);//获取ds对象的状态
            printf("获取属性:%d,pid:%d,myself:%d,key:%d\n",ds.shm_segsz,getpid(),ds.shm_cpid,ds.shm_perm.__key);
        }
     
     
        detchShm(memStart);//去关联
        sleep(10);
     
        delShm(shmid);//删除共享内存,client和server都能删除共享内存,尽量谁创建谁删
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    // shm_client.cc
    #include "comm.hpp"
    int main()
    {
        key_t k=getKey();
        printf("0X%x\n",k);
        int shmid=getShm(k);//获取共享内存
        sleep(5);
     
        char* memStart=(char*)attachShm(shmid);//让共享内存与虚拟内存建立联系
        printf("memStart address:%p\n",memStart); 
     
        //通信传输代码
        const char* massage="I am client";
        pid_t id=getpid();
        int cnt=0;//发送计数
        while(true)
        {
            snprintf(memStart,MAX_SIZE,"%s[%d]:%d\n",massage,getpid,++cnt);
            sleep(1);
        }
     
        detchShm(memStart);//去关联
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    🕒 5. 共享内存的优缺点

    优点:共享内存是所有进程间通信中速度最快的。(无需缓冲区,能大大减少通信数据的拷贝次数)

    缺点:如果服务端读取速度较快,用户端发送数据较慢,就会产生同一段消息被服务端读取多遍。共享内存是不进行同步和互斥的,没有对数据进行任何保护。

    共享内存大小的建议:因为系统分配共享内存是以4KB为基本单位,一般建议申请共享内存的大小为4KB的整数倍。


    OK,以上就是本期知识点“共享内存”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
    💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
    🎉如果觉得收获满满,可以点点赞👍支持一下哟~

    ❗ 转载请注明出处
    作者:HinsCoder
    博客链接:🔎 作者博客主页

  • 相关阅读:
    【基于Cocos Creator实现的赛车游戏】9.实现汽车节点的控制逻辑
    强化学习实战——Motion Imitation环境配置+所遇问题(win10)
    Linux基础-网络配置
    计算机网络-TCP协议
    Android网络通讯之OkHttp
    java面试强基(2)
    【LVGL 学习】样式(style)过渡动画学习
    LVGL_基础控件Button
    【linux命令讲解大全】045.网络数据分析利器:深度解读 tcpdump 抓包工具的使用方法
    Redis未授权访问漏洞复现(三种方式)
  • 原文地址:https://blog.csdn.net/HinsCoder/article/details/132029743