You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ctrl+C终止进程时安全删除共享内存?

共享内存残留问题的解决方案

问题背景

当前实现的ShmWorker类通过析构函数执行shmdt脱离共享内存,并调用shmctl(IPC_RMID)删除共享内存,但当进程被Ctrl+C(SIGINT信号)终止时,析构函数不会执行,导致共享内存无法被清理,长期残留占用系统资源。需要解决以下两个问题:

  • 如何在无存活进程关联共享内存时安全删除它?
  • 如何在下次连接该共享内存时清理残留的无关联共享内存?

当前实现代码

class ShmWorker {
 public:
  ShmWorker() = default;
  virtual ~ShmWorker() {
    shmdt(m_data);
    shmctl(shmid, IPC_RMID, 0); // 问题:Ctrl+C终止时析构函数不执行,共享内存无法删除
  }   

 protected:
  key_t get_keyid(const std::string & name) {
    const std::string & s = "./" + name;
    if (access(s.c_str(), F_OK) == -1) { 
      const std::string & s1 = "touch " + s; 
      system(s1.c_str()); 
    }
    key_t semkey = ftok(name.c_str(), 1); 
    CE(semkey == -1, "shm_file:%s not existed\n", s.c_str());
    return semkey;
  }   

  template <typename T>
  void init(const std::string& name, int size) {
    key_t m_key = get_keyid(name);
    shmid = shmget(m_key, 0, 0);  // TODO: check m_key existed
    char * p;
    if (shmid == -1) {
      CE(errno != ENOENT && errno != EINVAL, "errno is %s\n", strerror(errno));
      if (std::is_same_v<T, std::string>) 
        shmid = shmget(m_key, sizeof(Header) + STRING_SIZE * size, 0666 | IPC_CREAT | O_EXCL);
      else 
        shmid = shmget(m_key, sizeof(Header) + sizeof(T) * size, 0666 | IPC_CREAT | O_EXCL);
      CE(shmid == -1, "both connet and create are failed for shm\n")
      printf("creating new shm %s %p\n", name.c_str(), p);
      p = (char*)shmat(shmid, NULL, 0);
      Header h(size, 0);
      memcpy(p, &h, sizeof(Header));
    } else {
      p = (char*)shmat(shmid, 0, 0);
      printf("existed m_data = %p\n", p);
    }
    m_data = p; // 补充原代码缺失的m_data赋值
  }

 private:
  int shmid = -1;
  void* m_data = nullptr;
};

解决方案

1. 注册信号处理函数,捕获SIGINT执行清理

当进程收到Ctrl+C(SIGINT)时,手动触发共享内存的清理逻辑,替代析构函数的工作:

  • 在ShmWorker中添加静态成员变量保存当前实例的共享内存ID和映射地址,注册信号处理函数执行清理;
  • 注意信号处理函数只能调用异步安全的系统调用,避免使用printf这类非安全函数。
#include <signal.h>

class ShmWorker {
 public:
  // ... 原有代码 ...

  template <typename T>
  void init(const std::string& name, int size) {
    // ... 原有init逻辑 ...
    
    // 注册SIGINT信号处理函数
    struct sigaction sa;
    sa.sa_handler = &ShmWorker::sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, nullptr);
    
    // 保存当前实例的共享内存信息(多实例场景需改为容器管理)
    s_current_shmid = shmid;
    s_current_data = m_data;
  }

 private:
  static int s_current_shmid;
  static void* s_current_data;

  static void sigint_handler(int signum) {
    if (s_current_data != nullptr) {
      shmdt(s_current_data);
      s_current_data = nullptr;
    }
    if (s_current_shmid != -1) {
      shmctl(s_current_shmid, IPC_RMID, 0);
      s_current_shmid = -1;
    }
    exit(0);
  }

  // ... 原有成员变量 ...
};

// 静态变量初始化
int ShmWorker::s_current_shmid = -1;
void* ShmWorker::s_current_data = nullptr;

2. 下次连接时清理无关联的共享内存

在init函数中,检测到共享内存已存在时,先获取其关联进程数,若为0则直接删除并重新创建:

template <typename T>
void init(const std::string& name, int size) {
  key_t m_key = get_keyid(name);
  shmid = shmget(m_key, 0, 0);  
  char * p;
  if (shmid == -1) {
    // ... 原有创建逻辑 ...
  } else {
    // 获取共享内存状态
    struct shmid_ds shm_info;
    if (shmctl(shmid, IPC_STAT, &shm_info) != -1) {
      // 无进程关联则删除重建
      if (shm_info.shm_nattch == 0) {
        shmctl(shmid, IPC_RMID, 0);
        // 重新创建共享内存
        if (std::is_same_v<T, std::string>) 
          shmid = shmget(m_key, sizeof(Header) + STRING_SIZE * size, 0666 | IPC_CREAT | O_EXCL);
        else 
          shmid = shmget(m_key, sizeof(Header) + sizeof(T) * size, 0666 | IPC_CREAT | O_EXCL);
        CE(shmid == -1, "both connet and create are failed for shm\n")
        printf("recreate shm %s %p\n", name.c_str(), p);
        p = (char*)shmat(shmid, NULL, 0);
        Header h(size, 0);
        memcpy(p, &h, sizeof(Header));
      } else {
        // 正常关联已有共享内存
        p = (char*)shmat(shmid, 0, 0);
        printf("existed m_data = %p\n", p);
      }
    } else {
      // 获取状态失败,直接关联
      p = (char*)shmat(shmid, 0, 0);
      printf("existed m_data = %p\n", p);
    }
  }
  m_data = p;
}

3. 改用POSIX共享内存替代System V共享内存

System V共享内存管理繁琐、易残留,推荐使用POSIX标准的shm_open/shm_unlink接口:

  • shm_unlink标记共享内存为待删除,当最后一个进程关闭该对象时,内核会自动清理;
  • 即使进程异常终止,只要所有进程退出,共享内存会被自动回收,无需手动处理残留。
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

class PosixShmWorker {
 public:
  ~PosixShmWorker() {
    if (m_data != nullptr) {
      munmap(m_data, m_size);
      close(m_fd);
    }
    // 标记删除,最后一个进程关闭后自动清理
    shm_unlink(m_name.c_str());
  }

  template <typename T>
  void init(const std::string& name, int size) {
    m_name = "/shm_" + name;
    // 尝试打开共享内存
    m_fd = shm_open(m_name.c_str(), O_RDWR, 0666);
    if (m_fd == -1) {
      // 创建新的共享内存
      m_fd = shm_open(m_name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0666);
      CE(m_fd == -1, "create posix shm failed\n");
      // 设置大小
      m_size = sizeof(Header) + (std::is_same_v<T, std::string> ? STRING_SIZE * size : sizeof(T) * size);
      ftruncate(m_fd, m_size);
      // 映射到进程地址空间
      m_data = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
      CE(m_data == MAP_FAILED, "mmap posix shm failed\n");
      // 初始化Header
      Header h(size, 0);
      memcpy(m_data, &h, sizeof(Header));
    } else {
      // 获取已有共享内存大小
      struct stat st;
      fstat(m_fd, &st);
      m_size = st.st_size;
      // 映射
      m_data = mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
      CE(m_data == MAP_FAILED, "mmap posix shm failed\n");
    }
  }

 private:
  std::string m_name;
  int m_fd = -1;
  void* m_data = nullptr;
  size_t m_size = 0;
};

内容的提问来源于stack exchange,提问作者nick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 23:17:26