如何在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
相关产品推荐
相关产品推荐

