2>&1到底是什麼意思?

語言: CN / TW / HK
java -jar snapshot.jar > snapshot.log 2>&1 &

寫Java的朋友一定對上面的命令很熟悉,相信大部分人都知道 > 表示的是重定向,那麼什麼是重定向? 2>&1 又是什麼意思?

要從根兒上説明這個問題,我們有必要好好理解一下「文件描述符」的概念。

1. 文件描述符

1.1. 什麼是文件描述符

文件描述符(File descripter)就是一個整數,這個整數唯一標識了操作系統中某個被打開的“文件”。

1.2. 文件與I/O

説到“文件”必然離不開I/O。

很多人搞不明白I/O到底應該怎麼理解,字面上就是輸入/輸出罷了,但是站在不同角度,其表示的含義也不一樣。

站在計算機的角度,I/O表示的是計算機與外界的交互,交互的對象是硬件設備,輸入輸出自然也指的是和硬件之間的輸入輸出。

站在程序的角度,I/O的含義更寬泛,操作系統與所有能被當作文件的對象之間的交互就是I/O。

Linux的哲學思想是「一切皆文件」,文件(file,例如foo.txt)、管道(pipe)、網絡(socket),甚至打印機、顯示器、磁盤以及命令行(terminal)都算是文件。

文件描述符標識的就是這些文件。

文件描述符這個術語通常出現在Unix或類Unix系統中,比如Linux、MacOS以及BSD等。

在Windows系統中,他有另外一個響噹噹(或者臭名昭著)的名字—— 句柄 (File handle)。

2. 文件描述符原理

2.1. 進程私有文件描述符表

每個進程可以打開多個文件,所以每個進程都會有一個私有的文件描述符表(file descriptors table)。

注:下文稱 file descriptors table 中的每一個條目為 file descriptor ,稱 file descriptor 中的整數為 fd

需要注意的是,每個進程的fd 012 已經被佔用(下文會有解釋),之後分配的每個進程的 fd3 開始。

進程級的描述符表的每一個條目記錄了當前進程所有打開的文件的文件描述符,進程之間相互獨立,例如一個進程使用了文件描述符 99 ,另一個進程也可以用 99

fd 只是個數字而已,操作系統肯定需要根據這個數字來找到實際對應的文件。換句話説, file descripter 肯定指向了某個表示真實文件的數據結構,或者能夠再次根據這個數據結構來找到真實文件。

這個數據結構就是「全局文件表」。

2.2. 全局文件表

全局文件表(global file table),顧名思義,就是所有進程共享的一個數據結構。

當用户進程向內核發起一個針對文件的 system call (比如 open() )時,內核將

  1. 允許進程訪問;
  2. 向全局文件表(global file table)中插入一個條目,並向進程返回一個指向該條目的一個 file descriptor
  3. 進程將 file descriptor 插入到 file descriptors table ,並返回其在 file descriptors table 中的下標,也就是 fd

其實,根據global file table並不能直接找到對應文件進行操作,還需要根據其中的指針找到inode table的數據結構,進而再找到最終文件。但是這個技術細節對我們認識文件描述符沒有什麼作用,於是按下不表了。

2.3. 為什麼需要文件描述符

進程進行系統調用的時候,內核為什麼不直接返回指向文件的指針呢?反而多此一舉加了個 fd 來引用文件。

原因是為了防止用户空間的程序隨意讀寫操作系統內核的文件對象。

如果內核直接返回內核中文件對象的地址給進程,進程便可以繞過內核,肆意對該文件進行操作,這樣一來用户空間和內核空間的劃分便如同虛設。

有了文件描述符之後,由於global file table處於內核空間中,用户即使擁有 fd ,也無法得到實際文件對象的地址,除非把 fd 作為系統調用的參數來使用,如此一來,控制權又回到了內核手中,也便達到了權限控制的目的。

3. 標準輸入/標準輸出/標準錯誤

前面説到,進程的文件描述符表的前3項已經被默認使用了。

這些名詞怎麼理解?

我們在Java中使用 new Scanner(System.in) 接收從鍵盤的輸入,使用 System.out.println() 向顯示器寫數據,對應C語言分別是 scanf()printf()

需要明確的是, 函數並非直接使用鍵盤和顯示器,而是使用了標準輸入和標準輸出

説得再通俗一點就是,進程生來就有一個耳朵和兩張嘴,耳朵用來接受 標準輸入 裏的數據,一個嘴往 標準輸出 裏邊“説話”,另一張嘴往 標準錯誤 裏邊“吐槽”。

函數並不知道數據從哪裏來,也不關心數據要到哪裏去,它們只需要從 標準輸入 讀數據,向 標準輸出標準錯誤 中寫數據就行了。

這就是抽象啊,朋友們!

默認情況下,操作系統會把所有鍵盤輸入都發送到 標準輸入 ,會把從 標準輸出標準錯誤 中讀到數據發送到顯示器。

為了方便表示,下文不再將全局文件表畫出,而是用一張表來簡化表示文件描述符和數據流之間的對應關係。

接下來我們就可以解釋文章開頭提的問題了。

4. 輸入輸出重定向

標準輸入/輸出/錯誤 在描述表的位置雖然是固定的,但他們指向的數據流是可以改變的。

java -jar snapshot.jar > snapshot.log 2>&1 &

這條指令的意思就是將 snapshot.jar 程序用 > 運算符重定向標準輸出,由原本的指向顯示器改為 snapshot.log 文件。

2> 是用來重定向標準錯誤,因為 標準錯誤 在描述符表中的 fd 就是 2 ,同樣,其實重定向標準輸出也可以表示為 1> ,不過一般簡寫為 >

標準錯誤標準輸出 可以重定向到同一個地方,比如指令中的 &1 表示的就是 標準輸出2>&1 的含義就是重定向標準錯誤到 標準輸出 表示的數據流中。

完!