进程间通信:共享内存

目录

一、引言

二、共享内存概述

三、常用接口介绍

3.1 shmget

3.2 shmat

3.3 shmdt

3.4 shmctl

四、代码演示

五、共享内存的优缺点

六、结语



一、引言

进程间通信(Inter - Process Communication,IPC)是指在多进程环境下,不同进程之间进行信息交换、协同工作的机制和方法。通过上图可以看到,进程间通信的方式有很多种,管道、消息队列、共享内存……各有各的适用场景。本文聚焦的是如何通过共享内存来实现进程之间的通信。

二、共享内存概述

进程间不管是通过管道、消息队列还是共享内存等其他方式来进行通信,前提都是要让不同进程看到同一份资源。共享内存是通过映射相同的物理内存页到不同进程的虚拟地址空间,这样就可以让不相干的两个或多个进程看到了同一份资源,不论是哪个进程往该物理位置中写入,其他进程都可以看到。

三、常用接口介绍

3.1 shmget

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

  • 用于创建共享内存或者获取已经存在的共享内存的标识符。
  • key:用于标识共享内存,通常是由ftok函数生成,当然也可以自己指定。
  • size:共享内存的大小,单位是字节。
  • shmflg:指定标志位,比如IPC_CREAT表示共享内存不存在就创建。
  • 返回值:如果共享内存创建成功,则返回共享内存的标识符,否则返回-1。

3.2 shmat

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

  • 用于将共享内存挂载到调用进程的地址空间。
  • shmid:共享内存的标识符,表明要挂载到当前进程的共享内存,即shmget函数的返回值。
  • shmaddr:要挂载到调用进程的地址,一般置为nullptr,让系统自己选择。
  • shmflg:指定挂载的选项,比如 SHM_RDONLY 表示以只读方式挂载。
  • 返回值:成功时返回挂载的地址,后续往这个地址读取或写入数据,失败时返回 (void *)-1 。

3.3 shmdt

 int shmdt(const void *shmaddr);

  • 用于将共享内存从调用进程的地址空间剥离。
  • shmaddr:shmat 返回的地址。
  • 返回值:成功时返回0,失败时返回-1。

3.4 shmctl

 int shmctl(int shmid, int cmd, struct shmid_ds *buf);

  • 用于控制共享内存。
  • shmid:共享内存的标识符,shmget成功时的返回值。
  • cmd:指定操作命令,如 IPC_RMID 用于删除共享内存。
  • buf:用于获取共享内存的状态信息。
  • 返回值:成功时返回0,失败时返回-1。

共享内存的这一套函数和消息队列如出一辙。共享内存一旦创建,它的生命周期是随内核的,除非显示的将它删除,仅仅是和当前进程剥离并不会导致共享内存被删除。

四、代码演示

生产者负责将数据写入共享内存,消费者则负责读取共享内存中的数据。

//shm.hpp
#ifndef shm_hpp
#define shm_hpp

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

#define SHM_SIZE 1024

/**
 * @brief 共享内存封装类,用于创建、管理共享内存,并提供读写功能
 */
class Shm {
public:
    /**
     * @brief 构造函数,初始化共享内存相关参数,并创建并附加共享内存
     */
    Shm() 
        : _shmid(-1)               // 共享内存ID,初始为-1表示无效
        , _shmaddr(nullptr)        // 共享内存起始地址,初始为nullptr
        , _current_position_for_write(nullptr)
        , _current_position_for_read(nullptr)   // 当前读取位置指针
        , _remaining_space(SHM_SIZE)            // 剩余可用空间,初始化为总大小
    {
        createShm();
        attachShm();  // 附加共享内存到进程地址空间
    }
    void createShm() {
        key_t key = ftok("shm.hpp", 65);
        _shmid = shmget(key, SHM_SIZE, 0666|IPC_CREAT);
        if (_shmid == -1) {
            std::cerr << "shmget failed" << std::endl;
            exit(1);
        }
    } 
    void attachShm() {
        _shmaddr = shmat(_shmid, nullptr, 0);
        if (_shmaddr == (void*)-1) {
            std::cerr << "shmat failed" << std::endl;
            exit(2);
        }
        _current_position_for_write = (char*)_shmaddr;
        _current_position_for_read = (char*)_shmaddr;
    }
    void detachShm() {
        if (shmdt(_shmaddr) == -1) {
            std::cerr << "shmdt failed" << std::endl;
            exit(3);
        }
        _current_position_for_write = nullptr;
        _current_position_for_read = nullptr;
    }
    void destroyShm() {
        if (shmctl(_shmid, IPC_RMID, nullptr) == -1) {
            std::cerr << "shmctl failed" << std::endl;
            exit(4);
        }
    }
    void write(std::string &text) {
        if (_remaining_space > 0) {
            text += '\0';
            _remaining_space -= text.size();
            strcpy(_current_position_for_write, text.c_str());
            _current_position_for_write += text.size();
        }
    }
    void read(std::string &output) {
        if (*_current_position_for_read != '\0') {
           size_t len = strlen(_current_position_for_read);
           output = std::string(_current_position_for_read, len);
           _current_position_for_read += len + 1;
        }else {
            output = "";
        }
    }
    int getShmid() const { return _shmid; }
    void* getShmaddr() const { return _shmaddr; }
private:
    int _shmid;
    void* _shmaddr;
    int _remaining_space;
    char* _current_position_for_write;
    char* _current_position_for_read;
};

class Producer : public Shm {
public:
    void writeToShm(std::string &message) {
        write(message);
    }
    ~Producer() {
        detachShm();
    }
};

class Consumer : public Shm {
public:
    void readFromShm(std::string &message) {
        read(message);
    }
    ~Consumer() {
        detachShm();
        destroyShm();
        //std::cout << "shm destroyed" << std::endl;
    }
};
#endif /* shm_hpp */

//producer.cpp
#include "shm.hpp"

int main() 
{
    Producer producer;
    bool running = true;
    std::string message;
    while (running) {
        std::cout << "Enter message to send to consumer: ";
        std::getline(std::cin, message);
        producer.writeToShm(message);
        if (strcmp(message.c_str(), "exit") == 0) {
            running = false;
        }
    }
    return 0;
}

//consumer.cpp
#include "shm.hpp"

int main()
{
    Consumer consumer;
    bool running = true;
    std::string message;
    while (running) {
        consumer.readFromShm(message);
        if (!message.empty()) {
            std::cout << "Received message: " << message << std::endl;
            if (strcmp(message.c_str(), "exit") == 0) {
                running = false;
            }
        }
    }
    return 0;
}

五、共享内存的优缺点

共享内存是性能最高的进程间通信方式,没有之一。因为两个进程之间或者多个进程之间进行通信时,中间过程并不需要拷贝数据,因此它是最快的进程间通信方式。这是共享内存的一大优点。共享内存的不足也很鲜明,共享内存并没有要求通信双方同步,这就可能导致数据的不一致,需要引入其他机制来解决这个问题。

六、结语

共享内存本质上体现了系统设计的一种哲学:在保证安全的前提下,通过消除中间层来释放物理硬件的原生性能。正是由于这种设计理念,才使得共享内存不断演进。


完~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值