打開文件數,如字面意思,指的是打開文件的數量。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其實也是支持的。