深入剖析Linux文件系統(tǒng)之文件系統(tǒng)掛載(二)(超詳細(xì)~)
本文為文件系統(tǒng)掛載專題文章的第二篇,主要介紹如何通過掛載實例關(guān)聯(lián)掛載點和超級塊并添加到全局文件系統(tǒng)樹。
深入剖析Linux文件系統(tǒng)之文件系統(tǒng)掛載(一)(超詳細(xì)~)
4. 添加到全局文件系統(tǒng)樹
4.1 do_new_mount_fc
4.2 vfs_create_mount源碼分析
注:老內(nèi)核使用的是vfsmount來描述文件系統(tǒng)的一次掛載,現(xiàn)在內(nèi)核都使用mount來描述,而vfsmount被內(nèi)嵌到mount中,主要來描述文件系統(tǒng)的超級塊和跟dentry。
vfs_create_mount之后vfs對象數(shù)據(jù)結(jié)構(gòu)之間關(guān)系圖如下:

【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!?。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ? ?


lock_mount是最不好理解的函數(shù),下面詳細(xì)講解:
-> mp = lock_mount(mountpoint);
//不只是加鎖, 通過傳來的 掛載點的 path(vfsmout, dentry二元組),來查找最后一次掛載的文件系統(tǒng)的根dentry作為即將掛載文件系統(tǒng)的掛載點
我們看下這個函數(shù)
-> 這個函數(shù)主要從掛載點的path(即是掛載目錄的path結(jié)構(gòu),如掛載到/mnt下, path為mnt的path) 來找到真正的掛載點 兩種情況:
1.如果掛載點的path 是正常的目錄,原來不是掛載點,則直接返回這個目錄的dentry作為掛載點(mountpoint的m_dentry會指向掛載點的dentry)
2.如果掛載點的path不是正常的目錄,原來就是掛載點,說明這個目錄已經(jīng)有其他的文件系統(tǒng)掛載,那么它會查找最后一個掛載到這個目錄的文件系統(tǒng)的根dentry,作為真正的掛載點。
我們打開這個黑匣子看一下:首先傳遞來的path 是一個表示要解析的掛載目錄[vfsmount,dentry]二元組,如我們要掛載到 /mnt (path即為<mnt所在文件系統(tǒng)的vfsmount, mnt的dentry>)
1)get_mountpoint源碼分析
2)lookup_mnt源碼分析
它在文件系統(tǒng)掛載和路徑名查找都會使用到,作用為查找掛載在這個path下的第一個子vfsmount實例。
->文件系統(tǒng)掛載場景中,使用它查找合適的vfsmount實例作為父vfsmount。
->路徑名查找場景中,使用它查找一個合適的vfsmount實例作為下一級路徑名解析起點的vfsmount。
3)lock_mount情景分析
1)lock_mount傳遞的path 之前不是掛載點:
調(diào)用鏈為:
lock_mount ->mnt = lookup_mnt(path) //沒有子mount 返回NULL ->mp = get_mountpoint(dentry) //分配mountpoint 加入mountpoint hash表(dentry計算hash),設(shè)置dentry為掛載點 ->return mp //返回找到的掛載點實例
2)lock_mount傳遞的path 之前是掛載點:我們現(xiàn)在執(zhí)行 mount -t ext2 /dev/sda4 /mnt
之前 /mnt的掛載情況
mount /dev/sda1 /mnt (1)?
mount /dev/sda2 /mnt (2)?
mount /dev/sda3 /mnt (3)
調(diào)用鏈為:
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),設(shè)置dentry為掛載點((3)的根dentry作為掛載點)?
->return mp //返回找到的掛載點實例(也就是最后一次掛載(3) 文件系統(tǒng)的根dentry)
4.4 do_add_mount源碼分析
準(zhǔn)備好了掛載點之后,接下來子mount實例關(guān)聯(lián)掛載點以及添加子mount實例到全局的文件系統(tǒng)掛載樹中。
上面說了一大堆,主要為了實現(xiàn):
將mount實例與掛載點聯(lián)系起來(會將mount實例加入到mount 哈希表,父文件系統(tǒng)的vfsmount和真正的掛載點的dentry組成的二元組為索引,路徑名查找時便于查找),以及mount實例與文件系統(tǒng)的跟dentry聯(lián)系起來(路徑名查找的時候便于沿著跟dentry來訪問這個文件系統(tǒng)的所有文件)。
do_add_mount 之后vfs對象數(shù)據(jù)結(jié)構(gòu)之間關(guān)系圖(/mnt之前不是掛載點情況)如下:

5. mount的應(yīng)用
上面幾章我們分析了文件系統(tǒng)掛載的主要流程,創(chuàng)建并關(guān)聯(lián)了各個vfs的對象,為了打開文件等路徑名查找時做準(zhǔn)備。
5.1 路徑名查找到掛載點源碼分析
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發(fā)現(xiàn)它是掛載點,就會依次查找(1)的根目錄->(2)的根目錄 ->(3)的根目錄, 最終將(3)的vfsmount和 根目錄的dentry 填寫到path,進行下一步的查找, 最終查找到/dev/sda3 上的text.txt文件。
注:一個目錄被文件系統(tǒng)掛載時,原來目錄中包含的其他子目錄或文件會被隱藏。
6. 掛載圖解
為了便于講解圖示中各個實例表示如下:
Xyn ---> X表示哪個實例對象 如:mount實例使用M表示(第一個大小字母) dentry使用D表示 inode使用I表示 super_block用S表示 vfsmount使用V表示 y表示是父文件系統(tǒng)中的實例對象還是子文件系統(tǒng)中 如:p(parent)表示父文件系統(tǒng)中實例對象 c(child)表示子文件系統(tǒng)中實例對象 n 區(qū)分同一種對象的不同實例 例如:Dc1 表示子文件系統(tǒng)中一個dentry對象
1)mount、super_block、file_system_type三者關(guān)系圖解

解釋:mount實例、super_block實例、file_system_type實例三種層級逐漸升高,即一個file_system_type實例會包含多個super_block實例,一個super_block實例會包含多個mount實例。一種file_system_type必須先被注冊到系統(tǒng)中來宣誓這種文件系統(tǒng)存在,主要提供此類文件系統(tǒng)的掛載和卸載方法等,注冊即是加入全局的file_systems鏈表,等到有塊設(shè)備上的文件系統(tǒng)要掛載時就會根據(jù)掛載時傳遞的文件系統(tǒng)類型名查找file_system_type實例,如果查找到,就會調(diào)用它的掛載方法進行掛載。首先,在file_systems實例的super_block鏈表中查找有沒有super_block實例已經(jīng)被創(chuàng)建,如果有就不需要從磁盤讀取(這就是一個塊設(shè)備上的文件系統(tǒng)掛載到多個目錄上只有一個super_block實例的原因),如果沒有從磁盤讀取并加入對應(yīng)的file_systems實例的super_block鏈表。而每次掛載都會創(chuàng)建一個mount實例來聯(lián)系掛載點和super_block實例,并以(父vfsmount,掛載點dentry)為索引加入到全局mount哈希表,便于后面訪問這個掛載點的文件系統(tǒng)時的路徑名查找。
2)父子文件系統(tǒng)掛載關(guān)系圖解

解釋:圖中/dev/sda1中的子文件系統(tǒng)掛載到父文件系統(tǒng)的/mnt目錄下。當(dāng)掛載的時候會創(chuàng)建mount、super_block、跟inode、跟dentry四大數(shù)據(jù)結(jié)構(gòu)并建立相互關(guān)系,將子文件系統(tǒng)的mount加入到(Vp, Dp3)二元組為索引的mount哈希表中,通過設(shè)置mnt的目錄項(Dp3)的DCACHE_MOUNTED來將其標(biāo)記為掛載點,并與父文件系統(tǒng)建立親緣關(guān)系掛載就完成了。
當(dāng)需要訪問子文件系統(tǒng)中的某個文件時,就會通過路徑名各個分量解析到mnt目錄,發(fā)現(xiàn)其為掛載點,就會通過(Vp, Dp3)二元組在mount哈希表中找到子文件系統(tǒng)的mount實例(Mc),然后就會從子文件系統(tǒng)的跟dentry(Dc1)開始往下繼續(xù)查找,最終訪問到子文件系統(tǒng)上的文件。
3)單個文件系統(tǒng)多掛載點關(guān)系圖解

解釋:圖中將/dev/sda1中的文件系統(tǒng)分別掛載到父文件系統(tǒng)的/mnt/a和/mnt/b目錄下。當(dāng)?shù)谝淮螔燧d到/mnt/a時,會創(chuàng)建mount、super_block、跟inode、跟dentry四大數(shù)據(jù)結(jié)構(gòu)(分別對應(yīng)與Mc1、Sc、Dc1、Ic)并建立相互關(guān)系,將子文件系統(tǒng)的Mc1加入到(Vp, Dp3)二元組為索引的mount哈希表中,通過設(shè)置/mnt/a的目錄項的DCACHE_MOUNTED來將其標(biāo)記為掛載點,并與父文件系統(tǒng)建立親緣關(guān)系掛載就完成了。然后掛載到/mnt/b時, Sc、Dc1、Ic已經(jīng)創(chuàng)建好不需要再創(chuàng)建,內(nèi)存中只會有一份,會創(chuàng)建Mc2來關(guān)聯(lián)super_block和第二次的掛載點,建立這幾個數(shù)據(jù)結(jié)構(gòu)關(guān)系,將子文件系統(tǒng)的Mc2加入到(Vp, Dp4)二元組為索引的mount哈希表中,通過設(shè)置/mnt/b的目錄項的DCACHE_MOUNTED來將其標(biāo)記為掛載點,并與父文件系統(tǒng)建立親緣關(guān)系掛載就完成了。
當(dāng)需要訪問子文件系統(tǒng)中的某個文件時,就會通過路徑名各個分量解析到/mnt/a目錄,發(fā)現(xiàn)其為掛載點,就會通過(Vp, Dp3)在mount哈希表中找到子文件系統(tǒng)的Mc1,然后就會從子文件系統(tǒng)的Dc1開始往下繼續(xù)查找,最終訪問到子文件系統(tǒng)上的文件。同樣,如果解析到/mnt/b目錄,發(fā)現(xiàn)其為掛載點,就會通過(Vp, Dp4)在mount哈希表中找到子文件系統(tǒng)的Mc2,然后就會從子文件系統(tǒng)的Dc1開始往下繼續(xù)查找,最終訪問到子文件系統(tǒng)上的文件??梢园l(fā)現(xiàn),同一個塊設(shè)備上的文件系統(tǒng)掛載到不同的目錄上,相關(guān)聯(lián)的super_block和跟dentry是一樣的,這保證了無論從哪個掛載點開始路徑名查找都訪問到的是同一個文件系統(tǒng)上的文件。
3)多文件系統(tǒng)單掛載點關(guān)系圖解

解釋:最后我們來看多文件系統(tǒng)單掛載點的情況,圖中先將塊設(shè)備/dev/sda1中的子文件系統(tǒng)1掛載到/mnt目錄,然后再將塊設(shè)備/dev/sdb1中的子文件系統(tǒng)2掛載到/mnt目錄上。
當(dāng)子文件系統(tǒng)1掛載的時候,會創(chuàng)建mount、super_block、跟inode、跟dentry四大數(shù)據(jù)結(jié)構(gòu)(分別對應(yīng)與Mc1、Sc1、Dc1、Ic1)并建立相互關(guān)系,將子文件系統(tǒng)的Mc1加入到(Vp, Dp3)二元組為索引的mount哈希表中,通過設(shè)置/mnt的目錄項的DCACHE_MOUNTED來將其標(biāo)記為掛載點,并與父文件系統(tǒng)建立親緣關(guān)系掛載就完成了。
當(dāng)子文件系統(tǒng)2掛載的時候,會創(chuàng)建mount、super_block、跟inode、跟dentry四大數(shù)據(jù)結(jié)構(gòu)(分別對應(yīng)與Mc2、Sc2、Dc4、Ic2)并建立相互關(guān)系,這個時候會發(fā)現(xiàn)/mnt目錄是掛載點,則會將子文件系統(tǒng)1的根目錄(Dc1)作為文件系統(tǒng)2的掛載點,將子文件系統(tǒng)的Mc2加入到(Vc1, Dc1)二元組為索引的mount哈希表中,通過設(shè)置Dc1的DCACHE_MOUNTED來將其標(biāo)記為掛載點,并與父文件系統(tǒng)建立親緣關(guān)系掛載就完成了。
這個時候,子文件系統(tǒng)1已經(jīng)被子文件系統(tǒng)2隱藏起來了,當(dāng)路徑名查找到/mnt目錄時,發(fā)現(xiàn)其為掛載點,則通過(Vp, Dp3)二元組為索引在mount哈希表中找到Mc1,會轉(zhuǎn)向文件系統(tǒng)1的跟目錄(Dc1)開始往下繼續(xù)查找,發(fā)現(xiàn)Dc1也是掛載點,則(通過Vc1, Dc1)二元組為索引在mount哈希表中找到Mc2, 會轉(zhuǎn)向文件系統(tǒng)1的跟目錄(Dc4)開始往下繼續(xù)查找,于是就訪問到了文件系統(tǒng)2中的文件。除非,文件系統(tǒng)2被卸載,文件系統(tǒng)1的跟dentry(Dc1)不再是掛載點,這個時候文件系統(tǒng)1中的文件才能再次被訪問到。
7. 總結(jié)
Linux中,塊設(shè)備上的文件系統(tǒng)只有掛載到內(nèi)存的目錄樹中的一個目錄下,用戶進程才能訪問,而掛載是創(chuàng)建數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)塊設(shè)備上的文件系統(tǒng)和掛載點,使得路徑名查找的時候能夠通過掛載點目錄訪問到掛載在其下的文件系統(tǒng)。
7.1 掛載主要步驟
1.vfs_get_tree 調(diào)用具體文件系統(tǒng)的獲取填充超級塊方法(fs_context_operations.get_tree或者file_system_type.mount), 在內(nèi)存構(gòu)建super_block,然后構(gòu)建根inode和根dentry(磁盤文件系統(tǒng)可能需要從磁盤讀取磁盤超級塊構(gòu)建內(nèi)存的super_block,從磁盤讀取根inode構(gòu)建內(nèi)存的inode)。2.do_new_mount_fc 對于每次掛載都會分配mount實例,用于關(guān)聯(lián)掛載點到文件系統(tǒng)。當(dāng)一個要掛載的目錄不是掛載點,會設(shè)置這個目錄的dentry為掛載點,然后mount實例記錄這個掛載點。當(dāng)一個要掛載的目錄是掛載點(之前已經(jīng)有文件系統(tǒng)被掛載到這個目錄),那么新掛載的文件系統(tǒng)將掛載到這個目錄最后一次掛載的文件系統(tǒng)的根dentry,之前掛載的文件系統(tǒng)的文件都被隱藏(當(dāng)子掛載被卸載,原來的文件系統(tǒng)的文件才可見)。
7.2 文件系統(tǒng)的用戶可見性
只對內(nèi)核內(nèi)部可見:不需要將文件系統(tǒng)關(guān)聯(lián)到一個掛載點,內(nèi)核通過文件系統(tǒng)的super_block等結(jié)構(gòu)即可訪問到文件系統(tǒng)的文件(如bdev,sockfs)。
對于用戶可見:需要將文件系統(tǒng)關(guān)聯(lián)到一個掛載點,就需要通過給定的掛載點目錄名找到真正的掛載點,然后進行掛載操作, 掛載的實質(zhì)是:通過mount實例的mnt_mountpoint關(guān)聯(lián)真正的掛載點dentry,然后建立父mount關(guān)系,mount實例加入到全局的mount hash table(通過父vfsmount和真正的掛載點dentry作為hash索引),然后用戶打開文件的時候通過路徑名查找解析各個目錄分量,當(dāng)發(fā)現(xiàn)一個目錄是掛載點時,就會步進到最后一次掛載到這個目錄的文件系統(tǒng)的根dentry中繼續(xù)查找,知道根dentry就可以繼續(xù)查找到這個文件系統(tǒng)的任何文件。
7.3 幾條重要規(guī)律
1)文件系統(tǒng)被掛載后都會有以下幾大vfs對象被創(chuàng)建:
super_block mount
根inode
根dentry
注:其中mount為純軟件構(gòu)造的對象(內(nèi)嵌vfsmount對象),其他對象視文件系統(tǒng)類型,可能涉及到磁盤操作。
super_block 超級塊實例,描述一個文件系統(tǒng)的信息,有的需要磁盤讀取在內(nèi)存中填充來構(gòu)建(如磁盤文件系統(tǒng)),有的直接內(nèi)存中填充來構(gòu)建。
mount 掛載實例,描述一個文件系統(tǒng)的一次掛載,主要關(guān)聯(lián)一個文件系統(tǒng)到掛載點,為路徑名查找做重要準(zhǔn)備工作。
根inode 每個文件系統(tǒng)都會有根inode,有的需要磁盤讀取在內(nèi)存中填充來構(gòu)建(如磁盤文件系統(tǒng),根inode號已知),有的直接內(nèi)存中填充來構(gòu)建。
根dentry 每個文件系統(tǒng)都會有根dentry,根據(jù)根inode來構(gòu)建,路徑名查找時會步進到文件系統(tǒng)的根dentry來訪問這個文件系統(tǒng)的文件。
2)一個目錄可以被多個文件系統(tǒng)掛載。第一次掛載是直接掛載這個目錄上,新掛載的文件系統(tǒng)實際上是掛載在上一個文件系統(tǒng)的根dentry上。
3)一個目錄被多個文件系統(tǒng)掛載時,新掛載導(dǎo)致之前的掛載被隱藏。
4)一個目錄被文件系統(tǒng)掛載時,原來目錄中包含的其他子目錄或文件被隱藏。
5)每次掛載都會有一個mount實例描述本次掛載。
6)一個快設(shè)備上的文件系統(tǒng)可以被掛載到多個目錄,有多個mount實例,但是只會有一個super_block、根dentry 和根inode。
7)mount實例用于關(guān)聯(lián)掛載點dentry和文件系統(tǒng),起到路徑名查找時“路由”的作用。
8)掛載一個文件系統(tǒng)必須保證所要掛載的文件系統(tǒng)類型已經(jīng)被注冊。
9)掛載時會查詢文件系統(tǒng)類型的fs_type->fs_supers鏈表,檢查是否已經(jīng)有super_block被加入鏈表,如果沒有才會分配并讀磁盤超級塊填充。
10)對象層次:一個fs_type->fs_supers鏈表可以掛接屬于同一個文件系統(tǒng)的被掛載的超級塊,超級塊鏈表可以掛接屬于同一個超級塊的mount實例 fs_type -> super_block -> mount 從高到低的包含層次。
