Linux的一切皆檔案
Linux 中的各種事物比如像文件、目錄(Mac OS X 和 Windows 系統下稱之為資料夾)、鍵盤、監視器、硬碟、可移動媒體裝置、印表機、調變解調器、虛擬終端,還有程式間通訊(IPC)和網路通訊等輸入/輸出資源都是定義在檔案系統空間下的位元組流。
一切都可看作是檔案,其最顯著的好處是對於上面所列出的輸入/輸出資源,只需要相同的一套 Linux 工具、實用程式和 API。你可以使用同一套api(read, write)和工具(cat , 重定向, 管道)來處理unix中大多數的資源.
設計一個系統的終極目標往往就是要找到原子操作,一旦鎖定了原子操作,設計工作就會變得簡單而有序。“檔案”作為一個抽象概念,其原子操作非常簡單,只有讀和寫,這無疑是一個非常好的模型。通過這個模型,API的設計可以化繁為簡,使用者可以使用通用的方式去訪問任何資源,自有相應的中介軟體做好對底層的適配。
現代作業系統為解決資訊能獨立於程式之外被長期儲存引入了檔案,檔案作為程式建立資訊的邏輯單元可被多個程式併發使用。在 UNIX 系統中,作業系統為磁碟上的文字與影象、滑鼠與鍵盤等輸入裝置及網路互動等 I/O 操作設計了一組通用 API,使他們被處理時均可統一使用位元組流方式。換言之,UNIX 系統中除程式之外的一切皆是檔案,而 Linux 保持了這一特性。為了便於檔案的管理,Linux 還引入了目錄(有時亦被稱為資料夾)這一概念。目錄使檔案可被分類管理,且目錄的引入使 Linux 的檔案系統形成一個層級結構的目錄樹
在Linux系統中,一切都是檔案,理解檔案系統,對於學習Linux來說,是一個非常有必要的前提
Linux上的檔案系統一般來說就是EXT2或EXT3,但這篇文章並不準備一上來就直接講它們,而希望結合Linux作業系統並從檔案系統建立的基礎——硬碟開始,一步步認識Linux的檔案系統。
1. 機械硬碟的物理儲存機制
- 現代計算機大部分檔案儲存功能都是由機械硬碟這種裝置提供的。(現在的SSD和快閃記憶體從概念和邏輯上都部分繼承自機械硬碟,所以使用機械硬碟來進行理解也是沒有問題的)
- 機械硬碟能實現資訊儲存的功能基於:磁性儲存介質能夠被磁化,且磁化後會長久保留被磁化的狀態,這種被磁化狀態能夠被讀取出來,同時這種磁化狀態還能夠不斷被修改,磁化正好有兩個方向,所以可以表示0和1。
於是硬碟就是把這種磁性儲存介質做成一個個碟片,每一個碟片上都分佈著數量巨大的磁性儲存單位,使用磁性讀寫頭對碟片進行寫入和讀取(從原理上類似黑膠唱片的播放)。- 一個硬碟中的磁性儲存單位數以億計(1T硬碟就有約80億個),所以需要一套規則來規劃資訊如何存取(比如一本儲存資訊的書我們還會分為頁,每一頁從上到下從左到右讀取,同時還有章節目錄)
於是就有了這些物理、邏輯概念:
- 一個硬碟有多張碟片疊成,不同碟片有編號
- 每張碟片上的儲存顆粒成環形一圈圈地排布,每一圈稱為磁軌,有編號
- 每條磁軌上都有一圈儲存顆粒,每512*8(512位元組,0.5KB)個儲存顆粒作為一個扇區,扇區是硬碟上儲存的最小物理單位
- N個扇區可以組成簇,N取決於不同的檔案系統或是檔案系統的配置,簇是此檔案系統中的最小儲存單位
- 所有盤面上的同一磁軌構成一個圓柱,稱為柱面,柱面是系統分割槽的最小單位
磁頭讀寫檔案的時候,首先是分割槽讀寫的,由inode編號(區內唯一的編號後面介紹)找到對應的磁軌和扇區,然後一個柱面一個柱面地進行讀寫。機械硬碟的讀寫控制系統是一個令人歎為觀止的精密工程(一個盤面上有幾億個儲存單位,每個磁軌寬度不到幾十奈米,磁碟每分鐘上萬轉),同時關於讀寫的邏輯也是有諸多細節(比如扇區的編號並不是連續的),非常有意思,可以自行搜尋文章擴充閱讀。
有了硬碟並不意味著LInux可以立刻把它用來儲存,還需要組合進Linux的檔案體系才能被Linux使用。
2.Linux檔案體系
Linux以檔案的形式對計算機中的資料和硬體資源進行管理,也就是徹底的一切皆檔案,反映在Linux的檔案型別上就是:普通檔案、目錄檔案(也就是資料夾)、裝置檔案、連結檔案、管道檔案、套接字檔案(資料通訊的介面)等等。而這些種類繁多的檔案被Linux使用目錄樹進行管理, 所謂的目錄樹就是以根目錄(/)為主,向下呈現分支狀的一種檔案結構。不同於純粹的ext2之類的檔案系統,我把它稱為檔案體系,一切皆檔案和檔案目錄樹的資源管理方式一起構成了Linux的檔案體系,讓Linux作業系統可以方便使用系統資源。
所以檔案系統比檔案體系涵蓋的內容少很多,Linux檔案體系主要在於把作業系統相關的東西用檔案這個載體實現:檔案系統掛載在作業系統上,作業系統整個系統又放在檔案系統裡。但本文中檔案體系的相關內容不是很多,大部分地方都可以用檔案系統代替檔案體系。
1. Linux中的檔案型別:
1.1. 普通檔案(-)
從Linux的角度來說,類似mp4、pdf、html這樣應用層面上的檔案型別都屬於普通檔案
Linux使用者可以根據訪問許可權對普通檔案進行檢視、更改和刪除
1.2. 目錄檔案(d,directory file)
目錄檔案對於用慣Windows的使用者來說不太容易理解,目錄也是檔案的一種
目錄檔案包含了各自目錄下的檔名和指向這些檔案的指標,開啟目錄事實上就是開啟目錄檔案,只要有訪問許可權,你就可以隨意訪問這些目錄下的檔案(普通檔案的執行許可權就是目錄檔案的訪問許可權),但是隻有核心的程式能夠修改它們
雖然不能修改,但是我們能夠通過vim去檢視目錄檔案的內容
1.3. 符號連結(l,symbolic link)
這種型別的檔案類似Windows中的快捷方式,是指向另一個檔案的間接指標,也就是我們常說的軟連結
1.4. 塊裝置檔案(b,block)和字元裝置檔案(c,char)
這些檔案一般隱藏在/dev目錄下,在進行裝置讀取和外設互動時會被使用到
比如磁碟光碟機就是塊裝置檔案,串列埠裝置則屬於字元裝置檔案
系統中的所有裝置要麼是塊裝置檔案,要麼是字元裝置檔案,無一例外
1.5. FIFO(p,pipe)
管道檔案主要用於程式間通訊。比如使用mkfifo命令可以建立一個FIFO檔案,啟用一個程式A從FIFO檔案裡讀資料,啟動程式B往FIFO裡寫資料,先進先出,隨寫隨讀。
1.6. 套接字(s,socket)
用於程式間的網路通訊,也可以用於本機之間的非網路通訊
這些檔案一般隱藏在/var/run目錄下,證明著相關程式的存在
Linux 的檔案是沒有所謂的副檔名的,一個 Linux檔案能不能被執行與它是否可執行的屬性有關,只要你的許可權中有 x ,比如[ -rwx-r-xr-x ] 就代表這個檔案可以被執行,與檔名沒有關係。跟在 Windows下能被執行的副檔名通常是 .com .exe .bat 等不同。
不過,可以被執行跟可以執行成功不一樣。比如在 root 主目彔下的 install.log 是一個文字檔案,修改許可權成為 -rwxrwxrwx 後這個檔案能夠真的執行成功嗎? 當然不行,因為它的內容根本就沒有可以執行的資料。所以說,這個 x 代表這個檔案具有可執行的能力, 但是能不能執行成功,當然就得要看該檔案的內容了。
雖然如此,不過我們仍然希望能從副檔名來了解該檔案是什麼東西,所以一般我們還是會以適當的副檔名來表示該檔案是什麼種類的。
所以Linux 系統上的檔名真的只是讓你瞭解該檔案可能的用途而已, 真正的執行與否仍然需要許可權的規範才行。比如常見的/bin/ls 這個顯示檔案屬性的指令要是許可權被修改為無法執行,那麼ls 就變成不能執行了。這種問題最常發生在檔案傳送的過程中。例如你在網路上下載一個可執行檔案,但是偏偏在你的 Linux 系統中就是無法執行,那就可能是檔案的屬性被改變了。而且從網路上傳送到你 的 Linux 系統中,檔案的屬性許可權確實是會被改變的
2. Linux目錄樹
對Linux系統和使用者來說,所有可操作的計算機資源都存在於目錄樹這個邏輯結構中,對計算機資源的訪問都可以認為是目錄樹的訪問。就硬碟來說,所有對硬碟的訪問都變成了對目錄樹中某個節點也就是資料夾的訪問,訪問時不需要知道它是硬碟還是硬碟中的資料夾。
目錄樹的邏輯結構也非常簡單,就是從根目錄(/)開始,不斷向下展開各級子目錄。
3.硬碟分割槽
硬碟分割槽是硬碟結合到檔案體系的第一步,本質是「硬碟」這個物理概念轉換成「區」這個邏輯概念,為下一步格式化做準備。
所以分本身並不是必須的,你完全可以把一整塊硬碟作為一個區。但從資料的安全性以及系統效能角度來看,分割槽還是有很多用處的,所以一般都會對硬碟進行分割槽。
講分割槽就不得不先提每塊硬碟上最重要的第一扇區,這個扇區中有硬碟主開機記錄(Master boot record, MBR) 及分割槽表(partition table), 其中 MBR 佔有 446 bytes,而分割槽表佔有 64 bytes。硬碟主開機記錄放有最基本的引導載入程式,是系統開機啟動的關鍵環節,在附錄中有更詳細的說明。而分割槽表則跟分割槽有關,它記錄了硬碟分割槽的相關資訊,但因分割槽表僅有 64bytes , 所以最多隻能記彔四塊分割槽(分割槽本身其實就是對分割槽表進行設定)。
只能分四個區實在太少了,於是就有了擴充套件分割槽的概念,既然第一個扇區所在的分割槽表只能記錄四條資料, 那我可否利用額外的扇區來記錄更多的分割槽資訊。
把普通可以訪問的分割槽稱為主分割槽,擴充套件分割槽不同於主分割槽,它本身並沒有內容,它是為進一步邏輯分割槽提供空間的。在某塊分割槽指定為擴充套件分割槽後,就可以對這塊擴充套件分割槽進一步分成多個邏輯分割槽。作業系統規定:
- 四塊分割槽每塊都可以是主分割槽或擴充套件分割槽
- 擴充套件分割槽最多隻能有一個(也沒必要有多個)
- 擴充套件分割槽可以進一步分割為多個邏輯分割槽
- 擴充套件分割槽只是邏輯概念,本身不能被訪問,也就是不能被格式化後作為資料訪問的分割槽,能夠作為資料訪問的分割槽只有主分割槽和邏輯分割槽
- 邏輯分割槽的數量依作業系統而不同,在 Linux 系統中,IDE 硬碟最多有 59 個邏輯分割槽(5 號到 63 號), SATA 硬碟則有 11 個邏輯分割槽(5 號到 15 號)
一般給硬碟進行分割槽時,一個主分割槽一個擴充套件分割槽,然後把擴充套件分割槽劃分為N個邏輯分割槽是最好的
是否可以不要主分割槽呢?不知道,但好像不用管,你建立分割槽的時候會自動給你配置型別
特殊的,你最好單獨分一個swap區(記憶體置換空間),它獨為一類,功能是:當有資料被存放在實體記憶體裡面,但是這些資料又不是常被 CPU 所取用時,那麼這些不常被使用的程式將會被丟到硬碟的 swap 置換空間當中, 而將速度較快的實體記憶體空間釋放出來給真正需要的程式使用
4.格式化
我們知道Linux作業系統支援很多不同的檔案系統,比如ext2、ext3、XFS、FAT等等,而Linux把對不同檔案系統的訪問交給了VFS(虛擬檔案系統),VFS能訪問和管理各種不同的檔案系統。所以有了區之後就需要把它格式化成具體的檔案系統以便VFS訪問。
標準的Linux檔案系統Ext2是使用「基於inode的檔案系統」
我們知道一般作業系統的檔案資料除了檔案實際內容外, 還帶有很多屬性,例如 Linux 作業系統的檔案許可權(rwx)與檔案屬性(擁有者、群組、 時間引數等),檔案系統通常會將屬性和實際內容這兩部分資料分別存放在不同的區塊
在基於inode的檔案系統中,許可權與屬性放置到 inode 中,實際資料放到 data block 區塊中,而且inode和data block都有編號
Ext2 檔案系統在此基礎上
檔案系統最前面有一個啟動扇區(boot sector)
這個啟動扇區可以安裝開機管理程式, 這個設計讓我們能將不同的引導裝載程式安裝到個別的檔案系統前端,而不用覆蓋整個硬碟唯一的MBR, 也就是這樣才能實現多重引導的功能
把每個區進一步分為多個塊組 (block group),每個塊組有獨立的inode/block體系
如果檔案系統高達數百 GB 時,把所有的 inode 和block 通通放在一起會因為 inode 和 block的數量太龐大,不容易管理
這其實很好理解,因為分割槽是使用者的分割槽,實際計算機管理時還有個最適合的大小,於是計算機會進一步的在分割槽中分塊
(但這樣豈不是可能出現大檔案放不了的問題?有什麼機制善後嗎?)
每個塊組實際還會分為分為6個部分,除了inode table 和 data block外還有4個附屬模組,起到優化和完善系統效能的作用
所以整個分割槽大概會這樣劃分:
1. inode table
主要記錄檔案的屬性以及該檔案實際資料是放置在哪些block中,它記錄的資訊至少有這些:
大小、真正內容的block號碼(一個或多個)
訪問模式(read/write/excute)
擁有者與群組(owner/group)
各種時間:建立或狀態改變的時間、最近一次的讀取時間、最近修改的時間
沒有檔名!檔名在目錄的block中!
一個檔案佔用一個 inode,每個inode有編號
Linux 系統存在 inode 號被用完但磁碟空間還有剩餘的情況
注意,這裡的檔案不單單是普通檔案,目錄檔案也就是資料夾其實也是一個檔案,還有其他的也是
inode 的數量與大小在格式化時就已經固定了,每個inode 大小均固定為128 bytes (新的ext4 與xfs 可設定到256 bytes)
檔案系統能夠建立的檔案數量與inode 的數量有關,存在空間還夠但inode不夠的情況
系統讀取檔案時需要先找到inode,並分析inode 所記錄的許可權與使用者是否符合,若符合才能夠開始實際讀取 block 的內容
inode 要記錄的資料非常多,但偏偏又只有128bytes , 而inode 記錄一個block 號碼要花掉4byte ,假設我一個檔案有400MB 且每個block 為4K 時, 那麼至少也要十萬條block 號碼的記錄!inode 哪有這麼多空間來儲存?為此我們的系統很聰明的將inode 記錄block 號碼的區域定義為12個直接,一個間接, 一個雙間接與一個三間接記錄區(詳細見附錄)
2. data block
放置檔案內容資料的地方
在格式化時block的大小就固定了,且每個block都有編號,以方便inode的記錄
原則上,block 的大小與數量在格式化完就不能夠再改變了(除非重新格式化)
在Ext2檔案系統中所支援的block大小有1K, 2K及4K三種,由於block大小的區別,會導致該檔案系統能夠支援的最大磁碟容量與最大單一檔案容量各不相同:
Block 大小 1KB 2KB 4KB
最大單一檔案限制 16GB 256GB 2TB
最大檔案系統總容量 2TB 8TB 16TB
每個block 內最多隻能夠放置一個檔案的資料,但一個檔案可以放在多個block中(大的話)
若檔案小於block ,則該block 的剩餘容量就不能夠再被使用了(磁碟空間會浪費)
所以如果你的檔案都非常小,但是你的block 在格式化時卻選用最大的4K 時,可能會產生容量的浪費
既然大的block 可能會產生較嚴重的磁碟容量浪費,那麼我們是否就將block 大小定為1K ?這也不妥,因為如果block 較小的話,那麼大型檔案將會佔用數量更多的block ,而inode 也要記錄更多的block 號碼,此時將可能導致檔案系統不良的讀寫效能
事實上現在的磁碟容量都太大了,所以一般都會選擇4K 的block 大小
3. superblock
記錄整個檔案系統相關資訊的地方,一般大小為1024bytes,記錄的資訊主要有:
block 與inode 的總量
未使用與已使用的inode / block 數量
一個valid bit 數值,若此檔案系統已被掛載,則valid bit 為0 ,若未被掛載,則valid bit 為1
block 與inode 的大小 (block 為1, 2, 4K,inode 為128bytes 或256bytes);
其他各種檔案系統相關資訊:filesystem 的掛載時間、最近一次寫入資料的時間、最近一次檢驗磁碟(fsck) 的時間
Superblock是非常重要的, 沒有Superblock ,就沒有這個檔案系統了,因此如果superblock死掉了,你的檔案系統可能就需要花費很多時間去挽救
每個塊都可能含有superblock,但是我們也說一個檔案系統應該僅有一個superblock 而已,那是怎麼回事?事實上除了第一個塊內會含有superblock 之外,後續的塊不一定含有superblock,而若含有superblock則該superblock主要是做為第一個塊內superblock的備份,這樣可以進行superblock的救援
4. Filesystem Description
檔案系統描述
這個區段可以描述每個block group的開始與結束的block號碼,以及說明每個區段(superblock, bitmap, inodemap, data block)分別介於哪一個block號碼之間
5. block bitmap
塊對照表
如果你想要新增檔案時要使用哪個block 來記錄呢?當然是選擇「空的block」來記錄。那你怎麼知道哪個block 是空的?這就得要通過block bitmap了,它會記錄哪些block是空的,因此我們的系統就能夠很快速的找到可使用的空間來記錄
同樣在你刪除某些檔案時,那些檔案原本佔用的block號碼就得要釋放出來, 此時在block bitmap 中對應該block號碼的標誌位就得要修改成為「未使用中」
6. inode bitmap
與block bitmap 是類似的功能,只是block bitmap 記錄的是使用與未使用的block 號碼, 至於inode bitmap 則是記錄使用與未使用的inode 號碼
5.掛載
在一個區被格式化為一個檔案系統之後,它就可以被Linux作業系統使用了,只是這個時候Linux作業系統還找不到它,所以我們還需要把這個檔案系統「註冊」進Linux作業系統的檔案體系裡,這個操作就叫「掛載」 (mount)。
掛載是利用一個目錄當成進入點(類似選一個現成的目錄作為代理),將檔案系統放置在該目錄下,也就是說,進入該目錄就可以讀取該檔案系統的內容,類似整個檔案系統只是目錄樹的一個資料夾(目錄)。
這個進入點的目錄我們稱為「掛載點」。
由於整個 Linux 系統最重要的是根目錄,因此根目錄一定需要掛載到某個分割槽。 而其他的目錄則可依使用者自己的需求來給予掛載到不同的分去。
到這裡Linux的檔案體系的構建過程其實已經大體講完了,總結一下就是:硬碟經過分割槽和格式化,每個區都成為了一個檔案系統,掛載這個檔案系統後就可以讓Linux作業系統通過VFS訪問硬碟時跟訪問一個普通資料夾一樣。這裡通過一個在目錄樹中讀取檔案的實際例子來細講一下目錄檔案和普通檔案。
6.目錄樹的讀取過程
首先我們要知道
- 每個檔案(不管是一般檔案還是目錄檔案)都會佔用一個inode
- 依據檔案內容的大小來分配一個或多個block給該檔案使用
- 建立一個檔案後,檔案完整資訊分佈在3處地方,生成2個新檔案:
3.1 檔名記錄在該檔案所在目錄的目錄檔案的block中,沒有新檔案生成
3.2 檔案屬性、許可權資訊、記錄具體內容的block編號記錄在inode中,inode是新生成檔案
3.3 檔案具體記憶體記錄在block中,block是新生成檔案 - 因為檔名的記錄是在目錄的block當中,「新增/刪除/更名檔名」與目錄的w許可權有關
所以在Linux/Unix中,檔名稱只是檔案的一個屬性,叫別名也好,叫綽號也罷,僅為了方便使用者記憶和使用,但系統內部並不需要用檔名來定為檔案位置,這樣處理最直觀的好處就是,你可以對正在使用的檔案改名,換目錄,甚至放到廢紙簍,都不會影響當前檔案的使用,這在Windows裡是無法想象的。比如你開啟個Word檔案,然後對其進行重新命名操作,Windows會告訴你門兒都沒有,關閉檔案先!但在Mac裡就毫無壓力,因為Mac的作業系統同樣採用了inode的設計。
建立檔案過程
當在ext2下建立一個一般檔案時, ext2 會分配一個inode 與相對於該檔案大小的block 數量給該檔案
- 例如:假設我的一個block 為4 Kbytes ,而我要建立一個100 KBytes 的檔案,那麼linux 將分配一個inode 與25 個block 來儲存該檔案
- 但同時請注意,由於inode 僅有12 個直接指向,因此還要多一個block 來作為區塊號碼的記錄
建立目錄過程
當在ext2檔案系統建立一個目錄時(就是新建了一個目錄檔案),檔案系統會分配一個inode與至少一塊block給該目錄
- inode記錄該目錄的相關許可權與屬性,並記錄分配到的那塊block號碼
- 而block則是記錄在這個目錄下的檔名與該檔案對應的inode號
- block中還會自動生成兩條記錄,一條是.資料夾記錄,inode指向自身,另一條是..資料夾記錄,inode指向父資料夾
從目錄樹中讀取某個檔案過程
- 因為檔名是記錄在目錄的block當中,因此當我們要讀取某個檔案時,就一定會經過目錄的inode與block ,然後才能夠找到那個待讀取檔案的inode號碼,最終才會讀到正確的檔案的block內的資料。
- 由於目錄樹是由根目錄開始,因此作業系統先通過掛載資訊找到掛載點的inode號,由此得到根目錄的inode內容,並依據該inode讀取根目錄的block資訊,再一層一層的往下讀到正確的檔案。
舉例來說,如果我想要讀取/etc/passwd 這個檔案時,系統是如何讀取的呢?
先看一下這個檔案以及有關路徑資料夾的資訊:
1$ ll -di / /etc /etc/passwd
2 128 dr-xr-x r-x . 17 root root 4096 May 4 17:56 /
333595521 drwxr-x r-x . 131 root root 8192 Jun 17 00:20 /etc
436628004 -rw-r-- r-- . 1 root root 2092 Jun 17 00:20 /etc/passwd
複製程式碼
於是該檔案的讀取流程為:
- /的inode:
通過掛載點的資訊找到inode號碼為128的根目錄inode,且inode規定的許可權讓我們可以讀取該block的內容(有r與x) - /的block:
經過上個步驟取得block的號碼,並找到該內容有etc/目錄的inode號碼(33595521) - etc/的inode:
讀取33595521號inode得知具有r與x的許可權,因此可以讀取etc/的block內容 - etc/的block:
經過上個步驟取得block號碼,並找到該內容有passwd檔案的inode號碼(36628004) - passwd的inode:
讀取36628004號inode得知具有r的許可權,因此可以讀取passwd的block內容 - passwd的block:
最後將該block內容的資料讀出來
偶得一日閒,查閱Linux檔案系統相關文章,偶然發現該文章,通讀全文後茅塞頓開,感嘆原文作者Linux之透徹,佩服至極,因此摘錄到本人公眾號內,稍加修整,以便廣大Linux愛好者閱讀。
覺得本文對你有幫助?請分享給更多人
關注「程式設計無界」,提升裝逼技能