我們在日常維護中,常遇到"too many open file"的錯誤,有的系統,比如ES,要求啟動時候擴大打開檔案描述符的個數,不如會有如下的提示:
[1]: max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536]
是以搞清楚檔案描述符很重要,在限制檔案描述符的時候,也常有這樣的困惑,限制是對這個使用者來講的,還是對單個程序,還是對整個系統那,如果盲目擴大,可能引起一些資源耗盡性質的攻擊。我對這些概念的了解也不是十厘清楚,是以查閱了網際網路上的資料和自己測試,才寫了這篇文章,希望能對大家了解linux的檔案描述符,有所幫助。
一 ulimit
ulimit 其實是限制shell,以及shell啟動程序的使用資源情況,這樣看起來ulimit限制是程序級别,我們可以通過ulimit指令友善地臨時修改限制值。 比如我們修改打開檔案數量:
ulimit -n 100
這樣設定後,我們可以打開最多100個檔案句柄數(這裡面也是描述符數),測試如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char * argv[])
{
int i = 0;
for (i = 0; i < 101; i++) {
printf("open %d file.\n",i);
// 打開檔案會同時增加檔案描述符和檔案句柄數量
int f = open("/dev/zero",O_RDONLY);
if (f > 0) {
printf("open OK:%d \n",f);
} else {
printf("open error.\n");
}
}
}
用這樣的程式跑以下,會發現在兩個不同的終端,都最多可以打開97個檔案描述符(說明限制是程序級别):
open 0 file.
open OK:3
open 1 file.
...
...
open OK:97
open 95 file.
open OK:98
open 96 file.
open OK:99
open 97 file.
open error.
open 98 file.
open error.
open 99 file.
open error.
open 100 file.
open error.
為什麼不是100個,是因為程式預設打開了标準輸入,标準輸出和标準出錯三檔案描述符,是以隻剩下了97個檔案描述符,而且是從3開始的,也驗證了,打開檔案從最小的未用的整數開始。 通過ulimit修改隻是臨時,生效,要永久生效,可以寫到檔案中:
vim /etc/security/limits.conf
* soft noproc 20000 #軟連接配接
* hard noproc 20000 #硬連接配接
* soft nofile 4096
* hard nofile 4096
- 辨別任意使用者 soft 辨別軟限制,超過會告警;hard:硬限制,超過報錯 nofile 打開檔案描述符數量 nproc 打開程序數量。
重新開機後生效,如果不生效檢視下登入子產品是否引入限制如下:
[root@localhost ~]# cat /etc/pam.d/login|grep pam_limits.so
session required pam_limits.so
limits.conf 檔案實際是 Linux PAM(插入式認證子產品,Pluggable Authentication Modules)中 pam_limits.so 的配置檔案,而且隻針對于單個會話。
如果不做限制,一個指令可導緻必須重新開機機器:
:(){ :|:; }; :
你在設定ulimit -n 的時候,如果設定一個很大的值,會提示沒有權限,即使你是root使用者如下:
[root@localhost ~]# ulimit -n 3229825
-bash: ulimit: open files: cannot modify limit: Operation not permitted
原因是我們設定的值超過了單個程序能打開的最大檔案數量:
cat /proc/sys/fs/nr_open
[root@localhost ~]# cat /proc/sys/fs/nr_open
1048576
[root@localhost ~]# ulimit -n 1048576
[root@localhost ~]# ulimit -n 1048577
-bash: ulimit: open files: cannot modify limit: Operation not permitted
當我們設定小于等于單程序可以打開的最大檔案數量後,就可以設定成功了,臨時更改單個程序可以打開最大檔案數量如下:
[root@localhost ~]# echo 3229826 > /proc/sys/fs/nr_open
[root@localhost ~]# cat /proc/sys/fs/nr_open
3229826
[root@localhost ~]# ulimit -n 1048577
永久設定生效:
/etc/sysctl.conf 中添加或修 fs.nr_open值。
通過sysctl -p 生效,或者通過指令設定:
sysctl -w fs.nr_open=1000000
fs.nr_open限制單個程序打開最大檔案數量
/proc/sys/fs/nr_open
This file imposes a ceiling on the value to which the RLIMIT_NOFILE resource limit can be raised (see getrlimit(2)). This ceiling is enforced for both unprivileged and privileged process. The default value in this file is 1048576.
二 檔案句柄和檔案描述符
我們上面通過open函數傳回的就是檔案描述符,它是一個整數,每個程序都包含一個記錄項,每個記錄項内部都包含一個檔案描述符表,裡面包含檔案描述符fd和一個檔案指針,指向一個檔案表,同一個檔案打開多次會對應不同的檔案表,不同的檔案描述符,同上面的例子;多個檔案描述符也可以指向不同的或相同的檔案表, 這個檔案表稱為檔案句柄,如下圖:
為什麼要區分這兩者那,因為我們通常用lsof檢視的是檔案描述符的數量,file-nr為檔案句柄數量,通過指令檢視:
cat /proc/sys/fs/file-nr
得到三個值,含義:
已配置設定檔案句柄的數目 已配置設定未使用檔案句柄的數目 檔案句柄的最大數目
Historically, the three values in file-nr denoted the number of allocated file
handles, the number of allocated but unused file handles, and the maximum
number of file handles. Linux 2.6 always reports 0 as the number of free file
handles -- this is not an error, it just means that the number of allocated
file handles exactly matches the number of used file handles.
file-nr檔案裡面的第一個字段代表的是核心配置設定的struct file的個數,也就是檔案句柄個數,而不是檔案描述符。通過上面的圖關系我們可以看到,一個檔案描述符肯定要有一個檔案句柄,反過來,有一個檔案句柄了,可能有多個檔案描述符指向它,比如通過dup指派檔案描述符的情況或者父子程序情況,子程序會複制父程序的檔案描述符表。
為什麼要區分檔案描述符和檔案句柄,那是因為我們通過lsof檢視的時候,檢視到的是檔案描述符,再測試下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char * argv[])
{
int i = 0;
int fd = open("/dev/zero",O_RDONLY);
for (i = 0; i < 1000; i++ ){
// dup隻是會增加檔案描述符的數量,
// 可能少量增加檔案句柄數量或不增加看原來是否打開
int fdup = dup(fd);
if (fdup >0 ) {
printf("dup file:%d ,des:%d ok\n",i,fdup);
} else {
printf("dup file:%d error.\n",i);
}
}
pause();
}
我們寫了這樣一段代碼,編譯再另外一個終端運作,運作前面,通過下面的指令檢視打開的句柄數量,和檔案描述符的數量;運作後發現,lsof檢視的檔案描述符的數量增加了一千多,而檔案句柄的數量沒有增加,說明了,lsof檢視的是檔案描述符,通過測試我們也發現了ulimit -n限制的就是檔案描述符的數量。
是不是檔案描述符的數量一定比檔案句柄多那,找了個例子測試下:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void testm()
{
char * addr = NULL;
int fd = open("/dev/zero",O_RDONLY);
if (fd == -1 ){
printf("open error.\n");
}
addr = mmap(NULL,4096,PROT_READ,MAP_PRIVATE,fd,0);
if (addr == MAP_FAILED)
printf("Map error");
else {
// munmap(addr,4096);
}
close(fd);
}
int main(int argc,char * argv[])
{
int i = 0;
for (; i<1000; i++)
testm();
pause();
}
運作發現,檔案描述符的數量變化很小,而檔案句柄的數量增加一千個左右,原因是檔案描述符我們通過close(fd)關閉了,而通過mmap映射的記憶體塊沒有關閉,而檔案句柄包含了映射的記憶體,沒有釋放,通過:
[testm@localhost ~]$ pmap -p 7226|grep "dev/zero" |wc -l
1000
核對程序打開的映射記憶體是對的。
如果把注釋的代碼放開: munmap(addr,4096); ,這樣的話檔案句柄就不會怎麼增加的。
三 file-max
file-max即整個linux系統能打開的檔案總數,預設值是系統總記憶體(以KB為機關)/10, 檢視辦法:
cat /proc/sys/fs/file-max
766846
更改file-max的大,臨時生效
echo 786046 > /proc/sys/fs/file-max
或
sysctl -w fs.file-max=786046
永久生效:
echo fs.file-max=786046 >> /etc/sysctl.conf
sysctl -p
說明:
man 5 proc:
/proc/sys/fs/file-max
This file defines a system-wide limit on the number of open files for all processes. System calls that fail when encountering this limit fail with the error ENFILE.
四 總結
檔案句柄和檔案描述符是兩個不同東西,檔案句柄對應:
1. open系統調用打開檔案(path_openat核心函數)
2. 打開一個目錄(dentry_open函數)
3. 共享記憶體attach (do_shmat函數)
4. socket套接字(sock_alloc_file函數)
5. 管道(create_pipe_files函數)
6. epoll/inotify/signalfd等功能用到的匿名inode檔案系統(anon_inode_getfile函數)
通過:
cat /proc/sys/fs/file-nr
檢視占用的句柄數量
檔案描述符對應的有:
1. 檔案為REG
2. 目錄 DIR。
3. CHR表示字元裝置
4. BLK辨別塊裝置。
5. unix, FIFO, Ipv6分表表示UNIX域套接字,FIFO隊列和IP套接字。
lsof -n|awk ‘{print $2}’|sort|uniq -c|sort -nr|more | grep [PID]
指令看看程序打開的檔案描述符。
六 參考
[https://blog.csdn.net/u013256816/article/details/60778709](https://blog.csdn.net/u013256816/article/details/60778709)
[https://www.sohu.com/a/242941944_262549](https://www.sohu.com/a/242941944_262549)