打开文件数,如字面意思,指的是打开文件的数量。mysql
之前,我一直在想,"打开文件"是一个什么概念。后来,学了一点C语言,才明白,程序访问一个文件时是须要先打开文件的。体如今C语言编程中,就是程序会使用函数,如fopen( )函数,来打开该文件。好比,程序要将日志写入到/root/test.log文件中,就可能会使用 fopen("/root/test.log", "w") 来打开该文件,后面的w则限定了程序只能对该文件进行写入操做,而且程序会先将文件内容清空(若是文件不存在就会先建立文件),相似的还有"r" (只读)、"a+" (可读可写)等。程序打开文件后,才能进行读取文件内容或写入内容到文件等操做。当程序不使用某一个文件时,还要使用fclose( )函数来关闭文件。nginx
所以来讲,当程序打开一个文件时,就会产生1个打开文件数。程序打开几个文件就会产生几个打开文件数。而操做系统对程序所能打开的文件数量是有限制的。操做系统为何要限制呢?由于打开文件是须要消耗资源的,操做系统须要追踪记录哪些程序打开了哪些文件,而且有些文件的内容可能须要读入内存。因此操做系统会限制程序的"最大打开文件数"。web
尽管来讲,程序打开文件时会消耗系统资源,操做系统也会限制最大打开文件数,但那又有什么关系呢?一般来讲,Web服务器、数据库服务器等,若是都没什么访问量的话,咱们固然就没有必要关注打开文件数了。可是,访问量稍大一点时,就要必要了。特别是在RHEL/CentOS 6等有点年代的系统中。sql
在RHEL/CentOS系列的操做系统中,最大打开文件数限制有软限制(soft limit)和硬限制(hard limit)之分。shell
一般来讲,软限制值就是程序的最大打开文件数限值,在RHEL/CentOS 6中这个值默认是1024,程序打开的全部文件数量不能超过这个值。使用ulimit -n命令能够查看当前的软限制值:数据库
[tuser@gw ~]$ ulimit -n编程 1024vim |
可是,普通用户也能够将这个值调大。使用 ulimit -n number命令将软限制值临时调大或调小。可是,普通用户最多也只能将其调整到硬限制值。服务器
硬限制值就是限制用户的软限制值所能调整的最大上限,在RHEL/CentOS 6中这个值默认是4096,也就是说,普通用户本身若是要修改软限制值的话,最大只能修改到4096。硬限制值只有root用户能够修改。使用ulimit -n -H命令能够查看当前的硬限制值:网络
[tuser@gw ~]$ ulimit -n -H 4096 |
使用 ulimit -n -H number命令能够调整硬限制值。
固然,最好是在/etc/security/limits.conf文件中进行设置,以让其永久生效。怎么在该文件中设置,网上也有不少文章,我就不介绍了。可是,即使在该文件中设置了,也不是对全部状况都生效的,后面我会说到。它只能保证你从新登陆,或系统重启后你从新登陆,你看到的设置是仍然生效的。
先建立一个用于测试的用户,查看到它的当前打开文件数限制是1024:
[root@gw ~]# useradd tuser [root@gw ~]# su - tuser [tuser@gw ~]$ ulimit -n 1024 |
而后,再退出来,查看到该用户当前的已打开文件数是0:
[tuser@gw ~]$ exit logout [root@gw ~]# lsof -u tuser | wc -l 0 |
从新切换到该用户下,写一个用于测试打开文件数的简单C程序:
[tuser@gw ~]$ vim test_openfiles.c #include <stdio.h> #define OPEN_FILES 1025 #define LENGTH 20 #define SECOND 600 int main(void) { int count; char array[OPEN_FILES][LENGTH]; FILE * fp; for (count = 0; count < OPEN_FILES; count++) { sprintf(array[count], "tempdir/%d", count); fp = fopen(array[count], "w"); if ( fp != NULL ) { printf("Program has opened %d files.\n", count + 1); } else { printf("Program failed when opening the %dth file.\n", count + 1); } } sleep(SECOND); return 0; } |
该程序会尝试打开OPEN_FILES指定的文件个数,并输出打开成功与否,而后等待SECOND秒数,再终止。
编译:
[tuser@gw ~]$ gcc test_openfiles.c |
而后执行:
[tuser@gw ~]$ mkdir tempdir #先建个目录tempdir,用于存放打开的文件 [tuser@gw ~]$ ./a.out #执行程序 |
上面的程序简单改动一下,经屡次测试,能够获得以下结论:
一、 在操做系统中,最大打开文件数有soft限值和hard限值之分,而soft <= hard。测试发现,soft限值决定了打开文件数的限值,而hard限值只是决定了soft限值的最大值而已。实际的打开文件数绝对不会超过soft限值的限制。
二、 虽然,在操做系统中的/etc/security/limits.conf文件中,咱们是分用户来设定最大打开文件数限值的,据此,不一样的用户能够有不一样的最大打开文件数限值。可是,测试发现,打实是开文件数限制其限制该用户单个进程的最大打开文件数,而不是限制该用户全部进程的总的打开文件数。以普通用户默认限值1024为例,属于该普通用户的任意一个进程的最大打开文件数都不可能超过1024,可是该普通用户的全部进程加起来的总的打开文件数并无限制,好比总的打开文件数多是10000或更多。
三、 即使是同一个程序,屡次打开同一个文件,打开文件数也会相应增长,并不会被看做只有一个打开文件数。
四、 查看进程的打开文件数可使用lsof命令查看,如(另开一个终端)查看进程8670:
[root@gw ~]# lsof -p 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限值,使用命令:
[root@gw ~]# cat /proc/8670/limits |
固然,要查看该进程实际打开了多少个文件,则是前面介绍的,使用lsof命令。
其它问题
一、网络链接是否会占用打开文件数?
会,一个listening或established状态的网络链接会占用一个打开文件数。因此,在web应用的访问量稍大时,若是是单进程程序的话,即使不算应用自己打开的常规文件,因为网络链接数多,也会致使打开文件数轻轻松松就超过1024个。因此,对于CentOS/RedHat 6这种老系统来讲,因为默认值比较小,因此是颇有必要调整的。
根据man文档中的说法,一个打开文件多是一个常规文件、一个目录、一个块设备文件、一个字符设备文件、一个正在执行的文件引用、一个库、一个流或一个网络文件(网络socket,NFS文件或UNIX socket)。因此,网络链接也算。我估计,这多是由于在程序中,要访问这些对象时,都有点相似于访问文件那样,须要打开。
二、当你修改了/etc/security/limits.conf文件中的ulimit限值(包括打开文件数)后,是否须要重启正在运行的程序?
是。由于ulimit限值是跟你当前的shell绑定的,你在哪一个shell里面启动了程序,若是程序自己没有修改ulimit限值的话,程序就会继承那个shell环境的ulimit限值。因此,一般修改limits.conf文件中的限值后,要退出当前shell并从新登陆,让新的限值生效,再重启你的程序。
固然,正如我前面所说,要查看一个进程运行后实际生效的ulimit限值,使用cat /proc/pid/limits命令。若是程序自身有修改ulimit限值的话,你就会看到它的实际限值与你当前shell环境的限值是不同的。
三、是否修改了/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修改命令:
[root@gw ~]# vim /etc/init.d/mysql #!/bin/sh ulimit -n 65535 |
第二种,就是,不少程序其实都支持在程序配置文件中修改程序的最大打开文件数,这样就不用管shell环境的ulimit限值是什么了。好比,nginx能够经过worker_rlimit_nofile指令来设置它的worker进程的最大打开文件数。诸如MySQL其实也是支持的。