netstat和web主机socket文件分析
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
从这个命令的输出中,我们可以找到一些我们感兴趣的调用。首先,对于一台正常机器,我们截取如下的输出:
这个过程是访问7205号进程(server)的文件句柄的开头一部分过程,我们注意到从0到7号文件句柄是STDIN、STDOUT、STDERR和一些普通文件,到了8号时,有了第一个socket句柄(其inode号是24536735),于是访问cmdline文件,读取进程的命令(634行)。然后,截取“server”这部分,之后这会显示在netstat -nlp输出的PID/Program name列。读程序名的操作只进行一次,以后遇到socket只记录其inode号。
上述过程是一个正常的netstat调用。下面转到一台web前端机器,获取它的系统调用过程:
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的部分代码:
404行将“/proc/PID/cmdline”字符串写到line指针的地址,405行打开line指向的cmdline文件路径,408行将cmdline文件的内容(即进程的整条命令)读入cmdlbuf,415行从整条命令中截取程序名(相当于basename命令的作用)。
现在需要找到上述代码没有执行的原因,注意到396到398行的代码,即如果获取socket的inode失败,则下面的代码都不会被执行。那么有没有可能程序在获取socket的inode时失败呢?
找到extract_type_1_socket_inode函数,如下:
这个函数的作用是从形如“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 :端口号
建议继续学习:
- 推荐一些socket工具,TCP、UDP调试、抓包工具 (阅读:9415)
- 推荐一些socket工具,TCP、UDP调试、抓包工具 (阅读:7245)
- 用unix socket加速php-fpm、mysql、redis的连接 (阅读:6476)
- 浅析linux kernel network之socket创建 (阅读:5682)
- nginx、php-fpm默认配置与性能–TCP socket还是unix domain socket (阅读:4981)
- web socket 心跳包的实现方案 (阅读:4912)
- python中的socket代理 (阅读:4800)
- 使用socket.io和node.js搭建websocket应用 (阅读:4351)
- php socket为什么这么慢,直到超时 (阅读:3841)
- 基于express+socket.io的nodejs聊天室 (阅读:3581)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:wilbur 来源: Makeinstall
- 标签: netstat socket
- 发布时间:2009-11-10 09:09:44
- [66] Go Reflect 性能
- [65] Oracle MTS模式下 进程地址与会话信
- [64] 如何拿下简短的域名
- [59] android 开发入门
- [59] IOS安全–浅谈关于IOS加固的几种方法
- [58] 图书馆的世界纪录
- [58] 【社会化设计】自我(self)部分――欢迎区
- [53] 视觉调整-设计师 vs. 逻辑
- [47] 界面设计速成
- [46] 读书笔记-壹百度:百度十年千倍的29条法则