[譯] 在 UNIX 中,一切皆檔案

Wy_發表於2018-08-04

為了有計劃的發展架構設計、介面、文化和開發路線,UNIX 系統明確了一系列統一的概念和創想。這幾點裡面最重要的一點莫過於一句咒語:「一切皆檔案」,被廣泛認為是 UNIX 的定義之一。

最主要的設計原則是提供一個訪問大範圍輸入/輸出資源(包括檔案、資料夾、硬碟、CD-ROM、調變解調器、鍵盤、印表機、顯示器、終端機甚至跨程式和網路通訊)的統一的範例。竅門是提供一個所有這些資源的抽象物件,UNIX 之父把這個物件叫做「檔案」。因為每個「檔案」都由同一個 API[1]暴露,所以你可以用同一套命令來讀寫/操作磁碟、鍵盤、檔案或網路裝置。

這個基本概念有兩種含義:

  • 在 UNIX 中,一切都是位元組流
  • 在 UNIX 中,檔案系統被用作通用的名稱空間

在 UNIX 中,一切都是位元組流

在 UNIX 中,檔案是由什麼組成的呢?檔案其實和一系列可讀寫的位元組沒什麼區別。如果你有一個檔案的索引(我們稱之為「檔案描述符[2]」),那麼 UNIX 的 I/O 通道就已經準備好了,他們有著同樣的一套操作和 API —— 無論裝置的型別如何、底層硬體是什麼。

縱觀歷史,UNIX 是第一個把 I/O 抽象成一個統一的概念和一系列原語的系統。那時,大部分作業系統為每一種或一類裝置提供不同的 API。一些早期的微型計算機作業系統甚至需要你使用多個命令去拷貝檔案 —— 因為每個命令對應指定的軟盤大小!

對於大多數程式設計師和使用者來說,UNIX 向他們暴露了:

  • 硬碟中的檔案

  • 資料夾

  • 連結

  • 大容量儲存裝置(例如:硬碟、CD-ROM、磁帶、USB 裝置)

  • 跨程式通訊(例如:管線、共享記憶體、UNIX 套接字)

  • 網路通訊

  • 可互動終端

  • 幾乎其他所有裝置(例如:印表機、顯示卡)

對於位元組流這種形式你可以:

  • read(讀)
  • write(寫)
  • lseek(指標移動)
  • close(關閉)

統一的 API 特性對於 UNIX 程式來說是基礎也是非常有效的:在 UNIX 中你可以很很輕鬆地編寫一個處理檔案的程式,因為不需要關心檔案是儲存在本地磁碟中、儲存在遠端網路驅動器上、在網際網路中傳播、通過使用者互動輸入,還是通過其他程式在記憶體中生成。這顯著降低了程式的複雜性、減緩了開發者的學習曲線。並且,UNIX 架構的這個基礎特性也讓程式組合到一起非常簡單(你只需要傳輸兩個特殊的檔案:標準輸入和標準輸出)。

最後請注意,當所有的檔案提供一致的 API 時,一些特殊型別的裝置可能會不支援某些操作。舉個很明顯的例子,你不可以在滑鼠裝置上使用 lseek 命令,或在 CD-ROM 裝置上使用 write 命令(假設你的 CD 是隻讀的)。

檔案系統有通用名稱空間

在 UNIX 裡,檔案不僅僅是有一致 API 的位元組流,而且可以被統一的方式索引:檔案系統有著通用名稱空間。

全域性名稱空間和掛載機制

UNIX 的檔案系統路徑為標籤資源提供了一致的全域性方案,從而可以忽略他們的實體地址。舉幾個例子,你可以使用 /usr/local 命令訪問一個本地資料夾、/home/joe/memo.pdf 命令訪問一個檔案、/mnt/cdrom 命令訪問 CD-ROM、/usr 命令訪問網路驅動器上的一個資料夾、/dev/sda1 命令訪問硬碟分割槽、/tmp/mysql.sock 命令訪問 UNIX 域名套接字、/dev/tty0 命令訪問終端,甚至使用 /dev/mouse 命令來訪問滑鼠。這些通用名稱空間通常看起來像一個檔案層級或資料夾,其實就像前面舉的例子,這些只是一個方便的抽象概念,一個檔案路徑可以引用一切東西:一個檔案系統、一個裝置、一個網路共享或通道。

名稱空間是分層的,所有的資源都可以從根資料夾(/)訪問到。你可以使用同樣的名稱空間來訪問多個檔案系統:你只是在名稱空間的指定的位置(比如 /backups)「連線」了一個裝置或檔案系統(比如外接硬碟)。用 UNIX 術語來說,這個操作叫做 掛載mounting)一個檔案系統,你連線檔案系統的名稱空間位置叫做 掛載點mount point)。你可以通過給掛載的檔案系統中的所有資源新增以掛載點命名的字首,就像訪問通用名稱空間的一部分一樣,來訪問它的所有資源(比如 /backups/myproject-Oct07.zip 這個檔案)。

當不同的資源會被明顯覆蓋的情況下,我剛剛描述的掛載機制在建立一個統一的、明確的名稱空間時就至關重要。對比一下這種名稱空間和微軟作業系統中的檔案系統名稱空間 —— MS-DOS 和 Windows 把裝置視為檔案但是 不會把檔案系統放在通用名稱空間中,它的名稱空間是分割槽的並且每個物理儲存地址被視為獨特的實體[3]C:\ 是第一個硬碟,E:\ 是 CD-ROM 裝置等等。

偽檔案系統

早期,UNIX 因為提供全域性 API 以及將裝置掛載到統一的檔案系統名稱空間的特性,大幅提升了輸入/輸出資源的整合度。這個方法是如此成功,以至於從那時開始有一種將更多資源和系統服務暴露為檔案系統全域性名稱空間的趨勢。Plan 9 是這種做法的先驅,而現在所有新的 UNIX 系統都這麼做了。

這種方法導致產生了許多 偽檔案系統,這些系統看起來和一般的檔案系統一樣,但是可以存取沒有直接關聯傳統檔案系統的資源。比如你可以使用偽檔案系統來查詢控制程式、存取核心內部或建立 TCP 連線。這些偽檔案系統具有檔案系統語義,可以展示分層資訊,併為大部分物件提供了統一存取的方式。偽檔案系統有時也被稱為虛擬檔案系統,特點是沒有物理裝置也沒有備份儲存器,只依靠記憶體來工作。

偽檔案系統的例子:

  • procfs (/proc):proc 檔案系統包含一個特殊檔案層,這個檔案層可以用來查詢或控制執行中的程式,或通過標準檔案入口(大部分基於文字)一窺核心內部檔案。
  • devfs (/dev or /devices):devfs 將所有系統中的裝置以動態檔案系統名稱空間呈現。devfs 也可以通過核心裝置驅動直接管理這些名稱空間和介面,以此來提供智慧的裝置管理 —— 包括裝置入口註冊/反註冊。
  • tmpfs (/tmp):臨時檔案系統的內容會在重啟時消失,tmpfs 是為速度和效率而設計的,具有動態檔案系統大小、用以空間清理的顯式回退等特性。
  • portalfs (/p):通過 BSD 門戶檔案系統,你可以將一個伺服器程式連線到檔案系統通用名稱空間上。這樣可以提供明確的通過檔案系統對網路服務的存取過程。比如一個 App 可以通過開啟一個合規的檔案 /p/tcp/ph7spot.com/smtp 來和 ph7spot.com 上的 SMTP 伺服器進行互動。門戶檔案系統很神奇,因為它在檔案系統中可以提供套接字語義,還可以被 UNIX 系統工具傳輸和使用(比如:cat, grep, awk 等等)—— 甚至可以通過 shell 來使用!
  • ctfs (/system/contract):協定檔案系統作為一個以檔案為基礎的介面的 Solaris 協定子系統。Solaris 協定為各種各樣的事件和失敗情況定義了一個程式或程式組的表現形式 —— 比如,程式停止時重啟。 Solaris 協定為諸如群集故障轉移軟體,批處理排隊系統和網格計算引擎等環境中的軟體管理和監視提供了非常高階的功能。

能夠通過檔案系統語義進行管理的系統資源究竟涉及多麼大的範圍,上面的例子可以讓你對有一個清楚的認識了。

結論

在現代的 UNIX 作業系統中,所有裝置和大部分程式間通訊在檔案系統層級都以檔案或偽檔案的形式檢視和管理。「一切皆檔案」的 UNIX 基礎願景和設計原則,是 UNIX 成功和長久的關鍵因素。它提供了一個有力、簡單的抽象,使得系統、工具和社群可以在其之上建立。更重要的是它用一種專有的方式來解決問題,那就是為連結工具和應用提供了強有力的整合和基礎組合機制。

儘管「一切皆檔案」這個比喻很成功,但是一些人或多或少懷疑它的普遍性。當每個檔案都被視為位元組流時,產生的一個後果就是後設資料缺少標準支援:為了合適地處理一個檔案,每個應用必須想辦法計算檔案型別、架構和語義。並且,為了儲存後設資料,每個處理資料流的工具必須保持後設資料不變(比如照片中的 XMP 資訊)。因此,儘管 UNIX 檔案的一大堆位元組的形態對於連結文字介面的程式極度高效,同時也嚴重限制了多媒體和二進位制應用的組合。

儘管它有它的限制,但很多人也承認這個比喻的影響力,和它在作業系統一體化上的效果。自從 UNIX 第一次釋出以來,研究者們持續推進這一中心思想。比如 Plan 9 作業系統倡導一個將系統資源完全整合的方法:Plan 9 願景的基礎就是這樣的目標 —— 不僅僅裝置和通道,而是將 所有系統介面通過檔案系統代表。比如 Plan 9 的設計人員注意到在 UNIX 中,網路裝置不能 完全地被視為合格的檔案:它們通過套接字存取,而套接字有特有的開啟語義並且屬於一個不同的名稱空間(因特網套接字的主機和埠)。Plan 9 實現並且證明了,你可以在一個全域性名稱空間裡成功的統一所有本地和遠端裝置。這個想法最終以 portalfs 的形式在 UNIX 中實現。

其他來源於 Plan 9 的創新的概念也是基於「UNIX 中,一切皆檔案」原則建立的。比如 Plan 9 在統一名稱空間設計之上提供了另一個抽象層:檔案系統名稱空間可以被每個使用者、每個程式自定義,甚至動態調整[4]。最後,Plan 9 證明了「UNIX 中,一切皆檔案」這個比喻,可以被在更大的層面上實現。事實上,這個基礎概念在現代 UNIX 作業系統中正被繼續發揚光大[5]

參考文獻

  • 一本了不起的書 —— 《The Art of UNIX Programming》,Eric S. Raymond 著。
  • 《The Elements of Operating-System Style》和《Problems in the Design of UNIX》兩章對本文有很大幫助。
  • 《10 Things I Hate About (U)NIX》,David Chisnall 著。
  • 「Linux 情報專案」中的掛載定義。
  • Wikipedia 上的 《UNIX File Types》。
  • 《Understanding UNIX Concepts》,USAIL (UNIX System Administration Independent Learning) 著。
  • 《檔案系統層級標準》。
  • 《proc 檔案系統》,Redhat 出品。
  • 《BSD 系統下的模組化使用者模式檔案系統》。
  • 《Self-Healing in Modern Operating Systems》,Michael W 著。幫你更深入地理解 Solaris 協議子系統。

  1. 想知道更多關於 UNIX 作業系統的背景故事,請閱讀維基百科入口(譯者注:需科學上網)。
  2. 檔案描述符只是存取一個檔案的抽象鍵,檔案描述符通常是整型值並且關聯到一個開啟的檔案。
  3. 瞭解詳情請檢視維基百科(譯者注:原文沒有地址,自行搜尋吧)
  4. 這個概念最終以 unionfs 的形式在 UNIX 中實現
  5. 需要這個領域當前活動的一些例子,請檢視 unionfs、portalfs 和 objfs 獲取例項。

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章