Linux系统句柄问题分析
1 概念
- 句柄就是一个对象的标识符,只要获得对象的句柄,我们就可以对对象进行任意的操作,包括窗口,按钮,图标,输出设备,控件或者文件等;
- 句柄是一种特殊的智能指针,用一个唯一的整数值标识一个对象(即编号),并不指向实际的内核对象,而是内核对象的虚拟地址;
- 只有Windows中才有句柄,Windows中的句柄是指针的指针,因为windows中对象的经常会在内存中移动,所以地址值经常会变,所以就对外提供一个指针的指针即句柄给用户,句柄的地址是不会变的。
- Linux中是没有文件句柄的,只有文件描述符,只是大家习惯把它说成句柄,Linux中, 每当进程打开一个文件时,系统就为其分配一个唯一对应的整型文件描述符,用来标识这个文件;
在linux系统中,想要彻底的删除一个文件,取决于两个“计数器”,这两个计数器一个是磁盘引用的“计数器”(记录了这个文件有几个硬链接),另一个则是内存引用的“计数器”(纪录了这个文件正在被几个进程所调用),当这两个“计数器”全部为0,也就是这个文件没有硬链接,没有任何进程在调用的时候,这个文件才会真正的被删除。
可能原因:
- 该文件已经建立硬链接
这个原因很简单,hardlink是指多个文件名指向同一索引点(inode),只要其中仍然存在一个硬链接都文件内存都不会释放,所以文件具有硬链接是原因之一。 - 系统中有进程正在使用该文件
这个原因解释起来也很简单,如果系统中有进程使用该文件,删除该文件后如果内存释放,则进程会自动结束,造成无法预测的后果。 - 磁盘空余空间出现问题
这个问题原因就很复杂了,既有物理磁盘损坏又有些磁盘内部分配问题。这里提供一个可能,大家都知道LINUX文件系统是通过inode索引文件管理blocks区域,如果文件中的inode节点被用完了,则无法分配新空间。
2 linux lsof命令
lsof(list open files)是一个列出当前系统打开文件的工具。在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接口。
lsof输出各列信息的意义如下:
COMMAND:进程的名称 PID:进程标识符
USER:进程所有者
FD:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等 TYPE:文件类型,如DIR、REG等
DEVICE:指定磁盘的名称
SIZE:文件的大小
NODE:索引节点(文件在磁盘上的标识)
NAME:打开文件的确切名称
3 查看与修改句柄:
在linux系统中可以通过ulimit–n查看每个进程限制的最大句柄数,通过ulimit –HSn 10240修改进程的最大句柄数。当句柄数目达到限制后,就回出现”too many files open”。
查看进程占用的句柄数有几种办法:
- 通过cat /proc/pid/fd可以查看线程pid号打开的线程;
- 通过lsof命令, /usr/sbin/lsof-p 21404
4 解决方案
1 系统默认的ulimit对文件打开数量的限制是1024,修改/etc/security/limits.conf并加入以下配置,永久生效
- soft nofile 65535
- hard nofile 65535
硬限制是实际的限制,而软限制,是warnning限制,超过这个数值只会做出warning
注:
1)这个限制是针对单个程序的限制
2) 这个限制不会改变之前已经运行了的程序的限制
2 查找文件句柄问题的时候,还有一个很实用的程序lsof。可以很方便看到某个进程开了那些句柄,也可以看到某个文件/目录被什么进程占用了。
例如分析句柄数,查找原因,这是解决问题最根本的办法。那么如何分析那,就需要用到lsof这个命令了
(1) 统计各进程打开句柄数:lsof -n|awk ‘{print $2}’|sort|uniq -c|sort –nr
(2) 统计各用户打开句柄数:lsof -n|awk ‘{print $3}’|sort|uniq -c|sort -nr
(3) 统计各命令打开句柄数:lsof -n|awk ‘{print $1}’|sort|uniq -c|sort –nr
- 系统中还有一个/proc/sys/file-max的文件, 表示系统所有进程一共可以打开的文件数量
通常这个系统限制是Linux系統在启动时候根据系統硬件资源状况计算出来的最佳的最大同時打开文件数限制,如果沒有特殊需要,不应该修改此限制,除非想为用户级打开文件数限制设置超过此限制的值。
修改方法如下
方法一:
echo 6553560 > /proc/sys/fs/file-max
方法二:
/etc/sysctl.conf, 加入fs.file-max = 6553560
执行如下命令生效:
sysctl -p
另外还有一个常用命令,/proc/sys/fs/file-nr,可以看到整个系统目前使用的文件句柄数量
可以执行自动脚本如下
sed -i -e '44 i * soft nofile 65535' -e '44 i * hard nofile 65535' /etc/security/limits.conf
echo 6553560 > /proc/sys/fs/file-max
4 这里首先说明一下服务器的一些删除策略,由于Linux没有回收站功能,我们的线上服务器所有要删除的文件都会首先移动到系统/tmp目录下,然后定期清除/tmp目录下的数据。这个策略本身没有问题,但是通过检查发现这台服务器的系统分区中并没有单独划分/tmp分区,这样/tmp下的数据其实是占用了根分区的空间。
5 一个文件在文件系统中的存放分为两个部分:数据部分和指针部分,指针位于文件系统的meta-data中,数据被删除后,这个指针就从meta-data中清除了,而数据部分存储在磁盘中,数据对应的指针从meta-data中清除后,文件数据部分占用的空间就可以被覆盖并写入新的内容,之所以出现删除access_log文件后,空间还没释放,就是因为httpd进程还在一直向这个文件写入内容,导致虽然删除了access_log文件,但文件对应的指针部分由于进程锁定,并未从meta-data中清除,而由于指针并未被删除,那么系统内核就认为文件并未被删除,因此通过df命令查询空间并未释放也就不足为奇了。
解决这一类问题的方法有很多种,最简单的方法是关闭或者重启httpd进程,当然也可以重启操作系统,不过这并不是最好的方法,对待这种进程不停对文件写日志的操作,要释放文件占用的磁盘空间,最好的方法是在线清空这个文件,可以通过如下命令完成:
echo " " >/tmp/acess.log
通过这种方法,磁盘空间不但可以马上释放,也可保障进程继续向文件写入日志,这种方法经常用于在线清理Apache、Tomcat、Nginx等Web服务产生的日志文件。