ss -m 显示 TCP 内存占用巨大但进程 RSS 不高的 socket 泄漏定位

7次阅读

这是典型的内核socket缓冲区泄漏,ss -m显示的skmem_rmem/skmem_wmem属内核sk_buff内存,不计入进程RSS;需通过ino定位残留socket,结合状态(如CLOSE_WaiT)、重传、TCP参数等综合分析。

ss -m 显示 TCP 内存占用巨大但进程 RSS 不高的 socket 泄漏定位

ss -m 显示 skmem_rmem/skmem_wmem 巨大但 ps/top RSS 很低

这是典型的内核 socket 缓冲区泄漏,而非用户态内存泄漏。ss -m 中的 skmem_rmemskmem_wmem 是内核为 socket 分配的接收/发送队列内存(属于 slab 或 page 级别),不计入进程的 RSS(只统计用户态匿名页和文件映射页)。进程可能已退出,但 socket 仍处于 CLOSE_WAITFIN_WAIT2 或未正确 close() 的半关闭状态,导致内核无法释放缓冲区。

  • ss -m 显示的内存是 per-socket 的 sk->sk_rmem_alloc/sk->sk_wmem_alloc,单位是字节,可远超单个 socket 的 rmem_max/wmem_max(尤其在有大量未确认数据或排队 sk_buff 时)
  • 进程 RSS 低说明它没在用户态 malloc 大量内存,但可能反复 send() 后不 recv(),或 shutdown(SHUT_WR) 后不读完对端 FIN,造成接收队列持续
  • 注意 ss -m 输出中 ino 字段——这是 socket 对应的 inode 号,可用于关联 /proc/*/fd//proc/*/net/tcp

用 ino 定位残留 socket 所属进程(含已退出但未 fully close 的)

即使进程已 exit,只要 socket 还持有引用(如被子进程继承、或存在 file descriptor 引用但未 close),其 inode 就仍存在于 /proc/net/tcp/proc/net/tcp6 中。关键不是找“活着的进程”,而是找“谁最后打开过这个 socket”。

  • 运行 ss -tulnmp | grep 'skmem_rmem.*[0-9]{7,}' 提取高内存 socket 的 ino 值(例如 ino:123456789
  • find /proc/[0-9]*/fd/ -lname "socket:[123456789]" 2>/dev/NULL 查找所有指向该 inode 的 fd 符号链接;若无结果,说明进程已退出但 socket 未释放(常见于孤儿连接或内核 refcount 泄漏)
  • 若有结果,进入对应 /proc/PID/ 目录,检查 commcmdlinestack(需 root): cat /proc/PID/stack 可看线程是否卡在 tcp_recvmsgsock_sendmsgepoll_wait 等调用点

检查 netstat/ss 输出中的状态与重传标志

仅看内存值不够,必须结合 socket 状态判断泄漏类型。重点关注 CLOSE_WAIT(对端已 FIN,本端不 close)、ESTABLISHEDretrans 高、或 FIN_WAIT2 长时间不超时。

  • ss -tunp state close-wait | wc -l:CLOSE_WAIT 过多通常意味着应用层未调用 close(),尤其在 http keep-alive数据库连接池未正确归还连接时
  • ss -i(带 TCP info)可显示 retransqsizercv_space;若 retrans > 0qsize 持续增长,可能是对端不可达或应用层写阻塞未处理
  • netstat -s | grep -A5 "Tcp:" 查看全局 TcpExtTCPAbortOnMemoryTcpExtTCPMemoryPressures 计数,若频繁触发,说明内核已因 socket 内存耗尽开始 abort 连接

验证是否由 SO_RCVBUF/SO_SNDBUF 设置不当引发

显式调大 socket 缓冲区(如 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)))会直接放大 skmem_rmem 占用,但若应用未及时 recv(),缓冲区就变成“内存黑洞”。linux 4.6+ 默认启用自动调优(net.ipv4.tcp_rmem 第三项为 max),但手动设置会禁用 auto-tuning。

  • 检查 /proc/sys/net/ipv4/tcp_rmem/proc/sys/net/ipv4/tcp_wmem,若第二/三字段过大(如 4096 65536 16777216),且应用又未做流控,极易堆积
  • ss -i 观察单个 socket 的 rcv_ssthreshrcv_space:若 rcv_space 接近 tcp_rmem[2]rcv_ssthresh 很小,说明接收窗口已关死,但数据还在进
  • 临时缓解可调低全局上限:echo "4096 65536 4194304" > /proc/sys/net/ipv4/tcp_rmem,但根本解法是修复应用层 read loop 或增加超时

真正难定位的是那些 ino 找不到对应进程、ss -m 内存缓慢上涨、且 netstat -sTcpExtTCPAbortOnMemory 不增不减的情况——这往往指向内核模块 bug、eBPF 程序意外 hold socket ref、或 cgroup v1 的 socket 内存 accounting 残留。这时候得用 bpftrace hook tcp_closesk_stream_kill_queues 看谁没被调用。

text=ZqhQzanResources