在昨天的內容中,我們了解了 network namespace 與 system call 以及 proc 檔案系統之間的關聯。為了更深入理解 proc 檔案系統,我們首先需要了解 Linux 檔案系統的基礎架構,也就是虛擬檔案系統 (Virtual File System, VFS)。
VFS 是 Linux Kernel 中的一層抽象,透過 system call 和 VFS 的抽象,Linux 隔離了應用程式與實際的檔案系統,使得應用程式能夠以通用的 system call 來操作、讀取和寫入不同底層實現的檔案系統。

在 VFS 系統中,有幾個重要的元素:inode、dentry 和 file。
首先,inode 代表的是一個檔案,並且提供了該檔案的 metadata。
// include/linux/fs.h
struct inode {
umode_t i_mode; // 存取權限
kuid_t i_uid; // 檔案擁有者
kgid_t i_gid; // 檔案擁有群
...
unsigned long i_ino; // inode id
...
loff_t i_size; // 檔案大小
struct timespec64 i_atime; // 存取時間
struct timespec64 i_mtime; // 修改時間
...
} __randomize_layout;
在 inode 結構中,包含了如存取權限 (i_mode)、檔案擁有者 (i_uid, i_gid)、檔案大小 (i_size)、存取時間 (i_atime)、修改時間 (i_mtime) 等資訊。另一個關鍵欄位是 inode number (i_ino),每個 inode 都有一個唯一的 inode number,在單個檔案系統中是唯一的。
接下來是 dentry,dentry 是 directory entry 的縮寫,代表的是一個目錄或檔案。
// include/linux/dcache.h
struct dentry {
struct dentry *d_parent; // 父目錄
struct qstr d_name; // 目錄或檔案名稱
struct inode *d_inode; // 指向目錄或檔案的 inode
...
struct list_head d_child; // 子目錄或檔案的清單
...
} __randomize_layout;
dentry 中,最重要的欄位之一是 d_name,它表示目錄或檔案的名稱。在 Linux 檔案系統中,所有的檔案和目錄是以樹狀結構展開的,所有檔案都從根目錄 / 開始。透過 d_parent 指標以及 d_child List,我們可以在整個檔案系統中移動。例如,當我們想存取 /var/log/dpkg.log 時,Kernel 會從根目錄的 dentry 開始,逐步經過 var 和 log,直到找到 dpkg.log 的 dentry。
值得注意的是,dentry 只代表路徑而不代表實際的檔案。因此,dentry 結構內的 d_inode 指標會指向實際代表檔案的 inode。另外不論是實際保存資料的檔案或著是子目錄,從系統的角度來看都是檔案,所以都會有inode。
這樣的結構也意味著,可能有多個 dentry 指向相同的 inode。這種情況通常發生在我們使用 ln 指令建立硬連結之後,兩個不同路徑在底層指向同一個檔案。由於檔案權限等屬性保存在 inode 中,修改其中一個檔案的權限會影響到所有指向同一 inode 的路徑。
// include/linux/fs.h
struct file {
...
struct path f_path; // path
struct inode *f_inode; // 指向 inode
...
};
// include/linux/path.h
struct path {
struct vfsmount *mnt;
struct dentry *dentry; // 指向 dentry
} __randomize_layout;
最後一個重要的資料結構是 file。儘管結構名稱為 file,實際上是 inode 代表檔案本身,而 file 代表的是某個 process 已經打開的檔案。當某個 process 透過 open 之類的 system call 開啟一個檔案時,系統會為該 process 建立一個 file 資料結構,並且該結構會保存在該 process 的 task_struct 中。
// include/linux/sched.h
struct task_struct {
...
/* Open file information: */
struct files_struct *files;
...
}
// include/linux/fdtable.h
struct files_struct {
...
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
}
在 Linux Kernel 中,一個 process 的資訊都會保存在 task_struct 資料結構中,其中 files_struct 用來維護 process 打開的檔案。files_struct 中的 fd_array 陣列用來保存所有打開的檔案。當我們透過 file descriptor 進行檔案操作時,系統會從 fd_array 中取得相應的 file 資料結構,然後對應操作該檔案。

因此,當我們使用 system call、檔案路徑或檔案描述子來操作檔案系統時,實際上是在操作 inode、dentry 和 file 這些資料結構。
今天我們介紹了 Linux VFS 中幾個重要的資料結構,包括 inode、dentry 和 file。這些結構是 Linux 檔案系統運作的抽象元件。明天,我們將進一步探討這些資料結構是如何在實際操作中被使用的。