写点什么

【Linux 系统】一个常驻进程问题的再次分析

发布于: 2021 年 02 月 04 日
【Linux系统】一个常驻进程问题的再次分析

一 问题回顾

启动进程时,shm_attach()方法报错:failed for key 0x6104e88b: No space left on device


当时定位的原因是:

主进程异常退出,导致信号量和共享内存没有回收,资源耗尽,当再次申请资源时,无可用资源导致

解决方案:清理信号量及共享内存。

二 问题再次剖析

2.1 shm_attach()方法

根据 php 官方文档中的描述 shm_attach:创建或打开一个共享内存段(Creates or open a shared memory segment),说明:

shm_attach ( int $key , int|null $size = null , int $permissions = 0666 ) : SysvSharedMemory|false
复制代码

方法返回一个 id,这个 id 可以用来通过指定的 key 来访问 System V 共享内存,第一次调用时创建共享内存段,需要设置参数 size 和 可选参数 permissions,默认 $permissions 值为 0666

第二次调用如果使用相同的 key,将返回一个不同的 SysvSharedMemory 实例,但两个实例都访问相同的底层共享内存。size 和 permissions 参数都会被忽略。

2.2 System V 共享内存

System V IPC 机制下的共享内存本质是一段特殊的内存区域,进程间需要共享的数据被放在该共享内存区域中,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。共享内存允许一个或多个进程通过同时出现在他们的虚拟地址空间的内存进行通信,而这块虚拟内存的页面被每个共享进程的页表条目所引用,同时并不需要再所有进程的虚拟内存都有相同的地址。

1.System V 共享内存是一种最为搞笑的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

2.为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不要进行数据的拷贝,从而大大提高效率。

3.由于多个进程共享一段内存,因此也需要依靠某种同步机制。


System V 的 IPC(Inter-Process Communication,进程间通信)对象有共享内存、消息队列、信号量(灯)。

注意:在 IPC 的通信模式下,不管是共享内存、消息队列还是信号量,每个 IPC 的对象都有唯一的名字,称为"键(key)"。通过"键",进程能够识别所用的对象。"键"与 IPC 对象的关系就如同文件名称于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够公用一个文件。而在 IPC 的通信模式下,通过"键"的使用也能使得一个 IPC 对象能为多个进程所共用。

2.3 再看问题原因

报错信息是在 shm_attach()方法,而错误原因是 failed for key 0x6104e88b: No space left on device。比较容易确定非硬盘空间问题,加上已经对 shm_attach()方法有了上面的了解,那么就是出在共享内存分配/获取。

进一步定位,由 2.2 可知,System V 的 IPC 对象有共享内存、消息队列和信号量,其中可查的是共享内存空间和信号量,查询命令使用 ipcs,常用命令如下:

ipcs可用来显示当前Linux系统中的共享内存段、信号量集、消息队列等的使用情况。命令示例:ipcs -a或ipc 显示当前系统中共享内存段、信号量集、消息队列的使用情况;ipcs -m 显示共享内存段的使用情况;ipcs -s 显示信号量集的使用情况;ipcs -q 显示消息队列的使用情况;ipcrm可用来删除对应的共享内存段、信号量、消息队列;命令示例:ipcrm -s semid 删除对应的信号量集ipcrm -m shmid 删除对应的共享内存段ipcrm -q msqid 删除对应的消息队列
批量删除可以使用命令:ipcs -s|grep xxx|cut -d" " -f2|xargs -n1 ipcrm -sipcs -s|awk '/xxx/{print $2}'|xargs -n1 ipcrm -sipcs -s|awk '/xxx/{system("ipcrm -s "$2)}'for i in echo `ipcs|grep xxx|cut -d" " -f2`; do ipcrm -s $i; done
复制代码

通过 ipcs -m 和 ipcs -s,确认是共享内存和信号量满导致,所以直接的解决方法就是先清理共享内存和信号量:

2.4 根源

为什么会造成共享内存和信号量满?一个可以想到的原因就是二者在使用时并没有被正常释放。那么就需要其他信息来辅助我们更精确地定位问题。

通过与 OP 配合,以及当时常出现的问题(现象)结合考虑:

1)发布时间过长,脚本机 kill pid 失败后等待 90s 后触发 kill -9 pid,而强杀进程可能会导致共享变量和信号量无法正常释放,这是其一;

2)为什么 kill pid 会无法生效?通常来说,除非在代码中做了 hook 处理或触发其他异常情况(权限问题等)导致失败,通常不会触发这个问题;再考虑 kill pid 命令,等同于 kill -15 pid 命令,那么是否是我们的进程没有正确感知到这个信号量?基于这个思路,并在测试环境不断尝试 kill pid 动作及进程关闭效果(代码日志),最终定位到是所使用的 laravel 框架版本及依赖的 php 版本的问题,导致异步信号量支持的判断失效。后面又通过重写进程、进程管理及信号量管理,彻底解决了这一问题。

三 总结

问题发生在两年前,回顾当时,问题排查缓慢,最终还是其他同学解决了问题,主要还是因为对底层原理了解不够,另外问题分析思路也不够清晰。线上问题,尤其是涉及底层内存、共享内存、进程等等的问题,还是必须要对这些基本原理和运行机制有足够的了解,才能够快速定位并解决实际问题。学无止境。


发布于: 2021 年 02 月 04 日阅读数: 27
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
【Linux系统】一个常驻进程问题的再次分析