1. 從一個最常見的例子說起
在使用Linux的過程中, 我們平時經常看到下面這樣的用法:
echo log > /dev/null 2>&1
-
:表示將輸出結果重定向到哪里,例如:echo "123" > /home/123.txt
- /dev/null :表示空設備文件
所以 echo log > /dev/null 表示把日志輸出到空文件設備,也就是將打印信息丟棄掉,屏幕上什么也不顯示。 - 1 :表示stdout標准輸出
- 2 :表示stderr標准錯誤
- & :表示等同於的意思
所以 2>&1 表示2的輸出重定向等同於1,也就是標准錯誤輸出重定向到標准輸出。因為前面標准輸出已經重定向到了空設備文件,所以標准錯誤輸出也重定向到空設備文件。
這個用法平時很常見,重點是為什么這里是用 2 和 1 ,不是3456什么的呢?這要從 Linux 中的文件描述符說起。
2. Linux中的文件描述符(file descriptor)
我們知道在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。在操作這些所謂的文件的時候,我們每操作一次就找一次名字,這會耗費大量的時間和效率。所以Linux中規定每一個文件對應一個索引,這樣要操作文件的時候,我們直接找到索引就可以對其進行操作了。
文件描述符(file descriptor)就是內核為了高效管理這些已經被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用於指代被打開的文件,所有執行I/O操作的系統調用都通過文件描述符來實現。同時還規定系統剛剛啟動的時候,0是標准輸入,1是標准輸出,2是標准錯誤。這意味着如果此時去打開一個新的文件,它的文件描述符會是3,再打開一個文件文件描述符就是4......
Linux內核對所有打開的文件有一個文件描述符表格,里面存儲了每個文件描述符作為索引與一個打開文件相對應的關系,簡單理解就是下圖這樣一個數組,文件描述符(索引)就是文件描述符表這個數組的下標,數組的內容就是指向一個個打開的文件的指針。
上面只是簡單理解,實際上關於文件描述符,Linux內核維護了3個數據結構:
- 進程級的文件描述符表
- 系統級的打開文件描述符表
- 文件系統的i-node表
一個 Linux 進程啟動后,會在內核空間中創建一個 PCB(Process Control Block) 控制塊,PCB 內部有一個文件描述符表(File descriptor table),記錄着當前進程所有可用的文件描述符,也即當前進程所有打開的文件。進程級的描述符表的每一條記錄了單個進程所使用的文件描述符的相關信息,進程之間相互獨立,一個進程使用了文件描述符3,另一個進程也可以用3。除了進程級的文件描述符表,系統還需要維護另外兩張表:打開文件表、i-node 表。這兩張表存儲了每個打開文件的打開文件句柄(open file handle)。一個打開文件句柄存儲了與一個打開文件相關的全部信息。
系統級的打開文件描述符表:
- 當前文件偏移量(調用read()和write()時更新,或使用lseek()直接修改)
- 打開文件時的標識(open()的flags參數)
- 文件訪問模式(如調用open()時所設置的只讀模式、只寫模式或讀寫模式)
- 與信號驅動相關的設置
- 對該文件i-node對象的引用,即i-node 表指針
文件系統的i-node表:
-
文件類型(例如:常規文件、套接字或FIFO)和訪問權限
-
一個指針,指向該文件所持有的鎖列表
-
文件的各種屬性,包括文件大小以及與不同類型操作相關的時間戳
-
文件描述符、打開的文件句柄以及i-node之間的關系如下圖:
-
在進程 A 中,文件描述符 1 和 20 都指向了同一個打開文件表項,標號為 23(指向了打開文件表中下標為 23 的數組元素),這可能是通過調用 dup()、dup2()、fcntl() 或者對同一個文件多次調用了 open() 函數形成的。
-
進程 A 的文件描述符 2 和進程 B 的文件描述符 2 都指向了同一個文件,這可能是在調用 fork() 后出現的(即進程 A、B 是父子進程關系),或者是不同的進程獨自去調用 open() 函數打開了同一個文件,此時進程內部的描述符正好分配到與其他進程打開該文件的描述符一樣。
-
進程 A 的描述符 0 和進程 B 的描述符 3 分別指向不同的打開文件表項,但這些表項均指向 i-node 表的同一個條目(標號為 1976);換言之,它們指向了同一個文件。發生這種情況是因為每個進程各自對同一個文件發起了 open() 調用。同一個進程兩次打開同一個文件,也會發生類似情況。
這就說明:同一個進程的不同文件描述符可以指向同一個文件;不同進程可以擁有相同的文件描述符;不同進程的同一個文件描述符可以指向不同的文件(一般也是這樣,除了 0、1、2 這三個特殊的文件);不同進程的不同文件描述符也可以指向同一個文件。
3. Linux上打開文件舉例
比如在Linux上用 vim test.py 打開一個文件,保持打開狀態,再新打開一個新的shell,輸入命令pidof vim 獲取vim進程的pid號,然后 ll /proc/$pid/fd 查看vim 進程所使用的文件描述符列表。
/dev/pts是遠程登陸(telnet,ssh等)后創建的控制台設備文件所在的目錄。因為我是通過Xshell遠程登錄的,所以標准輸入0,標准輸出1,標准錯誤2的文件描述符都指向虛擬終端控制台 /dev/pts/6 。再看下面是新打開的 test.py 的文件描述符,竟然是4,說好的從3開始呢?
這個我也困擾了好久,查了各種資料,終於在一個大佬的幫助下在一個論壇找到原因,有時候中文查不到還是要試試英文搜索啊。因為vim這種編輯器的原理是先打開源文件並拷貝,然后關閉源文件再打開自己的副本,修改完文件保存的時候直接將副本重命名覆蓋源文件。所以打開源文件的時候用的文件描述符3,然后打開自己的副本是時候就該用文件描述符4了,然后關閉源文件,文件描述符3就被釋放了,我們查看的時候就只剩下了4,這里它指向的是vim創建的副本文件。這里只是說個大概意思,具體深究要去深入了解一下vim的實現原理——奧爾特星雲大使,下面是當時我看到的論壇上的資料截圖,鏈接在這:StackOverFlow。
4. C語言中文件描述符的使用
C語言中可以通過 open 函數返回一個文件的文件描述符,首先創建一個 test.py 文件用於打開,然后創建一個 test.c 文件,輸入下面代碼保存。 編譯后執行,發現新打開文件的文件描述符是3。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
int fd = open("test.py", O_RDONLY);
if (fd == -1) {
return -1;
}
printf("test.py fd = %d \n", fd); // test.py fd = 3
close(fd);
return 0;
}
5. Python中文件描述符的使用
Python中通過 sys 模塊封裝了標准輸入、標准輸出和錯誤輸出。通過我們平時常用的內建函數 open 可以獲取一個文件的文件描述符,首先創建一個 test.py 文件用於打開,然后創建一個 test2.py 文件,輸入下面代碼保存。 執行,發現新打開文件的文件描述符是3。
import sys
print('stdin fd = ', sys.stdin.fileno()) // stdin fd = 0
print('stdout fd = ', sys.stdout.fileno()) // stdout fd = 1
print('stderr fd = ', sys.stderr.fileno()) // stderr fd = 2
with open("test.py", "w") as f:
print('test.py fd = ', f.fileno()) // test.py fd = 3
6. Linux配置系統最大打開文件描述符個數
系統級限制
理論上系統內存有多少就可以打開多少的文件描述符,但是在實際中內核是會做相應的處理,一般最大打開文件數會是系統內存的10%(以KB來計算),稱之為系統級限制。這個數字可以通過 cat /proc/sys/fs/file-max 或者 sysctl -a | grep fs.file-max 命令查看。
更改系統級限制有臨時更改和永久更改兩種方式:
- 臨時更改:session斷開或者系統重啟后會恢復原來的設置值。使用命令 sysctl -w fs.file-max=xxxx,其中xxxx就是要設置的數字。
- 永久更改:vim編輯 /etc/sysctl.conf 文件,在后面添加 fs.file-max=xxxx,其中xxxx就是要設置的數字。保存退出后還要使用sysctl -p 命令使其生效。
用戶級限制
同時為了控制每個進程消耗的文件資源,內核也會對單個進程最大打開文件數做默認限制,即用戶級限制。32位系統默認值一般是1024,64位系統默認值一般是65535,可以使用 ulimit -n 命令查看。
更改用戶級限制也有臨時更改和永久更改兩種方式:
- 臨時更改:session斷開或者系統重啟后會恢復原來的設置值。使用命令 ulimit -SHn xxxx 命令來修改,其中xxxx就是要設置的數字。
- 永久更改:vim編輯 /etc/security/limits.conf 文件,修改其中的 hard nofile xxxx 和 soft nofile xxxx,其中xxxx就是要設置的數字。保存后退出。關於hard和soft的區別,參照下面參考鏈接中的第5個。
7. 參考鏈接
- 每天進步一點點——Linux中的文件描述符與打開文件之間的關系——cywosp
- Linux文件描述符到底是什么?——C語言中文網
- 句柄和文件描述符(FD)——陽光丶不銹
- 帶你破案:文件描述符到底是什么?——vran
- Linux配置調優:最大打開文件描述符個數——Idea Buffer
- 修改Linux系統下的最大文件描述符限制——BlueguyChui
————————————————
版權聲明:本文為CSDN博主「傑克小麻雀」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/yushuaigee/article/details/107883964