# FHS A simple description of the **UNIX** system, also applicable to **Linux**, is this: "On a UNIX system, everything is a file; if something is not a file, it is a process." == A **directory** is just a file containing names of other files. Programs, services, texts, images, and so forth, are **all files**. 為了以有序的方式管理所有這些檔案,人們喜歡將它們視為硬碟上有序的樹狀結構,例如 **MS-DOS(Disk Operation System)** --- ## Sort of files / File type - Directories: 包含其他檔案的檔案 As previously mentioned - Special files: 有關 I/O 的特殊檔案,大多數 special files 位於 `/dev` ,待會會說 `/dev` - Links: 一種讓檔案或目錄在系統檔案樹的其他地方可見的系統,簡單來說就是捷徑 - (Domain) sockets: 一種特殊的檔案類型,類似於 TCP/IP sockets,提供由檔案系統去訪問受到控制的 process 間的網絡 - Named pipes: 大致上像 sockets,但是是一種 process 之間的溝通方式,而不使用 sockets --- ## Partition 分割 源於 Linux 還沒有日誌檔案系統的時代,當時一次電力故障會對 Linux 帶來災難,因此出現分區的技術。分區的使用是出於安全性和穩定性的考量,讓系統某一部分的漏洞不會讓整台電腦都處於危險之中。 舉一個簡單的例子:使用者建立了一個 script、一個程式或是一個網頁,開始填滿磁碟。 - 如果磁碟只有一個大分區,當磁碟滿了,整個系統將停止運作。 - 如果使用者將資料存儲在單獨的分區上,那麼只有該(資料)分區會受到影響,而系統分區和其他的資料分區將能繼續運作。 --- ## Partition layout and types - data partition: 正常的 Linux 系統資料,包括`/` - swap partition: 從硬碟借點空間當作電腦的 RAM Kernel 在許多發行版中會有自己的分區,因為它是系統中最重要的檔案,所以你會發現你還有一個 `/boot` 分區,儲存你的 Kernel 和相關資料檔案。 其餘的硬碟通常被劃分為 data partition,儘管非系統重要的資料可能都會位於同一個分區上,但當資料需要被分開存放在不同的分區時,通常會遵循一個固定的模式: - a partition for user programs (/usr) - a partition containing the users' personal data (/home) - a partition to store temporary data like print- and mail-queues (/var) - a partition for third party and extra software (/opt) 當然這只是冰山一角 ## More file system layout 實際上 Linux 檔案系統通常長這樣 ![](https://tldp.org/LDP/intro-linux/html/images/FS-layout.png) > https://www.pathname.com/fhs/ | Directory | 內容 | --- | --- | | /bin | 存放基本的二進制可執行檔案 | /boot | 放置開機會使用到的檔案,包括 Linux Kernel 檔案以及開機選單與開機所需設定檔。 | /dev | 放特殊檔案或裝置檔案(鍵盤、滑鼠)的位置 | /etc | 系統主要的設定檔,例如網路設定、啟動 script、服務設定 | /home | 存放所有使用者的個人檔案和設定檔。 | /lib | 函式庫、開發軟體的子程式集合 | /lost+found | 當檔案系統發生錯誤時, 將一些遺失的片段放置到這個目錄下。 | /misc | 雜項, 各種各樣的(miscellaneous的縮寫) | /mnt | 外部檔案系統的標準掛載點,例如 CD-ROM 或數位相機 | /net | 整個遠端檔案系統的標準掛載點 | /opt | 第三方軟體放置的目錄、怕某個檔案汙染系統的話,可以把雜七雜八的東西放在這裡 | /proc | 存放系統Kernel、process、周邊裝置的狀態及網路狀態等 | /root | 系統管理員 (root) 的家目錄。注意 `/` 和 `/root` 之間的區別,前者是根目錄,後者是系統管理員 (root) 的家目錄 | /sbin | 供系統和系統管理員使用的程式。 | /tmp | 系統使用的臨時空間,重啟時會清除,因此不要用來儲存任何東東! | /usr | 「非系統基本必須的」東西,與使用者相關的 process、library、文檔等 | /var | 主要針對常態性變動的檔案,例如 Printer、快取 (cache)、紀錄檔 (log file) 以及某些軟體運作所產生的檔案,或在燒錄之前保留CD的映像 來看一下圖中有子目錄的目錄們 - /etc - /default - 某些命令的預設選項,例如 useradd - /sysconfig - 包含系統設定檔案的目錄:滑鼠、鍵盤、桌面、系統時鐘、電源管理 blablabla... - /X11 - 在 Linux 上頭的圖形介面! - /home - 就不同使用者 - /lib - /media - 可攜式儲存設備的掛載點 - /mnt - /cdrom - 掛載光碟機(CD/DVD-ROM) - /floppy - 掛載磁片(Floppy Disk) - 隨著技術發展,光碟機和磁片已經很少使用,並且 Linux 現在大多會使用 /media 來自動掛載可攜式儲存設備 - /usr - /X11R6 - X11R6 是 X Window System Release 6(1994 年發布) - 現代 Linux 系統多已淘汰,改用 /usr/lib/X11 或 /etc/X11 - /bin - 一般使用者執行的可執行檔 - /games - 早期 Linux 內建或安裝的遊戲會存放在這裡,例如 bsd-games(包含 tetris, snake, fortune) - /include - 存放 C/C++ 的標頭檔(header files),如 stdio.h, stdlib.h。 - /lib - 函式庫,給應用程式和開發工具使用 - /local - 使用者自行安裝的軟體 - /man - 早期存放 man 命令的手冊頁,但現在改用 /usr/share/man - /sbin - 只供 root 或管理員使用的執行檔,例如 ifconfig, iptables, fdisk - /share - 架構無關的共享資料(文檔、字型等) - /doc - 文件說明與授權資訊 - /man - 手冊頁 - /fonts - 字型檔 - /locale - 本地化(i18n, l10n) - /src - 原始碼,主要存放 Linux Kernel 與驅動程式原始碼 - /var - /gdm - GNOME Display Manager 相關數據(登入記錄、錯誤日誌) - 現代系統可能已改用 /run/gdm 或 /var/lib/gdm - /lib - 系統與應用程式的狀態數據,不會因重開機而清除,如套件管理、資料庫 - /lock - 鎖定文件,避免多重存取 - 現代 Linux 多改用 /run/lock - /run - 系統與服務的執行時(runtime)數據,如 PID、socket - 現代 Linux(systemd)已改為 /run - /log - 系統與應用程式的日誌檔 - /spool - 佇列數據,等待處理的檔案(排隊機制,類似 queue) - /tmp - **持久性暫存目錄**,存放可跨重開機的暫存檔案 如何找出一個目錄位於哪個分區? 使用 `df` 命令,用點 `.` 可以顯示當前目錄所屬的分區,並告知該分區上使用的空間量: ``` sandra:/lib> df -h . Filesystem Size Used Avail Use% Mounted on /dev/hda7 980M 163M 767M 18% / ``` <iframe src="https://i.sstatic.net/BlpRb.png" width="700px" height="500px" frameborder="0" scrolling="no"> </iframe> ![](https://i.sstatic.net/BlpRb.png) --- ## The file system in reality 對於大多數使用者,我們可以通過想像將所有檔案串在一起,形成整個系統的樹狀結構的概念,以樹狀結構去理解檔案系統挺輕鬆的,但電腦肯定對樹或樹狀結構一無所知 新的作業系統在規劃檔案系統時,一般檔案會有屬性(如權限、時間、身份資料紀錄等)以及實際資料的紀錄,同時整個檔案系統會紀錄全部的資訊,因此通常檔案系統會有如下幾個部份: - superblock:記錄此 filesystem 的整體資訊,包括 inode/block 的總量、使用量、剩餘量, 以及檔案系統的格式與相關資訊等; - inode:記錄檔案的屬性,一個檔案佔用一個 inode,同時記錄此檔案的資料所在的 block 號碼; - block:實際記錄檔案的內容,若檔案太大時,會佔用多個 block 。 --- ### Superblock (超級區塊) superblock 為整個檔案系統的總結資訊處,要讀取檔案系統一定要從 superblock 讀起。superblock 主要紀錄資料為: - block 與 inode 的總量; - 未使用與已使用的 inode / block 數量; - block 與 inode 的大小 (block 為 1, 2, 4K,inode 為 128bytes, 256bytes 或 512bytes); - filesystem 的掛載時間、最近一次寫入資料的時間、最近一次檢驗磁碟 (fsck) 的時間等檔案系統的相關資訊; - 一個 valid bit 數值,若此檔案系統已被掛載,則 valid bit 為 0 ,若未被掛載,則 valid bit 為 1 --- ### inode table (inode 表格) 在檔案系統中,檔案有一個 ==inode==,每一個 inode 都有號碼,而 inode 的內容在記錄檔案的屬性以及該檔案實際資料是放置在哪幾號 block 內。 inode 記錄的檔案資料至少有底下這些: - 該檔案的存取模式(read/write/excute); - 該檔案的擁有者與群組(owner/group); - 該檔案的容量; - 該檔案建立或狀態改變的時間(ctime); - 最近一次的讀取時間(atime); - 最近修改的時間(mtime); - 定義檔案特性的旗標(flag),如 SetUID...; - 該檔案真正內容的指向 (pointer); 每個 inode 描述硬碟上的一個資料結構,儲存檔案的屬性,包括檔案資料在硬碟上的實際位置。 當硬碟被初始化並準備接受資料讀寫時,通常會為每個分區建立固定數量的 inodes。 這個數量將會是該分區上可以同時存在的所有類型(包括目錄、特殊檔案、連結等)檔案的最大數量。我們通常計算 1 個 inode 大小是 2~8 KB。 --- ### data block (資料區塊) 檔案實際的資料存放在 data block 上面,每個 block 也都會有號碼,提供給檔案來儲存實際資料,也讓 inode 可以紀錄資料放在哪個 block 號碼內。 - 原則上,block 的大小與數量在格式化完就不能夠再改變了(除非重新格式化); - 每個 block 內最多只能夠放置一個檔案的資料; - 承上,如果檔案大於 block 的大小,則一個檔案會佔用多個 block 數量; - 承上,若檔案小於 block ,則該 block 的剩餘容量就不能夠再被使用了(磁碟空間會浪費)。 --- ## 檔案讀寫的操作程序 一般來說,檔案系統內的一個檔案被讀取時,流程是這樣的: 1. 讀到檔案的 inode 號碼 2. 由 inode 內的權限設定判定使用者能否存取此檔案 3. 若能讀取則開始讀取 inode 內所紀錄的資料放置於哪些 block 號碼內 4. 讀出 block 號碼內的資料,組合起來成為一個檔案的實際內容。 --- 至於新建檔案的流程則是這樣的: 1. 有寫入檔案的需求時,先到 metadata 區找到沒有使用中的 inode 號碼 2. 到該 inode 號碼內,將所需要的權限與屬性相關資料寫入,然後在 metadata 區規範該 inode 為使用中,且更新 superblock 資訊 3. 到 metadata 區找到沒有使用中的 block 號碼,將所需要的實際資料寫入 block 當中,若資料量太大,則繼續到 metadata 當中找到更多的未使用中的 block 號碼,持續寫入,直到寫完資料為止。 4. 同步更新 inode 的紀錄與 superblock 的內容。 --- 至於刪除檔案的流程則是這樣的: 1. 將該檔案的 inode 號碼與找到所屬相關的 block 號碼內容抹除 2. 將 metadata 區域的相對應的 inode 與 block 號碼規範為未使用。 3. 同步更新 superblock 資料。 --- ## DEMO ### inode 在路徑解析中扮演的角色 - 若是相對路徑(如 `./file.txt`),Linux 會基於當前目錄的 inode 來解析該路徑。 - 若是絕對路徑(如 `/home/user/file.txt`),Linux 會從根目錄 `/` 開始查找。 例如 `/home/user/file.txt` 這個絕對路徑: - 先找到根目錄 `/` 的 inode。 - 在 `/` 內查找 `home`,找到 `home` 目錄的 inode。 - 在 `home` 內查找 `user`,找到 `user` 目錄的 inode。 - 在 `user` 內查找 `file.txt`,取得其 inode 並存取內容。 ```cpp= #include <iostream> #include <sys/stat.h> #include <cstring> #include <vector> #include <sstream> void printInodes(const std::string &path) { std::vector<std::string> parts; std::stringstream ss(path); std::string segment; // 將路徑拆成一部分一部分 while (std::getline(ss, segment, '/')) { if (!segment.empty()) { parts.push_back(segment); } } std::string currentPath = "/"; struct stat fileStat; std::cout << "Path Segments and Inodes:\n"; // 從根目錄 `/` 開始,一層一層查找 for (const auto &part : parts) { currentPath += part; // 拼接路徑 if (stat(currentPath.c_str(), &fileStat) == 0) { std::cout << currentPath << " -> inode: " << fileStat.st_ino << std::endl; } else { std::cerr << "Error accessing: " << currentPath << std::endl; break; } currentPath += "/"; // 加上斜線,準備下一層 } } int main(int argc, char *argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <file_path>\n"; return 1; } printInodes(argv[1]); return 0; } ``` ```bash= g++ -o inode_finder inode_finder.cpp ./inode_finder /home/user/file.txt ``` 程式運作方式 1. 輸入一個檔案的完整路徑(例如 `/home/user/file.txt`)。 2. 拆解成 `/`, `/home`, `/home/user`, `/home/user/file.txt`,並逐層查詢 inode。 3. 使用 stat() 取得每個路徑的 inode,並輸出到終端機。 Example: ```bash= Path Segments and Inodes: / -> inode: 2 /home -> inode: 12345 /home/user -> inode: 67890 /home/user/file.txt -> inode: 13579 ``` 這表示: `/` 的 inode 是 2(Linux 中根目錄通常是 2)。 `/home` 的 inode 是 12345。 `/home/user` 的 inode 是 67890。 `/home/user/file.txt` 的 inode 是 13579。 理解系統如何找到檔案,如果想驗證 inode 是否正確指向檔案,可以試著對比 `ls -i` 輸出