昨天,我們介紹了 Linux VFS 中幾個重要的資料結構,包括 inode
、dentry
和 file
。不過,這些資料結構僅是保存在 Kernel 記憶體中,並不是實際存在於硬碟上的檔案。當我們透過 open
或 read
等 system call 建立或讀取檔案內容時,實際上需要有檔案系統驅動去執行對硬碟的讀寫。而且, dentry
並不會一開始就存在於 Kernel 中,而是在需要的時候,讀取硬碟來得知資料夾內的實際檔案和子目錄,然後建立對應的 dentry
結構,而不是一口氣把整個硬碟的目錄載入,不然記憶體跟載入時間的成本會過高。今天,我們將探討這些過程是如何實現的。
回顧我們之前提到的 network namespace
功能擴充方式,Linux 網路子系統定義了 pernet_operations
介面,讓 Kernel module 實作,當 namespace 新增或刪除時,系統會執行 Kernel module 所定義的操作函數。在 VFS 系統中,Linux 採用了類似的方式。
昨天,我們介紹了 Linux VFS
中幾個重要的資料結構,包括 inode
、dentry
和 file
。但是這些都只是保存在Kernel 中的資料,並不是實際存在硬碟上的檔案。當我們透過open/read system call去建立、讀取檔案內容的時候,總要有人實際去執行這些操作。 dentry也不可能是一開始就存在在Kernel中,總是要有人去讀取硬碟,得知該資料夾內實際上有那些檔案或子目錄,然後建立對應的dentry結構。今天就要來講這些是怎麼實現的。
在 VFS 中,對每個元件都定義了對應的操作介面,如 inode_operations
、file_operations
、dentry_operations
等。
// include/linux/fs.h
struct file {
...
const struct file_operations *f_op;
...
}
struct inode {
...
const struct inode_operations *i_op;
...
}
// include/linux/dcache.h
struct dentry {
...
const struct dentry_operations *d_op;
...
}
不同於 pernet_operations
的多重註冊方式,VFS 中,每個資料結構實例只會有一組對應的操作介面。
以file_operations為例,他定義了許多對file的操作函數。
// include/linux/fs.h
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
unsigned int flags);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
...
} __randomize_layout;
這個結構中定義了許多熟悉的操作函數名稱,如 read
、write
、open
和 mmap
。當 User Space 的程式呼叫這些 system call 來操作檔案時,內核會執行對應的 file_operations
。這些函數的實作則由具體的檔案系統驅動(例如 ext4、proc 等)來提供。不同的檔案系統驅動不一定會實作所有的操作函數,如果沒有定義,可能會使用系統內建的函數,或著表示不支援該操作。
今天還要介紹另一個重要的 VFS 資料結構:super_block
。在硬碟中,最小的儲存單位是 block,而硬碟的第一個 block 通常儲存硬碟的一些 metadata,例如 inode 和 block 的總數及分配狀況等。這個 block 被稱之為 super block。
在 VFS 中,super block對應到的資料結構就是 struct super_block
,因此可以說,每個 super_block
結構代表一個檔案系統。
// include/linux/fs.h
struct super_block {
...
unsigned long s_blocksize;
loff_t s_maxbytes; /* Max file size */
struct file_system_type *s_type;
const struct super_operations *s_op;
}
這裡需要特別提到兩個欄位。首先是 *s_op
。跟其他結構一樣,super_block提供了super_operations
介面:
// /include/linux/fs.h
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*free_inode)(struct inode *);
...
int (*sync_fs)(struct super_block *sb, int wait);
...
int (*show_devname)(struct seq_file *, struct dentry *);
...
}
當然 super_operations
就提供了一些針對整個檔案系統或著說硬碟的操作,當然就包含了 inode 的分配跟刪除。
另外一個欄位是 s_type
,這個欄位指向 file_system_type
結構。
file_system_type
對應檔案系統的類型,如 ext4 或 proc。當檔案系統驅動載入時,就會向Linux kernel註冊一個 file_system_type
結構。
// /include/linux/fs.h
struct file_system_type {
const char *name; // 檔案系統名稱 ext4, ...
int (*init_fs_context)(struct fs_context *);
...
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
...
}
其中比較重要的是 init_fs_context
和 mount
這兩個函數,當我們使用 mount
去掛載一個設備時,就會呼叫到這兩個函數來初始化檔案系統,建立super_block函數,及載入檔案系統根目錄的inode跟dentry,設置superblock/inode/dentry_operations等等,具體mount怎麼去初始化檔案系統和這些結構的就超出範圍為不談了。
我們可以透過 cat /proc/filesystems
來查看系統中註冊的檔案系統。
> cat /proc/filesystems
nodev sysfs
nodev tmpfs
nodev bdev
nodev proc
nodev cgroup
nodev cgroup2
nodev cpuset
nodev devtmpfs
nodev configfs
nodev debugfs
nodev tracefs
nodev securityfs
nodev sockfs
nodev bpf
nodev pipefs
nodev ramfs
nodev hugetlbfs
nodev devpts
ext3
ext2
ext4
squashfs
vfat
nodev ecryptfs
fuseblk
nodev fuse
nodev fusectl
nodev efivarfs
nodev mqueue
nodev pstore
nodev autofs
nodev rpc_pipefs
nodev binfmt_misc
nodev overlay
nodev aufs
今天我們介紹了 VFS 的各種操作介面 (file_operations
, inode_operations
, dentry_operations
),並介紹了 super block 還有檔案系統驅動。