当前位置: 首页 > article >正文

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

前言                

        SystemV通信一般包括三种:共享内存、消息队列和信号量。共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存通信

        1)共享内存通信的原理

        共享内存通信的原理是,两个进程的页表通信映射同一份物理内存完成通信,共享内存在系统中可以存在多份,实现多对进程进行通信。

2)创建共享内存

        linux提供了创建共享内存接口shmget,其中key值在操作系统中是唯一的,可以使用ftok系统调用获取,为了两个进程能够顺利读取到同一份共享内存,所以key值必须用户手动传参。

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

int shmget(key_t key, size_t size, int shmflg);
//key为共享内存的唯一标识符,用于区分不同的共享内存,其值需要两个进程约定
//size共享内存的大小,单位是字节,一般是4kB的整数倍
//shmflg为标志位传参,可以同时传多个参数使用|连接
//IPC_CREAT:创建新的共享内存,如果已经存在该key值,直接返回对应的共享内存。如果不使用该参数,则检查是否存在该key对应的共享内存
//IPC_EXCL:与IPC_CREAT搭配使用,如果key对应的共享内存已经存在,直接报错,用于确保共享内存是新创建的。
//perms:指定是内存的权限,三位八进制数字
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
//作用是根据用户传入的参数生成一个唯一的key值
//pathname和proj_id可以随意指定,只有两个参数完全相同时,生成的key值才会相同
//返回值:生成的key,为一个数字

3)管理共享内存

因为操作系统中存在多份共享内存,操作系统必须对共享内存进行描述组织管理。

查看共享内存资源

ipcs -m
ipsc表示查看进程间通信,可以用于查看消息队列,共享内存和信号量
-m用于指定查看共享内存

控制共享内存资源

内核接口shmctl

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid:共享内存的id
//cmd:指定操作,常用操作有
IPC_STAT:将与shmid对应内核数据结构复制到buf中
IPC_SET:将shmid对应的共享内部分值设置为buf对应值
IPC_RMID:删除指定id的共享内存
//shmid_ds*buf:shmid_ds是操作系统内部对共享内存管理的数据结构
struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
 };

struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
};

挂接共享内存

        共享内存创建出来不属于进程,而属于操作系统,挂接的作用是将共享内存首地址挂接到进程的页表当中,使用shmat接口实现挂接。如果挂接成功,就意味着进程的页表中创建了与物理地址的映射,并且可以直接通过虚拟地址访问共享内存地址。

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

void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid是共享内存的id
//shmaddr是用户指明的挂接位置,默认为nullptr即可
//shmflg是标志位,默认可读写挂接设置为0即可
//返回值:如果挂接成功,返回挂接成功的地址,否则返回(void*)(-1)

去关联共享内存

        和挂接相反,去关联就是解除页表中虚拟地址和共享内存的关联信息,系统调用为shmdt


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

int shmdt(const void *shmaddr);
//shmaddr为共享内存的虚拟内存地址
//返回值为0表示解除关联成功,-1表示失败

4)销毁共享内存

        创建共享内存后,进程关闭,共享内存不会主动释放,会一直存在,下一次创建提示共享内存已经存在,除非重启系统,即共享内存声明周期不随进程而随内核。

        删除共享命令

ipcrm -m shmid

        删除指定id的共享内存

shmctl(shmid,IPC_RMID,nullptr);

共享内存的特性

1)共享内存不提供进程协同的任何特性,即使内存中还未写入数据,也可以直接读内存。可能导致两个进程数据不一致,需要用户提供同步机制,可以使用管道或者是信号量来完成两个进程的同步。

2)共享内存通信的速度是最快的,因为进程读写数据是对共享内存直接读写,共享内存同时被两个进程映射,通信过程中不需要系统调用。

综合以上两个特性,可以使用管道和共享内存结合的方式,完成进程间通信,管道为共享内存提供协同机制,而共享内存用于实际数据通信。

共享内存与命名管道结合通信实例

//common_fifo
#ifndef __COMMON_FIFO_HPP__
#define __COMMON_FIFO_HPP__

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
using namespace std;

// 管道路径
string path = "./fifo";
// 创建mode
#define Mode 0666

class fifo
{
public:
    fifo(const string &path) : _path(path)
    {
        umask(0);
        int n = mkfifo(_path.c_str(), Mode);
        // 管道创建失败,打印失败信息
        if (n < 0)
        {
            cerr << "mkfifo fail" << " ,errorno " << errno << " , errstring: " << strerror(errno) << endl;
        }
        else
        {
            cout<<"mkfifo sucess"<<endl;
        }
    }
    ~fifo()
    {
        // 通信结束,删除管道
        int n = unlink(_path.c_str());
        if (n == 0)
        {
            cout << "remove fifo file" << _path << " sucess " << endl;
        }
        else
        {
            cout << "remove fifo file" << _path << " fail " << endl;
        }
    }

private:
    string _path;
};

class Sync
{
public:
    Sync()
    : _wfd(-1), _rfd(-1)
    {
        cout<<"Sync"<<endl;
    }

    ~Sync()
    {
        if(_wfd != -1)
        {
            close(_wfd);
        }
        if(_rfd != -1)
        {
            close(_rfd);
        }
    }

    //以只读方式打开
    void openRead()
    {
        int rfd = open(path.c_str(),O_RDONLY);
        if(rfd < 0)
        {
            cerr<<"openRead fail,errno is"<<errno<<"error string is"<<strerror(errno)<<endl;
            exit(1);
        }
        else 
        {
            cout<<"openRead sucess"<<endl;
        }
        _rfd = rfd;
    }
    //以只写方式打开
    void openWrite()
    {   
        int wfd = open(path.c_str(),O_WRONLY);
        if(wfd < 0)
        {
            cout<<"openWrite fail,errno is"<<errno<<" ,error string is"<<strerror(errno)<<endl;
            exit(1);
        }
        else 
        {
            cout<<"openWrite sucess"<<endl;
        }
        _wfd = wfd;
    }

    bool wait()
    {
        char c = 0;
        ssize_t n = read(_rfd,&c,sizeof(c));
        if(n <= 0)
        {
            cout<<"wait error"<<endl;
            return false;
        }
        return true;
    }

    void wakeUp()
    {
        char wake = 'A';
        ssize_t n = write(_wfd,&wake,sizeof(wake));
        if(n < 0)
        {
            cout<<"wake up error"<<endl;
        }
    }

private:
    int _wfd;
    int _rfd;
};

#endif
//Common_shm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>

using namespace std;
#define SIZE 4096

const char* pathName = "/home/yw";
const int proj_id = 0x6666;


key_t getShmKey()
{
    key_t key = ftok(pathName,proj_id);
    if(key < 0)
    {
        cerr<<"ftok error,errno: "<<errno<<", error string: "<<strerror(errno)<<endl;
        exit(-1);
    }

    return key;
}

class shmIPC
{
public:
    shmIPC(key_t key)
    :_key(key),_shmid(0),_size(SIZE),_address(nullptr)
    {}

    ~shmIPC()
    {
        //解除挂接
        int n = shmdt(_address);
        if(n < 0)
        {
            exit(-1);
            cout<<"shm Deattach fail, errno: "<<errno<<" ,error: "<<strerror(errno)<<endl;
        }
        _address = nullptr;


        //删除共享内存
        n = shmctl(_shmid,IPC_RMID,nullptr);
        if(n < 0)
        {
            exit(-1);
            cerr<<"shm del fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
        }
        else
        {
            cout<<"shm del sucess"<<endl;
        }

    }

    void createShm(size_t size = SIZE)
    {
        _size = size;
        int shmid = shmget(_key,_size,IPC_CREAT | IPC_EXCL);
        if(shmid < 0)
        {
            cerr<<"shm create fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
            exit(-1);
        }
        _shmid = shmid;
    }

    void getShm()
    {
        int shmid = shmget(_key,_size,IPC_CREAT);
        if(shmid < 0)
        {
            cerr<<"shm get fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
            exit(-1);   
        }
        _shmid = shmid;
    }

    void print()
    {
        struct shmid_ds shmds;
        int n = shmctl(_shmid,IPC_STAT,&shmds);
        if(n < 0)
        {
            cerr<<"shm print fail,errno: "<<errno<<" ,strerror: "<<strerror(errno)<<endl;
            exit(-1);
        }
        cout<<"shmds.shm_segsz: "<<shmds.shm_segsz<<endl;
        cout<<"shmds.shm_nattch: "<<shmds.shm_nattch<<endl;
        cout<<"shmds.shm_ctime: "<<shmds.shm_ctime<<endl;
        cout<<"shmds.shm_perm.__key: "<<shmds.shm_perm.__key<<endl;
        cout<<"shmid: "<<_shmid<<endl;
    }

    
    void *shmAttach()
    {
        void *address = shmat(_shmid,nullptr,0);
        if(address == (void*) (-1))
        {
            cout<<"shm attach fail, errno: "<<errno<<" ,error: "<<strerror(errno)<<endl;
            exit(-1);
        }
        _address = address;
        return address;
    }


private:
    key_t _key;
    int _shmid;
    size_t _size;
    void* _address;
};

//shmClient.cpp
#include "Common_shm.hpp"
#include "Common_fifo.hpp"

int main()
{
    //0.创建监视对象
    Sync sync;
    sync.openRead();
    //1.创建对象
    shmIPC shmipc(getShmKey());
    //2.创建共享内存
    shmipc.createShm(SIZE);
    //3.挂接共享内存
    char *address = (char*) shmipc.shmAttach() ;
    cout<< address <<endl;

    //TO DO
    while(true)
    {
        bool isWakeUp = sync.wait();
        if(isWakeUp)
            cout<<"shm content: "<<address<<endl;
        else
            break;
    }
    return 0;
}
//Common_shmServer.cpp
#include "Common_shm.hpp"
#include "Common_fifo.hpp"

int main()
{
    //0.创建监管对象
    fifo f1(path);
    Sync sync;
    sync.openWrite();   
    //1.创建对象
    shmIPC shmipc(getShmKey());
    //2.获取共享内存
    shmipc.getShm();
    //3.挂接共享内存
    char *address = (char*) shmipc.shmAttach() ;
    cout<< address <<endl;

    //TO DO
    int i=10;
    memset(address,0,SIZE);
    for(int i=0;i<10;i++)
    {
        address[i] = i + 'A';
        sleep(2);
        sync.wakeUp();
    }

    return 0;
}

        在上面的代码中,shmServer每隔1s向共享内存中写入一个字符并且唤醒shmClient进程,shmClient进程每次唤醒打印共享内存中的数据。


http://www.kler.cn/a/530929.html

相关文章:

  • csapp笔记3.6节——控制(1)
  • Vue 3 30天精进之旅:Day 13 - 路由守卫
  • 数据库、数据仓库、数据湖有什么不同
  • python算法和数据结构刷题[1]:数组、矩阵、字符串
  • Linux环境下的Java项目部署技巧:环境安装
  • 3、从langchain到rag
  • (回溯递归dfs 电话号码的字母组合 remake)leetcode 17
  • OpenCV4.8 开发实战系列专栏之 30 - OpenCV中的自定义滤波器
  • html中的列表元素
  • 全域旅游景区导览系统小程序独立部署
  • 使用冒泡排序模拟实现qsort函数
  • NeetCode刷题第20天(2025.2.1)
  • STM32 DMA+AD多通道
  • 音标-- 02-- 重音 音节 变音
  • 2024美团秋招硬件开发笔试真题及答案解析
  • Day33【AI思考】-分层递进式结构 对数学数系的 终极系统分类
  • C++:结构体和类
  • 刷题记录 动态规划-5: 62. 不同路径
  • python的pre-commit库的使用
  • leetcode——从前序与中序遍历序列构造二叉树(java)
  • stm32小白成长为高手的学习步骤和方法
  • NOTEPAD++编写abap
  • 国土安全保障利器,高速巡飞无人机技术详解
  • CMD模块
  • 嵌入式八股文面试题(一)C语言部分
  • 如何处理 Typecho Joe 主题被抄袭或盗版的问题