測試Linux最大打開文件數參數
打開文件數,如字面意思,指的是打開文件的數量。
以前,我一直在想,"打開文件"是一個什麽概念。後來,學了一點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命令可以查看當前的軟限制值:
[tuser@gw ~]$ ulimit -n 1024 |
但是,普通用戶也可以將這個值調大。使用 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 #執行程序 |
上面的程序簡單改動一下,經多次測試,可以得到如下結論:
1、 在操作系統中,最大打開文件數有soft限值和hard限值之分,而soft <= hard。測試發現,soft限值決定了打開文件數的限值,而hard限值只是決定了soft限值的最大值而已。實際的打開文件數絕對不會超過soft限值的限制。
2、 雖然,在操作系統中的/etc/security/limits.conf文件中,我們是分用戶來設定最大打開文件數限值的,據此,不同的用戶可以有不同的最大打開文件數限值。但是,測試發現,打實是開文件數限制其限制該用戶單個進程的最大打開文件數,而不是限制該用戶所有進程的總的打開文件數。以普通用戶默認限值1024為例,屬於該普通用戶的任意一個進程的最大打開文件數都不可能超過1024,但是該普通用戶的所有進程加起來的總的打開文件數並沒有限制,比如總的打開文件數可能是10000或更多。
3、 即便是同一個程序,多次打開同一個文件,打開文件數也會相應增加,並不會被看作只有一個打開文件數。
4、 查看進程的打開文件數可以使用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命令。
其它問題
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修改命令:
[root@gw ~]# vim /etc/init.d/mysql #!/bin/sh ulimit -n 65535 |
第二種,就是,很多程序其實都支持在程序配置文件中修改程序的最大打開文件數,這樣就不用管shell環境的ulimit限值是什麽了。比如,nginx可以通過worker_rlimit_nofile指令來設置它的worker進程的最大打開文件數。諸如MySQL其實也是支持的。
測試Linux最大打開文件數參數