• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            小默

            【zz】解析 Linux 中的 VFS 文件系統(tǒng)機(jī)制

            本文闡述 Linux 中的文件系統(tǒng)部分,源代碼來(lái)自基于 IA32 的 2.4.20 內(nèi)核。總體上說(shuō) Linux 下的文件系統(tǒng)主要可分為三大塊:一是上層的文件系統(tǒng)的系統(tǒng)調(diào)用,二是虛擬文件系統(tǒng) VFS(Virtual Filesystem Switch),三是掛載到 VFS 中的各實(shí)際文件系統(tǒng),例如 ext2,jffs 等。本文側(cè)重于通過(guò)具體的代碼分析來(lái)解釋 Linux 內(nèi)核中 VFS 的內(nèi)在機(jī)制,在這過(guò)程中會(huì)涉及到上層文件系統(tǒng)調(diào)用和下層實(shí)際文件系統(tǒng)的如何掛載。文章試圖從一個(gè)比較高的角度來(lái)解釋 Linux 下的 VFS 文件系統(tǒng)機(jī)制。

            1. 摘要

            本文闡述 Linux 中的文件系統(tǒng)部分,源代碼來(lái)自基于 IA32 的 2.4.20 內(nèi)核。總體上說(shuō) Linux 下的文件系統(tǒng)主要可分為三大塊:一是上層的文件系統(tǒng)的系統(tǒng)調(diào)用,二是虛擬文件系統(tǒng) VFS(Virtual Filesystem Switch),三是掛載到 VFS 中的各實(shí)際文件系統(tǒng),例如 ext2,jffs 等。本文側(cè)重于通過(guò)具體的代碼分析來(lái)解釋 Linux 內(nèi)核中 VFS 的內(nèi)在機(jī)制,在這過(guò)程中會(huì)涉及到上層文件系統(tǒng)調(diào)用和下層實(shí)際文件系統(tǒng)的如何掛載。文章試圖從一個(gè)比較高的角度來(lái)解釋 Linux 下的 VFS 文件系統(tǒng)機(jī)制,所以在敘述中更側(cè)重于整個(gè)模塊的主脈絡(luò),而不拘泥于細(xì)節(jié),同時(shí)配有若干張插圖,以幫助讀者理解。

            相對(duì)來(lái)說(shuō),VFS 部分的代碼比較繁瑣復(fù)雜,希望讀者在閱讀完本文之后,能對(duì) Linux 下的 VFS 整體運(yùn)作機(jī)制有個(gè)清楚的理解。建議讀者在閱讀本文前,先嘗試著自己閱讀一下文件系統(tǒng)的源代碼,以便建立起 Linux 下文件系統(tǒng)最基本的概念,比如至少應(yīng)熟悉 super block, dentry, inode,vfsmount 等數(shù)據(jù)結(jié)構(gòu)所表示的意義,這樣再來(lái)閱讀本文以便加深理解。





            回頁(yè)首


            2. VFS 概述

            VFS 是一種軟件機(jī)制,也許稱它為 Linux 的文件系統(tǒng)管理者更確切點(diǎn),與它相關(guān)的數(shù)據(jù)結(jié)構(gòu)只存在于物理內(nèi)存當(dāng)中。所以在每次系統(tǒng)初始化期間,Linux 都首先要在內(nèi)存當(dāng)中構(gòu)造一棵 VFS 的目錄樹(在 Linux 的源代碼里稱之為 namespace),實(shí)際上便是在內(nèi)存中建立相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。VFS 目錄樹在 Linux 的文件系統(tǒng)模塊中是個(gè)很重要的概念,希望讀者不要將其與實(shí)際文件系統(tǒng)目錄樹混淆,在筆者看來(lái),VFS 中的各目錄其主要用途是用來(lái)提供實(shí)際文件系統(tǒng)的掛載點(diǎn),當(dāng)然在 VFS 中也會(huì)涉及到文件級(jí)的操作,本文不闡述這種情況。下文提到目錄樹或目錄,如果不特別說(shuō)明,均指 VFS 的目錄樹或目錄。圖 1 是一種可能的目錄樹在內(nèi)存中的影像:


            圖 1:VFS 目錄樹結(jié)構(gòu)
            圖 1:VFS 目錄樹結(jié)構(gòu)




            回頁(yè)首


            3. 文件系統(tǒng)的注冊(cè)

            這里的文件系統(tǒng)是指可能會(huì)被掛載到目錄樹中的各個(gè)實(shí)際文件系統(tǒng),所謂實(shí)際文件系統(tǒng),即是指VFS 中的實(shí)際操作最終要通過(guò)它們來(lái)完成而已,并不意味著它們一定要存在于某種特定的存儲(chǔ)設(shè)備上。比如在筆者的 Linux 機(jī)器下就注冊(cè)有 "rootfs"、"proc"、"ext2"、"sockfs" 等十幾種文件系統(tǒng)。

            3.1 數(shù)據(jù)結(jié)構(gòu)

            在 Linux 源代碼中,每種實(shí)際的文件系統(tǒng)用以下的數(shù)據(jù)結(jié)構(gòu)表示:

            struct file_system_type {
                        const char *name;
                        int fs_flags;
                        struct super_block *(*read_super) (struct super_block *, void *, int);
                        struct module *owner;
                        struct file_system_type * next;
                        struct list_head fs_supers;
                        };
                        

            注冊(cè)過(guò)程實(shí)際上將表示各實(shí)際文件系統(tǒng)的 struct file_system_type 數(shù)據(jù)結(jié)構(gòu)的實(shí)例化,然后形成一個(gè)鏈表,內(nèi)核中用一個(gè)名為 file_systems 的全局變量來(lái)指向該鏈表的表頭。

            3.2 注冊(cè) rootfs 文件系統(tǒng)

            在眾多的實(shí)際文件系統(tǒng)中,之所以單獨(dú)介紹 rootfs 文件系統(tǒng)的注冊(cè)過(guò)程,實(shí)在是因?yàn)樵撐募到y(tǒng) VFS 的關(guān)系太過(guò)密切,如果說(shuō) ext2/ext3 是 Linux 的本土文件系統(tǒng),那么 rootfs 文件系統(tǒng)則是 VFS 存在的基礎(chǔ)。一般文件系統(tǒng)的注冊(cè)都是通過(guò) module_init 宏以及 do_initcalls() 函數(shù)來(lái)完成(讀者可通過(guò)閱讀module_init 宏的聲明及 arch\i386\vmlinux.lds 文件來(lái)理解這一過(guò)程),但是 rootfs 的注冊(cè)卻是通過(guò) init_rootfs() 這一初始化函數(shù)來(lái)完成,這意味著 rootfs 的注冊(cè)過(guò)程是 Linux 內(nèi)核初始化階段不可分割的一部分。

            init_rootfs() 通過(guò)調(diào)用 register_filesystem(&rootfs_fs_type) 函數(shù)來(lái)完成 rootfs 文件系統(tǒng)注冊(cè)的,其中rootfs_fs_type 定義如下:

             struct file_system_type rootfs_fs_type = { \
                        name:		"rootfs", \
                        read_super:	ramfs_read_super, \
                        fs_flags:	FS_NOMOUNT|FS_LITTER, \
                        owner:		THIS_MODULE, \
                        }
                        

            注冊(cè)之后的 file_systems 鏈表結(jié)構(gòu)如下圖2所示:


            圖 2: file_systems 鏈表結(jié)構(gòu)
            圖 2: file_systems 鏈表結(jié)構(gòu)




            回頁(yè)首


            4. VFS 目錄樹的建立

            既然是樹,所以根是其賴以存在的基礎(chǔ),本節(jié)闡述 Linux 在初始化階段是如何建立根結(jié)點(diǎn)的,即 "/"目錄。這其中會(huì)包括掛載 rootfs 文件系統(tǒng)到根目錄 "/" 的具體過(guò)程。構(gòu)造根目錄的代碼是在 init_mount_tree() 函數(shù) (fs\namespace.c) 中。

            首先,init_mount_tree() 函數(shù)會(huì)調(diào)用 do_kern_mount("rootfs", 0, "rootfs", NULL) 來(lái)掛載前面已經(jīng)注冊(cè)了的 rootfs 文件系統(tǒng)。這看起來(lái)似乎有點(diǎn)奇怪,因?yàn)楦鶕?jù)前面的說(shuō)法,似乎是應(yīng)該先有掛載目錄,然后再在其上掛載相應(yīng)的文件系統(tǒng),然而此時(shí) VFS 似乎并沒(méi)有建立其根目錄。沒(méi)關(guān)系,這是因?yàn)檫@里我們調(diào)用的是 do_kern_mount(),這個(gè)函數(shù)內(nèi)部自然會(huì)創(chuàng)建我們最關(guān)心也是最關(guān)鍵的根目錄(在 Linux 中,目錄對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)是 struct dentry)。

            在這個(gè)場(chǎng)景里,do_kern_mount() 做的工作主要是:

            1)調(diào)用 alloc_vfsmnt() 函數(shù)在內(nèi)存里申請(qǐng)了一塊該類型的內(nèi)存空間(struct vfsmount *mnt),并初始化其部分成員變量。

            2) 調(diào)用 get_sb_nodev() 函數(shù)在內(nèi)存中分配一個(gè)超級(jí)塊結(jié)構(gòu) (struct super_block) sb,并初始化其部分成員變量,將成員 s_instances 插入到 rootfs 文件系統(tǒng)類型結(jié)構(gòu)中的 fs_supers 指向的雙向鏈表中。

            3) 通過(guò) rootfs 文件系統(tǒng)中的 read_super 函數(shù)指針調(diào)用 ramfs_read_super() 函數(shù)。還記得當(dāng)初注冊(cè)rootfs 文件系統(tǒng)時(shí),其成員 read_super 指針指向了 ramfs_read_super() 函數(shù),參見圖2.

            4) ramfs_read_super() 函數(shù)調(diào)用 ramfs_get_inode() 在內(nèi)存中分配了一個(gè) inode 結(jié)構(gòu) (struct inode) inode,并初始化其部分成員變量,其中比較重要的有 i_op、i_fop 和 i_sb:

            inode->i_op = &ramfs_dir_inode_operations;
                        inode->i_fop = &dcache_dir_ops;
                        inode->i_sb = sb;
                        

            這使得將來(lái)通過(guò)文件系統(tǒng)調(diào)用對(duì) VFS 發(fā)起的文件操作等指令將被 rootfs 文件系統(tǒng)中相應(yīng)的函數(shù)接口所接管。


            圖3
            圖3

            5) ramfs_read_super() 函數(shù)在分配和初始化了 inode 結(jié)構(gòu)之后,會(huì)調(diào)用 d_alloc_root() 函數(shù)來(lái)為 VFS的目錄樹建立起關(guān)鍵的根目錄 (struct dentry)dentry,并將 dentry 中的 d_sb 指針指向 sb,d_inode 指針指向 inode。

            6) 將 mnt 中的 mnt_sb 指針指向 sb,mnt_root 和 mnt_mountpoint 指針指向 dentry,而 mnt_parent指針則指向自身。

            這樣,當(dāng) do_kern_mount() 函數(shù)返回時(shí),以上分配出來(lái)的各數(shù)據(jù)結(jié)構(gòu)和 rootfs 文件系統(tǒng)的關(guān)系將如上圖 3 所示。圖中 mnt、sb、inode、dentry 結(jié)構(gòu)塊下方的數(shù)字表示它們?cè)趦?nèi)存里被分配的先后順序。限于篇幅的原因,各結(jié)構(gòu)中只給出了部分成員變量,讀者可以對(duì)照源代碼根據(jù)圖中所示按圖索驥,以加深理解。

            最后,init_mount_tree() 函數(shù)會(huì)為系統(tǒng)最開始的進(jìn)程(即 init_task 進(jìn)程)準(zhǔn)備它的進(jìn)程數(shù)據(jù)塊中的namespace 域,主要目的是將 do_kern_mount() 函數(shù)中建立的 mnt 和 dentry 信息記錄在了 init_task 進(jìn)程的進(jìn)程數(shù)據(jù)塊中,這樣所有以后從 init_task 進(jìn)程 fork 出來(lái)的進(jìn)程也都先天地繼承了這一信息,在后面用sys_mkdir 在 VFS 中創(chuàng)建一個(gè)目錄的過(guò)程中,我們可以看到這里為什么要這樣做。為進(jìn)程建立 namespace 的主要代碼如下:

            	namespace = kmalloc(sizeof(*namespace), GFP_KERNEL);
                        list_add(&mnt->mnt_list, &namespace->list);  //mnt is returned by do_kern_mount()
                        namespace->root = mnt;
                        init_task.namespace = namespace;
                        for_each_task(p) {
                        get_namespace(namespace);
                        p->namespace = namespace;
                        }
                        set_fs_pwd(current->fs, namespace->root, namespace->root->mnt_root);
                        set_fs_root(current->fs, namespace->root, namespace->root->mnt_root);
                        

            該段代碼的最后兩行便是將 do_kern_mount() 函數(shù)中建立的 mnt 和 dentry 信息記錄在了當(dāng)前進(jìn)程的 fs結(jié)構(gòu)中。

            以上講了一大堆數(shù)據(jù)結(jié)構(gòu)的來(lái)歷,其實(shí)最終目的不過(guò)是要在內(nèi)存中建立一顆 VFS 目錄樹而已,更確切地說(shuō), init_mount_tree() 這個(gè)函數(shù)為 VFS 建立了根目錄 "/",而一旦有了根,那么這棵數(shù)就可以發(fā)展壯大,比如可以通過(guò)系統(tǒng)調(diào)用 sys_mkdir 在這棵樹上建立新的葉子節(jié)點(diǎn)等,所以系統(tǒng)設(shè)計(jì)者又將 rootfs 文件系統(tǒng)掛載到了這棵樹的根目錄上。關(guān)于 rootfs 這個(gè)文件系統(tǒng),讀者如果看一下前面圖 2 中它的file_system_type 結(jié)構(gòu),會(huì)發(fā)現(xiàn)它的一個(gè)成員函數(shù)指針 read_super 指向的是 ramfs_read_super,單從這個(gè)函數(shù)名稱中的 ramfs,讀者大概能猜測(cè)出這個(gè)文件所涉及的文件操作都是針對(duì)內(nèi)存中的數(shù)據(jù)對(duì)象,事實(shí)上也的確如此。從另一個(gè)角度而言,因?yàn)?VFS 本身就是內(nèi)存中的一個(gè)數(shù)據(jù)對(duì)象,所以在其上的操作僅限于內(nèi)存,那也是非常合乎邏輯的事。在接下來(lái)的章節(jié)中,我們會(huì)用一個(gè)具體的例子來(lái)討論如何利用 rootfs所提供的函樹為 VFS 增加一個(gè)新的目錄節(jié)點(diǎn)。

            VFS 中各目錄的主要用途是為以后掛載文件系統(tǒng)提供掛載點(diǎn)。所以真正的文件操作還是要通過(guò)掛載后的文件系統(tǒng)提供的功能接口來(lái)進(jìn)行。





            回頁(yè)首


            5. VFS 下目錄的建立

            為了更好地理解 VFS,下面我們用一個(gè)實(shí)際例子來(lái)看看 Linux 是如何在 VFS 的根目錄下建立一個(gè)新的目錄 "/dev" 的。

            要在 VFS 中建立一個(gè)新的目錄,首先我們得對(duì)該目錄進(jìn)行搜索,搜索的目的是找到將要建立的目錄其父目錄的相關(guān)信息,因?yàn)?皮之不存,毛將焉附"。比如要建立目錄 /home/ricard,那么首先必須沿目錄路徑進(jìn)行逐層搜索,本例中先從根目錄找起,然后在根目錄下找到目錄 home,然后再往下,便是要新建的目錄名 ricard,那么前面講得要先對(duì)目錄搜索,在該例中便是要找到 ricard 這個(gè)新目錄的父目錄,也就是 home 目錄所對(duì)應(yīng)的信息。

            當(dāng)然,如果搜索的過(guò)程中發(fā)現(xiàn)錯(cuò)誤,比如要建目錄的父目錄并不存在,或者當(dāng)前進(jìn)程并無(wú)相應(yīng)的權(quán)限等等,這種情況系統(tǒng)必然會(huì)調(diào)用相關(guān)過(guò)程進(jìn)行處理,對(duì)于此種情況,本文略過(guò)不提。

            Linux 下用系統(tǒng)調(diào)用 sys_mkdir 來(lái)在 VFS 目錄樹中增加新的節(jié)點(diǎn)。同時(shí)為配合路徑搜索,引入了下面一個(gè)數(shù)據(jù)結(jié)構(gòu):

            struct nameidata {
                        struct dentry *dentry;
                        struct vfsmount *mnt;
                        struct qstr last;
                        unsigned int flags;
                        int last_type;
                        };
                        

            這個(gè)數(shù)據(jù)結(jié)構(gòu)在路徑搜索的過(guò)程中用來(lái)記錄相關(guān)信息,起著類似"路標(biāo)"的作用。其中前兩項(xiàng)中的 dentry記錄的是要建目錄的父目錄的信息,mnt 成員接下來(lái)會(huì)解釋到。后三項(xiàng)記錄的是所查找路徑的最后一個(gè)節(jié)點(diǎn)(即待建目錄或文件)的信息。 現(xiàn)在為建立目錄 "/dev" 而調(diào)用 sys_mkdir("/dev", 0700),其中參數(shù) 0700 我們不去管它,它只是限定將要建立的目錄的某種模式。sys_mkdir 函數(shù)首先調(diào)用 path_lookup("/dev", LOOKUP_PARENT, &nd);來(lái)對(duì)路徑進(jìn)行查找,其中 nd 為 struct nameidata nd 聲明的變量。在接下來(lái)的敘述中,因?yàn)楹瘮?shù)調(diào)用關(guān)系的繁瑣,為了突出過(guò)程主線,將不再嚴(yán)格按照函數(shù)的調(diào)用關(guān)系來(lái)進(jìn)行描述。

            path_lookup 發(fā)現(xiàn) "/dev" 是以 "/" 開頭,所以它從當(dāng)前進(jìn)程的根目錄開始往下查找,具體代碼如下:

            nd->mnt = mntget(current->fs->rootmnt);
                        nd->dentry = dget(current->fs->root);
                        

            記得在 init_mount_tree() 函數(shù)的后半段曾經(jīng)將新建立的 VFS 根目錄相關(guān)信息記錄在了 init_task 進(jìn)程的進(jìn)程數(shù)據(jù)塊中,那么在這個(gè)場(chǎng)景里,nd->mnt 便指向了圖 3 中 mnt 變量,nd->dentry 便指向了圖 3 中的 dentry 變量。

            然后調(diào)用函數(shù) path_walk 接著往下查找,找到最后通過(guò)變量 nd 返回的信息是 nd.last.name="dev",nd.last.len=3,nd.last_type=LAST_NORM,至于 nd 中 mnt 和 dentry 成員,在這個(gè)場(chǎng)景里還是前面設(shè)置的值,并無(wú)變化。這樣一圈下來(lái),只是用 nd 記錄下相關(guān)信息,實(shí)際的目錄建立工作并沒(méi)有真正展開,但是前面所做的工作卻為接下來(lái)建立新的節(jié)點(diǎn)收集了必要的信息。

            好,到此為止真正建立新目錄節(jié)點(diǎn)的工作將會(huì)展開,這是由函數(shù) lookup_create 來(lái)完成的,調(diào)用這個(gè)函數(shù)時(shí)會(huì)傳入兩個(gè)參數(shù):lookup_create(&nd, 1);其中參數(shù) nd 便是前面提到的變量,參數(shù)1表明要建立一個(gè)新目錄。

            這里的大體過(guò)程是:新分配了一個(gè) struct dentry 結(jié)構(gòu)的內(nèi)存空間,用于記錄 dev 目錄所對(duì)應(yīng)的信息,該dentry 結(jié)構(gòu)將會(huì)掛接到其父目錄中,也就是圖 3 中 "/" 目錄對(duì)應(yīng)的 dentry 結(jié)構(gòu)中,由鏈表實(shí)現(xiàn)這一關(guān)系。接下來(lái)會(huì)再分配一個(gè) struct inode 結(jié)構(gòu)。Inode 中的 i_sb 和 dentry 中的 d_sb 分別都指向圖 3 中的 sb,這樣看來(lái),在同一文件系統(tǒng)下建立新的目錄時(shí)并不需要重新分配一個(gè)超級(jí)塊結(jié)構(gòu),因?yàn)楫吘顾鼈兌紝儆谕晃募到y(tǒng),因此一個(gè)文件系統(tǒng)只對(duì)應(yīng)一個(gè)超級(jí)塊。

            這樣,當(dāng)調(diào)用 sys_mkdir 成功地在 VFS 的目錄樹中新建立一個(gè)目錄 "/dev" 之后,在圖 3 的基礎(chǔ)上,新的數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系便如圖 4 所示。圖 4 中顏色較深的兩個(gè)矩形塊 new_inode 和 new_entry 便是在sys_mkdir() 函數(shù)中新分配的內(nèi)存結(jié)構(gòu),至于圖中的 mnt,sb,dentry,inode 等結(jié)構(gòu),仍為圖 3 中相應(yīng)的數(shù)據(jù)結(jié)構(gòu),其相互之間的鏈接關(guān)系不變(圖中為避免過(guò)多的鏈接曲線,忽略了一些鏈接關(guān)系,如 mnt 和 sb,dentry之間的鏈接,讀者可在圖 3 的基礎(chǔ)上參看圖 4)。

            需要強(qiáng)調(diào)一點(diǎn)的是,既然 rootfs 文件系統(tǒng)被 mount 到了 VFS 樹上,那么它在 sys_mkdir 的過(guò)程中必然會(huì)參與進(jìn)來(lái),事實(shí)上在整個(gè)過(guò)程中,rootfs 文件系統(tǒng)中的 ramfs_mkdir、ramfs_lookup 等函數(shù)都曾被調(diào)用過(guò)。


            圖 4: 在 VFS 樹中新建一目錄 "dev"
            圖 4: 在 VFS 樹中新建一目錄 "dev"




            回頁(yè)首


            6. 在 VFS 樹中掛載文件系統(tǒng)

            在本節(jié)中,將描述在 VFS 的目錄樹中向其中某個(gè)目錄(安裝點(diǎn) mount point)上掛載(mount)一個(gè)文件系統(tǒng)的過(guò)程。

            這一過(guò)程可簡(jiǎn)單描述為:將某一設(shè)備(dev_name)上某一文件系統(tǒng)(file_system_type)安裝到VFS目錄樹上的某一安裝點(diǎn)(dir_name)。它要解決的問(wèn)題是:將對(duì) VFS 目錄樹中某一目錄的操作轉(zhuǎn)化為具體安裝到其上的實(shí)際文件系統(tǒng)的對(duì)應(yīng)操作。比如說(shuō),如果將 hda2 上的根文件系統(tǒng)(假設(shè)文件系統(tǒng)類型為 ext2)安裝到了前一節(jié)中新建立的 "/dev" 目錄上(此時(shí),"/dev" 目錄就成為了安裝點(diǎn)),那么安裝成功之后應(yīng)達(dá)到這樣的目的,即:對(duì) VFS 文件系統(tǒng)的 "/dev" 目錄執(zhí)行 "ls" 指令,該條指令應(yīng)能列出 hda2 上 ext2 文件系統(tǒng)的根目錄下所有的目錄和文件。很顯然,這里的關(guān)鍵是如何將對(duì) VFS 樹中 "/dev" 的目錄操作指令轉(zhuǎn)化為安裝在其上的 ext2 這一實(shí)際文件系統(tǒng)中的相應(yīng)指令。所以,接下來(lái)的敘述將抓住如何轉(zhuǎn)化這一核心問(wèn)題。在敘述之前,讀者不妨自己設(shè)想一下 Linux 系統(tǒng)會(huì)如何解決這一問(wèn)題。記住:對(duì)目錄或文件的操作將最終由目錄或文件所對(duì)應(yīng)的 inode 結(jié)構(gòu)中的 i_op 和 i_fop 所指向的函數(shù)表中對(duì)應(yīng)的函數(shù)來(lái)執(zhí)行。所以,不管最終解決方案如何,都可以設(shè)想必然要通過(guò)將對(duì) "/dev" 目錄所對(duì)應(yīng)的 inode 中 i_op 和 i_fop 的調(diào)用轉(zhuǎn)換到 hda2 上根文件系統(tǒng) ext2 中根目錄所對(duì)應(yīng)的 inode 中 i_op 和 i_fop 的操作。

            初始過(guò)程由 sys_mount() 系統(tǒng)調(diào)用函數(shù)發(fā)起,該函數(shù)原型聲明如下:

            asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,
                        unsigned long flags, void * data);
                        

            其中,參數(shù) char *type 為標(biāo)識(shí)將要安裝的文件系統(tǒng)類型字符串,對(duì)于 ext2 文件系統(tǒng)而言,就是"ext2"。參數(shù) flags 為安裝時(shí)的模式標(biāo)識(shí)數(shù),和接下來(lái)的 data 參數(shù)一樣,本文不將其做為重點(diǎn)。

            為了幫助讀者更好地理解這一過(guò)程,筆者用一個(gè)具體的例子來(lái)說(shuō)明:我們準(zhǔn)備將來(lái)自主硬盤第 2 分區(qū)(hda2)上的 ext2 文件系統(tǒng)安裝到前面創(chuàng)建的 "/dev" 目錄中。那么對(duì)于 sys_mount() 函數(shù)的調(diào)用便具體為:

            sys_mount("hda2","/dev ","ext2",…);
                        

            該函數(shù)在將這些來(lái)自用戶內(nèi)存空間(user space)的參數(shù)拷貝到內(nèi)核空間后,便調(diào)用 do_mount() 函數(shù)開始真正的安裝文件系統(tǒng)的工作。同樣,為了便于敘述和講清楚主流程,接下來(lái)的說(shuō)明將不嚴(yán)格按照具體的函數(shù)調(diào)用細(xì)節(jié)來(lái)進(jìn)行。

            do_mount() 函數(shù)會(huì)首先調(diào)用 path_lookup() 函數(shù)來(lái)得到安裝點(diǎn)的相關(guān)信息,如同創(chuàng)建目錄過(guò)程中敘述的那樣,該安裝點(diǎn)的信息最終記錄在 struct nameidata 類型的一個(gè)變量當(dāng)中,為敘述方便,記該變量為nd。在本例中當(dāng) path_lookup() 函數(shù)返回時(shí),nd 中記錄的信息如下:nd.entry = new_entry; nd.mnt = mnt; 這里的變量如圖 3 和 4 中所示。

            然后,do_mount() 函數(shù)會(huì)根據(jù)調(diào)用參數(shù) flags 來(lái)決定調(diào)用以下四個(gè)函數(shù)之一:do_remount()、 do_loopback()、do_move_mount()、do_add_mount()。

            在我們當(dāng)前的例子中,系統(tǒng)會(huì)調(diào)用 do_add_mount() 函數(shù)來(lái)向 VFS 樹中安裝點(diǎn) "/dev " 安裝一個(gè)實(shí)際的文件系統(tǒng)。在 do_add_mount() 中,主要完成了兩件重要事情:一是獲得一個(gè)新的安裝區(qū)域塊,二是將該新的安裝區(qū)域塊加入了安裝系統(tǒng)鏈表。它們分別是調(diào)用 do_kern_mount() 函數(shù)和 graft_tree() 函數(shù)來(lái)完成的。這里的描述可能有點(diǎn)抽象,諸如安裝區(qū)域塊、安裝系統(tǒng)鏈表等,不過(guò)不用著急,因?yàn)樗鼈兌际枪P者自己定義出來(lái)的概念,等一下到后面會(huì)有專門的圖表解釋,到時(shí)便會(huì)清楚。

            do_kern_mount() 函數(shù)要做的事情,便是建立一新的安裝區(qū)域塊,具體的內(nèi)容在前面的章節(jié) VFS 目錄樹的建立中已經(jīng)敘述過(guò),這里不再贅述。

            graft_tree() 函數(shù)要做的事情便是將 do_kern_mount() 函數(shù)返回的一 struct vfsmount 類型的變量加入到安裝系統(tǒng)鏈表中,同時(shí) graft_tree() 還要將新分配的 struct vfsmount 類型的變量加入到一個(gè)hash表中,其目的我們將會(huì)在以后看到。

            這樣,當(dāng) do_kern_mount() 函數(shù)返回時(shí),在圖 4 的基礎(chǔ)上,新的數(shù)據(jù)結(jié)構(gòu)間的關(guān)系將如圖 5 所示。其中,紅圈區(qū)域里面的數(shù)據(jù)結(jié)構(gòu)便是被稱做安裝區(qū)域塊的東西,其中不妨稱 e2_mnt 為安裝區(qū)域塊的指針,藍(lán)色箭頭曲線即構(gòu)成了所謂的安裝系統(tǒng)鏈表。

            在把這些函數(shù)調(diào)用后形成的數(shù)據(jù)結(jié)構(gòu)關(guān)系理清楚之后,讓我們回到本章節(jié)開始提到的問(wèn)題,即將 ext2 文件系統(tǒng)安裝到了 "/dev " 上之后,對(duì)該目錄上的操作如何轉(zhuǎn)化為對(duì) ext2 文件系統(tǒng)相應(yīng)的操作。從圖 5上看到,對(duì) sys_mount() 函數(shù)的調(diào)用并沒(méi)有直接改變 "/dev " 目錄所對(duì)應(yīng)的 inode (即圖中的 new_inode變量)結(jié)構(gòu)中的 i_op 和 i_fop 指針,而且 "/dev " 所對(duì)應(yīng)的 dentry(即圖中的 new_dentry 變量)結(jié)構(gòu)仍然在 VFS 的目錄樹中,并沒(méi)有被從其中隱藏起來(lái),相應(yīng)地,來(lái)自 hda2 上的 ext2 文件系統(tǒng)的根目錄所對(duì)應(yīng)的 e2_entry 也不是如當(dāng)初筆者所想象地那樣將 VFS 目錄樹中的 new_dentry 取而代之,那么這之間的轉(zhuǎn)化到底是如何實(shí)現(xiàn)的呢?

            請(qǐng)讀者注意下面的這段代碼:

            	while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry));
                        

            這段代碼在 link_path_walk() 函數(shù)中被調(diào)用,而 link_path_walk() 最終又會(huì)被 path_lookup() 函數(shù)調(diào)用,如果讀者閱讀過(guò) Linux 關(guān)于文件系統(tǒng)部分的代碼,應(yīng)該知道 path_lookup() 函數(shù)在整個(gè) Linux 繁瑣的文件系統(tǒng)代碼中屬于一個(gè)重要的基礎(chǔ)性的函數(shù)。簡(jiǎn)單說(shuō)來(lái),這個(gè)函數(shù)用于解析文件路徑名,這里的文件路徑名和我們平時(shí)在應(yīng)用程序中所涉及到的概念相同,比如在 Linux 的應(yīng)用程序中 open 或 read 一個(gè)文件 /home/windfly.cs 時(shí),這里的 /home/windfly.cs 就是文件路徑名,path_lookup() 函數(shù)的責(zé)任就是對(duì)文件路徑名中進(jìn)行搜索,直到找到目標(biāo)文件所屬目錄所對(duì)應(yīng)的 dentry 或者目標(biāo)直接就是一個(gè)目錄,筆者不想在有限的篇幅里詳細(xì)解釋這個(gè)函數(shù),讀者只要記住 path_lookup() 會(huì)返回一個(gè)目標(biāo)目錄即可。

            上面的代碼非常地不起眼,以至于初次閱讀文件系統(tǒng)的代碼時(shí)經(jīng)常會(huì)忽略掉它,但是前文所提到從 VFS 的操作到實(shí)際文件系統(tǒng)操作的轉(zhuǎn)化卻是由它完成的,對(duì) VFS 中實(shí)現(xiàn)的文件系統(tǒng)的安裝可謂功不可沒(méi)。現(xiàn)在讓我們仔細(xì)剖析一下該段代碼: d_mountpoint(dentry) 的作用很簡(jiǎn)單,它只是返回 dentry 中 d_mounted 成員變量的值。這里的dentry 仍然還是 VFS 目錄樹上的東西。如果 VFS 目錄樹上某個(gè)目錄被安裝過(guò)一次,那么該值為 1。對(duì)VFS 中的一個(gè)目錄可進(jìn)行多次安裝,后面會(huì)有例子說(shuō)明這種情況。在我們的例子中,"/dev" 所對(duì)應(yīng)的new_dentry 中 d_mounted=1,所以 while 循環(huán)中第一個(gè)條件滿足。下面再來(lái)看__follow_down(&nd->mnt, &dentry)代


            圖 5:安裝 ext2 類型根文件系統(tǒng)到 "/dev " 目錄上
            圖 5:安裝 ext2 類型根文件系統(tǒng)到 "/dev " 目錄上

            碼做了什么?到此我們應(yīng)該記住,這里 nd 中的 dentry 成員就是圖 5 中的 new_dentry,nd 中的 mnt成員就是圖 5 中的 mnt,所以我們現(xiàn)在可以把 __follow_down(&nd->mnt, &dentry) 改寫成__follow_down(&mnt, &new_dentry),接下來(lái)我們將 __follow_down() 函數(shù)的代碼改寫(只是去處掉一些不太相關(guān)的代碼,并且為了便于說(shuō)明,在部分代碼行前加上了序號(hào))如下:

            static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
                        {
                        struct vfsmount *mounted;
                        [1]	mounted = lookup_mnt(*mnt, *dentry);
                        if (mounted) {
                        [2]		*mnt = mounted;
                        [3]		*dentry = mounted->mnt_root;
                        return 1;
                        }
                        return 0;
                        }
                        

            代碼行[1]中的 lookup_mnt() 函數(shù)用于查找一個(gè) VFS 目錄樹下某一目錄最近一次被 mount 時(shí)的安裝區(qū)域塊的指針,在本例中最終會(huì)返回圖 5 中的 e2_mnt。至于查找的原理,這里粗略地描述一下。記得當(dāng)我們?cè)诎惭b ext2 文件系統(tǒng)到 "/dev" 時(shí),在后期會(huì)調(diào)用 graft_tree() 函數(shù),在這個(gè)函數(shù)里會(huì)把圖 5 中的安裝區(qū)域塊指針 e2_mnt 掛到一 hash 表(Linux 2.4.20源代碼中稱之為 mount_hashtable)中的某一項(xiàng),而該項(xiàng)的鍵值就是由被安裝點(diǎn)所對(duì)應(yīng)的 dentry(本例中為 new_dentry)和 mount(本例中為 mnt)所共同產(chǎn)生,所以自然地,當(dāng)我們知道 VFS 樹中某一 dentry 被安裝過(guò)(該 dentry 變成為一安裝點(diǎn)),而要去查找其最近一次被安裝的安裝區(qū)域塊指針時(shí),同樣由該安裝點(diǎn)所對(duì)應(yīng)的 dentry 和 mount 來(lái)產(chǎn)生一鍵值,以此值去索引 mount_hashtable,自然可找到該安裝點(diǎn)對(duì)應(yīng)的安裝區(qū)域塊指針形成的鏈表的頭指針,然后遍歷該鏈表,當(dāng)發(fā)現(xiàn)某一安裝區(qū)域塊指針,記為 p,滿足以下條件時(shí):

            (p->mnt_parent == mnt && p->mnt_mountpoint == dentry)
                        

            P 便為該安裝點(diǎn)所對(duì)應(yīng)的安裝區(qū)域塊指針。當(dāng)找到該指針后,便將 nd 中的 mnt 成員換成該安裝區(qū)域塊指針,同時(shí)將 nd 中的 dentry 成員換成安裝區(qū)域塊中的 dentry 指針。在我們的例子中,e2_mnt->mnt_root成員指向 e2_dentry,也就是 ext2 文件系統(tǒng)的 "/" 目錄。這樣,當(dāng) path_lookup() 函數(shù)搜索到 "/dev"時(shí),nd 中的 dentry 成員為 e2_dentry,而不再是原來(lái)的 new_dentry,同時(shí) mnt 成員被換成 e2_mnt,轉(zhuǎn)化便在不知不覺(jué)中完成了。

            現(xiàn)在考慮一下對(duì)某一安裝點(diǎn)多次安裝的情況,同樣作為例子,我們假設(shè)在 "/dev" 上安裝完一個(gè) ext2文件系統(tǒng)后,再在其上安裝一個(gè) ntfs 文件系統(tǒng)。在安裝之前,同樣會(huì)對(duì)安裝點(diǎn)所在的路徑調(diào)用path_lookup() 函數(shù)進(jìn)行搜索,但是這次由于在 "/dev" 目錄上已經(jīng)安裝過(guò)了 ext2 文件系統(tǒng),所以搜索到最后,由 nd 返回的信息是:nd.dentry = e2_dentry, nd.mnt = e2_mnt。由此可見,在第二次安裝時(shí),安裝點(diǎn)已經(jīng)由 dentry 變成了 e2_dentry。接下來(lái),同樣地,系統(tǒng)會(huì)再分配一個(gè)安裝區(qū)域塊,假設(shè)該安裝區(qū)域塊的指針為 ntfs_mnt,區(qū)域塊中的 dentry 為 ntfs_dentry。ntfs_mnt 的父指針指向了e2_mnt,mnfs_mnt 中的 mnt_root 指向了代表 ntfs 文件系統(tǒng)根目錄的 ntfs_dentry。然后,系統(tǒng)通過(guò) e2_dentry和 e2_mnt 來(lái)生成一個(gè)新的 hash 鍵值,利用該值作為索引,將 ntfs_mnt 加入到 mount_hashtable 中,同時(shí)將 e2_dentry 中的成員 d_mounted 值設(shè)定為 1。這樣,安裝過(guò)程便告結(jié)束。

            讀者可能已經(jīng)知道,對(duì)同一安裝點(diǎn)上的最近一次安裝會(huì)隱藏起前面的若干次安裝,下面我們通過(guò)上述的例子解釋一下該過(guò)程:

            在先后將 ext2 和 ntfs 文件系統(tǒng)安裝到 "/dev" 目錄之后,我們?cè)僬{(diào)用 path_lookup() 函數(shù)來(lái)對(duì)"/dev" 進(jìn)行搜索,函數(shù)首先找到 VFS 目錄樹下的安裝點(diǎn) "/dev" 所對(duì)應(yīng)的 dentry 和 mnt,此時(shí)它發(fā)現(xiàn)dentry 成員中的 d_mounted 為 1,于是它知道已經(jīng)有文件系統(tǒng)安裝到了該 dentry 上,于是它通過(guò) dentry 和 mnt 來(lái)生成一個(gè) hash 值,通過(guò)該值來(lái)對(duì) mount_hashtable 進(jìn)行搜索,根據(jù)安裝過(guò)程,它應(yīng)該能找到 e2_mnt 指針并返回之,同時(shí)原先的 dentry 也已經(jīng)被替換成 e2_dentry。回頭再看一下前面已經(jīng)提到的下列代碼: while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry)); 當(dāng)?shù)谝淮窝h(huán)結(jié)束后, nd->mnt 已經(jīng)是 e2_mnt,而 dentry 則變成 e2_dentry。此時(shí)由于 e2_dentry 中的成員 d_mounted 值為 1,所以 while 循環(huán)的第一個(gè)條件滿足,要繼續(xù)調(diào)用 __follow_down() 函數(shù),這個(gè)函數(shù)前面已經(jīng)剖析過(guò),當(dāng)它返回后 nd->mnt 變成了 ntfs_mnt,dentry 則變成了 ntfs_dentry。由于此時(shí) ntfs_dentry 沒(méi)有被安裝過(guò)其他文件,所以它的成員 d_mounted 應(yīng)該為 0,循環(huán)結(jié)束。對(duì) "/dev" 發(fā)起的 path_lookup() 函數(shù)最終返回了 ntfs 文件系統(tǒng)根目錄所對(duì)應(yīng)的 dentry。這就是為什么 "/dev" 本身和安裝在其上的 ext2 都被隱藏的原因。如果此時(shí)對(duì) "/dev" 目錄進(jìn)行一個(gè) ls 命令,將返回安裝上去的 ntfs 文件系統(tǒng)根目錄下所有的文件和目錄。





            回頁(yè)首


            7. 安裝根文件系統(tǒng)

            有了前面章節(jié) 5 的基礎(chǔ),理解 Linux 下根文件系統(tǒng)的安裝并不困難,因?yàn)椴还茉趺礃樱惭b一個(gè)文件系統(tǒng)到 VFS 中某一安裝點(diǎn)的過(guò)程原理畢竟都是一樣的。

            這個(gè)過(guò)程大致是:首先要確定待安裝的 ext2 文件系統(tǒng)的來(lái)源,其次是確定 ext2 文件系統(tǒng)在 VFS中的安裝點(diǎn),然后便是具體的安裝過(guò)程。

            關(guān)于第一問(wèn)題,Linux 2.4.20 的內(nèi)核另有一大堆的代碼去解決,限于篇幅,筆者不想在這里去具體說(shuō)明這個(gè)過(guò)程,大概記住它是要解決到哪里去找要安裝的文件系統(tǒng)的就可以了,這里我們不妨就認(rèn)為要安裝的根文件系統(tǒng)就來(lái)自于主硬盤的第一分區(qū) hda1.

            關(guān)于第二個(gè)問(wèn)題,Linux 2.4.20 的內(nèi)核把來(lái)自于 hda1 上 ext2 文件系統(tǒng)安裝到了 VFS 目錄樹中的"/root" 目錄上。其實(shí),把 ext2 文件系統(tǒng)安裝到 VFS 目錄樹下的哪個(gè)安裝點(diǎn)并不重要(VFS 的根目錄除外),只要是這個(gè)安裝點(diǎn)在 VFS 樹中是存在的,并且內(nèi)核對(duì)它沒(méi)有另外的用途。如果讀者喜歡,盡可以自己在 VFS 中創(chuàng)建一個(gè) "/Windows" 目錄,然后將 ext2 文件系統(tǒng)安裝上去作為將來(lái)用戶進(jìn)程的根目錄,沒(méi)有什么不可以的。問(wèn)題的關(guān)鍵是要將進(jìn)程的根目錄和當(dāng)前工作目錄設(shè)定好,因?yàn)楫吘怪挥糜脩暨M(jìn)程才去關(guān)心現(xiàn)實(shí)的文件系統(tǒng),要知道筆者的這篇稿子可是要存到硬盤上去的。

            在 Linux 下,設(shè)定一個(gè)進(jìn)程的當(dāng)前工作目錄是通過(guò)系統(tǒng)調(diào)用 sys_chdir() 進(jìn)行的。初始化期間,Linux 在將 hda1 上的 ext2 文件系統(tǒng)安裝到了 "/root" 上后,通過(guò)調(diào)用 sys_chdir("/root") 將當(dāng)前進(jìn)程,也就是 init_task 進(jìn)程的當(dāng)前工作目錄(pwd)設(shè)定為 ext2 文件系統(tǒng)的根目錄。記住此時(shí) init_task進(jìn)程的根目錄仍然是圖 3 中的 dentry,也就是 VFS 樹的根目錄,這顯然是不行的,因?yàn)橐院?Linux 世界中的所有進(jìn)程都由這個(gè) init_task 進(jìn)程派生出來(lái),無(wú)一例外地要繼承該進(jìn)程的根目錄,如果是這樣,意味著用戶進(jìn)程從根目錄搜索某一目錄時(shí),實(shí)際上是從 VFS 的根目錄開始的,而事實(shí)上卻是從 ext2 的根文件開始搜索的。這個(gè)矛盾的解決是靠了在調(diào)用完 mount_root() 函數(shù)后,系統(tǒng)調(diào)用的下面兩個(gè)函數(shù):

            	sys_mount(".", "/", NULL, MS_MOVE, NULL);
                        sys_chroot(".");
                        

            其主要作用便是將 init_task 進(jìn)程的根目錄轉(zhuǎn)化成安裝上去的 ext2 文件系統(tǒng)的根目錄。有興趣的讀者可以自行去研究這一過(guò)程。

            所以在用戶空間下,更多地情況是只能見到 VFS 這棵大樹的一葉,而且還是被安裝過(guò)文件系統(tǒng)了的,實(shí)際上對(duì)用戶空間來(lái)說(shuō)還是不可見。我想,VFS 更多地被內(nèi)核用來(lái)實(shí)現(xiàn)自己的功能,并以系統(tǒng)調(diào)用的方式提供過(guò)用戶進(jìn)程使用,至于在其上實(shí)現(xiàn)的不同文件系統(tǒng)的安裝,也只是其中的一個(gè)功能罷了。





            回頁(yè)首


            8. 結(jié)束語(yǔ)

            文件系統(tǒng)在整個(gè) Linux 的內(nèi)核中具有舉足輕重的地位,代碼量也很復(fù)雜繁瑣。但是因?yàn)槠渲匾牡匚唬雽?duì) Linux 的內(nèi)核有比較深入的理解,必須要能越過(guò)文件系統(tǒng)這一關(guān)。當(dāng)然閱讀其源代碼便是其中最好的方法,本文試圖給那些已經(jīng)嘗試著去閱讀,但是目前尚有困惑的讀者畫一張 VFS 文件系統(tǒng)的草圖,希望能對(duì)讀者有些許啟發(fā)。但是想在如此有限的篇幅里去闡述清楚 Linux 中整個(gè)文件系統(tǒng)的來(lái)龍去脈,是根本不現(xiàn)實(shí)的。而且本文也只是側(cè)重于剖析 VFS 的機(jī)制,對(duì)于象具體的文件讀寫,為提高效率而引入的各種 buffer,hash 等內(nèi)容以及文件系統(tǒng)的安全性方面,都沒(méi)有提到。畢竟,本文只想幫助讀者理清一個(gè)大體的脈絡(luò),最終的理解與領(lǐng)悟,還得靠讀者自己去潛心研究源代碼。最后,對(duì)本文相關(guān)的任何問(wèn)題或建議,都?xì)g迎用 email 和筆者聯(lián)系。



            關(guān)于作者

             

            Ricard Chen,男,感興趣的領(lǐng)域:Linux 系統(tǒng)內(nèi)核,BIOS,文件系統(tǒng),XScale 等。讀者可以通過(guò)email: ricard_chen@yahoo.com 和他聯(lián)系。

            posted on 2010-04-04 13:27 小默 閱讀(208) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Linux

            導(dǎo)航

            統(tǒng)計(jì)

            留言簿(13)

            隨筆分類(287)

            隨筆檔案(289)

            漏洞

            搜索

            積分與排名

            最新評(píng)論

            閱讀排行榜

            久久亚洲AV成人无码电影| 久久伊人五月天论坛| 久久久久国产精品人妻| 久久久久久久亚洲精品| 99久久成人18免费网站| 国产精品美女久久久久网| 精品久久久久久国产潘金莲| 99久久精品国产一区二区| 中文字幕无码精品亚洲资源网久久 | 欧美激情精品久久久久久久九九九| 久久亚洲国产午夜精品理论片| 国产精品一区二区久久国产 | 久久精品国产日本波多野结衣| 日韩久久久久中文字幕人妻| 香蕉久久夜色精品国产尤物| 欧美日韩久久中文字幕| 色偷偷偷久久伊人大杳蕉| 国产成人精品白浆久久69| 久久免费精品视频| 午夜精品久久久久久久无码| 一本色道久久88精品综合| 久久精品亚洲中文字幕无码麻豆| 国产一区二区三区久久| 久久伊人色| 国产亚洲色婷婷久久99精品| 成人精品一区二区久久| 伊人久久无码精品中文字幕| 国产精品一区二区久久不卡| 久久国产成人午夜AV影院| 久久人人爽人人爽人人片AV不| 国产69精品久久久久777| 国产免费久久久久久无码| 国产精品久久新婚兰兰| 青青草国产成人久久91网| 久久综合鬼色88久久精品综合自在自线噜噜 | 久久精品人妻一区二区三区| 精品人妻伦九区久久AAA片69| 蜜桃麻豆www久久| 99久久99久久精品国产片果冻| 99久久国产亚洲高清观看2024| 国内精品久久久久影院亚洲|