基础概念
打开文件数,如字面意思,指的是打开文件的数量。
以前,我一直在想,"打开文件"是一个什么概念。后来,学了一点C语言,才明白,程序访问一个文件时是需要先打开文件的。体现在C语言编程中,就是程序会使用函数,如fopen( )函数,来打开该文件。比如,程序要将日志写入到/root/test.log文件中,就可能会使用 fopen("/root/test.log", "w") 来打开该文件,后面的w则限定了程序只能对该文件进行写入操作,并且程序会先将文件内容清空(如果文件不存在就会先创建文件),类似的还有"r" (只读)、"a+" (可读可写)等。程序打开文件后,才能进行读取文件内容或写入内容到文件等操作。当程序不使用某一个文件时,还要使用fclose( )函数来关闭文件。
因此来说,当程序打开一个文件时,就会产生1个打开文件数。程序打开几个文件就会产生几个打开文件数。而操作系统对程序所能打开的文件数量是有限制的。操作系统为什么要限制呢?因为打开文件是需要消耗资源的,操作系统需要追踪记录哪些程序打开了哪些文件,并且有些文件的内容可能需要读入内存。所以操作系统会限制程序的"最大打开文件数"。
尽管来说,程序打开文件时会消耗系统资源,操作系统也会限制最大打开文件数,但那又有什么关系呢?通常来说,Web服务器、数据库服务器等,如果都没什么访问量的话,我们当然就没有必要关注打开文件数了。但是,访问量稍大一点时,就要必要了。特别是在RHEL/CentOS 6等有点年代的系统中。
查看及修改最大打开文件数
在RHEL/CentOS系列的操作系统中,最大打开文件数限制有软限制(soft limit)和硬限制(hard limit)之分。
通常来说,软限制值就是程序的最大打开文件数限值,在RHEL/CentOS 6中这个值默认是1024,程序打开的所有文件数量不能超过这个值。使用ulimit -n命令可以查看当前的软限制值:
|
但是,普通用户也可以将这个值调大。使用 ulimit -n number命令将软限制值临时调大或调小。但是,普通用户最多也只能将其调整到硬限制值。
硬限制值就是限制用户的软限制值所能调整的最大上限,在RHEL/CentOS 6中这个值默认是4096,也就是说,普通用户自己如果要修改软限制值的话,最大只能修改到4096。硬限制值只有root用户可以修改。使用ulimit -n -H命令可以查看当前的硬限制值:
|
使用 ulimit -n -H number命令可以调整硬限制值。
当然,最好是在/etc/security/limits.conf文件中进行设置,以让其永久生效。怎么在该文件中设置,网上也有很多文章,我就不介绍了。但是,即便在该文件中设置了,也不是对所有情况都生效的,后面我会说到。它只能保证你重新登录,或系统重启后你重新登录,你看到的设置是仍然生效的。
进行测试
先创建一个用于测试的用户,查看到它的当前打开文件数限制是1024:
|
然后,再退出来,查看到该用户当前的已打开文件数是0:
|
重新切换到该用户下,写一个用于测试打开文件数的简单C程序:
|
该程序会尝试打开OPEN_FILES指定的文件个数,并输出打开成功与否,然后等待SECOND秒数,再终止。
编译:
|
然后执行:
|
上面的程序简单改动一下,经多次测试,可以得到如下结论:
1、 在操作系统中,最大打开文件数有soft限值和hard限值之分,而soft <= hard。测试发现,soft限值决定了打开文件数的限值,而hard限值只是决定了soft限值的最大值而已。实际的打开文件数绝对不会超过soft限值的限制。
2、 虽然,在操作系统中的/etc/security/limits.conf文件中,我们是分用户来设定最大打开文件数限值的,据此,不同的用户可以有不同的最大打开文件数限值。但是,测试发现,打实是开文件数限制其限制该用户单个进程的最大打开文件数,而不是限制该用户所有进程的总的打开文件数。以普通用户默认限值1024为例,属于该普通用户的任意一个进程的最大打开文件数都不可能超过1024,但是该普通用户的所有进程加起来的总的打开文件数并没有限制,比如总的打开文件数可能是10000或更多。
3、 即便是同一个程序,多次打开同一个文件,打开文件数也会相应增加,并不会被看作只有一个打开文件数。
4、 查看进程的打开文件数可以使用lsof命令查看,如(另开一个终端)查看进程8670:
|
直接使用lsof -p 2961 | wc -l 命令来计算进程的打开文件数并不准确,得到的是一个粗略的值。下面是一个示例输出:
可以看到,在输出的第四列,其实程序已经告诉我们了每一个文件是第几个打开的文件。由于是从0开始编号的,看图中,编号已经到1023了,所以进程8670现在已经是打开了1024个文件了,当前打开文件数的soft限值也是1024。
lsof命令输出的第四列FD (File Descriptor)列的含义:
该列字段的值可能是,文件的文件描述符编号或下图中的之一。如果是文件描述符编号,它后面会跟有一个模式字符(mode character)和一个锁字符(lock character)。
模式字符表示该文件所处的打开模式,取值可能是下面五种之一:
锁字符表示应用于该文件的锁的类型,取值可能是下面之一:
当一个进程在运行中时,查看一个进程实际应用的ulimit限值(包括最大打开文件数)的最准确的方式,是查看它的/proc/pid/limits文件。比如,要查看进程8670的应用的ulimit限值,使用命令:
|
当然,要查看该进程实际打开了多少个文件,则是前面介绍的,使用lsof命令。
其它问题
1、网络连接是否会占用打开文件数?
会,一个listening或established状态的网络连接会占用一个打开文件数。所以,在web应用的访问量稍大时,如果是单进程程序的话,即便不算应用本身打开的常规文件,由于网络连接数多,也会导致打开文件数轻轻松松就超过1024个。所以,对于CentOS/RedHat 6这种老系统来说,由于默认值比较小,所以是很有必要调整的。
根据man文档中的说法,一个打开文件可能是一个常规文件、一个目录、一个块设备文件、一个字符设备文件、一个正在执行的文件引用、一个库、一个流或一个网络文件(网络socket,NFS文件或UNIX socket)。所以,网络连接也算。我估计,这可能是因为在程序中,要访问这些对象时,都有点类似于访问文件那样,需要打开。
2、当你修改了/etc/security/limits.conf文件中的ulimit限值(包括打开文件数)后,是否需要重启正在运行的程序?
是。因为ulimit限值是跟你当前的shell绑定的,你在哪个shell里面启动了程序,如果程序本身没有修改ulimit限值的话,程序就会继承那个shell环境的ulimit限值。所以,通常修改limits.conf文件中的限值后,要退出当前shell并重新登录,让新的限值生效,再重启你的程序。
当然,正如我前面所说,要查看一个进程运行后实际生效的ulimit限值,使用cat /proc/pid/limits命令。如果程序自身有修改ulimit限值的话,你就会看到它的实际限值与你当前shell环境的限值是不一样的。
3、是否修改了/etc/security/limits.conf文件中的ulimit限值(包括打开文件数)后,就能保证它对所有的程序生效?
这是错误的。事实上来说,limits.conf文件中的限值对通过启动脚本来启动的程序并不生效。比如,nginx程序有一个启动脚本/etc/init.d/nginx并设置了开机启动。那么,即便你修改了limits.conf文件中的限值,当服务器重启后,nginx程序自动启动了,它的ulimit限值将还会是默认值,而不会是你设置的值。当然,如果你此时登录进系统,并通过nginx开机启动脚本重启了nginx程序,nginx进程的ulimit限值自然会变为你在limits.conf文件中设置的限值。
关于这个问题的原因,我也没有找到什么权威的资料说明,但我估计可能是这样的。以CentOS 6系统为例,因为系统启动时,系统中的所有进程都是由第一支程序/sbin/init带起的。而limits.conf文件中的限值对/sbin/init程序并不生效,所以/sbin/init进程的ulimit限值仍然是默认值。这就导致它所启动的所有子进程,即系统中的所有其它程序,都继承它的ulimit限值,即默认值。
对于这个问题,我想到的有两种解决办法。
第一种,是在程序的启动脚本里面最前面加上ulimit修改命令:
|
第二种,就是,很多程序其实都支持在程序配置文件中修改程序的最大打开文件数,这样就不用管shell环境的ulimit限值是什么了。比如,nginx可以通过worker_rlimit_nofile指令来设置它的worker进程的最大打开文件数。诸如MySQL其实也是支持的。
转载于:https://blog.51cto.com/techsnail/2137383