全站資源開放下載,感謝廣大網友的支持
鏈接失效請移步職業司平臺
非盈利平臺

非盈利平臺

只為分享一些優質內容

Java幫幫-微信公眾號

Java幫幫-微信公眾號

將分享做到極致

微信小程序

微信小程序

更方便的閱讀

職業司微信公眾號

職業司微信公眾號

實時動態通知

安卓APP

安卓APP

我們從此不分開

程序員生活志-公眾號

程序員生活志-公眾號

程序員生活學習圈,互聯網八卦黑料

支付寶贊助-Java幫幫社區
微信贊助-Java幫幫社區

文件句柄?文件描述符?傻傻分不清楚

2
發表時間:2020-07-29 17:01


概述

在實際工作中會經常遇到一些bug,有些就需要用到文件句柄,文件描述符等概念,比如報錯: too many open files, 如果你對相關知識一無所知,那么debug起來會異常痛苦。在linux操作系統中,文件句柄(包括Socket句柄)、打開文件、文件指針、文件描述符的概念比較繞,而且windows的文件句柄又與此有何關聯和區別?這一系列的問題是我們不得不面對的。

筆者通過翻閱相關資料,并采用了一些demo來驗證相關觀點。如果文中有理解偏差,歡迎指正。

這里先籠統的將一下筆者對上面的問題的一些理解:

句柄,熟悉Windows編程的人知道,句柄是Windows用來標識被應用程序所建立或使用的對象的唯一整數,windows使用各種各樣的句柄標識諸如應用程序實例、窗口、控制、位圖等。Windows的句柄有點像C語言中的文件句柄。更通俗的理解,句柄是一種指向指針的指針。

在linux系統中文件句柄(file handles)和文件描述符(file descriptor)是一個一一對應的關系(如果錯誤,歡迎指正),按照C語言的理解文件句柄是FILE*(fopen()返回),而文件描述符是fd(int型,open()函數返回),FILE這個結構體中有一個字段是_fileno,其就是指fd(文章末尾通過程序驗證),且FILE*和fd可以通過C語言函數進行互相轉換,故此筆者認為linux的文件句柄和文件描述符應該是一個一一對應的關系。文件指針即指FILE*,即指文件句柄。打開文件(open files)包括文件句柄但不僅限于文件句柄,由于linux所有的事物都以文件的形式存在,要使用諸如共享內存、信號量、消息隊列、內存映射等都會打開文件,但這些是不會占用文件句柄。

ulimit

查看進程允許打開的最大文件句柄數:ulimit -n。設置進程能打開的最大文件句柄數:ulimit -n xxx。

ulimit在系統允許的情況下,提供對特定shell可利用的資源的控制(Provides control over the resources avaliable to the shell and to processes started by it, on systems that allow such control)。-H和-S選項設定指定資源的硬限制和軟限制。硬限制設定之后不能再添加,而軟限制則可以增加到硬限制規定的值。如果-H和-S選項都沒有指定,則軟限制和硬限制同時設定。限制值可以是指定資源的數值或者hard, soft, unlimited這些特殊值,其中hard代表當前硬限制, soft代表當前軟件限制, unlimited代表不限制. 如果不指定限制值, 則打印指定資源的軟限制值, 除非指定了-H選項.如果指定了不只一種資源, 則限制名和單位都會在限制值前顯示出來.

[root@hidden ~]# ulimit -Sn
1024
[root@hidden ~]# ulimit -Hn
4096

需要注意的是ulimit提供的是對特定shell可利用的資源的控制,而shell是與具體用戶相關的。因此ulimit提供的是對單個用戶的限制。包括以下項:

[root@hidden ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 62799
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 65536
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

其中就有個“open files”的限制,默認是1024,也就是這個用戶最大可以打開1024個文件。如果使用ulimit -n修改最大文件打開數,那么只對當前shell用戶有用,同時也只對當前shell和這個shell fork出來的子shell生效,重啟之后會重新恢復為默認值。

limits.conf

limits.conf這個文件是在/etc/security/目錄下,因此這個文件是出于安全考慮的。limits.conf文件是用于提供對系統中的用戶所使用的資源進行控制和限制,對所有用戶的資源設定限制是非常重要的,這可以防止用戶發起針對處理器和內存數量等的拒絕服務攻擊。這些限制必須在用戶登錄時限制。

[root@hidden ~]#  cat /etc/security/limits.conf
(省略若干....)
# End of file
apps soft nofile 65535
apps hard nofile 65535
apps soft nproc 10240
apps hard nproc 10240

其中含義如下:

  • 第一列表示域(domain),可以使用用戶名(root等),組名(以@開頭),通配置*和%,%可以用于%group參數。

  • 第二列表示類型(type),值可以是soft或者hard

  • 第三列表示項目(item),值可以是core, data, fsize, memlock, nofile, rss, stack, cpu, nproc, as, maxlogins, maxsyslogins, priority, locks, msgqueue, nie, rtprio。其中nofile(Number of Open File)就是文件打開數。

  • 第四列表示值.

關于第三列的詳細解釋如下:

#<item> can be one of the following:
#        - core - limits the core file size (KB)
#        - data - max data size (KB)
#        - fsize - maximum filesize (KB)
#        - memlock - max locked-in-memory address space (KB)
#        - nofile - max number of open file descriptors
#        - rss - max resident set size (KB)
#        - stack - max stack size (KB)
#        - cpu - max CPU time (MIN)
#        - nproc - max number of processes
#        - as - address space limit (KB)
#        - maxlogins - max number of logins for this user
#        - maxsyslogins - max number of logins on the system
#        - priority - the priority to run user process with
#        - locks - max number of file locks the user can hold
#        - sigpending - max number of pending signals
#        - msgqueue - max memory used by POSIX message queues (bytes)
#        - nice - max nice priority allowed to raise to values: [-20, 19]
#        - rtprio - max realtime priority

limits.conf與ulimit的區別在于前者是針對所有用戶的,而且在任何shell都是生效的,即與shell無關,而后者只是針對特定用戶的當前shell的設定。在修改最大文件打開數時,最好使用limits.conf文件來修改,通過這個文件,可以定義用戶,資源類型,軟硬限制等。也可修改/etc/profile文件加上ulimit的設置語句來是的全局生效。當達到上限時,會報錯:too many open files或者遇上Socket/File: Cannot open so many files等。

file-max & file-nr

[root@hidden ~]# cat /proc/sys/fs/file-max 
798282
[root@hidden fd]# sysctl -a | grep fs.file-max
fs.file-max = 798282

該文件指定了可以分配的文件句柄的最大數目(系統全局的可用句柄數目. The value in file-max denotes the maximum number of file handles that the Linux kernel will allocate)。如果用戶得到的錯誤消息諸如“由于打開文件數已經達到了最大值”之類,那么說明他們不能打開更多文件,則可能需要增加該值。可將這個值設置成任意多個文件,并且能通過將一個新數字值寫入該文件來更改該值。這個參數的默認值和內存大小有關系,可以使用公式:file-max ≈ 內存大小/ 10k.

[root@hidden ~]# cat /proc/sys/fs/file-nr
1440        0   798282

關于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-max一樣)。

[root@hidden fd]# lsof | wc -l
2538

lsof是列出系統所占用的資源(list open files),但是這些資源不一定會占用句柄。比如共享內存、信號量、消息隊列、內存映射等,雖然占用了這些資源,但不占用句柄。 如果出了某些故障,使用lsof | wc -l的結果,這個時候可以通過file-nr粗略的估算一下。

查看硬盤信息:df -m 查看內存信息:free -m 查看CPU信息:cat /proc/cpuinfo 查看內核所能打開的線程數:cat /proc/sys/kernel/threads-max

為什么有限制?

為什么Linux內核對文件句柄數、線程和進程的最大打開數進行了限制?以及如果我們把它調的太大,會產生什么樣的后果?

原因1 - 資源問題:the operating system needs memory to manage each open file, and memory is a limited resource - especially on embedded systems. 原因2 - 安全問題:if there were no limits, a userland software would be able to create files endlessly until the server goes down.

What’s more? If the file descriptors are tcp sockets, etc, then you risk using up a large amount for the socket buffers and other kernel objects, this memory is not going to be swappable.

最主要的是資源問題,為防止某一單一進程打開過多文件描述符而耗盡系統資源,對進程打開文件數做了限制。

lsof

lsof(list open files)是一個列出當前系統打開文件的工具。在linux環境下,任何事物都以文件的形式存在,通過文件不僅僅可以訪問常規數據,還可以訪問網絡連接和硬件。所以如TCP和UDP等,系統在后臺都為該應用程序分配了一個文件描述符,無論這個文件的本質如何,該文件描述符為應用程序與基礎操作系統之間的交互提供了通用接口。因為應用程序打開文件的描述符列表提供了大量關于這個應用程序本身的信息,因此通過lsof工具能夠查看這個列表對系統檢測以及拍錯將是很有幫助的。

在終端下輸入lsof即可顯示系統打開的文件,因為lsof需要訪問核心內存和各種文件,所以必須以root身份運行它才能夠充分地發揮其功能。

[root@hidden linuxC]# lsof -p 14895
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF    NODE NAME
java    14895 root  cwd    DIR              252,1     4096 1310824 /root/util/kafka_2.10-0.8.2.1
java    14895 root  rtd    DIR              252,1     4096       2 /
java    14895 root  txt    REG              252,1     7734 1583642 /root/util/jdk1.8.0_112/bin/java
java    14895 root  mem    REG              252,1 10485760 1325066 /tmp/kafka-logs/default_channel_kafka_zzh_demo-4/00000000000003626728.index
...(省略若干)
java    14895 root   85u   REG              252,1        0 1311594 /tmp/kafka-logs/default_channel_kafka_zzh_demo-4/00000000000003626728.log
java    14895 root   87u   REG              252,1        0 1325038 /tmp/kafka-logs/default_channel_kafka_zzh_demo-3/00000000000003915669.log
java    14895 root   88u  IPv6           40855648      0t0     TCP zhuzhonghua2-fqawb:XmlIpcRegSvc->xx.xx.139.85:64708 (ESTABLISHED)
java    14895 root   89u   REG              252,1        0 1325037 /tmp/kafka-logs/default_channel_kafka_zzh_demo-2/00000000000005892533.log
java    14895 root   93u   REG              252,1        0 1325040 /tmp/kafka-logs/default_channel_kafka_zzh_demo-1/00000000000005494790.log
java    14895 root   94u   REG              252,1        0 1325043 /tmp/kafka-logs/default_channel_kafka_zzh_demo-0/00000000000003858999.log
[root@hidden linuxC]# ls /proc/14895/fd | wc -l
89
[root@hidden linuxC]# ls /proc/14895/fd 
0  10  12  14  16  18  2   21  23  25  27  29  30  32  34  36  38  4   41  43  45  47  49  50  52  54  56  58  6   61  63  65  67  69  70  72  75  77  79  80  82  85  88  9   94
1  11  13  15  17  19  20  22  24  26  28  3   31  33  35  37  39  40  42  44  46  48  5   51  53  55  57  59  60  62  64  66  68  7   71  74  76  78  8   81  83  87  89  93

lsof輸出各列信息的意義如下:

COMMAND:進程的名稱
PID: 進程標識符
USER:進程所有者
FD:文件描述符,應用程序通過文件描述符識別該文件。如cwd, rtd, txt, mem, DEL, 0u, 3w, 4r等
TYPE:文件類型,如DIR, REG, CHR, Ipv6, unix, FIFO等
DEVICE:指定磁盤的名稱
SIZE/OFF:文件的大小
NODE:索引節點
NAME:打開文件的確切名稱

FD列中的文件描述符cwd表示應用程序的當前工作目錄,這是該應用程序啟動的目錄,除非它本身對這個目錄進行更改;txt類型的文件是程序代碼,如應用程序二進制文件本身或共享庫,如上列表中顯示的、sbin/init程序;數值表示應用程序的文件描述符,這是打開該文件時返回的一個整數,如“lsof -p 14895”命令解析出來的最后一行的文件描述符為94,u表示該文件被打開處于讀寫模式,而不是只讀r或只寫w模式,同時還有大寫的W表示該應用程序具有對整個文件的寫鎖。該文件描述符用于確保每次只能打開一個應用程序實例。初始打開每個應用程序時,都有三個文件描述符:0、1、2,分別表示標準輸入、標準輸出、錯誤流。所以大多數應用程序所打開的文件的FD都是從3開始的。

TYPE列比較直觀。文件和目錄分別為REG和DIR。而CHR和BLK分別表示字符和塊設備。或者unix, FIFO, Ipv6分表表示UNIX域套接字,FIFO隊列和IP套接字。

查看當前進程打開了多少文件:lsof -n|awk ‘{print $2}’|sort|uniq -c|sort -nr|more | grep [PID]

[root@hidden fd]# lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more | grep 14895
    173 14895

第一列是句柄數,第二列是進程號PID.

[root@hidden proc]# lsof -p 14895 | wc -l
174

這里多了一個是由于:

COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF    NODE NAME
java    14895 root  cwd    DIR              252,1     4096 1310824 /root/util/kafka_2.10-0.8.2.1
java    14895 root  rtd    DIR              252,1     4096       2 /
java    14895 root  txt    REG              252,1     7734 1583642 /root/util/jdk1.8.0_112/bin/java
java    14895 root  mem    REG              252,1 10485760 1325066 /tmp/kafka-logs/default_channel_kafka_zzh_demo-4/00000000000003626728.index
java    14895 root  mem    REG              252,1 10485760 1325044 /tmp/kafka-logs/default_channel_kafka_zzh_demo-0/00000000000003858999.index
java    14895 root  mem    REG              252,1 10485760 1325042 /tmp/kafka-logs/default_channel_kafka_zzh_demo-2/00000000000005892533.index
java    14895 root  mem    REG              252,1 10485760 1325041 /tmp/kafka-logs/default_channel_kafka_zzh_demo-1/00000000000005494790.index
....(省略若干)
java    14895 root   85u   REG              252,1        0 1311594 /tmp/kafka-logs/default_channel_kafka_zzh_demo-4/00000000000003626728.log
java    14895 root   87u   REG              252,1        0 1325038 /tmp/kafka-logs/default_channel_kafka_zzh_demo-3/00000000000003915669.log
java    14895 root   88u  IPv6           40855648      0t0     TCP zhuzhonghua2-fqawb:XmlIpcRegSvc->xx.xx.139.85:64708 (ESTABLISHED)
java    14895 root   89u   REG              252,1        0 1325037 /tmp/kafka-logs/default_channel_kafka_zzh_demo-2/00000000000005892533.log
java    14895 root   93u   REG              252,1        0 1325040 /tmp/kafka-logs/default_channel_kafka_zzh_demo-1/00000000000005494790.log
java    14895 root   94u   REG              252,1        0 1325043 /tmp/kafka-logs/default_channel_kafka_zzh_demo-0/00000000000003858999.log

多了“COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME”這一行。而文件描述符的個數為90:

[root@zhuzhonghua2-fqawb linuxC]# ls /proc/14895/fd |wc -l
90
[root@zhuzhonghua2-fqawb linuxC]# ls /proc/14895/fd
0  10  12  14  16  18  2   21  23  25  27  29  30  32  34  36  38  4   41  43  45  47  49  50  52  54  56  58  6   61  63  65  67  69  70  72  75  77  79  80  82  84  87  89  93
1  11  13  15  17  19  20  22  24  26  28  3   31  33  35  37  39  40  42  44  46  48  5   51  53  55  57  59  60  62  64  66  68  7   71  74  76  78  8   81  83  85  88  9   94

文件描述符(file descriptor)

對于linux而言,所有對設備和文件的操作都使用文件描述符來進行的。文件描述符是一個非負的整數,它是一個索引值,指向內核中每個進程打開文件的記錄表。當打開一個現存文件或創建一個新文件時,內核就向進程返回一個文件描述符;當需要讀寫文件時,也需要把文件描述符作為參數傳遞給相應的函數。 通常,一個進程啟動時,都會打開3個文件:標準輸入、標準輸出和標準出錯處理。這3個文件分別對應文件描述符為0、1和2(宏STD_FILENO、STDOUT_FILENO和STDERR_FILENO)。

每一個文件描述符會與一個打開文件相對應,同時,不同的文件描述符也會指向同一個文件。相同的文件可以被不同的進程打開也可以在同一個進程中被多次打開。系統為每一個進程維護了一個文件描述符表,該表的值都是從0開始的,所以在不同的進程中你會看到相同的文件描述符,這種情況下相同文件描述符有可能指向同一個文件,也有可能指向不同的文件。具體情況要具體分析,要理解具體其概況如何,需要查看由內核維護的3個數據結構。

  1. 進程級的文件描述符表

  2. 系統級的打開文件描述符表

  3. 文件系統的i-node表

由于進程級文件描述符表的存在,不同的進程中會出現相同的文件描述符,它們可能指向同一個文件,也可能指向不同的文件。兩個不同的文件描述符,若指向同一個打開文件句柄,將共享同一文件偏移量。因此,如果通過其中一個文件描述符來修改文件偏移量,那么從另一個文件描述符中也會觀察到變化,無論這兩個文件描述符是否屬于不同進程,還是同一個進程,情況都是如此。

文件句柄 vs 文件描述符

文件句柄也稱為文件指針(FILE *):C語言中使用文件指針做為I/O的句柄。文件指針指向進程用戶區中的一個被稱為FILE結構的數據結構。FILE結構包括一個緩沖區和一個文件描述符。而文件描述符是文件描述符表的一個索引,因此從某種意義上說文件指針就是句柄的句柄(在Windows系統上,文件描述符被稱作文件句柄)。

C語言中FILE結構體的定義:

/* Define outside of namespace so the C++ is happy.  */
struct _IO_FILE;

__BEGIN_NAMESPACE_STD
/* The opaque type of streams.  This is the definition used elsewhere.  */
typedef struct _IO_FILE FILE;
__END_NAMESPACE_STD
#if defined __USE_LARGEFILE64 || defined __USE_SVID || defined __USE_POSIX \
    || defined __USE_BSD || defined __USE_ISOC99 || defined __USE_XOPEN \
    || defined __USE_POSIX2
__USING_NAMESPACE_STD(FILE)
#endif

# define __FILE_defined 1
#endif /* FILE not defined.  */
#undef  __need_FILE


#if !defined ____FILE_defined && defined __need___FILE

/* The opaque type of streams.  This is the definition used elsewhere.  */
typedef struct _IO_FILE __FILE;
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;

signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

這個_IO_FILE結構體中的“int _fileno”就是fd,即文件描述符。這個可以通過程序驗證:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int main(){
        char buf[50] = {"file descriptor demo"};
        FILE *myfile;

        myfile = fopen("test","w+");
        if(!myfile){
                printf("error: openfile failed!\n");
        }
        printf("The openfile's descriptor is %d\n", myfile->_fileno);
        if(write(myfile->_fileno,buf,50)<0){
                perror("error: write file failed!\n");
                exit(1);
        }else{
                printf("writefile successed!\n");
        }

        exit(0);
}

編譯:g++ fileno.cpp -o fileno.out 執行+輸出:

[root@hidden linuxC]# ./fileno.out 
The openfile's descriptor is 3
writefile successed!

查看test文件:

[root@hidden linuxC]# cat test
file descriptor demo

免費學習視頻歡迎關注云圖智聯:http://e.yuntuzhilian.com/  



Java幫幫學習群生態

Java幫幫學習群生態

總有一款能幫到你

Java學習群

Java學習群

與大牛一起交流

大數據學習群

大數據學習群

在數據中成長

九點編程學習群

九點編程學習群

深夜九點學編程

python學習群

python學習群

人工智能,爬蟲

測試學習群

測試學習群

感受測試的魅力

Java幫幫生態承諾

Java幫幫生態承諾

一直堅守,不負重望

初心
勤儉
誠信
正義
分享
友鏈交換:加幫主QQ2524138991 留言即可 24小時內答復  
業司
教育資訊
會員登錄
獲取驗證碼
登錄
登錄
我的資料
留言
回到頂部