PHP shmop跨用户无法删除共享内存段的权限问题咨询
我目前有个轻量数据共享的场景,为了避免用Memcached/Redis或者数据库(数据量从160字节到10KB不等,那些方案太重了),选择了PHP的shmop系列函数实现共享内存,但跨用户操作时遇到了权限问题,想请教大家。
场景背景
我有三个不同系统用户的进程需要共享数据:
- 服务进程用用户A执行bash脚本,调用PHP CLI处理
- 网站进程用用户B运行,需要读取CLI进程的事件数据
- 还有我手动用用户C运行的脚本
我实现了基础的增删查逻辑:每次修改数据时先删除旧段再创建新段,还给部分条目加了TTL。目前跨用户读写共享内存都正常,但跨用户删除共享内存段的时候遇到了权限卡点——哪怕创建段时用了0777权限,其他用户还是没法调用shmop_delete删除别人创建的段。
报错信息与测试代码
我最初的删除逻辑是这样的:
@$id = shmop_open($this->m_dataCacheId, "a", 0, 0); if(false !== $id){ shmop_delete($id); @shmop_close($id); }
运行后直接抛出警告:
Warning: shmop_delete(): Can't mark segment for deletion (are you the owner?)
之后再尝试打开这个段:
$id = shmop_open($this->m_dataCacheId, "a", 0, 0);
又会报:
Warning: shmop_open(): Unable to attach or create shared memory segment "Invalid argument"
创建共享内存段的代码我是这么写的(段ID用ftok生成,跨用户调用时ID是一致的):
$this->m_dataCacheId = ftok(__FILE__, "0"); $id = shmop_open($this->m_dataCacheId, "c", 0777, strlen(serialize($data)));
为了复现问题,我写了完整的测试脚本:
<?php $m_dataCacheId = ftok(__FILE__, "0"); // 写入测试 $demoData = "Hi"; set_cache($demoData); // 读取测试 $demoData = get_cache(); if(false !== $demoData){ echo("Mem data: " . $demoData . PHP_EOL); }else{ echo("No data stored." . PHP_EOL); } function set_cache($data) { global $m_dataCacheId; echo("set_cache id: " . $m_dataCacheId . PHP_EOL); // 先尝试删除旧缓存 @$id = shmop_open($m_dataCacheId, "a", 0777, 0); if(false !== $id){ shmop_delete($id); @shmop_close($id); } $id = shmop_open($m_dataCacheId, "c", 0777, strlen(serialize($data))); if ($id) { $shmopStatus = shmop_write($id, serialize($data), 0); @shmop_close($id); return $shmopStatus; } return false; } function get_cache() { global $m_dataCacheId; echo("get_cache id: " . $m_dataCacheId . PHP_EOL); @$id = shmop_open($m_dataCacheId, "a", 0777, 0); if($id){ $data = unserialize(shmop_read($id, 0, shmop_size($id))); @shmop_close($id); if($data){ return $data; } } return false; }
测试步骤:
- 用普通用户运行脚本,创建共享内存段
- 用
sudo(root用户)再次运行脚本,此时root尝试删除普通用户创建的段,触发权限报错 - 切回普通用户运行,会遇到
shmop_open的「Invalid argument」错误
排查结果与疑问
后来我用C语言写了简单的共享内存测试程序,发现了同样的问题:root创建的段普通用户删不了,普通用户创建的段root能删。查了Linux的shmctl手册后发现,这其实是Linux系统的原生行为:
IPC_RMID Mark the segment to be destroyed. The caller must be the owner or creator of the segment, or be privileged.
也就是说,shmop_delete底层调用的是shmctl(IPC_RMID),Linux内核要求必须是段的创建者/所有者,或者特权用户才能标记段为删除状态——哪怕给段设了0777权限也没用,因为这个权限控制的是读写,不是删除操作。
目前我临时的解决办法是分两个共享内存段:一个给普通用户用,一个给特权用户用,但这毕竟是权宜之计。现在想请教两个问题:
- 这种跨用户无法删除shmop段的情况,确实是正常的系统行为,而不是PHP shmop的bug吗?
- 我当时把问题标了
php标签,但现在发现本质是Linux IPC的权限问题,要不要修改问题的标签?
内容来源于stack exchange

