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

2022-09-25 12:01:06
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項已經被預設使用了。

  • 0:標準輸入(stdin)
  • 1:標準輸出(stdout)
  • 2:標準錯誤(stderr)

這些名詞怎麼理解?

我們在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的含義就是重定向標準錯誤到標準輸出表示的資料流中。


完!