Linux:进程间通信->共享内存

1. 共享内存的概念

System V共享内存,是一个高效的进程间通信IPC机制,允许多个进程共享同一块物理内存区实现快速的数据交换。如下图所示

这两个进程分别通过页表映射到这一块共享内存中

2. 共享内存的函数

shmget

功能: 创建新的共享内存段,或获取已经存在的共享内存标识符(shmid)

头文件和原型如下

cpp 复制代码
#include <sys/shm.h>

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

参数如下:

1. key:共享内存的唯一键值(由ftok生成或IPC_PRIVATE)

2. size:共享内存的大小(字节),创建时必须指定,获取已有的共享内存可设为0。

3. shmflg:权限标识(例如0666),可用权限如下

IPC_CREAT:如果不存在就创建新段

IPC_EXCL:与IPC_CREAT一起使用,若段已存在则失败。

SHM_HUGETLB:使用大页内存(需要内核支持)

返回值:成功返回共享内存标识符(shmid),失败返回-1,错误码存在error

shmat

功能:将共享内存段附加到当前进程的地址空间,返回指向共享内存的指针。

原型:

cpp 复制代码
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

1. shmid:共享内存标识符

2. shmaddr:指定附加地址(通常设为NULL)

3. shmflg:选项如下

SHM_RDONLY:以只读模式附加

SHM_REMAP(Linux特供):替换现有映射

返回值:成功返回共享内存的起始地址指针,失败返回(void*) -1

shmdt

功能:将共享内存段从当前进程的地址空间分离

cpp 复制代码
int shmdt(const void *shmaddr);

参数:

shmaddr:shmat返回的指针

返回值: 成功返回0,失败返回-1

shmctl

在linux下可以用下面指令删除

bash 复制代码
ipcrm -m shmid

功能:对共享内存段执行控制操作(删除、查询状态)

cpp 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

1. shmid:共享内存标识符

2. cmd:控制命令,选项如下

IPC_RMID:标记共享内存段为待删除(所有进程分离后销毁)

IPC_STAT:获取共享内存段的状态信息

IPC_SET:修改共享内存段的权限或所有者

3. buf:指向struct shmid_ds的指针,用于存储或设置状态信息

返回值:成功返回0,失败返回-1设置错误信息errno

ftok函数生成键值

功能:根据文件路径和项目标识符生成唯一键值(key_t)

功能和头文件

cpp 复制代码
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);

参数:
1. pathname:一个存在的文件路径(例如/tmp/shmfile)。

2. proj_id:项目标识符(一个字节,通常用一个字符)

返回值:成功返回生成的键值,失败返回-1

3. 共享内存通信

cpp 复制代码
#define ERR_EXIT(m)  \
do \
{  \
    perror(m);  \
    exit(EXIT_FAILURE); \
}while(0)  \
   //处理错误的宏  
写端创建共享内存
cpp 复制代码
        int _k = ftok(".", 48);
        _size= 4096;       
        _shmid=shmget(_k,_size,IPC_CREAT|IPC_EXCL|0666);
        if(_shmid<0)
        {
            ERR_EXIT("shmid");
        }
读端获取共享内存
cpp 复制代码
        int _k = ftok(".", 48);
        _size= 4096;       
        _shmid=shmget(_k,_size,IPC_CREAT);
        if(_shmid<0)
        {
            ERR_EXIT("shmid");
        }
获取指向共享内存的指针

获取指向共享内存的指针

cpp 复制代码
        _start_mem=shmat(_shmid,nullptr,0);
        if((long long)_start_mem<0)
        {
            std::cout<<"shmat 失败"<<std::endl;
            ERR_EXIT("shmat");
        }
将指针与共享内存分离

进程退出时没解除绑定,共享内存段就仍然保留在系统的共享内存资源中,直到shmctl或系统重启

cpp 复制代码
        int n=shmdt(_start_mem);//将指针与共享内存分离
        if(n==0)//成功
        {
            printf("shmctl delete shm: %d success!\n",_shmid);
        }
        else
        {
            ERR_EXIT("shmdt");
        }

共享内存也采用引用计数,shmdt成功时,引用计数-1

查看共享内存
cpp 复制代码
#include <sys/shm.h>

struct shmid_ds {
    struct ipc_perm shm_perm;   // 权限和所有者信息
    size_t          shm_segsz;  // 共享内存段大小(字节)
    time_t          shm_atime;  // 最后附加时间(attach)
    time_t          shm_dtime;  // 最后分离时间(detach)
    time_t          shm_ctime;  // 最后修改时间(创建或属性变更)
    pid_t           shm_cpid;   // 创建者进程的PID
    pid_t           shm_lpid;   // 最后操作(attach/detach)的进程PID
    shmatt_t        shm_nattch; // 当前附加到该段的进程数
    // ... 其他系统特定字段(可能因操作系统不同而扩展)
};

struct ipc_perm {
    key_t   __key;    // 共享内存的键值(ftok生成)
    uid_t   uid;      // 所有者的用户ID
    gid_t   gid;      // 所有者的组ID
    uid_t   cuid;     // 创建者的用户ID
    gid_t   cgid;     // 创建者的组ID
    unsigned short mode;  // 权限标志(如0666)
    unsigned short __seq; // 序列号(内核使用)
};

进程结束后共享内存不会销毁,导致资源泄露

cpp 复制代码
        struct shmid_ds ds;//结构体
        int n=shmctl(_shmid,IPC_STAT,&ds);
        printf("shm_segsz: %ld\n",ds.shm_segsz);
        printf("key: 0x%x\n",ds.shm_perm.__key);

IPC_STAT通过shmctl获取共享内存段的元数据(存入我们存入的struct shmid_ds结构体)

销毁共享内存
cpp 复制代码
        //_shmid=shmget(_k,_size,IPC_CREAT|IPC_EXCL|0666);
        //_shmid值如上所示
        if(_shmid==gdefaultid) return;//没初始化
        int n=shmctl(_shmid,IPC_RMID,nullptr);
        if(n==0)//删除成功返回0
        {
            int n=shmdt(_start_mem);//将指针与共享内存分离
            if(n==0)//成功
            {
                printf("shmctl delete shm: %d success!\n",_shmid);
            }
            else
            {
                ERR_EXIT("shmdt");
            }
        }
        else//删除失败返回-1
        {
            ERR_EXIT("shmctr");
        }

IPC_RMID是 shmctl第二个参数时->当引用计数为0即shmdt将所有附加进程都分离后,内核自动销毁该段。

4. 共享内存优缺点

优点

直接读写共享内存,没有数据拷贝 例如管道中拷贝到管道文件再拷贝出来的操作。资源占用就少。并且独立于进程存在,可以通过shmctl来显式删除

缺点

附加到共享内存后进程可以自由读写(没设置SHM_RDONLY),恶意进程可能破坏数据。

没有同步机制,容易导致数据不一致,由于数据更新不及时。可以通过命名管道的同步机制解决这一问题

5. 源码

Shm.hpp
cpp 复制代码
#pragma once
#include<iostream>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<string>
#include<unistd.h>
const int gdefaultid=-1;
const int gsize=4096;
std::string pathname=".";
const int projid=0x66;
const int mode=0666;
#define CREATER "creater" 
#define USER "user"

#define ERR_EXIT(m)  \
do \
{  \
    perror(m);  \
    exit(EXIT_FAILURE); \
}while(0)  \

class Shm
{
private:
    void CreateHelper(int flg)
    {
        printf("key: 0x%x\n",_k);
        _shmid=shmget(_k,_size,flg);
        if(_shmid<0)
        {
            ERR_EXIT("shmid");
        }
        printf("shmid: %d\n",_shmid);        
    }
    void Creat()//创建共享内存
    {
        // std::cout<<"创建共享内存的是: "<<_usertype<<std::endl;
        CreateHelper(IPC_CREAT|IPC_EXCL|mode);//要给权限
    }
    void Get()//获取共享内存
    {
        CreateHelper(IPC_CREAT);
    }
    void Detach()
    {
        int n=shmdt(_start_mem);//将指针与共享内存分离
        if(n==0)//成功
        {
            printf("shmctl delete shm: %d success!\n",_shmid);
        }
        else
        {
            ERR_EXIT("shmdt");
        }
    }
public:
    Shm(const std::string& pathname,int projid,const std::string& usertype)
    :_shmid(gdefaultid)
    ,_size(gsize)
    ,_start_mem(nullptr)
    ,_usertype(usertype)
    {
        _k=ftok(pathname.c_str(),projid);
        if(_k<0)
        {
            ERR_EXIT("ftok");
        }

        if(_usertype==CREATER) Creat();//如果是创建者
        else if(_usertype==USER) Get();//用户使用
        else{}

        Attach();
    }

    void Destroy()
    {
        if(_shmid==gdefaultid) return;//没初始化
        int n=shmctl(_shmid,IPC_RMID,nullptr);
        if(n==0)//删除成功返回0
        {
            Detach();//分离指针
            // printf("shmctl delete shm: %d success!\n",_shmid);
        }
        else//删除失败返回-1
        {
            ERR_EXIT("shmctr");
        }
    }
    void Attach()//
    {
        _start_mem=shmat(_shmid,nullptr,0);
        if((long long)_start_mem<0)
        {
            std::cout<<"shmat 失败"<<std::endl;
            ERR_EXIT("shmat");
        }
        printf("Attach success\n");
    }
    void* VirtualAddr()//返回虚拟地址
    {
        printf("VirtualAddr %p\n",_start_mem);
        return _start_mem;
    }
    int Size()
    {
        return _size;
    }
    void Attr()
    {
        //System V 共享内存机制中用于描述共享内存段状态的核心数据结构
        //通过 shmctl() 系统调用进行读取或修改。
        struct shmid_ds ds;//结构体
        int n=shmctl(_shmid,IPC_STAT,&ds);
        printf("shm_segsz: %ld\n",ds.shm_segsz);
        printf("key: 0x%x\n",ds.shm_perm.__key);
    }
    ~Shm()
    {
        if(_usertype==CREATER)//创建的负责删除
        Destroy();
    }
private:
    int _shmid;
    key_t _k;
    int _size;
    void* _start_mem;
    std::string _usertype;
};
fifo.hpp

通过命名管道来同步

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#define FIFO_FILE "fifo"
// #define path '.'

class Fifo
{
public:
    Fifo(const std::string& path,const std::string& name)
    {
        umask(0);
        _fifoname=path+'/'+name;
        int n=mkfifo(_fifoname.c_str(),0666);
        if(n<0)
        {
            std::cerr<<"mkfifo error"<<std::endl;//失败
        }
    }
    ~Fifo()
    {
       int n=unlink(_fifoname.c_str());//删除管道文件
       if(n==0)
       {
           std::cout<<"remove fifo success"<<std::endl;
       }
       else
       {
           std::cout<<"remove fifo failed"<<std::endl;
       }
    }
private:
    std::string _fifoname;
};

class FileOper
{
public:
    FileOper(const std::string& path,const std::string& name)
    :_path(path),_name(name)
    {
        _fifoname=_path+"/"+_name;
    }
    void OpenForRead()
    {
        _fd=open(_fifoname.c_str(),O_RDONLY);
        if(_fd<0)
        {
            std::cerr<<"open fifo error"<<std::endl;
            return ;
        }
    }
    void OpenForWrite()
    {
        _fd=open(_fifoname.c_str(),O_WRONLY);
        if(_fd<0)
        {
            std::cerr<<"open fido success"<<std::endl;
            return ;
        }
    }
    void Wakeup()//唤醒
    {
        char c;
        c='a';
        int n=write(_fd,&c,1);
        if(n<0)
        std::cerr<<"write error"<<std::endl;
    }
    bool Wait()//看是否等待成功
    {
        char c;
        int n=read(_fd,&c,1);
        if(n>0) return true;
        
        return false;
    }
    void Close()
    {
        if(_fd>0)
        close(_fd);
    }
    ~FileOper()
    {}
private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
    int _fd;
};
server.cpp
cpp 复制代码
#include"Shm.hpp"
#include"fifo.hpp"
int main()
{    
    Shm shm(pathname,projid,CREATER);
    shm.Attr();
    char* mem=(char*)shm.VirtualAddr();
   
    umask(0);
    Fifo fifo(".","fifo");

    FileOper wait(".","fifo");
    wait.OpenForRead();//

    // sleep(8);

    while(true)
    {
        if(wait.Wait())//可以读了
        {
            printf("%s\n",mem);
            // sleep(1);
        }
        else
        break;
    }
    // sleep(10);
    wait.Close();
    return 0;
}
client.cpp
cpp 复制代码
#include"Shm.hpp"
#include"fifo.hpp"
int main()
{
    umask(0);
    
    FileOper writefile(".","fifo");
    writefile.OpenForWrite();

    Shm shm(pathname,projid,USER);
    char* mem=(char*)shm.VirtualAddr();
    int cur=0;
    for(int i=0;i<26;i++)
    {
        mem[cur++]='a'+i;
        // sleep(1);
        mem[cur++]='a'+i;
        // sleep(1);
        mem[cur]=0;//设置为0
        writefile.Wakeup();//唤醒
        sleep(2);
    }

    writefile.Close();
    return 0;
}

这篇就到这里啦(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

相关推荐
望获linux5 分钟前
智能清洁机器人中的实时操作系统应用研究
大数据·linux·服务器·人工智能·机器人·操作系统
io无心7 分钟前
Docker绑定端口报错
运维·docker·容器
绵绵细雨中的乡音2 小时前
Linux进程学习【环境变量】&&进程优先级
linux·运维·学习
天下·第二3 小时前
【Nginx】负载均衡配置详解
运维·nginx·负载均衡
GanGuaGua3 小时前
linux:进程的替换
linux·运维·服务器
梓䈑4 小时前
【Linux系统】详解Linux权限
linux·运维·bash
小茬粥4 小时前
kvm网卡发现的采集信息脚本COLT_CMDB_KVM_NETDISC.sh
linux·kvm
Mr_sun.4 小时前
Day23-Web开发——Linux
linux·运维·服务器
星雨流星天的笔记本5 小时前
1、Linux操作系统下,ubuntu22.04版本切换中英文界面
linux·学习