深入理解 Linux 文件系統之文件系統掛載 -下-

本文爲文件系統掛載專題文章的第二篇,主要介紹如何通過掛載實例關聯掛載點和超級塊並添加到全局文件系統樹。

  1. 添加到全局文件系統樹 =============

4.1 do_new_mount_fc

do_new_mount  //fs/namespace.c
->do_new_mount_fc
 ->     struct vfsmount *mnt;                      
    ->   struct mountpoint *mp;                     
 ->  struct super_block *sb = fc->root->d_sb;  //獲得vfs的超級塊 (之前已經構建好)
 >  mnt = vfs_create_mount(fc);   //爲一個已配置的超級塊 分配mount實例
    ->    mp = lock_mount(mountpoint);  //尋找掛載點 如果掛載目錄是掛載點(已經有文件系統掛載其上),則將最後一次掛載的文件系統根目錄作爲掛載點    
 ->  do_add_mount(real_mount(mnt), mp, mountpoint, mnt_flags);  //關聯掛載點 加入全局文件系統樹

4.2 vfs_create_mount 源碼分析

vfs_create_mount
    ->     mnt = alloc_vfsmnt(fc->source ?: "none");  //分配mount實例 
    ->     mnt->mnt.mnt_sb         = fc->root->d_sb;  //mount關聯超級塊 (使用vfsmount關聯)
    ->    mnt->mnt.mnt_root       = dget(fc->root);   //mount關聯根dentry (使用vfsmount關聯)
    ->    mnt->mnt_mountpoint     = mnt->mnt.mnt_root; // mount關聯掛載點 (臨時指向根dentry,後面會指向真正的掛載點,以至於對用戶可見)
    ->  mnt->mnt_parent         = mnt;  //父掛載指向自己 (臨時指向 後面會設置)              
    ->     return &mnt->mnt;  //返回內嵌的vfsmount

注:老內核使用的是 vfsmount 來描述文件系統的一次掛載,現在內核都使用 mount 來描述,而 vfsmount 被內嵌到 mount 中,主要來描述文件系統的超級塊和跟 dentry。

vfs_create_mount 之後 vfs 對象數據結構之間關係圖如下:

4.3 lock_mount 源碼分析

lock_mount 是最不好理解的函數,下面詳細講解:

-> mp = lock_mount(mountpoint); 

// 不只是加鎖, 通過傳來的 掛載點的 path(vfsmout, dentry 二元組),來查找最後一次掛載的文件系統的根 dentry 作爲即將掛載文件系統的掛載點

我們看下這個函數

-> 這個函數主要從掛載點的 path(即是掛載目錄的 path 結構,如掛載到 / mnt 下, path 爲 mnt 的 path) 來找到真正的掛載點 兩種情況:

  1. 如果掛載點的 path 是正常的目錄,原來不是掛載點,則直接返回這個目錄的 dentry 作爲掛載點(mountpoint 的 m_dentry 會指向掛載點的 dentry)

  2. 如果掛載點的 path 不是正常的目錄,原來就是掛載點,說明這個目錄已經有其他的文件系統掛載,那麼它會查找最後一個掛載到這個目錄的文件系統的根 dentry, 作爲真正的掛載點。

我們打開這個黑匣子看一下:首先傳遞來的 path 是一個表示要解析的掛載目錄 [vfsmount,dentry] 二元組,如我們要掛載到 /mnt  (path 即爲 < mnt 所在文件系統的 vfsmount, mnt 的 dentry>)

//include/linux/path.h  描述一個路徑
struct path { 
        struct vfsmount *mnt;
        struct dentry *dentry;
} __randomize_layout;

 //fs/mount.h       描述一個掛載點
struct mountpoint {       
        struct hlist_node m_hash; 
        struct dentry *m_dentry;  
        struct hlist_head m_list; 
        int m_count;              
};                                


static struct mountpoint *lock_mount(struct path *path)           
{                                                                 
        struct vfsmount *mnt;                                     
        struct dentry *dentry = path->dentry; //獲得掛載目錄的dentry                     
retry:                                                            
        inode_lock(dentry->d_inode);     //寫方式申請 inode的讀寫信號量                        
        if (unlikely(cant_mount(dentry))) { //判斷掛載目錄能否被掛載
                inode_unlock(dentry->d_inode);                    
                return ERR_PTR(-ENOENT);                          
        }                                                         
        namespace_lock();  //寫方式申請 命名空間讀寫信號量                                      
        mnt = lookup_mnt(path); //查找掛載在path上的第一個子mount    //!!!重點函數,後面分析 !!!                               
        if (likely(!mnt)) { // mnt爲空 說明沒有文件系統掛載在這個path上  是我們要找的目標 
    
    //1.如果dentry之前是掛載點 則從mountpoint hash表 查找mountpoint (dentry計算hash)
    // 2. 如果dentry之前不是掛載點 分配mountpoint 加入mountpoint hash表(dentry計算hash),設置dentry爲掛載點
                struct mountpoint *mp = get_mountpoint(dentry); //!!!重點函數,後面會分析 !!!
      
                if (IS_ERR(mp)) {                                 
                        namespace_unlock();                       
                        inode_unlock(dentry->d_inode);            
                        return mp;                                
                }                                                 
                return mp; //返回找到的掛載點實例 (這個掛載點的dentry之前沒有被掛載)                                      
        }
        namespace_unlock(); //釋放命名空間讀寫信號量
        inode_unlock(path->dentry->d_inode); //釋放 inode的讀寫信號量 
        path_put(path);
        path->mnt = mnt; // path->mnt指向找到的vfsmount                                          
        dentry = path->dentry = dget(mnt->mnt_root); //path->dentry指向找到的vfsmount的根dentry!
        goto retry; //繼續查找下一個掛載
}

1)get_mountpoint 源碼分析

static struct mountpoint *get_mountpoint(struct dentry *dentry)                      
{                                                                                    
        struct mountpoint *mp, *new = NULL;                                          
        int ret;                                                                     
                                                                                     
        if (d_mountpoint(dentry)) {  //dentry爲掛載點 (當dentry爲掛載點時 會設置dentry->d_flags 的DCACHE_MOUNTED標誌)                                                
                /* might be worth a WARN_ON() */                                     
                if (d_unlinked(dentry))                                              
                        return ERR_PTR(-ENOENT);                                     
mountpoint:                                                                          
                read_seqlock_excl(&mount_lock);                                      
                mp = lookup_mountpoint(dentry); // 從mountpoint hash表 查找mountpoint (dentry計算hash)                                    
                read_sequnlock_excl(&mount_lock);                                    
                if (mp)                                                              
                        goto done;  //找到直接返回mountpoint實例                                               
        }                                                                            
                                                                                     
        if (!new)    //mountpoint哈希表中沒有找到    則分配                                                             
                new = kmalloc(sizeof(struct mountpoint), GFP_KERNEL);             
        if (!new)                                                                    
                return ERR_PTR(-ENOMEM);                                             
                                                                                     
                                                                                     
        /* Exactly one processes may set d_mounted */                                
        ret = d_set_mounted(dentry);   //設置dentry爲掛載點
   ->dentry->d_flags |= DCACHE_MOUNTED;  //設置掛載點標誌很重要  路徑名查找時發現爲掛載點則會步進到相關文件系統的跟dentry
                                                                                     
        /* Someone else set d_mounted? */                                            
        if (ret == -EBUSY)                                                           
                goto mountpoint;                                                     
                                                                                     
        /* The dentry is not available as a mountpoint? */                           
        mp = ERR_PTR(ret);                                                           
        if (ret)                                                                     
                goto done;                                                           
                                                              
        /* Add the new mountpoint to the hash table */        
        read_seqlock_excl(&mount_lock);                       
        new->m_dentry = dget(dentry);  //設置mountpoint實例  的m_dentry 指向dentry                       
        new->m_count = 1;                                      
        hlist_add_head(&new->m_hash, mp_hash(dentry));   // mountpoint實例添加到 mountpoint_hashtable     
        INIT_HLIST_HEAD(&new->m_list);  //初始化 掛載鏈表   mount實例會加入到這個鏈表                      
        read_sequnlock_excl(&mount_lock);                     
                                                              
        mp = new;   //指向掛載點                                          
        new = NULL;                                           
done:                                                         
        kfree(new);                                           
        return mp;  //返回掛載點                                           
}

2)lookup_mnt 源碼分析

它在文件系統掛載和路徑名查找都會使用到,作用爲查找掛載在這個 path 下的第一個子 vfsmount 實例。

-> 文件系統掛載場景中,使用它查找合適的 vfsmount 實例作爲父 vfsmount。        

-> 路徑名查找場景中,使用它查找一個合適的 vfsmount 實例作爲下一級路徑名解析起點的 vfsmount。

//fs/namespace.c
lookup_mnt(const struct path *path)
->  struct mount *child_mnt;
 struct vfsmount *m;     
 child_mnt = __lookup_mnt(path->mnt, path->dentry);  //委託__lookup_mnt
 m = child_mnt ? &child_mnt->mnt : NULL; //返回mount實例的vfsmount實例 或NULL            
 return m;

->
struct mount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)        
{               
        struct hlist_head *head = m_hash(mnt, dentry);  // 根據 父vfsmount實例 和 掛載點的dentry查找  mount_hashtable的一個哈希表項
        struct mount *p;
        
        hlist_for_each_entry_rcu(p, head, mnt_hash)  //從哈希表項對應的鏈表中查找   遍歷鏈表的每個節點
                if (&p->mnt_parent->mnt == mnt && p->mnt_mountpoint == dentry) //節點的mount實例的父mount爲mnt 且mount實例的掛載點爲 dentry
                        return p; //找到返回mount實例
        return NULL;  //沒找到返回NULL
}

3)lock_mount 情景分析

1)lock_mount 傳遞的 path 之前不是掛載點:

調用鏈爲:

lock_mount 

  ->mnt = lookup_mnt(path) // 沒有子 mount 返回 NULL

  ->mp = get_mountpoint(dentry) // 分配 mountpoint 加入 mountpoint hash 表(dentry 計算 hash), 設置 dentry 爲掛載點

  ->return mp  // 返回找到的掛載點實例

2)lock_mount 傳遞的 path 之前是掛載點:

我們現在執行  mount -t ext2 /dev/sda4 /mnt

之前 /mnt 的掛載情況

mount /dev/sda1 /mnt   (1) 

mount /dev/sda2 /mnt   (2) 

mount /dev/sda3 /mnt (3)

調用鏈爲:

lock_mount 

  ->mnt = lookup_mnt(path) // 返回(1)的 mount 實例

  ->path->mnt = mnt  // 下一次查找的 path->mnt 賦值(1)的 mount 實例

  ->dentry = path->dentry = dget(mnt->mnt_root) // // 下一次查找 path->dentry 賦值(1)的根 dentry

  ->mnt = lookup_mnt(path) // 返回(2)的 mount 實例 

  ->path->mnt = mnt  // 下一次查找的  path->mnt 賦值(2)的 mount 實例

  ->dentry = path->dentry = dget(mnt->mnt_root) // // 下一次查找 path->dentry 賦值(2)的根 dentry

  ->mnt = lookup_mnt(path) // 返回(3)的 mount 實例

  ->path->mnt = mnt  // 下一次查找的  path->mnt 賦值(3)的 mount 實例

  ->dentry = path->dentry = dget(mnt->mnt_root) // // 下一次查找 path->dentry 賦值(3)的根 dentry

-> mnt = lookup_mnt(path) // 沒有子 mount 返回 NULL

->mp = get_mountpoint(dentry) // 分配 mountpoint 加入 mountpoint hash 表(dentry 計算 hash), 設置 dentry 爲掛載點((3)的根 dentry 作爲掛載點)

->return mp  // 返回找到的掛載點實例 (也就是最後一次掛載(3) 文件系統的根 dentry)

4.4 do_add_mount 源碼分析

準備好了掛載點之後,接下來子 mount 實例關聯掛載點以及添加子 mount 實例到全局的文件系統掛載樹中。

do_add_mount  //添加mount到全局的文件系統掛載樹中
->struct mount *parent = real_mount(path->mnt); //獲得父掛載點的掛載實例
->graft_tree(newmnt, parent, mp)
 -> mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt)
  ->   child_mnt->mnt_mountpoint = mp->m_dentry;   //關聯子mount到掛載點的dentry                 
    child_mnt->mnt_parent = mnt; //子mount->mnt_parent指向父mount
    child_mnt->mnt_mp = mp; //子mount->mnt_mp指向掛載點
    hlist_add_head(&child_mnt->mnt_mp_list, &mp->m_list); //mount添加到掛載點鏈表

 ->commit_tree //提交掛載樹
  ->__attach_mnt(mnt, parent)
   ->  hlist_add_head_rcu(&mnt->mnt_hash,
       ¦  m_hash(&parent->mnt, mnt->mnt_mountpoint));  //添加到mount hash表 ,通過父掛載點的vfsmount和掛載點的dentry作爲索引(如上面示例中的<(3)的vfsmount , (3)的根dentry>)
    list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);  //添加到父mount鏈表

上面說了一大堆,主要爲了實現:

將 mount 實例與掛載點聯繫起來(會將 mount 實例加入到 mount 哈希表,父文件系統的 vfsmount 和真正的掛載點的 dentry 組成的二元組爲索引,路徑名查找時便於查找),以及 mount 實例與文件系統的跟 dentry 聯繫起來(路徑名查找的時候便於沿着跟 dentry 來訪問這個文件系統的所有文件)。

do_add_mount  之後 vfs 對象數據結構之間關係圖(/mnt 之前不是掛載點情況)如下:

  1. mount 的應用 ============

上面幾章我們分析了文件系統掛載的主要流程,創建並關聯了各個 vfs 的對象,爲了打開文件等路徑名查找時做準備。

5.1 路徑名查找到掛載點源碼分析

//fs/namei.c 查找一個路徑分量
walk_component  
->step_into
 ->handle_mounts
  ->traverse_mounts
   ->flags = smp_load_acquire(&path->dentry->d_flags); //獲得dentry標誌
   ->__traverse_mounts(path, flags, jumped, count, lookup_flags); //查找掛載點  返回不再是掛載點的path
     -> {
     
       while (flags & DCACHE_MANAGED_DENTRY) {    //找到的dentry是掛載點 則繼續查找 ,不是則退出循環
        ...
        if (flags & DCACHE_MOUNTED) {   // something's mounted on it..  是掛載點 和上面lock_mount分析邏輯類似 
         //不斷的查找掛載點  直到最後查找到的dentry不是掛載點 退出循環
         struct vfsmount *mounted = lookup_mnt(path);  //查找第一個子mount          
         if (mounted) {          // ... in our namespace         
           dput(path->dentry);                            
           if (need_mntput)
             mntput(path->mnt);
           path->mnt = mounted; //path->mnt賦值子mount
           path->dentry = dget(mounted->mnt_root); //path->dentry 賦值子mount的根dentry (是掛載點就會步進到掛載點的跟dentry)
           // here we know it's positive
           flags = path->dentry->d_flags; //獲得dentry標誌
           need_mntput = true;
           continue; //繼續查找
        }
        ...

       }
       
       //最終返回找到的path(它不再是掛載點),後面繼續查找下一個路徑
      }

5.2 舉例說明

我們做以下的路徑名查找:/mnt/test/text.txt

/mnt/ 目錄掛載情況爲

mount /dev/sda1 /mnt   (1)

mount /dev/sda2 /mnt  (2)

mount /dev/sda3 /mnt  (3)

test/text.txt 文件在 /dev/sda3 上

則路徑名查找時,查找到 mnt 的 dentry 發現它是掛載點,就會依次查找(1)的根目錄 ->(2)的根目錄 ->(3)的根目錄, 最終將(3)的 vfsmount 和 根目錄的 dentry 填寫到 path,進行下一步的查找, 最終查找到 /dev/sda3 上的 text.txt 文件。

注:一個目錄被文件系統掛載時,原來目錄中包含的其他子目錄或文件會被隱藏。

  1. 掛載圖解 =======

爲了便於講解圖示中各個實例表示如下:

Xyn   --->  X 表示哪個實例對象   如:mount 實例使用 M 表示(第一個大小字母) dentry 使用 D 表示  inode 使用 I 表示  super_block 用 S 表示 vfsmount 使用 V 表示

                y 表示是父文件系統中的實例對象還是子文件系統中  如:p(parent)表示父文件系統中實例對象  c(child)表示子文件系統中實例對象

                n 區分同一種對象的不同實例

例如:Dc1 表示子文件系統中一個 dentry 對象

1)mount、super_block、file_system_type 三者關係圖解

解釋:mount 實例、super_block 實例、file_system_type 實例三種層級逐漸升高,即一個 file_system_type 實例會包含多個 super_block 實例,一個 super_block 實例會包含多個 mount 實例。一種 file_system_type 必須先被註冊到系統中來宣誓這種文件系統存在,主要提供此類文件系統的掛載和卸載方法等,註冊即是加入全局的 file_systems 鏈表,等到有塊設備上的文件系統要掛載時就會根據掛載時傳遞的文件系統類型名查找 file_system_type 實例,如果查找到,就會調用它的掛載方法進行掛載。首先,在 file_systems 實例的 super_block 鏈表中查找有沒有 super_block 實例已經被創建,如果有就不需要從磁盤讀取(這就是一個塊設備上的文件系統掛載到多個目錄上只有一個 super_block 實例的原因),如果沒有從磁盤讀取並加入對應的 file_systems 實例的 super_block 鏈表。而每次掛載都會創建一個 mount 實例來聯繫掛載點和 super_block 實例,並以(父 vfsmount, 掛載點 dentry)爲索引加入到全局 mount 哈希表,便於後面訪問這個掛載點的文件系統時的路徑名查找。

2)父子文件系統掛載關係圖解

解釋:圖中 / dev/sda1 中的子文件系統掛載到父文件系統的 / mnt 目錄下。當掛載的時候會創建 mount、super_block、跟 inode、跟 dentry 四大數據結構並建立相互關係,將子文件系統的 mount 加入到 (Vp, Dp3) 二元組爲索引的 mount 哈希表中,通過設置 mnt 的目錄項(Dp3)的 DCACHE_MOUNTED 來將其標記爲掛載點,並與父文件系統建立親緣關係掛載就完成了。

當需要訪問子文件系統中的某個文件時,就會通過路徑名各個分量解析到 mnt 目錄,發現其爲掛載點,就會通過 (Vp, Dp3) 二元組在 mount 哈希表中找到子文件系統的 mount 實例(Mc), 然後就會從子文件系統的跟 dentry(Dc1)開始往下繼續查找,最終訪問到子文件系統上的文件。

2)單個文件系統多掛載點關係圖解

解釋:圖中將 / dev/sda1 中的文件系統分別掛載到父文件系統的 / mnt/a 和 / mnt/b 目錄下。當第一次掛載到 / mnt/a 時,會創建 mount、super_block、跟 inode、跟 dentry 四大數據結構 (分別對應與 Mc1、Sc、Dc1、Ic) 並建立相互關係,將子文件系統的 Mc1 加入到 (Vp, Dp3) 二元組爲索引的 mount 哈希表中,通過設置 / mnt/a 的目錄項的 DCACHE_MOUNTED 來將其標記爲掛載點,並與父文件系統建立親緣關係掛載就完成了。然後掛載到 / mnt/b 時, Sc、Dc1、Ic 已經創建好不需要再創建,內存中只會有一份,會創建 Mc2 來關聯 super_block 和第二次的掛載點,建立這幾個數據結構關係,將子文件系統的 Mc2 加入到 (Vp, Dp4) 二元組爲索引的 mount 哈希表中,通過設置 / mnt/b 的目錄項的 DCACHE_MOUNTED 來將其標記爲掛載點,並與父文件系統建立親緣關係掛載就完成了。

當需要訪問子文件系統中的某個文件時,就會通過路徑名各個分量解析到 / mnt/a 目錄,發現其爲掛載點,就會通過 (Vp, Dp3) 在 mount 哈希表中找到子文件系統的 Mc1, 然後就會從子文件系統的 Dc1 開始往下繼續查找,最終訪問到子文件系統上的文件。同樣,如果解析到 / mnt/b 目錄, 發現其爲掛載點,就會通過 (Vp, Dp4) 在 mount 哈希表中找到子文件系統的 Mc2, 然後就會從子文件系統的 Dc1 開始往下繼續查找,最終訪問到子文件系統上的文件。可以發現,同一個塊設備上的文件系統掛載到不同的目錄上,相關聯的 super_block 和跟 dentry 是一樣的,這保證了無論從哪個掛載點開始路徑名查找都訪問到的是同一個文件系統上的文件。

3)多文件系統單掛載點關係圖解

解釋:最後我們來看多文件系統單掛載點的情況,圖中先將塊設備 / dev/sda1 中的子文件系統 1 掛載到 / mnt 目錄,然後再將塊設備 / dev/sdb1 中的子文件系統 2 掛載到 / mnt 目錄上。

當子文件系統 1 掛載的時候,會創建 mount、super_block、跟 inode、跟 dentry 四大數據結構 (分別對應與 Mc1、Sc1、Dc1、Ic1) 並建立相互關係,將子文件系統的 Mc1 加入到 (Vp, Dp3) 二元組爲索引的 mount 哈希表中,通過設置 / mnt 的目錄項的 DCACHE_MOUNTED 來將其標記爲掛載點,並與父文件系統建立親緣關係掛載就完成了。

當子文件系統 2 掛載的時候,會創建 mount、super_block、跟 inode、跟 dentry 四大數據結構 (分別對應與 Mc2、Sc2、Dc4、Ic2) 並建立相互關係,這個時候會發現 / mnt 目錄是掛載點,則會將子文件系統 1 的根目錄(Dc1)作爲文件系統 2 的掛載點,將子文件系統的 Mc2 加入到 (Vc1, Dc1) 二元組爲索引的 mount 哈希表中,通過設置 Dc1 的 DCACHE_MOUNTED 來將其標記爲掛載點,並與父文件系統建立親緣關係掛載就完成了。

這個時候,子文件系統 1 已經被子文件系統 2 隱藏起來了,當路徑名查找到 / mnt 目錄時,發現其爲掛載點,則通過 (Vp, Dp3) 二元組爲索引在 mount 哈希表中找到 Mc1,會轉向文件系統 1 的跟目錄(Dc1)開始往下繼續查找,發現 Dc1 也是掛載點,則 (通過 Vc1, Dc1) 二元組爲索引在 mount 哈希表中找到 Mc2, 會轉向文件系統 1 的跟目錄(Dc4)開始往下繼續查找,於是就訪問到了文件系統 2 中的文件。除非,文件系統 2 被卸載,文件系統 1 的跟 dentry(Dc1)不再是掛載點,這個時候文件系統 1 中的文件才能再次被訪問到。

  1. 總結 =====

Linux 中,塊設備上的文件系統只有掛載到內存的目錄樹中的一個目錄下,用戶進程才能訪問,而掛載是創建數據結構關聯塊設備上的文件系統和掛載點,使得路徑名查找的時候能夠通過掛載點目錄訪問到掛載在其下的文件系統。

7.1 掛載主要步驟

1.vfs_get_tree 調用具體文件系統的獲取填充超級塊方法 (fs_context_operations.get_tree 或者 file_system_type.mount), 在內存構建 super_block,然後構建根 inode 和根 dentry(磁盤文件系統可能需要從磁盤讀取磁盤超級塊構建內存的 super_block,從磁盤讀取根 inode 構建內存的 inode)。2.do_new_mount_fc 對於每次掛載都會分配 mount 實例,用於關聯掛載點到文件系統。當一個要掛載的目錄不是掛載點,會設置這個目錄的 dentry 爲掛載點,然後 mount 實例記錄這個掛載點。當一個要掛載的目錄是掛載點(之前已經有文件系統被掛載到這個目錄),那麼新掛載的文件系統將掛載到這個目錄最後一次掛載的文件系統的根 dentry, 之前掛載的文件系統的文件都被隱藏(當子掛載被卸載,原來的文件系統的文件纔可見)。

7.2 文件系統的用戶可見性

**只對內核內部可見:**不需要將文件系統關聯到一個掛載點,內核通過文件系統的 super_block 等結構即可訪問到文件系統的文件(如 bdev,sockfs)。

**對於用戶可見:**需要將文件系統關聯到一個掛載點,就需要通過給定的掛載點目錄名找到真正的掛載點,然後進行掛載操作, 掛載的實質是:通過 mount 實例的 mnt_mountpoint 關聯真正的掛載點 dentry, 然後建立父 mount 關係,mount 實例加入到全局的 mount hash table(通過父 vfsmount 和真正的掛載點 dentry 作爲 hash 索引),然後用戶打開文件的時候通過路徑名查找解析各個目錄分量,當發現一個目錄是掛載點時,就會步進到最後一次掛載到這個目錄的文件系統的根 dentry 中繼續查找,知道根 dentry 就可以繼續查找到這個文件系統的任何文件。

7.3 幾條重要規律

1)文件系統被掛載後都會有以下幾大 vfs 對象被創建:

super_block
mount

根 inode 

根 dentry

注:其中 mount 爲純軟件構造的對象(內嵌 vfsmount 對象),其他對象視文件系統類型,可能涉及到磁盤操作。

super_block    超級塊實例,描述一個文件系統的信息,有的需要磁盤讀取在內存中填充來構建(如磁盤文件系統),有的直接內存中填充來構建。

mount     掛載實例,描述一個文件系統的一次掛載,主要關聯一個文件系統到掛載點,爲路徑名查找做重要準備工作。

根 inode    每個文件系統都會有根 inode,有的需要磁盤讀取在內存中填充來構建(如磁盤文件系統,根 inode 號已知),有的直接內存中填充來構建。

根 dentry   每個文件系統都會有根 dentry,根據根 inode 來構建,路徑名查找時會步進到文件系統的根 dentry 來訪問這個文件系統的文件。

2)一個目錄可以被多個文件系統掛載。第一次掛載是直接掛載這個目錄上,新掛載的文件系統實際上是掛載在上一個文件系統的根 dentry 上。

3)一個目錄被多個文件系統掛載時,新掛載導致之前的掛載被隱藏。

4)一個目錄被文件系統掛載時,原來目錄中包含的其他子目錄或文件被隱藏。

5)每次掛載都會有一個 mount 實例描述本次掛載。

6)一個快設備上的文件系統可以被掛載到多個目錄,有多個 mount 實例,但是隻會有一個 super_block、根 dentry 和根 inode。

7)mount 實例用於關聯掛載點 dentry 和文件系統,起到路徑名查找時 “路由” 的作用。

8)掛載一個文件系統必須保證所要掛載的文件系統類型已經被註冊。

9)掛載時會查詢文件系統類型的 fs_type->fs_supers 鏈表,檢查是否已經有 super_block 被加入鏈表,如果沒有才會分配並讀磁盤超級塊填充。

10)對象層次:一個 fs_type->fs_supers 鏈表可以掛接屬於同一個文件系統的被掛載的超級塊,超級塊鏈表可以掛接屬於同一個超級塊的 mount 實例 fs_type ->  super_block -> mount 從高到低的包含層次。

參考文檔: 《存儲技術原理分析  基於 Linux2.6 內核源代碼》

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/H1usV8e4QoeIPsuMARkwfg