Linux —— 进程间通信 - system V进程间通信 - 共享内存(1)

目录

[1. system V共享内存](#1. system V共享内存)

[1.1 直接谈原理](#1.1 直接谈原理)

[2. 直接创建共享内存 --- 研究特性](#2. 直接创建共享内存 --- 研究特性)

[2.1 如何创建共享内存?](#2.1 如何创建共享内存?)

[2.2 如何删除共享内存?](#2.2 如何删除共享内存?)

[2.3 shmctl 如何控制呢?](#2.3 shmctl 如何控制呢?)

[2.4 挂接](#2.4 挂接)

[2.5 去关联](#2.5 去关联)

[3. 修改后,完整的代码:](#3. 修改后,完整的代码:)

[4. 总结:共享内存函数](#4. 总结:共享内存函数)


1. system V共享内存

共享内存区是最快的IPC形式。一旦这样的内存映射到共享他的进程的地主之空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存:是用来做进程间通信的。

1.1 直接谈原理

2. 直接创建共享内存 --- 研究特性

2.1 如何创建共享内存?

创建共享内存命令:shmget

shmflg 常用的标志位是 IPC_CREAT、IPC_EXCL,还有一种是IPC_PRIVATE:

所以,如果不想用malloc,也可以用共享内存的方式来申请内存,其实还有一种方案mmap:

之前的匿名管道和命名管道,都是一方将数据创建好,一方来获取资源。同样的共享内存也是这样的,一方创建资源,另一方进行获取资源。

如果键值 key = 1122,由系统生成,返回给进程A,进程A知道共享内存的key值是1122,想要进程间通信的话,就要获取刚创建的共享内存的 key值,进程B如何知道自己要访问的是哪一个共享内存,此时有人就说,让进程A把键值给进程B就好了啊,进程B就拿着键值去找,不就找到同一个了!!但是如果进程A能够把键值给进程B的话,那还用什么共享内存,因为进程AB间都可以通信了。

进程A本来就是不能将数据给进程B,如果键值由系统生成,返回给进程A,进程A是没有办法让进程B看到进程A创建的共享内存的。所以键值是不能由系统形成的,所以键值不能让系统形成!!!

所以让进程A和进程B包含同一个头文件,头文件包含同样的一个路径,同一个proj_id,然后使用同样的算法,ftok() ,此时就可以获得一个同一个键值。进程A在创建共享内存时,就将这个键值设置到共享内存中,进程A得到该共享内存,同样进程B拿着形成的key值到共享内存中去找,就可以看到同一个键值了!

代码部分进行验证:

bash 复制代码
//Shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__

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

// 随便写,形成的key可能和系统中的共享内存的键值是会冲突的,冲突了,将两个参数改改就行了
std::string gpathname = ".";
int gproj_id = 0x66;
int gdefaultsize = 4096;

// 一个共享内存; 多个共享内存  --- vector
class SharedMemory
{
public:
    SharedMemory(int size = gdefaultsize):_size(size),_key(0),_shmid(-1) 
    {
        
    }
    bool Create()
    {
        // 系统调用
        _key = ftok(gpathname.c_str(),gproj_id);
        if(_key < 0)
        {
            perror("ftok");
            return false;
        }
        printf("形成键值成功:0x%x\n",_key);  //键值的内容

        _shmid = shmget(_key, _size, IPC_CREAT | IPC_EXCL);  //创建全新的共享内存
        if(_shmid < 0)
        {
            perror("shmget");
            return false;
        }
        printf("shmid:%d\n",_shmid); 

        return true;
    }
    ~SharedMemory() {}

private:
    key_t _key;
    int _size;  //共享内存的大小
    int _shmid;
};

#endif
cpp 复制代码
//server.hpp

#include "Shm.hpp"
#include <unistd.h>

int main()
{
    SharedMemory shm;
    shm.Create();
    sleep(1);
    return 0;
}

代码修改后运行结果:

出现这种情况是因为没有将刚刚建立的共享内存释放掉,第二次访问时就冲突了。

之前的 malloc / new 不释放就会造成内存泄漏,如果进程一退出,内存泄漏的问题还在不在??

我们将经常在内存中待着的软件,启动起来不怎么退的软件,这些软件都会变为进程,我们称为常驻内存的进程!!!

但是这里为什么没有释放???是因为系统的吗??--- 不是的!!

第二次执行./server的时候,第一次的./server 早就退了。

在堆上 new / malloc 的空间,打开的文件描述符,这类资源都是随进程的。进程一退,资源自动被归还了,但是共享内存的生命周期不是随进程的。

如何证明??

命令:ipcs -m:查到所有的IPC资源

不释放的话,就会一直存在!!!

删除共享内存的命令:使用的不是键值,而是shmid!!

从上面的操作来看,删除成功。此时,在运行./server 就会成功

再将这个共享内存删除掉:

再次创建共享内存:

现在可以创建共享内存了,可是创建了,不释放的话,就会形成系统级别的内存泄露问题,所以我们就得用完之后就得释放掉对应的内存。不能一直用命令来删除,比较麻烦。得用代码来操作:

2.2 如何删除共享内存?

删除共享内存的命令:shmctl

2.3 shmctl 如何控制呢?

shmctl 的返回值:

所以命令在删除的时候和键值没有关系,因为键值只用来区分共享内存在系统中的唯一性的,在用户级别的操作共享内存用的都是shmid,这个跟我们之前的文件,上层访问我们访问的都是文件描述符,但真正标识对应的文件拿的是struct file对象的地址,重定向改变的就是指向,拿的是数据结构的对象的地址来标识资源的。但是我们并没有拿struct file对象的地址,所以我们用的是文件描述符。

Shm.hpp 新增删除共享内存的函数:

cpp 复制代码
    bool RemoveShm()
    {
        int n = shmctl(_shmid, IPC_RMID, nullptr);
        if( n < 0)
        {
            perror("shmctl");
            return false;
        }
        std::cout << "删除shm成功" << n << std::endl;
        return true;
    }
cpp 复制代码
//server.cpp

#include "Shm.hpp"
#include <unistd.h>

int main()
{
    SharedMemory shm;
    shm.Create();
    sleep(5);

    shm.RemoveShm();
    sleep(5);
    return 0;
}

再次启动的话,也是能够创建新的一个共享内存,之后又成功释放掉该共享内存:

创建好共享内存之后,如何将共享内存映射到当前进程的对应的地址空间,也就是如何将当前共享内存挂接到当前进程的虚拟地址空间??

2.4 挂接

挂接的命令:shmat

你的进程虽然能够用shmid找到共享内存,但是未来读写共享内存并不是通过shmid来读写共享内存的,因为它并不是真正意义上的文件描述符,所以要做的就是把当前进程挂接到自己的虚拟地址空间里。

代码如何实现?

cpp 复制代码
//Shm.hpp

class SharedMemory
{
public:
    SharedMemory(int size = gdefaultsize)
    :_size(size),_key(0),_shmid(-1),_start_addr(nullptr) 
    {
        
    }
    bool Create()
    {
       //......
    }

    bool Attach()
    {
        _start_addr = shmat(_shmid, nullptr, 0);
         if((int)_start_addr == -1)
         {
            perror("shmat");
            return false;
         }
         return true;
    }

    bool RemoveShm()
    {
       //......
    }
    ~SharedMemory() {}

private:
    key_t _key;
    int _size;  //共享内存的大小
    int _shmid;
    void *_start_addr; //起始地址
};
cpp 复制代码
//server.cpp

#include "Shm.hpp"
#include <unistd.h>

int main()
{
    SharedMemory shm;
    shm.Create();
    sleep(5);

    shm.Attach();
    sleep(5);

    shm.RemoveShm();
    sleep(5);
    return 0;
}

编译出错:

挂接时出现错误:是因为没有给共享内存设置权限

设置好权限后,再次运行:

2.5 去关联

去关联的命令:shmdt

cpp 复制代码
    bool Detach()
    {
        int n = shmdt(_start_addr);
        if(n < 0)
        {
            perror("shmdt");
            return false;
        }
        std::cout << "将指定的共享内存从进程的地址空间移除" << std::endl;
        return true;
    }
cpp 复制代码
//server.cpp
#include "Shm.hpp"
#include <unistd.h>

int main()
{
    SharedMemory shm;
    shm.Create();
    sleep(5);

    shm.Attach();
    sleep(5);

    //使用

    shm.Detach();
    sleep(5);

    shm.RemoveShm();
    sleep(5);
    return 0;
}

必须看到:前5秒,共享内存创建出来;中间5秒,挂接上;之后5秒,去关联;最后5秒,移除。

一个细节:

规定共享内存的大小是:4096

但是就是想创建 4097大小的 共享内存呢?

之前说过,共享内存的大小一般是4KB的整数倍。

运行起来后的共享内存的大小确实是4097个大小

OS会在内核层面上作4KB对齐,但是我们怎么没有看见4096*2 = 8192的大小,看到的就是4097个大小:

OS底层真实申请了2倍的4KB,但是你只要4097,所以只能给你用4097,因为你只要4097。不告诉你,也不打算让你知道。因为你看到了申请的8192的大小,可能会产生越界的情况,最后可能还会怪罪OS申请的空间太多了,导致自己发生了越界。

代码:

cpp 复制代码
#ifndef __SHM_HPP__
#define __SHM_HPP__

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

// 随便写,形成的key可能和系统中的共享内存的键值是会冲突的,冲突了,将两个参数改改就行了
std::string gpathname = ".";
int gproj_id = 0x66;
int gdefaultsize = 4096; // 共享内存的大小,4KB的整数倍,4KB的对齐(内核层面上)

// 一个共享内存; 多个共享内存  --- vector
class SharedMemory
{
private:
    bool CreateHelper(int flags)
    {
        // 系统调用
        _key = ftok(gpathname.c_str(), gproj_id);
        if (_key < 0)
        {
            perror("ftok");
            return false;
        }
        printf("形成键值成功:0x%x\n", _key); // 键值的内容

        _shmid = shmget(_key, _size, flags); // 创建全新的共享内存
        if (_shmid < 0)
        {
            perror("shmget");
            return false;
        }
        printf("shmid:%d\n", _shmid);

        return true;
    }

public:
    SharedMemory(int size = gdefaultsize)
        : _size(size), _key(0), _shmid(-1), _start_addr(nullptr)
    {
    }

    bool Create()
    {
        return CreateHelper(IPC_CREAT | IPC_EXCL | 0666);
    }
    bool Get()
    {
        return CreateHelper(IPC_CREAT);
    }

    bool Attach()
    {
        _start_addr = shmat(_shmid, nullptr, 0);
        if ((long long)_start_addr == -1)
        {
            perror("shmat");
            return false;
        }
        std::cout << "将指定的共享内存挂接到自己进程的地址空间" << std::endl;
        return true;
    }

    bool Detach()
    {
        int n = shmdt(_start_addr);
        if (n < 0)
        {
            perror("shmdt");
            return false;
        }
        std::cout << "将指定的共享内存从进程的地址空间移除" << std::endl;
        return true;
    }

    bool RemoveShm()
    {
        int n = shmctl(_shmid, IPC_RMID, nullptr);
        if (n < 0)
        {
            perror("shmctl");
            return false;
        }
        std::cout << "删除shm成功" << n << std::endl;
        return true;
    }
    ~SharedMemory() {}

private:
    key_t _key;
    int _size; // 共享内存的大小
    int _shmid;
    void *_start_addr; // 起始地址
};

#endif
cpp 复制代码
//server.cpp

#include "Shm.hpp"

int main()
{
    SharedMemory shm(4097);
    shm.Create();
    sleep(5);

    shm.Attach();
    sleep(5);

    //使用

    shm.Detach();
    sleep(5);

    shm.RemoveShm();
    sleep(5);
    return 0;
}
cpp 复制代码
//client.cpp

#include "Shm.hpp"

int main()
{
    SharedMemory shm(4097);
    shm.Get();  //获取已经存在的共享内存
    sleep(5);

    shm.Attach();
    sleep(5);

    //使用

    shm.Detach();
    sleep(5);

    sleep(5);
    return 0;
}

先让server运行起来,过个1~2秒,让client运行起来,nattch的变化过程为:

0 -> 1 -> 2 -> 1 -> 0

  • 0:创建共享内存
  • 1:server端挂接
  • 2:client端挂接
  • 1:server端去关联
  • 0:client端去关联
  • 最后server退出,client退出

到现在我们还没有用共享内存,还没有进行通信!!!一直在建立通信信道!!因为进程时具有独立性的,所以想让两个独立的进程进行通信,是一件麻烦的事情,匿名管道要fork(),使用命名管道要建立管道文件,共享内存也是一样的:比较复杂,是因为进程之间具有独立性!!!

cpp 复制代码
// Shm.hpp
bool Attach()
    {
        _start_addr = shmat(_shmid, nullptr, 0);
        if ((long long)_start_addr == -1)
        {
            perror("shmat");
            return false;
        }
        std::cout << "将指定的共享内存挂接到自己进程的地址空间" << std::endl;
        printf("_sart_addr:%p\n",_start_addr);
        return true;
    }

可以看见两端的起始虚拟地址是不一样的:

共享内存块映射到地址空间的堆栈之间的共享区

添加两个接口:

void AddChar(); 表示向共享内存中添加对应的字符:

cpp 复制代码
    void AddChar(char ch)
    {
        if(_num == _size)
            return;
        ((char*)_start_addr)[_windex++] = ch;
        _windex %= _size;
        _num++;
    }

void PopChar(); 表示未来另一个人进行读

cpp 复制代码
    void PopChar(char *ch)
    {
        if(_num == 0)
            return;
        *ch =  ((char*)_start_addr)[_rindex++];
        _rindex %= _size;
        _num--;
    }

上面两个接口用 _num 来判断的话,就会出现问题,因为server端和client端中就会各自有一个_num,导致在进行 ++ 或 -- 的时候操作的不是同一个 _num 。如何解决呢?

将num定义在共享内存中的最前面的4个字节中。此时进程AB访问的就是同一个num了。

3. 修改后,完整的代码:

cpp 复制代码
//Shm.hpp
#ifndef __SHM_HPP__
#define __SHM_HPP__

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

// 随便写,形成的key可能和系统中的共享内存的键值是会冲突的,冲突了,将两个参数改改就行了
std::string gpathname = ".";
int gproj_id = 0x66;
int gdefaultsize = 4096; // 共享内存的大小,4KB的整数倍,4KB的对齐(内核层面上)

struct data
{
    int num;
    char buffer[4092];
};

// 一个共享内存; 多个共享内存  --- vector
class SharedMemory
{
private:
    bool CreateHelper(int flags)
    {
        // 系统调用
        _key = ftok(gpathname.c_str(), gproj_id);
        if (_key < 0)
        {
            perror("ftok");
            return false;
        }
        printf("形成键值成功:0x%x\n", _key); // 键值的内容

        _shmid = shmget(_key, _size, flags); // 创建全新的共享内存
        if (_shmid < 0)
        {
            perror("shmget");
            return false;
        }
        printf("shmid:%d\n", _shmid);

        return true;
    }

public:
    SharedMemory(int size = gdefaultsize)
        : _size(size), _key(0), _shmid(-1), 
          _start_addr(nullptr),_windex(0),_rindex(0),
          _datastart(nullptr),
          _num(nullptr)
    {
    }

    bool Create()
    {
        return CreateHelper(IPC_CREAT | IPC_EXCL | 0666);
    }
    bool Get()
    {
        return CreateHelper(IPC_CREAT);
    }

    bool Attach()
    {
        _start_addr = shmat(_shmid, nullptr, 0);
        if ((long long)_start_addr == -1)
        {
            perror("shmat");
            return false;
        }
        std::cout << "将指定的共享内存挂接到自己进程的地址空间" << std::endl;
        printf("_sart_addr:%p\n",_start_addr);
        _num = (int*)_start_addr;
        _datastart =(char*)_start_addr + sizeof(int);

        return true;
    }

    void SetZero()
    {
        *_num = 0;
    }

    bool Detach()
    {
        int n = shmdt(_start_addr);
        if (n < 0)
        {
            perror("shmdt");
            return false;
        }
        std::cout << "将指定的共享内存从进程的地址空间移除" << std::endl;
        return true;
    }

    void AddChar(char ch)
    {
        if(*_num == _size)
            return;    
        ((char*)_datastart)[_windex++] = ch;
        ((char*)_datastart)[_windex] = '\0';
        std::cout<<"debug" << _windex<< ", "<< ch << std::endl;
        _windex %= _size;
       (*_num)++;
    }

    void PopChar(char *ch)
    {
        if(*_num == 0)
            return;  
        *ch =  ((char*)_datastart)[_rindex++];
        _rindex %= _size;
        (*_num)--;

        printf("%s\n",_datastart);
    }

    // void AddInt(){}
    // void AddString(){}

    bool RemoveShm()
    {
        int n = shmctl(_shmid, IPC_RMID, nullptr);
        if (n < 0)
        {
            perror("shmctl");
            return false;
        }
        std::cout << "删除shm成功" << n << std::endl;
        return true;
    }
    ~SharedMemory() {}

private:
    key_t _key;
    int _size; // 共享内存的大小
    int _shmid;
    void *_start_addr; // 起始地址
    int *_num;
    char *_datastart;  //数据的开始
    int _windex;  //当数组看,共享内存访问的写的下标
    int _rindex;
};

#endif
cpp 复制代码
//server.cpp

#include "Shm.hpp"

int main()
{
    SharedMemory shm;
    shm.Create();
    // sleep(5);

    shm.Attach();
    // sleep(5);

    //使用  ---- 一次读一个字符
    while(true)
    {
        char c;
        shm.PopChar(&c);

        printf("server get char:%c\n",c);

        sleep(1);
    }

    shm.Detach();
    // sleep(5);

    shm.RemoveShm();
    // sleep(5);
    return 0;
}
cpp 复制代码
// client.cpp

#include "Shm.hpp"

int main()
{
    SharedMemory shm;
    shm.Create();
    // sleep(5);

    shm.Attach();
    // sleep(5);

    //使用  ---- 一次读一个字符
    while(true)
    {
        char c;
        shm.PopChar(&c);

        printf("server get char:%c\n",c);

        sleep(1);
    }

    shm.Detach();
    // sleep(5);

    shm.RemoveShm();
    // sleep(5);
    return 0;
}

运行结果:

4. 总结:共享内存函数

shmget 函数

功能:用来创建共享内存

原型:

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

参数:

key:键值,这个共享内存段的名字

size:共享内存的大小

shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的,

取值为 IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。

取值为 IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。

返回值:

成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

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的整数倍。公式: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

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

相关推荐
csdn小瓯1 小时前
三层监控系统设计:从API日志到DevOps健康检查
运维·devops
CC城子1 小时前
EtherCAT研究之物理层PHY(一)
linux·运维·数据库
yyuuuzz1 小时前
国际云服务器的技术特点与使用经验
运维·服务器·网络·数据库·云计算·aws
代码AC不AC1 小时前
【Linux】信号保存 及 信号捕获
linux·信号保存·信号捕获
团象科技1 小时前
别盲目布局全球化,先理清海外云服务器能覆盖的业务边界
大数据·服务器·人工智能
wzhao1011 小时前
动态链接器(十一):线程局部存储
linux·rust·gnu
nix.gnehc1 小时前
Langfuse v3 Docker 部署
运维·人工智能·docker·容器·langfuse
JiaWen技术圈1 小时前
Web 安全防护 介绍
运维·nginx·安全
neo_Ggx231 小时前
Linux 日志检索速查:按时间、接口、Trace ID 查询完整请求链路
java·linux·服务器