Linux 虛擬文件系統介紹

1.1 概述

在 Linux 系統中,一切皆文件,除了一般所說的狹義的文件(文本文件和二進制文件) 之外,目錄、設備、套接字和管道等都是文件。html

文件系統在不一樣的上下文中有不一樣的含義:node

  1. 在存儲設備上組織文件的方法,包括數據結構和訪問方法。到存儲設備。
  2. 按照某種文件系統類型格式化的一塊存儲介質。咱們常說在某個目錄下掛載或卸載文件系統, 這裏的文件系統就是這種意思。
  3. 內核中負責管理和存儲文件的模塊,即文件系統模塊。

Linux文件系統的架構以下圖所示,分爲用戶空間、內核空間和硬件3個層面: linux

注意上圖中方塊對齊關係,不少時候咱們分不清內核文件系統中 「cache」 和 「buffer」 的區別,畢竟二者均可以翻譯爲 「緩存區」,可是從圖中,就能夠很清晰的看出所謂的 「cache」 其實指的就是圖中的 「頁緩存」 它是針對文件來講的,除了 「DAX"(直接訪問方式的設備)它不使用 「緩存」,其餘的閃存類,塊設備類設備都會使用到 「頁緩存」 也就是 「cache」,而 「buffer」 其實指的就是圖中的 「塊緩存」 它是針對塊設備的。

虛擬文件系統中各數據結構之間的關係: 算法

1.2 用戶空間層面

應用程序能夠直接使用內核提供的系統調用訪問文件:編程

  1. 一個存儲設備上的文件系統,只有掛載到內存中目錄樹的某個目錄下,進程才能訪 問這個文件系統。 系統調用 mount 用來把文件系統掛載到內存中目錄樹的某個目錄下。 以執行命令 「mount -t fstype device dir」,把文件系統掛載到某個目錄下,mount 命令調 系統調用 mount 來掛載文件系統。
  2. 系統調用 umount 用來卸載某個目錄下掛載的文件系統能夠執行命令 「umount dir」 卸載文件系統, umount 命令調用系統調用 umount。
  3. 使用 open 打開文件。
  4. 使用 close 關閉文件。
  5. 使用 read 讀文件。
  6. 使用 write 寫文件。
  7. 使用 lseek 設置文件偏移。
  8. 當咱們寫文件的時候,內核的文件系統模塊把數據保存在頁緩存中,不會當即寫 存儲設備。咱們可使用 fsync 把文件修改過的屬性和數據當即寫到存儲設備,或者使 fdatasync 把文件修改過的數據當即寫到存儲設備。

應用程序也可使用 glibc 庫封裝的標準 IO 流函數訪問文件,標準流提供了緩衝區, 目的是儘量減小調用 read 和 write 的次數,提升性能 glic 庫封裝的標準流函數以下所示:數組

  1. 使用 fopen 打開流。
  2. 使用 fclose 關閉流。
  3. 使用 fread 讀流。
  4. 使用 fwrite 寫流。
  5. 使用 fseek 設置文件偏移。
  6. 使用 fwrite 能夠把數據寫到用戶空間緩衝區,但不會當即寫到內核。咱們可使 fush 沖刷流,即把寫到用戶空間緩衝區的數據當即寫到內核。

1.3 硬件層面

外部存儲設備分爲塊設備、閃存和 NVDIMM 設備 3 類。 塊設備主要有如下兩種。緩存

  1. 機械硬盤:機械硬盤的讀寫單位是扇區。訪問機械硬盤的時候,須要首先沿着半徑 方向移動磁頭尋找磁道,而後轉動盤片找到扇區。
  2. 閃存類塊設備:使用閃存做爲存儲介質,裏面的控制器運行固化的驅動程序,驅動 程序的功能之一是閃存轉換層(Flash Translation Layer,FTL),把閃存轉換爲塊設備, 外表現爲塊設備。常見的閃存類塊設備是在我的計算機和筆記本電腦上使用的固態硬盤 splid State Drives,SSD),以及在手機和平板電腦上使用的嵌入式多媒體存儲卡(embedded Multi Media Card,eMMc)和通用閃存存儲(Universal Flash Storage,UFS)。 閃存類塊設備相對機械硬盤的優點是:訪問速度快,由於沒有機械操做:抗振性很高, 便於攜帶。

閃存(Flash Memory)的主要特色以下。服務器

  1. 在寫入數據以前須要擦除一個擦除塊,由於向閃存寫數據只能把一個位從 1 變成 0,不能從 0 變成 1,擦除的目的是把擦除塊的全部位設置爲 1
  2. 一個擦除塊的最大擦除次數有限,NOR閃存的擦除塊的最大擦除次數是 10^4~10^3, NAND 閃存的擦除塊的最大擦除次數是 10^3~10^6

閃存按存儲結構分爲 NAND 閃存和 NOR 閃存,二者的區別以下。網絡

  1. NOR閃存的容量小,NAND 閃存的容量大。
  2. NOR 閃存支持按字節尋址,支持芯片內執行(eXecute In Place,XIP),能夠直接 在閃存內執行程序,不須要把程序讀到內存中; NAND 閃存的最小讀寫單位是頁或子頁, 一個擦除塊分爲多個頁,有的 NAND 閃存把頁劃分爲多個子頁。
  3. NOR 閃存讀的速度比 NAND 閃存塊,寫的速度和擦除的速度都比 NAND 閃存慢
  4. NOR 閃存沒有壞塊;NAND 閃存存在壞塊,主要是由於消除壞塊的成本過高 NOR 閃存適合存儲程序,通常用來存儲引導程序好比 uboot 程序;NAND 閃存適 合存儲數據。

爲何要針對閃存專門設計文件系統?主要緣由以下。數據結構

  1. NAND 閃存存在壞塊,軟件須要識別而且跳過壞塊。
  2. 須要實現損耗均衡( wear leveling),損耗均衡就是使全部擦除塊的擦除次數均衡, 避免一部分擦除塊先損壞。

機械硬盤和 NAND 閃存的主要區別以下。

  1. 機械硬盤的最小讀寫單位是扇區,扇區的大小通常是 512 字節:NAND 閃存的最 小讀寫單位是頁或子頁。
  2. 機械硬盤能夠直接寫入數據:NAND 閃存在寫入數據以前須要擦除一個擦除塊。
  3. 機械硬盤的使用壽命比 NAND 閃存長:機械硬盤的扇區的寫入次數沒有限制: NAND 閃存的擦除塊的擦除次數有限。
  4. 機械硬盤隱藏壞的扇區,軟件不須要處理壞的扇區:NAND 閃存的壞塊對軟件可 見,軟件須要處理壞塊。

NVDIMM(Nonn-Volatile DIMM,非易失性內存:DIMM 是 Dual-Inline-Memory-Modules 的縮寫,表示雙列直插式存儲模塊,是內存的一種規格)設備把 NAND 閃存、內存和超級 電容集成到一塊兒,訪問速度和內存同樣快,而且斷電之後數據不會丟失。在斷電的瞬間, 超級電容提供電力,把內存中的數據轉移到 NAND 閃存。

1.4 內核空間層面

在內核的目錄 fs 下能夠看到,內核支持多種文件系統類型。爲了對用戶程序提供統一的 文件操做接口,爲了使不一樣的文件系統實現可以共存,內核實現了一個抽象層,稱爲虛擬文件 系統( Virtual File System,VFS),也稱爲虛擬文件系統切換( Virtual Filesystem Switch,VFS) 文件系統分爲如下 4 種。

  1. 塊設備文件系統,存儲設備是機械硬盤和固態硬盤等塊設備,經常使用的塊設備文件 系統是 EXT 和 btrfs。EXT 文件系統是 Linux 原創的文件系統,目前有 3 個 成版本:EXT二、EXT3 和 EXT4。
  2. 閃存文件系統,存儲設備是 NAND 閃存和 NOR 閃存,經常使用的閃存文件系統是 JFFS2 ,(日誌型閃存文件系統版本2, Journalling Flash File System version2)和 UBIFS(無序區塊鏡像文件系統, Unsorted Block Image File System)。
  3. 內存文件系統,文件在內存中,斷電之後文件丟失,經常使用的內存文件系統是 tmpfs, 用來建立臨時文件。
  4. 僞文件系統,是假的文件系統,只是爲了使用虛擬文件系統的編程接口,經常使用的 僞文件系統以下所示。
    1. sockfs,這種文件系統使得套接字(socket)可使用讀文件的接口 read 接收報文, 使用寫文件的接口 write 發送報文。
    2. proc 文件系統,最初開發 proc 文件系統的目的是把內核中的進程信息導出到用戶空間, 後來擴展到把內核中的任何信息導出到用戶空間,一般把 proc 文件系統掛載在目錄 「proc」 下。
    3. sysfs,用來把內核的設備信息導出到用戶空間,一般把 sysfs 文件系統掛載在目錄 「/sys」下。
    4. hugetlbfs,用來實現標準巨型頁。
    5. cgroup 文件系統,控制組(control group cgroup)用來控制一組進程的資源, cgroup 文件系統使管理員可使用寫文件的方式配置 cgroup。
    6. cgroup2 文件系統, cgroup2 是 cgroup 的第二個版本, cgroup2 文件系統使管理員可 以使用寫文件的方式配置 cgroup2。
  • 頁緩存:訪問外部存儲設備的速度很慢,爲了不每次讀寫文件時訪問外部存儲設備,文件系 統模塊爲每一個文件在內存中建立了一個緩存,由於緩存的單位是頁,因此稱爲頁緩存。
  • 塊設備層:塊設備的訪問單位是塊,塊大小是扇區大小的整數倍。內核爲全部塊設備實現了統一 的塊設備層。
  • 塊緩存:爲了不每次讀寫都須要訪問塊設備,內核實現了塊緩存,爲每一個塊設備在內存中創 建一個塊緩存。緩存的單位是塊,塊緩存是基於頁緩存實現的。
  • IO 調度器:訪問機械硬盤時,移動磁頭尋找磁道和扇區很耗時,若是把讀寫請求按照扇區號排序, 能夠減小磁頭的移動,提升吞吐量。IO 調度器用來決定讀寫請求的提交順序,針對不一樣的 使用場景提供了多種調度算法:NOOP(No Operation)、CFQ(徹底公平排隊, Complete Fair Queuing)和 deadline(限期)。NOOP 調度算法適合閃存類塊設備,CFQ 和 deadline調度算 法適合機械硬盤。
  • 塊設備驅動程序:每種塊設備須要實現本身的驅動程序。

內核把閃存稱爲存儲技術設備( Memory Technology Device,MTD),爲全部閃存實現 了統一的 MTD 層,每種閃存須要實現本身的驅動程序。

針對 NVDIMM 設備,文件系統須要實現 DAX(Direct Access直接訪問:X 表明 eXciting,沒有意義,只是爲了讓名字看起來酷),繞過頁緩存和塊設備層,把 NVDIMM 設備裏面的內存直接映射到進程或內核的虛擬地址空間。

libnvdimm 子系統提供對 3 種 NVDIMM 設備的支持:持久內存(persistent memory,PMEM) 模式的 NVDIMM 設備,塊設備(block,BLK)模式的 NVDIMM 設備,以及同時支持PMEM 和 BLK 兩種訪問模式的 NVDIMM 設備。PMEM 訪問模式是把 NVDIMM 設備看成內存,BLK 訪問模式是把 NVDIMM 設備看成塊設備。每種 NVDIMM 設備須要實現本身的驅動程序。

2 虛擬文件系統的數據結構

雖然不一樣文件系統類型的物理結構不一樣,可是虛擬文件系統定義了一套統一的數據結構。

  1. 超級塊(super_block)。文件系統的第一塊是超級塊,描述文件系統的整體信息,掛載文件系統 的時候在內存中建立超級塊的副本:結構體 super_block。
  2. 虛擬文件系統在內存中把目錄組織爲一棵樹。一個文件系統,只有掛載到內存中目錄 樹的一個目錄下,進程才能訪問這個文件系統。每次掛載件系統,虛擬文件系統就會建立一個 掛載描述符: mount 結構體,而且讀取文件系統的超級塊,在內存中建立超級塊的一個副本。
  3. 每種文件系統的超級塊的格式不一樣,須要向虛擬文件系統註冊文件系統類型 file_system_type,而且實現 mount 方法用來讀取和解析超級塊
  4. 索引節點(inode)。每一個文件對應一個索引節點,每一個索引節點有一個惟一的編號。當內 核訪問存儲設備上的一個文件時,會在內存中建立索引節點的一個副本:結構體 inode。
  5. 目錄項(dentry)。文件系統把目錄看做文件的一種類型,目錄的數據是由目錄項組成的, 每一個目錄項存儲一個子目錄文件 的名稱以及對應的索引節點號。當內核訪問存儲設備上 的一個目錄項時,會在內存中建立該目錄項的一個副本:結構體 dentry。
  6. 當進程打開一個文件的時候,虛擬文件系統就會建立文件的一個打開實例:file 結構體,而後在進程的打開文件表中分配一個索引,這個索引稱爲文件描述符(fd),最後把文 件描述符和 file 結構體的映射添加到打開文件表中。

An individual dentry usually has a pointer to an inode. Inodes are filesystem objects such as regular files, directories, FIFOs and other beasts. They live either on the disc (for block device filesystems) or in the memory (for pseudo filesystems). Inodes that live on the disc are copied into the memory when required and changes to the inode are written back to disc. A single inode can be pointed to by multiple dentries (hard links, for example, do this).

單個 dentry 一般具備指向 inode 的指針。 inode 是文件系統對象,例如常規文件,目錄,FIFO 和其餘類型。 它們存在於 disc(用於塊設備文件系統)或在內存中(用於僞文件系統)中。 存在於 disc 上的 inode 在須要時被複制到內存中,而且對 inode 的更改將被寫回到 disc。 單個 inode 能夠由多個 dentry 指向(硬連接,例如,這樣作)。

注意: 這段話有三個重點:一是 inode 是用來表示文件系統對象,有些地方說是用來表明「文件」,這也沒錯,但卻沒有說這個「文件」不只僅是指常規文件,它還包括 目錄,FIFO(管道文件),socket,塊設備,字符設備,符號連接一共七種類型文件,因此很容易產生誤解,理解這點很重要。 二是 inode 的存儲位置。三是一個 inode 或者說「文件」 能夠被多個 dentry 指向,這裏還特地提了是硬連接,其實也就說明硬連接實際上是建立了一個 dentry ,而這個 dentry 的 d_inode 指向了同一個 inode,而對於符號連接它就是一個「文件」 也就是表示它有一個對應的 inode,而這個 inode 裏面的內容是指向了另一個 inode 的路徑。

To look up an inode requires that the VFS calls the lookup() method of the parent directory inode. This method is installed by the specific filesystem implementation that the inode lives in. Once the VFS has the required dentry (and hence the inode), we can do all those boring things like open(2) the file, or stat(2) it to peek at the inode data. The stat(2) operation is fairly simple: once the VFS has the dentry, it peeks at the inode data and passes some of it back to userspace.

要查找 inode, VFS 須要調用父目錄 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系統實現安裝。 一旦 VFS 有了目標 dentry(間接可以得到 inode),咱們能夠作全部那些無聊的事情,好比 open(2) 文件,或者 stat(2) 查看 inode 數據。stat(2) 操做至關簡單:一旦 VFS 具備 dentry, 就能夠獲取 inode 的數據,並將其中一些傳遞迴用戶空間。

注意: 這段話的一個重點是咱們是使用父目錄 inode 對應的 lookup() 方法來查找 inode 的,也就是說在查找當前 inode 的時候,已經確保了父目錄是沒問題,但有時候在查找後也會再次檢查父目錄有沒有發生改變。

2.1 超級塊

文件系統的第一塊是超級塊,用來描述文件系統的整體信息。當咱們把文件系統掛載 到內存中目錄樹的一個目錄下時,就會讀取文件系統的超級塊,在內存中建立超級塊的副 本:結構體 super_block,主要成員以下: [include/linux/fs.h]

struct super_block {
	struct list_head	s_list;		// Keep this first
	dev_t			s_dev;		// search index; _not_ kdev_t
	unsigned char		s_blocksize_bits;
	unsigned long		s_blocksize;
	loff_t			s_maxbytes;	// Max file size
	struct file_system_type* s_type;
	const struct super_operations* s_op;
	...
	unsigned long		s_flags;
	unsigned long		s_iflags;	// internal SB_I_* flags
	unsigned long		s_magic;
	struct dentry* s_root;

    ...
	struct hlist_bl_head	s_anon;		// anonymous dentries for (nfs) exporting
	struct list_head	s_mounts;	// list of mounts; _not_ for fs use
	struct block_device*  s_bdev;
	struct backing_dev_info*  s_bdi;
	struct mtd_info*  s_mtd;
	struct hlist_node	s_instances;

    ...
	void*  s_fs_info;	// Filesystem private info
	...
};
複製代碼
  • 成員 s_list 用來把全部超級塊實例連接到全局鏈表 super_blocks。
  • 成員 sdev 和 bdev 保存文件系統所在的塊設備,前者保存設備號,後者指向內 存中的一個 block_device 實例。
  • 成員 s_blocksize 是塊長度,成員 blocksize_bits 是塊長度以 2 爲底的對數。
  • 成員 s_maxbytes 是文件系統支持的最大文件長度。
  • 成員 s_flags 是標誌位。
  • 成員 s_type 指向文件系統類型。
  • 成員 s_op 指向超級塊操做集合。
  • 成員 magic 是文件系統類型的魔幻數每種文件系統類型被分配一個惟一的魔幻數。
  • 成員 sroot 指向根目錄的結構體 dentry。
  • 成員 sfs_info 指向具體文件系統的私有信息。
  • 成員 instances 用來把同一個文件系統類型的全部超級塊實例連接在一塊兒,鏈表 的頭節點是結構體 fle_system_type 的成員 fs_supers。

超級塊操做集合的數據結構是結構體 super_operations,主要成員以下: [include/linux/fs.h]

struct super_operations {
  struct inode *(*alloc_inode)(struct super_block *sb);
	void (*destroy_inode)(struct inode *);

  void (*dirty_inode) (struct inode *, int flags);
	int (*write_inode) (struct inode *, struct writeback_control *wbc);
	int (*drop_inode) (struct inode *);
	void (*evict_inode) (struct inode *);
	void (*put_super) (struct super_block *);
	int (*sync_fs)(struct super_block *sb, int wait);
	int (*freeze_super) (struct super_block *);
	int (*freeze_fs) (struct super_block *);
	int (*thaw_super) (struct super_block *);
	int (*unfreeze_fs) (struct super_block *);
	int (*statfs) (struct dentry *, struct kstatfs *);
	int (*remount_fs) (struct super_block *, int *, char *);
	int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *);
	void *(*clone_mnt_data) (void *);
	void (*copy_mnt_data) (void *, void *);
	void (*umount_begin) (struct super_block *);

	int (*show_options)(struct seq_file *, struct dentry *);
	int (*show_options2)(struct vfsmount *,struct seq_file *, struct dentry *);
	int (*show_devname)(struct seq_file *, struct dentry *);
	int (*show_path)(struct seq_file *, struct dentry *);
	int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
	ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
	ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
	struct dquot **(*get_dquots)(struct inode *);
#endif
	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
	long (*nr_cached_objects)(struct super_block *,
				  struct shrink_control *);
	long (*free_cached_objects)(struct super_block *,
				    struct shrink_control *);
};
複製代碼
  • 成員 alloc_inode 用來爲一個索引節點分配內存而且初始化。
  • 成員 destroy_inode 用來釋放內存中的索引接點。
  • 成員 dirty_inode 用來把索引節點標記爲髒。
  • 成員 write_inode 用來把一個索引節點寫到存儲設備。
  • 成員 drop_inode 用來在索引節點的引用計數減到 0 時調用。
  • 成員 evict_inode 用來從存儲設備上的文件系統中刪除一個索引節點。
  • 成員 put_super 用來釋放超級塊。
  • 成員 sync_fs 用來把文件系統修改過的數據同步到存儲設備。
  • 成員 statfs 用來讀取文件系統的統計信息。
  • 成員 remountfs 用來在從新掛載文件系統的時候調用。
  • 成員 umount_begin 用來在卸載文件系統的時候調用。

2.2 掛載描述符

一個文件系統,只有掛載到內存中目錄樹的一個目錄下,進程才能訪問這個文件系統 每次掛載文件系統,虛擬文件系統就會建立一個掛載描述符: mount 結構體。掛載描述符 用來描述文件系統的一個掛載實例,同一個存儲設備上的文件系統能夠屢次掛載,每次掛 載到不一樣的目錄下。 [include/linux/fs.h]

struct mount {
   struct hlist_node mnt_hash;
   struct mount *mnt_parent;
   struct dentry *mnt_mountpoint;
   struct vfsmount mnt;
   union {
   	struct rcu_head mnt_rcu;
   	struct llist_node mnt_llist;
   };
#ifdef CONFIG_SMP
   struct mnt_pcp __percpu *mnt_pcp;
#else
   int mnt_count;
   int mnt_writers;
#endif
   struct list_head mnt_mounts;	/* list of children, anchored here */
   struct list_head mnt_child;	/* and going through their mnt_child */
   struct list_head mnt_instance;	/* mount instance on sb->s_mounts */
   const char *mnt_devname;	/* Name of device e.g. /dev/dsk/hda1 */
   struct list_head mnt_list;
 ...
   struct mnt_namespace *mnt_ns;	/* containing namespace */
   struct mountpoint *mnt_mp;	/* where is it mounted */
   struct hlist_node mnt_mp_list;	/* list mounts with the same mountpoint */
 ...
};
複製代碼

假設咱們把文件系統 2 掛載到目錄 「/a」 下,目錄 a 屬於文件系統 1。目錄 a 稱爲掛載 點,文件系統 2 的 mount 實例是文件系統 1 的 mount 實例的孩子,文件系統 1 的 mount 實 是文件系統 2 的 mount 實例的父親。

  • 成員 mnt_parent 指向父親,即文件系統 1 的 mount 實例。
  • 成員 mnt_mountpoint 指向做爲掛載點的目錄,即文件系統 1 的目錄 a,目錄 a 的 dentry 實例的成員 d_flags 設置了標誌位 DCACHE_MOUNTED
  • 成員 mnt 的類型以下:
    struct vfsmount{
       struct dentry *mnt_root;
       struct super_block *mnt_sb;
       int mnt_flags;
    }
    複製代碼
    mnt_root 指向文件系統 2 的根目錄,mnt_sb 指向文件系統 2 的超級塊。
  • 成員 mnt_hash 用來把掛載描述符加入全局散列表 mount_hashtable,關鍵字是{父載描述符,掛載點}。
  • 成員 mnt_mounts 是孩子鏈表的頭節點。
  • 成員 mnt_child 用來加入父親的孩子鏈表。
  • 成員 mnt_instance 用來把掛載描述符添加到超級塊的掛載實例鏈表上,同一個存儲設備上的文件系統,能夠屢次掛載,每次掛載到不一樣的目錄下。
  • 成員 mnt_devname 指向存儲設備的名稱。
  • 成員 mnt_ns 指向掛載命名空間,後面會介紹。
  • 成員 mnt_mp 指向掛載點,類型以下:
    struct mountpoint{
      struct hlist_node m_hash;
      struct dentry *m_dentry;
      struct hlist_head m_list;
      int m_count;
    }
    複製代碼
    m_dentry 指向做爲掛載點的目錄, m_list 用來把同一個掛載點下的全部掛載描述符鏈 接起來。爲何同一個掛載點下會有多個掛載描述符?這和掛載命名空間有關。
  • 成員 mnt_mp_list 用來把掛載描述符加入同一個掛載點的掛載描述符鏈表,鏈表 頭節點是成員 mnt_mp 的成員 m_list。

2.3 文件系統類型

由於每種文件系統的超級塊的格式不一樣,因此每種文件系統須要向虛擬文件系統註冊 文件系統類型 file_system_type,而且實現 mount 方法用來讀取和解析超級塊。結構體 file_system_type 以下: [include/linux/fs.h]

struct file_system_type {
	const char *name;
	int fs_flags;
#define FS_REQUIRES_DEV		1
#define FS_BINARY_MOUNTDATA	2
#define FS_HAS_SUBTYPE		4
#define FS_USERNS_MOUNT		8	/* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT	16 /* A userns mount does not imply MNT_NODEV */
#define FS_USERNS_VISIBLE	32	/* FS must already be visible */
#define FS_RENAME_DOES_D_MOVE	32768	/* FS will handle d_move() during rename() internally. */
	struct dentry *(*mount) (struct file_system_type *, int,
		       const char *, void *);
	struct dentry *(*mount2) (struct vfsmount *, struct file_system_type *, int,
			       const char *, void *);
	void *(*alloc_mnt_data) (void);
	void (*kill_sb) (struct super_block *);
	struct module *owner;
	struct file_system_type * next;
	struct hlist_head fs_supers;

	struct lock_class_key s_lock_key;
	struct lock_class_key s_umount_key;
	struct lock_class_key s_vfs_rename_key;
	struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

	struct lock_class_key i_lock_key;
	struct lock_class_key i_mutex_key;
	struct lock_class_key i_mutex_dir_key;
};
複製代碼
  • 成員 name 是文件系統類型的名稱。
  • 方法 mount 用來在掛載文件系統的時候讀取而且解析超級塊。
  • 方法 kill_sb 用來在卸載文件系統的時候釋放超級塊。
  • 多個存儲設備上的文件系統的類型可能相同,成員 fs_supers 用來把相同文件系統類型的 超級塊連接起來。

2.4 索引節點

在文件系統中,每一個文件對應一個索引節點,索引節點描述兩類信息。

  1. 文件的屬性,也稱爲元數據(metadata),例如文件長度、建立文件的用戶的標識符、上一次 訪問的時間和上一次修改的時間,等等。
  2. 文件數據的存儲位置。

每一個索引節點有一個惟一的編號。 當內核訪問存儲設備上的一個文件時,會在內存中建立索引節點的一個副本:結構體 inode,主要 成員以下: [include/linux/fs.h]

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;

#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif

	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;

    ...

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	/* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */
	union {
		const unsigned int i_nlink;
		unsigned int __i_nlink;
	};
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;
	unsigned int		i_blkbits;
	enum rw_hint		i_write_hint;
	blkcnt_t		i_blocks;

    ...
	struct hlist_node	i_hash;
	struct list_head	i_io_list;	/* backing dev IO list */
    ...
	struct list_head	i_lru;		/* inode LRU list */
	struct list_head	i_sb_list;
	union {
		struct hlist_head	i_dentry;
		struct rcu_head		i_rcu;
	};
	u64			i_version;
	atomic_t		i_count;
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
#ifdef CONFIG_IMA
	atomic_t		i_readcount; /* struct files open RO */
#endif
	const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct file_lock_context	*i_flctx;
	struct address_space	i_data;
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
		char			*i_link;
	};
    ...
	void			*i_private; /* fs or device private pointer */
};

複製代碼
  • i_mode 是文件類型和訪問權限,i_uid 是建立文件的用戶的標識符,i_gid 是建立文件 用戶所屬的組標識符。
  • i_ino 是索引節點的編號。
  • i_size 是文件長度; i_blocks 是文件的塊數,即文件長度除以塊長度的商;i_bytes 是文件 長度除以塊長度的餘數;i_blkbits 是塊長度以 2 爲底的對數,塊長度是 2 的 i_blkbits 次冪。
  • i_atime(access time)是上一次訪問文件的時間, i_mtime(modified time)是上一次修改文 件數據的時間,i_ctime(change time)是上一次修改文件索引節點的時間。
  • i_sb 指向文件所屬的文件系統的超級塊。
  • i_mapping 指向文件的地址空間。
  • i_count 是索引節點的引用計數,i_nlink 是硬連接計數。
  • 若是文件的類型是字符設備文件或塊設備文件,那麼 i_rdev 是設備號,i_bdev 指向塊, i_cdev 指向字符設備。

文件分爲如下幾種類型。

  • 普通文件(regular file):就是咱們一般說的文件,是狹義的文件。
  • 目錄:目錄是一種特殊的文件,這種文件的數據是由目錄項組成的,每一個目錄項 存儲一個子目錄文件 的名稱以及對應的索引節點號。
  • 符號連接(也稱爲軟連接):這種文件的數據是另外一個文件的路徑。
  • 字符設備文件。
  • 塊設備文件。
  • 命名管道(FIFO)。
  • 套接字(socket)

字符設備文件、塊設備文件、命名管道和套接字是特殊的文件,這些文件只有索引節點, 沒有數據。字符設備文件和塊設備文件用來存儲設備號,直接把設備號存儲在索引節點中。

內核支持兩種連接。

  1. 軟連接,也稱爲符號連接,這種文件的數據是另外一個文件的路徑。它是一個真正的文件,有本身的 inode 對象,inode 存儲的數據就是符號連接指向路徑的路徑名。
  2. 硬連接,至關於給一個文件取了多個名稱,多個文件名稱對應同一個索引節點, 索引節點的成員 i_nlink 是硬連接計數。 索引節點的成員 iop 指向索引節點操做集合 inode operations,成員 ifop 指向文件操 做集合 file_operations 二者的區別是: inodeoperations用來操做目錄(在一個目錄下建立 或刪除文件)和文件屬性,file_operations 用來訪問文件的數據 索引節點操做集合的數據結構是結構體 inode_operations,主要成員以下:
struct inode_operations {
	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
	const char * (*follow_link) (struct dentry *, void **);
	int (*permission) (struct inode *, int);
	int (*permission2) (struct vfsmount *, struct inode *, int);
	struct posix_acl * (*get_acl)(struct inode *, int);

	int (*readlink) (struct dentry *, char __user *,int);
	void (*put_link) (struct inode *, void *);

	int (*create) (struct inode *,struct dentry *, umode_t, bool);
	int (*link) (struct dentry *,struct inode *,struct dentry *);
	int (*unlink) (struct inode *,struct dentry *);
	int (*symlink) (struct inode *,struct dentry *,const char *);
	int (*mkdir) (struct inode *,struct dentry *,umode_t);
	int (*rmdir) (struct inode *,struct dentry *);
	int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
	int (*rename) (struct inode *, struct dentry *,
			struct inode *, struct dentry *);
	int (*rename2) (struct inode *, struct dentry *,
			struct inode *, struct dentry *, unsigned int);
	int (*setattr) (struct dentry *, struct iattr *);
	int (*setattr2) (struct vfsmount *, struct dentry *, struct iattr *);
	int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
	int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
	ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
	ssize_t (*listxattr) (struct dentry *, char *, size_t);
	int (*removexattr) (struct dentry *, const char *);
	int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
		      u64 len);
	int (*update_time)(struct inode *, struct timespec *, int);
	int (*atomic_open)(struct inode *, struct dentry *,
			   struct file *, unsigned open_flag,
			   umode_t create_mode, int *opened);
	int (*tmpfile) (struct inode *, struct dentry *, umode_t);
	int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
複製代碼

lookup 方法用來在一個目錄下查找文件。 系統調用 open 和 creat 調用 create 方法來建立普通文件,系統調用 link 調用 link 方法 來建立硬連接,系統調用 symlink 調用 symlink 方法來建立符號連接,系統調用 mkdir 調用 mkdir 方法來建立目錄,系統調用 mknod 調用 mknod 方法來建立字符設備文件、塊設備文件、 命名管道和套接字。 系統調用 unlink 調用 unlink 方法來刪除硬連接,系統調用 rmdir 調用 rmdir 方法來刪除目錄。 系統調用 rename 調用 rename 方法來給文件換一個名字。 系統調用 chmod 調用 setattr 方法來設置文件的屬性,系統調用 stat 調用 getattr 方法來建立目錄讀取文件的屬性。 系統調用 listxattr 調用 listxattr 方法來列出文件的全部的擴展屬性。

2.5 目錄項

文件系統把目錄當作文件,這種文件的數據是有目錄項組成的,每一個目錄項存儲一個子目錄或文件的名稱以及對應的索引節點號。 當內核訪問存儲設備上的一個目錄項時,會在內存中建立目錄項的一個副本:結構體 dentry,主要成員以下: [include/linux/dcache.h]

struct dentry {
	/* RCU lookup touched fields */
	unsigned int d_flags;		/* protected by d_lock */
	seqcount_t d_seq;		/* per dentry seqlock */
	struct hlist_bl_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* parent directory */
	struct qstr d_name;
	struct inode *d_inode;		/* Where the name belongs to - NULL is * negative */
	unsigned char d_iname[DNAME_INLINE_LEN];	/* small names */

	/* Ref lookup also touches following */
	struct lockref d_lockref;	/* per-dentry lock and refcount */
	const struct dentry_operations *d_op;
	struct super_block *d_sb;	/* The root of the dentry tree */
	unsigned long d_time;		/* used by d_revalidate */
	void *d_fsdata;			/* fs-specific data */

	struct list_head d_lru;		/* LRU list */
	struct list_head d_child;	/* child of parent list */
	struct list_head d_subdirs;	/* our children */
	/* * d_alias and d_rcu can share memory */
	union {
		struct hlist_node d_alias;	/* inode alias list */
	 	struct rcu_head d_rcu;
	} d_u;
};
複製代碼
  • d_name 存儲文件名稱,qstr是字符串的包裝器,存儲字符串的地址、長度和散列值;若是文件名稱比較短,把文件名稱存儲在 d_iname;d_inode 指向文件的索引節點。
  • d_parent 指向父目錄,d_child 用來把本目錄加入父目錄的子目錄鏈表。
  • d_lockref 是引用計數。
  • d_subdirs 是子目錄鏈表。
  • d_hash 用來把目錄項加入散列表 dentry_hashtable
  • d_lru 用來把目錄項加入超級塊的最近最少使用(Least Recently Used,LRU)鏈表 s_dentry_lru 中,當目錄項的引用計數減到 0 時,把目錄項添加到超級塊的 LRU 鏈表中。
  • d_alias 用來把同一個文件的全部硬連接對應的目錄項連接起來。

以文件 「/a/c.txt」 爲例,目錄項和索引節點的關係以下:

目錄項操做集合的數據結構是結構體 dentry_operations,其代碼以下:

struct dentry_operations {
	int (*d_revalidate)(struct dentry *, unsigned int);
	int (*d_weak_revalidate)(struct dentry *, unsigned int);
	int (*d_hash)(const struct dentry *, struct qstr *);
	int (*d_compare)(const struct dentry *, const struct dentry *,
			unsigned int, const char *, const struct qstr *);
	int (*d_delete)(const struct dentry *);
	void (*d_release)(struct dentry *);
	void (*d_prune)(struct dentry *);
	void (*d_iput)(struct dentry *, struct inode *);
	char *(*d_dname)(struct dentry *, char *, int);
	struct vfsmount *(*d_automount)(struct path *);
	int (*d_manage)(struct dentry *, bool);
	struct inode *(*d_select_inode)(struct dentry *, unsigned);
	struct dentry *(*d_real)(struct dentry *, struct inode *);
	void (*d_canonical_path)(const struct path *, struct path *);
} ____cacheline_aligned;
複製代碼

參考 vfs.txt 文檔:

d_revalidate: called when the VFS needs to revalidate a dentry. This is called whenever a name look-up finds a dentry in the dcache. Most local filesystems leave this as NULL, because all their dentries in the dcache are valid. Network filesystems are different since things can change on the server without the client necessarily being aware of it. This function should return a positive value if the dentry is still valid, and zero or a negative error code if it isn't. d_revalidate may be called in rcu-walk mode (flags & LOOKUP_RCU).If in rcu-walk mode, the filesystem must revalidate the dentry without blocking or storing to the dentry, d_parent and d_inode should not be used without care (because they can change and, in d_inode case, even become NULL under us). If a situation is encountered that rcu-walk cannot handle, return -ECHILD and it will be called again in ref-walk mode.

當 VFS 須要驗證一個 dentry 有效性時會被調用。在 dcache 中找到 name 對應的 dentry 的時候就被調用。大多數本地文件系統直接返回 NULL,由於這類文件系統在 dcache 中全部的 dentry 都是有效的。可是網絡文件系統不同,由於事情會在服務器上已經發生了,可是客戶端殊不知道。這個函數應該返回一個正值來表示該 dentry 依然是有效的,0 或者 負數來表示錯誤。

d_automount: called when an automount dentry is to be traversed (optional).This should create a new VFS mount record and return the record to the caller. The caller is supplied with a path parameter giving the automount directory to describe the automount target and the parent VFS mount record to provide inheritable mount parameters. NULL should be returned if someone else managed to make the automount first. If the vfsmount creation failed, then an error code should be returned. If -EISDIR is returned, then the directory will be treated as an ordinary directory and returned to pathwalk to continue walking.

當碰到一個自動掛載 automount 目錄項時,d_automount 會被調用。(可選的)。它應該建立一個新的 VFS 掛載記錄,而且把這個記錄返回給調用者。爲調用者提供了一個 path 參數,該參數給出了automount 目錄(用於描述 automount 目標)和父 VFS 掛載記錄(以提供可繼承的掛載參數)。若是已經有其餘調用者 在該 automount 作了處理,那麼返回 NULL。若是 vfsmount 建立失敗那麼返回錯誤碼。若是返回了 -EISDIR,那麼該目錄被當成普通目錄而且返回到路徑查找流程繼續下一步查找。

If a vfsmount is returned, the caller will attempt to mount it on the mountpoint and will remove the vfsmount from its expiration list in the case of failure. The vfsmount should be returned with 2 refs on it to prevent automatic expiration - the caller will clean up the additional ref.

若是返回了 vfsmount,那麼調用者將會嘗試把它掛載在該掛載點上。而且若是掛載失敗,則會將 vfsmount 從它的 expiration 列表中移除。vfsmount 在返回時應該帶有兩個引用(指向它本身的引用)用來防止 自動卸載(過時)。調用者會清除附加的引用。

This function is only used if DCACHE_NEED_AUTOMOUNT is set on the dentry. This is set by __d_instantiate() if S_AUTOMOUNT is set on the inode being added.

只有 dentry 被設置了 DCACHE_NEED_AUTOMOUNT 標誌,該函數纔會被使用。若是 inode 的 S_AUTOMOUNT 被設置了,那麼能夠經過 __d_instantiate() 函數來設置 DCACHE_NEED_AUTOMOUNT 標誌。

d_manage: called to allow the filesystem to manage the transition from a dentry (optional). This allows autofs, for example, to hold up clients waiting to explore behind a 'mountpoint' whilst letting the daemon go past and construct the subtree there. 0 should be returned to let the calling process continue. -EISDIR can be returned to tell pathwalk to use this directory as an ordinary directory and to ignore anything mounted on it and not to check the automount flag. Any other error code will abort pathwalk completely.

d_manage 容許文件系統來管理一個 dentry 的轉換。(可選的)。它容許使用 autofs。例如:掛起一個 等待去探索某 mountpoint 下路徑的客戶端,與此同時讓守護進程經過該掛載點而且在其下構建子樹。返回 0 表示讓掛起的客戶端繼續執行。返回 -EISDIR,表示路徑查找進程應該把該目錄當作普通目錄處理,而且忽略在其上掛載的任何東西,以及不會去檢查 automou 標誌。其它任何錯誤碼錶示應該中斷本次路徑查找。

If the 'rcu_walk' parameter is true, then the caller is doing a pathwalk in RCU-walk mode. Sleeping is not permitted in this mode, and the caller can be asked to leave it and call again by returning -ECHILD. -EISDIR may also be returned to tell pathwalk to ignore d_automount or any mounts.

若是 'rcu_walk' 參數爲 true,那麼調用者的路徑查找處於 RCU 模式。這種模式下是不容許睡眠,因此 調用者被要求離開該函數而且返回 -ECHILD。一樣的,返回 -EISDIR 告訴當前路徑查找忽略 d_automount 或者其餘任何掛載。

This function is only used if DCACHE_MANAGE_TRANSIT is set on the dentry being transited from.

該函數只有當前遍歷的 dentry 被設置了 DCACHE_MANAGE_TRANSIT 標誌是才能使用。

d_hash: called when the VFS adds a dentry to the hash table. The first dentry passed to d_hash is the parent directory that the name is to be hashed into.

VFS 添加一個 dentry 到散列表的時候被調用。第一個參數是父 dentry。

d_compare 用來比較兩個目錄項的文件系統。 d_delete 用來在目錄項的引用計數減到 0 時判斷是否能夠釋放目錄項的內存。 d_release 用來在釋放目錄項的內存以前調用。 d_iput 用來釋放目錄項關聯的索引節點。

2.6 文件的打開實例和打開文件表。

當進程打開一個文件的時候,虛擬文件系統就會建立文件的一個打開實例:file 結構體,主要成員以下:

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */
	spinlock_t		f_lock;
	atomic_long_t		f_count;
	unsigned int 		f_flags;
	fmode_t			f_mode;
	struct mutex		f_pos_lock;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	const struct cred	*f_cred;
	struct file_ra_state	f_ra;

	u64			f_version;
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	struct list_head	f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
} __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */
複製代碼
  1. f_path 存儲文件在目錄樹中的位置,類型以下:
    struct path {
       struct vfsmount *mnt;
        struct dentry *dentry;
    };
    複製代碼
    mnt 指向文件所屬文件系統的掛載描述符的成員 mnt,dentry 是文件對應的目錄項。
  2. f_inode 指向文件的索引節點。
  3. f_op 指向文件操做集合。
  4. f_count 是 file 結構體的引用計數。
  5. f_mode 是訪問模式。
  6. f_pos 是文件偏移,即進程當前正在訪問的位置。
  7. f_mapping 指向文件的地址空間。

文件的打開實例和索引節點的關係如圖:

進程描述符有兩個文件系統相關的成員:成員 fs 指向進程的文件系統信息結構體,主要是進程的根目錄和當前工做目錄;成員 files 指向打開文件表。

[include/linux/sched.h]

struct task_struct{
  ...
  struct fs_struct *fs;
  struct files_struct *files;
  ...
}
複製代碼

文件系統信息結構體的主要成員以下:

[include/linux/fs_struct.h]

struct fs_struct{
    ...
    struct path root, pwd;
  }
複製代碼

成員 root 存儲進程的根目錄,成員 pwd 存儲進程的當前工做目錄。 假設首先調用系統調用 chroot,把目錄「/a」 設置爲進程的根目錄,而後建立子進程,子進程繼承 父進程的文件系統信息,那麼把子進程能看到的目錄範圍限制爲以目錄 「/a」 爲根的子樹。但子進程打開文件 「/b.txt」(文件路徑是絕對路徑,以「/」開頭)時,真實的文件路徑是 「/a/b.txt」。 假設調用系統調用 chdir,把目錄 「/c」 設置爲進程的當前工做目錄,當子進程打開文件 「d.txt」 (文件路徑是相對路徑,不以「/」開頭)時,真實的文件路徑是 「/c/d.txt」。

打開文件表也稱爲文件描述符表,數據結構以下圖:

結構體 files_struct 是打開文件表的包裝器,主要成員以下: [include/linux/fdtable.h]

struct files_struct {
  /* * read mostly part */
	atomic_t count;
	bool resize_in_progress;
	wait_queue_head_t resize_wait;

	struct fdtable __rcu *fdt;
	struct fdtable fdtab;
  /* * written part on a separate cache line in SMP */
	spinlock_t file_lock ____cacheline_aligned_in_smp;
	int next_fd;
	unsigned long close_on_exec_init[1];
	unsigned long open_fds_init[1];
	unsigned long full_fds_bits_init[1];
	struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
複製代碼

成員 count 是結構體 files_struct 的引用計數。 成員 fdt 指向打開文件表。 當進程剛剛建立的時候,成員 fdt 指向成員 fdtab,運行一段時間後,進程打開的文件數量超過了 NR_OPEN_DEFAULT,就會擴大打開文件表,從新分配 fdtable 結構體,成員 fdt 指向新的 fdtable 結構體。 打開文件表的數據以下: [include/linux/fdtable]

struct fdtable {
	unsigned int max_fds;
	struct file __rcu **fd;      /* current fd array */
	unsigned long *close_on_exec;
	unsigned long *open_fds;
	unsigned long *full_fds_bits;
	struct rcu_head rcu;
};
複製代碼

成員 max_fds 是打開文件表的當前大小,即成員 fd 指向的 file 指針數組的大小。隨着進程 打開文件的數量增長,打開文件表逐步擴大。 成員 fd 指向 file 指針數組。當進程調用 open 打開文件的時候,返回的文件描述符是 file 指針 數組的索引。 成員 close_on_exec 指向一個位圖,指示在指向 execve() 以裝載新程序的時候須要關閉哪些文件 描述符。 成員 open_fds 指向文件描述符位圖,指示哪些文件描述符被分配。

2.7 目錄項緩存 Directory Entry Cache (dcache)

The VFS implements the open(2), stat(2), chmod(2), and similar system calls. The pathname argument that is passed to them is used by the VFS to search through the directory entry cache (also known as the dentry cache or dcache). This provides a very fast look-up mechanism to translate a pathname (filename) into a specific dentry. Dentries live in RAM and are never saved to disc: they exist only for performance.

VFS實現 open(2)、stat(2)、chmod(2) 和相似的系統調用。傳遞給這些函數的路徑名參數被 VFS 用來在目錄項緩存(也稱爲 dentry 緩存或 dcache)進行搜索目錄項。這提供了一個很是快速的查找機制,將路徑名(filename)轉換爲一個特定的 dentry。這些 dentry 存在內存中,並無保存到磁盤:它們的存在於只是爲了提升性能。

The dentry cache is meant to be a view into your entire filespace. As most computers cannot fit all dentries in the RAM at the same time, some bits of the cache are missing. In order to resolve your pathname into a dentry, the VFS may have to resort to creating dentries along the way, and then loading the inode. This is done by looking up the inode.

dentry 緩存是指向整個文件空間的視圖。因爲大多數計算機不能同時在 RAM 中容納全部的 dentry,緩存中的一些位丟失了。爲了將路徑名解析爲 dentry, VFS 可能不得不在此過程當中建立 dentry,而後加載 inode。這是經過查找 inode 完成的。

有幾個文件系統用來維護目錄項的函數:

dget:對一個已經存在的 dentry 打開一個新的句柄(handle)(只是增長 dentry 的使用計數)。

dput: 對一個 dentry 關閉一個句柄(減小使用計數)。若是使用計數變爲 0,而且這個 dentry 仍是它父目錄的 hash 表中,那麼 "d_delete" 方法被調用,用來檢查它是否應該被緩存。若是 不該該被緩存,或者該 dentry 沒有被 hash,那麼刪除它。不然把該已緩存的 dentry 移動到 LRU 鏈表中,未來當內存不夠時能夠回收它。

d_drop:它用來從父目錄的 hash 表中 unhashes 一個 dentry。若是此時它的使用計數(usage count) 已經降到 0,隨後調用 dput() 來釋放這個(deallocate) dentry,;

d_delete:刪除一個 dentry。若是它沒有其餘開放的引用,那麼它變成一個 negative dentry, 而後 d_iput() 被調用。若是有其餘引用,那麼調用 d_drop。

d_add: 增長一個 dentry 到它的父目錄的 hash 列表中,而後調用 d_instantiate()。

d_instantiate: 增長一個 dentry 到相應 inode 的 alise hash 鏈表中,而且更新 d_inode 成員。 inode 的 i_count 成員應該被 「set/incremented」。若是該 dentry 的 inode 指針爲空, 那麼該 dentry 稱爲 "negative dentry"。該函數一般在爲 "negative dentry" 建立一個 inode 時被調用。

d_lookup: 給定一個父目錄和路徑名稱份量,而後查找相應的 dentry。它從緩存(decahe)的散列表中 經過給定的名稱查找相應的子項。若是查找到了,增長引用計數並返回 dentry。當完成使用後, 調用者必須調用 dput() 來釋放這個 dentry。

2.8 註冊文件系統類型

由於每種文件系統的超級塊的格式不一樣,因此每種文件系統須要向虛擬文件系統註冊文件系統類型 file_system_type, 實現 mount 方法用來讀取和解析超級塊。 函數 register_filesystem 用來註冊文件系統類型: int register_filesystem(struct file_system_type *fs); 函數 unregister_filesystem 用來註銷文件系統類型: int unregister_filesystem(struct file_system_type *fs); 管理員能夠執行命令 「cat/proc/filesystems」 來查看已經註冊的文件系統類型。

最後給出兩張數據結構圖:第一張基於 Linux 4.4,後一張基於 Linux 3.x 版本。圖片較大,可先點擊放大後再右鍵「在新標籤頁中打開圖片」,可查看高清圖。

虛擬文件系統版本4.x
虛擬文件系統版本3.x

參考:

Linux虛擬文件系統簡介

Linux虛擬文件系統源碼分析

Linux kernel 文檔

《Linux 內核深度解析》餘華兵 著

相關文章
相關標籤/搜索