1. 程式人生 > >測試Linux最大打開文件數參數

測試Linux最大打開文件數參數

應用 ret tro 數量 RKE 語言 自然 導致 scrip

基礎概念

打開文件數,如字面意思,指的是打開文件的數量。

以前,我一直在想,"打開文件"是一個什麽概念。後來,學了一點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最大打開文件數參數