Linux下共享内存清理的最优实现方案探讨
Linux共享内存自动清理方案:进程异常退出时的资源回收
我正在开发一个Rust库,用来简化Linux下的共享内存操作——所有进程只能通过这个库请求共享内存,但无法避免进程panic或被强制终止的情况。这个库用于「运行周期(run)」场景:周期内至少有一个进程在使用共享内存,周期结束后必须清理内存;但既无法保证进程全程运行,也无法保证最后退出的进程是正常退出的。现在需要解决的核心问题是:如何让共享内存在进程运行时保留,在最后一个进程退出/终止后立即自动清理?
我了解Linux下创建共享内存有两种方式:
- System V风格的
shmat - POSIX风格的
shm_open(个人更倾向这种,因为它创建的共享内存可通过文件系统访问,但这并非硬性要求)
我原本以为如果新进程能附加到已标记删除的System V共享内存,问题就解决了,比如这样:
unsafe { let shmid = shmget(KEY, SHMEM_SIZE, IPC_CREAT | IPC_EXCL | 0o770); let shmaddr = shmat(shmid, null_mut(), 0); shmctl(shmid, IPC_RMID, null_mut()); // 在这里标记为待删除 let ptr = shmaddr as *mut [u8; SHMEM_SIZE as usize]; *ptr = [12; SHMEM_SIZE as usize]; }
但在另一个进程中尝试附加时会失败——因为KEY已经被标记删除,shmget无法获取到对应的共享内存:
unsafe { let shmid = shmget(KEY, SHMEM_SIZE, IPC_EXCL); // 这里无法成功,因为KEY已标记删除 let shmaddr = shmat(shmid, null_mut(), 0); let ptr = shmaddr as *mut [u8; SHMEM_SIZE as usize]; *ptr = [13; SHMEM_SIZE as usize]; }
目前我有两个不太理想的解决方案:
选项1:守护进程监控方案
启动一个后台守护进程,新进程需要向它发送PID完成注册。守护进程通过pidfd_open系统调用获取进程的文件描述符,使用poll监控进程状态,当监控的PID列表为空时删除共享内存。
问题:方案较为脆弱,而且不想为了监控共享内存单独启动额外进程。
附半成品实现代码:pub fn handle_unix_socket( &self, pollfd: &mut PollFd, ) -> Result<(Option<OwnedFd>, Option<u32>), std::io::Error> { let mut buffer = [0; 4]; if pollfd .revents() .unwrap_or(PollFlags::empty()) .contains(PollFlags::POLLIN) { if let Ok((mut stream, _)) = self.listener.accept() { let _ = stream.read(&mut buffer)?; let pid = u32::from_be_bytes(buffer); let fd = unsafe { syscall2(syscalls::Sysno::pidfd_open, pid as usize, 0).unwrap() }; let fd = unsafe { OwnedFd::from_raw_fd(fd as i32) }; stream.write_all(&buffer)?; Ok((Some(new_fd), Some(new_pid))) } else { todo!() } } else { Ok((None, None)) } } pub fn event_loop(&mut self) -> Result<(), std::io::Error> { loop { let mut pollfds = vec![PollFd::new(self.listener.as_fd(), PollFlags::POLLIN)]; for fd in self.fds.iter() { pollfds.push(PollFd::new(fd.as_fd(), PollFlags::POLLIN)); } poll(&mut pollfds, PollTimeout::NONE)?; let (new_fd, new_pid) = self.handle_unix_socket(&mut pollfds[0])?; let remove_indicies = (1..pollfds.len()) .rev() .filter(|i| { if let Some(pflag) = pollfds[*i].revents() { if pflag.contains(PollFlags::POLLIN) { return true; } } false }) .collect::<Vec<_>>(); for i in remove_indicies { self.pids.remove(i - 1); self.fds.remove(i - 1); } match (new_fd, new_pid) { (Some(new_fd), Some(new_pid)) => { self.fds.push(new_fd); self.pids.push(new_pid); } _ => {} } if self.pids.is_empty() { return Ok(()); } } }选项2:eBPF跟踪方案
用eBPF跟踪所有打开共享内存文件的进程,由eBPF程序维护一个PID列表,当列表为空时自动删除共享内存。
问题:对eBPF经验不足,不确定方案是否可行;而且eBPF通常需要特权环境,希望避免这种依赖。
内容的提问来源于stack exchange,提问作者sesh
相关产品推荐
相关产品推荐

