江湖上的人都碰到过Too many open
files的错误(无论你是多线程,网络socket,还是别的什么幺蛾子,这个错误很常见)。笔者一个朋友刚好也碰到过,经过一番搜索最终解决了问题。但是始终没有搞清楚——“到底问题出在哪里?”。我当然也讲不清楚(否则就不会有这篇文章咯)网上也找不到相关资料。作为一个有良知的自媒体公众账号,我决定要把替大家去深究一下这个问题。(花了两个晚上~~,记得打赏我)
网上流传的三种做法:
- 修改ulimit命令修改,这种修改只能在当前会话有效或者/etc/security/limits.conf设置hard soft nofile,可以一直有效
- sysctl修改fs.file-max
- 修改/proc/sys/fs/nr_open(可选)
还有一种传说这是有优先级的——limit.conf < fs.file-max < nr_open
然而这都是扯淡,纯粹的臆想。有良知的自媒体公众账号是讲道理的,正所谓——没代码你说个屁啊!!!;所以我就顺着Linux Kernel的代码挖了下去。
Linux/Unix一个著名的哲学就是——“万物皆文件”,无论是一个线程、socket、还是真正的文件都会被当做“文件”。Too may
open
files通常意味着“文件描述符”不足,它一般会发生在“创建线程”、“创建socket”、“打开文件”这种场景下。我选“创建socket”作为出发点
文件描述符的限制?不对!!
调用socket函数的时候内核会分两步操作——填充数据结构,分配fd。我们重点看socket_map_fd
关键的地方来了,get_unused_fd_flags会尝试分配一个fd,但是这个仅仅是fd——是一个数字而已;就是我们常说的——文件描述符。仅仅有一个数字并不代表什么,它相当于一个占位符,系统并没有实际的分配资源。socket_alloc_file才是真正的建立文件结构(内核的数据结构:struct
file)。打开get_unsed_fd_flags摸下去:
同志们,重点又来了。rlimit(RLIMIT_NOFILE)这个函数得到的是soft nofile,我们继续看__alloc_fd
fd备用有三部分组成,进程当前预分配的(fd位图中设置了标记,fdt->next_fd);进程当前可用的(fd位图中没有设置标记,fdt->max_fds);进程扩展的(fd位图中都不存在,需要执行expand_files扩展fd位图)所以__alloc_fd函数分为了三步尝试分配fd。
- 尝试“预分配”的fd(直接分配)
- 尝试分配“可用的”的fd(需要填充位图)
- 尝试扩展fd位图大小
如果fd超过soft nofile,这个函数会直接返回“错误”。所以soft nofile是fd大小限制的第一道关卡,hard nofile全程没用。soft nofile的准确而含义是——当前可以使用多少fd。
当前是跟“进程”有关系的,详细内容请看最后一部分。我们继续看“扩充”fd:
fs.nr_open是文件描述符的最后一道关卡,当我们尝试扩充文件描述符的时候只要你不大于它系统就允许你扩充,它的最大值是2147483584。
结论:
- soft nofile、fs.nr_open是用来控制文件描述符数量的
- soft nofile其实是linux的pam_limit模块设置的如果你不启用这个模块,你只能通过ulimit命令调整。如果不调整它的值是4096(可以看最后的代码图)
- nr_open表示文件描述符最大数量。它的最大值是2147483584(64位机器上2^31-64)。这也是是soft nofile、fs.nr_open可以设置的最大值。
文件结构体
文件描述符在内核中其实是一个数字,它代表的是一个“索引”而索引的内容是“文件结构体”(内核数据结构 struct
file)。内核分配资源的时候把“索引”和“内容”当做两种资源来分配。先申请“索引”后申请“内容”。跳回sock_map_fd看第二步——分配文件结构,它调用了sock_alloc_file函数。
顺着这个函数走下去你会发现——file-max(为了节省版面,完整的代码图我附在后面)
file-max是指struct file的上限。你可以把soft
nofile、fs.nr_open设置成天文数字,但是不设置file-max就意味着没法分配struct
file,文件描述符就没用了,依旧资源分配不成功。(像12306,你抢到票还不行还得“排队”。抢到的仅仅是一个占位符,到最后可能“没票了”。对,我没买到车票,等大家众筹机票了。)
总结:
- fs.file-max是用来控制文件结构体数量的
等等,还没结束
上面已经扒出了三个参数的真实意义,但是作为一个——有良知的自媒体公众号必须把道理讲清楚。所以我就挖出了soft nofile的前生今世。
PAM(Pluggable Authentication
Modules)是Linux的认证框架,在系统启动成功后无论是后台服务进程还是bash都会通过setup_limits加载/etc/security/limit.conf文件然后调用setrlimit重新设置进程的rlimt——其中就包括了soft
nofile。(pam_limit不在内核代码中它有自己独立的代码仓库,为了做有良知的自媒体我是不是特别拼?)
ulimit这个命令其实是系统的内部命令(不信你打which ulimit)它也是调用setrlimit完成的设置。二者的区别是pam_limit是自动加载的(属于linux的“认证模块”),ulimit你必须动手输入命令。
作者:邢森
来源:51CTO