|
文件句柄?文件描述符?傻傻分不清楚2
發表時間:2020-07-29 17:01 概述在實際工作中會經常遇到一些bug,有些就需要用到文件句柄,文件描述符等概念,比如報錯: too many open files, 如果你對相關知識一無所知,那么debug起來 筆者通過翻閱相關資料,并采用了一些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.conflimits.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 其中含義如下:
關于第三列的詳細解釋如下: #<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
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粗略的估算一下。
為什么有限制?為什么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. 最主要的是資源問題,為防止某一單一進程打開過多文件描述符而耗盡系統資源,對進程打開文件數做了限制。 lsoflsof(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個數據結構。
由于進程級文件描述符表的存在,不同的進程中會出現相同的文件描述符,它們可能指向同一個文件,也可能指向不同的文件。兩個不同的文件描述符,若指向同一個打開文件句柄,將共享同一文件偏移量。因此,如果通過其中一個文件描述符來修改文件偏移量,那么從另一個文件描述符中也會觀察到變化,無論這兩個文件描述符是否屬于不同進程,還是同一個進程,情況都是如此。 文件句柄 vs 文件描述符文件句柄也稱為文件指針(FILE *):C語言中使用文件指針 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/ |