JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

檔案,到底是什麼?

你存過成千上萬個檔案,但「一個檔案」究竟是什麼?這篇導覽會剝掉圖示與名稱,找出底下那個樸素的真相:一個檔案,不過是作業系統替你保管的一段有名字、持久存在的位元組——外加作業系統為它保管的一點點帳目。我們會從頭把檔案這個抽象蓋起來,再預告本階接下來要探索的目錄、連結與權限。

你最熟悉、卻從未定義過的東西

你爬完了儲存那一階,學到了底下那個令人不太舒服的真相:磁碟是一長排編了號的區塊,每個區塊都是轉動的金屬或快閃晶片上、那殘酷地慢、好幾千位元組大的一塊。這很誠實,但沒人想住在那裡。沒有人是靠要求「邏輯區塊第 42,891 號」來打開文件的。我們說的是「打開我的作文」,然後它就成了。這兩個世界之間的落差——底下是赤裸裸的編號區塊,上面是一個你能命名的東西——正是檔案這個抽象要彌合的落差,而這整個一階,講的就是作業系統如何蓋起、又如何守護那座橋。

所以讓我們別管那個圖示,問一個直白的問題:檔案,到底是什麼?最樸素而誠實的答案是:一個檔案,是一段儲存在持久裝置上、有名字、彼此相關的位元組集合。把其他一切都剝掉,核心就是這個:它有個名字,好讓人找得到;它是位元組,所以裝得下任何東西;它是持久的,所以能在斷電後存活——不像一個執行中程式所住的、易失的 RAM。作業系統是在給一個承諾:今天用一個名字把位元組交給我,明天,重開機之後,同一個名字會把同樣的位元組還給你。

內容不是檔案的全部:屬性

如果一個檔案只有它的位元組,你就永遠沒辦法依日期排序、在檔案瀏覽器裡看到大小,或者被告知「權限不足」。這一切都來自每個檔案的第二個、獨立的部分:它的屬性(attributes)——也就是作業系統替這個檔案保管、與內容分開存放的帳目。把它想成一本圖書館的書:位元組是印在書頁上的故事,而屬性是那張目錄卡——書名、所在書架、誰可以借、上次被借走是什麼時候。卡片不是故事,但少了它,整座圖書館就沒法用了。

典型的屬性包括名稱、以位元組計的大小、各種時間戳記(建立、最後修改、最後存取)、擁有者與群組,以及決定誰可以讀或寫的保護位元。還有一個屬性值得單獨點名:檔案的類型(type)。作業系統怎麼知道你的檔案是一張圖片、而不是一個程式?常見有兩招。在 Windows 上,類型是從副檔名猜的——也就是點之後的那一段,像 .jpg 或 .exe——而那只是個任何人都能改的命名慣例。Unix 風格的系統則常常改去偷看開頭那幾個位元組:許多檔案格式都以一個固定的簽章開頭,叫魔術數字(magic number,例如一張 PNG 圖片,永遠以同樣那一小撮位元組開頭),這比靠改名要難偽造得多。

檔案也是個動詞:操作

檔案不只是系統儲存的一個名詞;它也是你能對它做的那一小組事情。這些檔案操作刻意地少,正因為「笨位元組流」這個觀點讓同樣一小撮操作能套用在所有東西上。最根本的六個是:建立(create,讓一個新的空檔案存在)、開啟(open,告訴作業系統你即將要用這個檔案,好讓它把帳目準備好)、讀取(read,把位元組從檔案複製到你的記憶體裡)、寫入(write,把位元組從你的記憶體複製到檔案裡)、刪除(delete,把它移除),以及關閉(close,告訴作業系統你用完了)。你對一個檔案做的幾乎每一件事,都是這些的某種組合。

其中兩個值得多留意,因為新手常跳過它們。open 不是個形式——它正是作業系統把昂貴的準備工作「一次」做完的地方,好讓之後的讀寫變便宜,而它正是下一篇導覽的主題。而 close 比它看起來更要緊:作業系統可能把你寫出的位元組先擱在一塊快速的記憶體緩衝區裡(你在儲存那一階以「緩衝快取」的形式認識過這個想法),稍後才把它們推到慢速的磁碟上,所以關閉——或明確地清出(flush)——正是你確保自己的位元組真的落地的一部分。忘了關閉,是漏掉資源的經典方式,偶爾還會弄丟仍擱在緩衝區裡的資料。

你怎麼指向位元組:把柄、位置、存取

當你打開一個檔案,作業系統不會每次你要讀的時候都再把那長長的路徑名遞給你——那會很浪費,因為它每一次操作都得在磁碟上重新找一遍這個檔案。它改成讓 open 回傳一個小小的整數,叫檔案描述符(file descriptor):一張編了號的票,在這次工作階段接下來的時間裡,代表那個已開啟的檔案。從此你就說 read(fd, buf, n)——「用 fd 這張票,把 n 個位元組複製到緩衝區 buf 裡」——而核心用這張票去查它早就準備好的一切。下一篇導覽會把這張票背後的那張掀開來看;現在,只要先握住這幅畫面:一個便宜的號碼,代替了一次昂貴的查找。

在那張票背後,作業系統還追蹤著第二樣東西:一個檔案位置(file position),常被叫做游標或偏移量(offset)。一個檔案是一串位元組,而位置不過就是「下一次讀或寫會發生在那串位元組的哪裡」。每讀 n 個位元組,位置就往前推 n,所以再讀一次自然會從你上次停下的地方接著讀——就像書籤在一本書裡往前移。這也是為什麼兩個打開同一個檔案的程式,各自會拿到自己私有的位置:位元組是共用的,但每一次開啟都有自己的書籤。從頭到尾走過一個檔案、讓位置一路往前行進,叫做循序存取(sequential access)。

循序不是唯一的方式。靠一個 seek(尋找)操作,你可以把位置直接跳到任何一個位元組偏移量——比方說,直接跳到第 5,000 個位元組——然後讀;這叫直接(隨機)存取,正是它讓一個資料庫能一躍跳到第 3,000 筆紀錄,而不必爬過前面 2,999 筆。一個程式用哪種存取方法,是個有真實代價的真實選擇:在上一階那顆轉動式磁碟上,跳來跳去意味著一次又一次地付出尋軌與旋轉,這也是為什麼循序讀取飛快、隨機讀取慢如爬行。檔案這個抽象讓你兩種都能做;底下的物理,決定它要花多少代價。

從一個檔案到很多檔案:目錄與名字

一個檔案很簡單。一萬個檔案就是混亂——除非你有辦法把它們整理好、並能靠名字找到它們。這正是目錄(資料夾)的工作:它骨子裡,不過是一個特殊的檔案,內容是一張把人類看得懂的名字、對映到裡頭各個檔案的清單。關鍵是,目錄項目裝的是名字,但檔案真正的屬性、以及它位元組的所在位置,通常活在別處,存在一筆每檔一份的紀錄裡。在 Unix 風格的系統上,那筆紀錄就是赫赫有名的 inode(索引節點)——每個檔案都有一個 inode,保管它的大小、擁有者、權限、時間戳記,以及它資料的區塊位址——而目錄只是把一個名字跟一個 inode 號碼配成一對。名字與檔案是兩樣分開的東西,由目錄牽在一起;這一個想法,正是「硬連結」的種子,本階第四篇導覽會把它長成某種威力強大的東西。

目錄本身又可以裝目錄,而這種層層相套,給出了你在任何檔案瀏覽器裡都見過的樹狀目錄:頂端一個根,往下分枝成資料夾裡的資料夾。要在這棵樹裡指名一個檔案,你會給一個路徑名(path name)——從某個起點往下、一路走到那個檔案的目錄名軌跡,像 /home/ana/essays/draft.txt。絕對路徑從那唯一的根出發,在任何地方都毫不含糊;相對路徑從你的目前工作目錄(你此刻「身在其中」的資料夾)出發,對於附近的檔案會比較短。本階第三篇導覽會走遍整棵樹——包括怎麼讓一個檔案掛在兩條路徑底下共用,以及「掛載」如何把第二顆磁碟接進同一棵樹裡,彷彿它一直都在那兒。

  DIRECTORY (a list of name -> inode#)        INODE #1733        DATA BLOCKS
  +----------------+---------+                +-------------+    +----------+
  | name           | inode # |                | size, owner |--->| the      |
  +----------------+---------+                | perms, time |    | actual   |
  | draft.txt      |  1733   | -------------> | block ptrs  |    | bytes    |
  | notes.md       |  1810   |                +-------------+    +----------+
  | photos/  (dir) |  1902   |
  +----------------+---------+

  the NAME lives in the directory; the FILE (attributes + bytes) lives in the inode
目錄只是把名字對映到 inode 號碼。inode 保管屬性,並指向資料區塊。把名字與檔案分開,正是後來能讓兩個名字指向同一個 inode 的關鍵。

這是誰的檔案?初探保護

在任何一台由好幾個使用者——或者只是好幾個程式——共用的機器上,一個檔案需要回答「誰被允許動它、又能怎麼動?」這就是保護(protection),而那個經典、巧妙又精簡的方案,就活在一個檔案的屬性裡,化身為九個權限位元。作業系統把世界分成三群人——檔案的擁有者(owner)、它的群組(group),以及其他所有人(其他,other)——而對每一群,它存三個位元:你可不可以(read)、(write)、或執行(execute,把它當程式跑)。寫出來,這九個位元長得像 rwxr-xr-x,等同於數字 chmod 755:擁有者可讀、可寫、可執行;群組與其他人可讀、可執行,但不能寫。

九個位元簡單得令人讚嘆,但對它的極限要誠實:三群人,太粗了。如果你想讓「剛好只有 Ana 和 Bao」能寫、其他人都不行,而他們又不在同一個群組裡呢?這些位元表達不出來。比較豐富的答案是一張存取控制清單(access-control list,ACL),它替檔案附上一份完整的、逐使用者(與逐群組)的允許清單——更有彈性,但儲存與推理起來都更沉重。這兩種方案、以及它們背後更深的想法,正是第五篇導覽的主題。現在,只要留意:內容、屬性與保護,是一個檔案的三個各自分明的層,而你已經把三個都見過了。