近期遇到服务器宕机,重启后排查系统日志,看到/var/log/messages中在宕机前出现大量file-max limit 65536 reached错误:
报file-max limit reached,说明机器上文件句柄的使用量超过了设定值,以下是排查过程的整理。先说怎么看文件句柄数,这个数据记在/proc/sys/fs/file-nr中:
图中1056表示当前已打开文件句柄数量,65536表示最大数量。最大值由/proc/sys/fs/file-max控制,可以通过echo 100000 > /proc/sys/fs/file-max来修改。由于出现宕机前新部署了应用,因此怀疑与它有关,通过不断刷新,发现此应用运行时确实导致文件句柄上升,一段时间后上升至6000多。在linux下很多操作都会占用文件句柄,比如打开文件、打开目录、socket、pipe、共享内存等,首先怀疑有文件open后没有close,用lsof查下:
可以看到数量很少,排查系统内其它进程,lsof -n | awk '{print $2}' | sort | uniq -c | sort -nr | head,看到没有哪个进程占用大量文件,那多出的5000多个句柄应该不是文件,往共享内存方向排查,用pmap看看进程是否有mmap相关操作:
5392,这下对上了,用pmap进一步分析,发现有大量config.dat存在:
找到开发排查源码,发现对此文件的使用涉及了mmap操作,先open文件,然后用mmap把文件映射到内存,再close文件,结果用完后没有munmap操作,导致文件句柄不断上涨。
问题定位了,翻回来看为什么文件close了依然会导致文件句柄上涨。每个打开的文件都会有一个句柄用于描述文件属性,进程中用一个整数型的文件描述符指向具体的句柄。由于linux内核中对文件描述符是使用引用计数管理,mmap后,引用计数+1,此时尽管close了文件描述符,但引用计数不为0,内核不会回收文件句柄。
写个小程序验证一下:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/mman.h> int main() { int i; for(i = 0;i < 1000;++i) { int fd; char * addr; fd = open("/dev/zero", O_RDONLY); //打开文件 if(!fd) exit(1); addr = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0); //映射 //munmap(addr, 4096); //此行不执行就会导致句柄上涨 close(fd); //关闭文件 } pause(); }
重复1000次,每次open和close是匹配的,但mmap没有munmap,执行后的效果:
和预期一致,加上munmap后,对比测试则不会再出现这1000个句柄占用。