IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

netstat和web主机socket文件分析

Makeinstall 2009-11-10 09:09:44 累计浏览 5,540 次
本机暂存

1. 内容提要

    “netstat -nlp”命令可以显示当前用户运行的正在监听的网络进程的状态。在正常情况下,在显示结果的PID/Program name一列显示了进程的进程号和程序的名称。但是在web前端的机器上,apache等均不能正常显示。

     问题出在哪儿?首先,可以排除netstat手册中所说的没有权限查看的问题,实际上,即使是使用root账号,仍然无法查看到上述进程的PID/Program name;而且,上述状况只是出现在web前端的机器,其它都是正常的。我们需要逐步分析,来找到问题的答案。


2. netstat问题分析

     首先,我们来了解一下netstat是如何获取主机中的各个服务的进程号和程序名称的。过程如下:

  • 当执行netstat -nlp时,该命令首先遍历所有的/proc/PID/fd目录,如果某个进程非本账号所有,则无权访问/proc/PID目录,跳过该PID,访问下一个。
  • 当某个/proc/PID/fd目录中有一个或多个socket句柄时,则读该/proc/PID目录下的cmdline文件,获取该进程的所执行程序的名称;同时,记录/proc/PID/fd目录下所有socket句柄的inode号;当所有进程遍历完成后,访问proc/net/{tcp,udp,unix}等文件,查找与上面记录的socket句柄inode号相同、并且状态为OA(即LISTEN)的行,从这些行获取local_address、rem_address,这些adress中均已包含了端口号。
  • 通过组合上述过程所获取的信息,输出“Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name”各列的信息。

      具体的,我们可以通过如下命令获取整个过程的系统调用:

      $ strace netstat -nlp

      从这个命令的输出中,我们可以找到一些我们感兴趣的调用。首先,对于一台正常机器,我们截取如下的输出:

a1

      这个过程是访问7205号进程(server)的文件句柄的开头一部分过程,我们注意到从0到7号文件句柄是STDIN、STDOUT、STDERR和一些普通文件,到了8号时,有了第一个socket句柄(其inode号是24536735),于是访问cmdline文件,读取进程的命令(634行)。然后,截取“server”这部分,之后这会显示在netstat -nlp输出的PID/Program name列。读程序名的操作只进行一次,以后遇到socket只记录其inode号。

      上述过程是一个正常的netstat调用。下面转到一台web前端机器,获取它的系统调用过程:

a2

       4862进程是abc,可以看到,在遍历到6号文件描述符时出现了socket句柄socket:[3279427707](其inode号是3279427707,这个inode号很大)。但是,这次与上面的正常机器不同的是,程序直接跳过了这个socket,并未去读取cmdline中的程序名。

      至此,我们可以肯定,造成web前端机器的netstat -nlp不能显示PID/Program name的直接原因就是netstat并未去读取cmdline中的内容。那么,是什么使得netstat不去读cmdline呢,我们需要去看看netstat的源码。

      netstat 1.42的源码文件netstat.c,查找与读取程序名称相关的函数。最后,我们定位在static void prg_cache_load(void)函数,如下是该函数的读取cmdline的部分代码:

a3

      404行将“/proc/PID/cmdline”字符串写到line指针的地址,405行打开line指向的cmdline文件路径,408行将cmdline文件的内容(即进程的整条命令)读入cmdlbuf,415行从整条命令中截取程序名(相当于basename命令的作用)。

      现在需要找到上述代码没有执行的原因,注意到396到398行的代码,即如果获取socket的inode失败,则下面的代码都不会被执行。那么有没有可能程序在获取socket的inode时失败呢?

      找到extract_type_1_socket_inode函数,如下:

a4

      这个函数的作用是从形如“socket:[12345]”的字符串中截取“12345”。318行是这个函数所有异常退出的条件。那么,对于web前端机器abc进程的socket:[3279427707],有可能会产生异常吗?

       我们定位到最后一个条件*inode_p >= INT_MAX。在web前端机器上运行getconf INT_MAX,得到INT_MAX的值是2147483647,而3279427707确实是大于2147483647,所以会导致函数返回-1,同样extract_type_2_socket_inode函数因格式不对也返回-1,最终导致prg_cache_load函数未读取cmdline的内容。

       至此,我们确认,造成web前段机器的netstat -nlp不能显示PID/Program name的根本原因是:socket inode号过大,超过了INT_MAX

我们发现了netstat的一个bug,即作者未充分考虑到scoket的inode可能很大,以至超过int的最大值。但是,更让人感兴趣的问题出现了:web前段机器上的socket inode号为什么会大到十位数,而其它机器上的几乎都只是八位数。

3. VFS和socket inode分析

      首先,我们需要了解socket的inode是什么。

      socket文件的inode存在于Linux的VFS虚拟文件系统中。VFS是一个异构文件系统之上的软件粘合层,可以让open()、read()、write()等系统调用不用关心底层的存储介质和文件系统类型就可以工作。通过VFS,一个抽象的通用访问接口屏蔽了底层文件系统和物理介质的差异性,每一种类型的文件系统代码都隐藏了实现的细节。对于VFS层和内核的其它部分而言,每一种类型的文件系统看起来都是一样的。

      VFS inode和磁盘文件系统中的inode不同。比如ext2文件系统,它的inode位于磁盘上划分的块组中,每个inode 128字节。在分割扇区时,系统会先做出一堆inode供以后使用,inode 的数量关系着系统中可以建立的文件及目录总数。磁盘上的每个文件都有且仅有一个inode,即使文件中没有数据,inode也是存在的。

      VFS inode只存在于内存中,可以通过inode缓存访问。每个文件在VFS中都有相应的inode结点,包括普通文件、目录以及特殊文件,如socket、pipe等。在需要某个文件的时候系统会在内存中为其建立相应的inode数据结构,建立的inode结构将形成一个链表,可以通过遍历这个链表去得到我们需要的文件结点。但是,VFS对于不同文件的inode号分配方式是不同的。具体如下:

  • 对于ext2等磁盘文件系统中的普通文件,每个文件在磁盘上都有对应的inode,该inode号也是已知的。在访问这些文件时,VFS会在内存中为这个文件分配VFS inode,将在磁盘文件系统中确定的inode号赋给inode结构。可见,一般普通文件的inode号都不会太大。
  • 对于socket等特殊文件来说,并没有像磁盘文件一样的inode号的来源,内核在实现中维护了一个unsigned long型的静态变量来保存目前分配的inode号的最大值,新分配的inode号在此基础上加1来实现。这个静态变量的值会一直增大而不会减小,直至机器重启。

      知道了socket文件inode的分配方式,我们可以假设,在某台机器上,socket文件被频繁的创建(并关闭),则新产生的socket文件对应的inode号会以较快的速度增大,在经过足够长的时间后,inode号的大小会超过INT_MAX的值,这是如果运行netstat -nlp则无法得到其程序名。

      这样的假设确实出现在了web前端机器上。Web全段机器每天负责响应大量用户的连接请求,每个连接都需要通过建立socket来进行通信,这导致socket的inode号会一直快速增长下去。统计了一台的socket inode的情况,记录了在5天内每天同一时间的socket inode最大值,分析得到每天大约会增大70,000,000左右。

      很显然,socket inode的增长情况和前端机器流量在各时段的分布是一致的。这也证明前端机器极大的流量使得socket文件极频繁的创建,导致了在前端机器上出现了超大的inode。

      socket过大除了对netstat的显示结果造成影响外,会不会大到超过unsigned long的最大值而产生问题呢?答案是不会,在线上64位的机器上,unsigned long的最大值是20位数,以每天70,000,000的增量,要达到ULONG_MAX是不可能的。

4. 解决方案

      对于因socket过大导致netstat -nlp不能显示PID/Program name的问题,有没有什么解决方法或者替代方案呢?

  • 修改netstat源码,去除对socket inode最大值判断的限制;
  • 使用lsof命令来查看监听在某个端口的进程,如: $ /usr/sbin/lsof -i :端口号

同分类推荐文章

  1. 从零重建 macOS 开发机:可复现的环境初始化流程 (2026-06-14 20:36:00)
  2. 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 (2026-06-11 08:10:28)
  3. How to Set Up Homebrew Tap for Private CLI Tools: A Complete Guide (2026-05-27 02:13:03)

查看更多 DevOps 文章 →

建议继续学习

  1. 在Apache2.2.XX下安装Mod-myvhost模块 (累计阅读 13,057)
  2. server日志的路径分析 (累计阅读 11,241)
  3. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 10,844)
  4. AWStats简介:Apache/Windows IIS的日志分析工具的下载,安装,配置样例和使用(含6.9中文定义补丁) (累计阅读 10,088)
  5. 查看 Apache并发请求数及其TCP连接状态 (累计阅读 10,068)
  6. PHP程序的执行流程 (累计阅读 10,032)
  7. Cacti 添加 Apache 监控 (累计阅读 9,245)
  8. 大型高并发高负载网站的系统架构分析 (累计阅读 9,005)
  9. 推荐一些socket工具,TCP、UDP调试、抓包工具 (累计阅读 8,840)
  10. 使用Apache 和Passenger来运行puppetmaster (累计阅读 8,316)