zmwillow (journeyman)不知正確與否。 這樣是表示對作者的尊重。 如果有不對,請通知我,我將即時更改. 一 VFS分析 Linux 操作系統(tǒng)支持多種不同的文件系統(tǒng),包括 ext2(the Second Extended file-system),nfs(the Network File-system),F(xiàn)AT(the MS-DOS File Allocation Table file system),minix,以及其他許多文件系統(tǒng)。為了使得 linux 內(nèi)核中的高層子系統(tǒng)能夠以相同的方式處理這些不同的文件系統(tǒng),Linux 定義了一個抽象層,即虛擬文件系統(tǒng)VFS,又叫作虛擬文件系統(tǒng)轉(zhuǎn)換(Virtual Filesystem Switch)。VFS 是 Linux 內(nèi)核中的一個子系統(tǒng),其他的子系統(tǒng),如IPC,SCHED,MM,NET,都只與 VFS 聯(lián)系,換句話說,具體的邏輯文件系統(tǒng)對于 Linux 內(nèi)核中的其他子系統(tǒng)是透明的。 而proc文件系統(tǒng),對于 Linux 來說,正是一個邏輯文件系統(tǒng),因此 proc 文件系統(tǒng)的實(shí)現(xiàn),也完全遵循 VFS 的規(guī)范,在對 proc 文件系統(tǒng)進(jìn)行分析之前,我們必須對 VFS 進(jìn)行一個詳細(xì)的分析。 (一) 基本設(shè)計(jì)原理 對于邏輯文件系統(tǒng)來說,VFS 是一個管理者,而對于內(nèi)核的其他部分,則是一個接口,整個linux 中文件系統(tǒng)的邏輯關(guān)系,如圖2.1.1所示。 VFS提供了一個統(tǒng)一的接口(即幾個有關(guān)操作的數(shù)據(jù)結(jié)構(gòu)),一個邏輯文件系統(tǒng)要想被 Linux 支持,那么就必須按照這個接口來編寫自己的操作函數(shù),從而將自己的細(xì)節(jié)對其他子系統(tǒng)隱藏起來。因而,對于內(nèi)核其他子系統(tǒng)來說,所有的文件系統(tǒng)都是一樣的。 (二) 基本對象與方法 虛擬文件系統(tǒng)的接口由一組對象及其由這些對象調(diào)用的一組方法所構(gòu)成的。這些基本的對象是 files(文件),file-systems(文件系統(tǒng)),inodes (索引節(jié)點(diǎn))以及 names for inodes(索引節(jié)點(diǎn)名字),下面對這些對象進(jìn)行簡單的介紹: 1 Files: 文件是一個可讀可寫的對象,它也可以映射到內(nèi)存中,這和 UNIX 中文件描述符的概念很接近。文件在 Linux 中使用一個"struct file"結(jié)構(gòu)來實(shí)現(xiàn),并且該結(jié)構(gòu)有一組操作函數(shù),保存在結(jié)構(gòu)"struct file_operations"中。
圖 2.1.1 2 Inodes: 索引節(jié)點(diǎn)是文件系統(tǒng)中的基本對象。它可以是一個正常文件,一個目錄,一個符號鏈接,或者是其他什么東西。VFS 并不明顯地區(qū)分這些對象,而把它們留給真正的文件系統(tǒng),讓它們自己實(shí)現(xiàn)適宜的行為。從而使內(nèi)核的高層子系統(tǒng)對于不同的對象區(qū)別對待。 每一個 索引節(jié)點(diǎn)節(jié)點(diǎn)都由一個"struct inode"結(jié)構(gòu)表現(xiàn),它的一組方法保存在結(jié)構(gòu)"struct inode_operations"中。 文件(Files)和索引節(jié)點(diǎn)(Inodes)也許看起來很相像,但它們之間有一些非常重要的不同,要注意的一點(diǎn)是,有些東西有索引節(jié)點(diǎn),但卻沒有文件,比如,一個符號鏈接。與之相對應(yīng),有些文件卻沒有索引節(jié)點(diǎn),如管道(pipes)和 sockets。 3 File_systems 文件系統(tǒng)就是 inode 的集合,其中有一個不同的節(jié)點(diǎn),被稱為根結(jié)點(diǎn)(root)。其他的 inode 以 root 為起始點(diǎn)進(jìn)行訪問,并且通過文件名來查找其他的 inode 。 每一個文件系統(tǒng)有一組唯一的特征,應(yīng)用于本文件系統(tǒng)內(nèi)的所有 inode 之上。其中有一些是標(biāo)志,比如只讀 READ-ONLY 標(biāo)志。另一個重要的內(nèi)容是 blocksize。 每一個文件系統(tǒng)都通過一個結(jié)構(gòu)"struct super_block"來表現(xiàn),而針對超級塊的一組方法則存儲在結(jié)構(gòu)"struct super_operations"之中。 在 Linux 中,超級塊(super-blocks)和 設(shè)備號(device number)之間有緊密的聯(lián)系。每一個文件系統(tǒng)必須有一個唯一的設(shè)備號,該文件系統(tǒng)即建立在此設(shè)備之上。有一些文件系統(tǒng)(比如 nfs 和 我們要研究的 proc 文件系統(tǒng))被標(biāo)志為不需要真實(shí)的設(shè)備,因此,對于這些文件系統(tǒng),主設(shè)備號(major number)為0的匿名設(shè)備將會自動地分配給它們。 Linux VFS 了解不同的文件系統(tǒng)類型,每一個文件系統(tǒng)類型都使用一個"struct file_system_type"結(jié)構(gòu)來表示,在這個結(jié)構(gòu)中,只包含一個方法,即 "read_super",使用這個方法來實(shí)例化一個指定文件系統(tǒng)的超級塊。 4 Names 在一個文件系統(tǒng)內(nèi),所有的 inodes 都是通過名字來訪問的。由于對于某些文件系統(tǒng)來說,名字到 inode 的轉(zhuǎn)換非常耗時的,因此,Linux 的 VFS 層為當(dāng)前活動的和最近使用的名字維護(hù)了一個 cache,這個 cache 被稱為 目錄高速緩存(dcache)。 dcache 在內(nèi)存中組織為樹狀結(jié)構(gòu)。樹中的每一個節(jié)點(diǎn)都對應(yīng)于一個指定目錄,指定名稱的inode。一個inode可以與多個樹中的節(jié)點(diǎn)相聯(lián)系。 如果dcache不是一棵完整的文件樹,那么它一定是文件樹的前綴部分,也就是說,如果一個文件樹的節(jié)點(diǎn)在cache中,那么該節(jié)點(diǎn)的所以祖先也一定在cache中。 每一個樹的節(jié)點(diǎn)都使用一個結(jié)構(gòu)"struct dentry"來表現(xiàn),它的一組方法存儲在"struct dentry_operations"之中。 dentry 在 Files 和 Inodes 之間扮演了中間人的角色。每一個打開的文件都指向一個dentry,而每一個dentry 則指向它所涉及的inode。這意味著,對于每一個打開的文件,該文件的dentry 和該文件所有的父節(jié)點(diǎn)都在內(nèi)存中被cache,這使得被打開文件的全路徑可以更容易地檢測。 (三) 文件系統(tǒng)的注冊和裝載過程 1 文件系統(tǒng)的注冊 在使用一個文件系統(tǒng)之前,必須要對該文件系統(tǒng)進(jìn)行注冊。在Linux編譯的時候,可以選定支持哪些文件系統(tǒng),這些編譯進(jìn)內(nèi)核的文件系統(tǒng),在系統(tǒng)引導(dǎo)的時候,就會在VFS中注冊。而如果一個文件系統(tǒng)被編譯為內(nèi)核可裝載模塊,那么將在模塊安裝的時候進(jìn)行注冊,在模塊卸載的時候注銷。 每一個文件系統(tǒng),都會在自己的初始化例程中填寫一個 file_system_type 的數(shù)據(jù)結(jié)構(gòu),然后調(diào)用注冊函數(shù)register_filesystem(struct file_system_type *fs) 進(jìn)行注冊。下面我們分析一下 file_system_type 的結(jié)構(gòu): file_system_type 在 include/linux/fs.h 中定義: struct file_system_type { const char *name; int fs_flags; struct super_block *(*read_super) (struct super_block *, void *, int); struct module *owner; struct vfsmount *kern_mnt; /* For kernel mount, if it's FS_SINGLE fs */ struct file_system_type * next; }; 而文件系統(tǒng)的注冊和注銷函數(shù)也在該頭文件中聲明: extern int register_filesystem(struct file_system_type *); extern int unregister_filesystem(struct file_system_type *); 函數(shù) register_filesystem 成功時返回0,當(dāng) fs == NULL時返回 -EINVAL,而當(dāng)fs->next!=NULL 或者已經(jīng)有同名文件系統(tǒng)注冊時,則返回-EBUSY。當(dāng)文件系統(tǒng)作為模塊時,必須直接或者間接地在init_module中調(diào)用這個注冊函數(shù),而如果要編譯進(jìn)內(nèi)核,則必須在fs/filesystem.c中的filesystem_setup中注冊。而unregister_filesystem 則只能在模塊的cleanup_module例程中調(diào)用。 所有的已注冊文件系統(tǒng)的 file_system_type 結(jié)構(gòu)最終會形成一個鏈表,被稱之為"注冊鏈表"。下圖即為內(nèi)核中 file_system_type 的鏈表示意圖,鏈表頭由 file_systems 指定。
2 文件系統(tǒng)的安裝 要真正使用一個文件系統(tǒng),僅僅注冊是不行的,還必須安裝這個文件系統(tǒng)。在安裝linux時,已經(jīng)(默認(rèn))安裝了EXT2文件系統(tǒng),作為根文件系統(tǒng)。我們可以在文件/etc/fstab中指定自動安裝的文件系統(tǒng),和使用mount命令一樣,我們要為每種文件系統(tǒng)的安裝提供三種信息:文件系統(tǒng)的名稱,包含該文件系統(tǒng)的物理設(shè)備,以及該文件系統(tǒng)的安裝點(diǎn)。例如下面的命令: mount -t vfat /dev/fd0 /mnt/floppy 將把軟盤(物理設(shè)備fd0)中的vfat文件系統(tǒng)安裝到/mnt/floppy目錄上,下面我們分析一下上述命令的執(zhí)行過程: 尋找對應(yīng)的文件系統(tǒng)的信息。VFS通過file_systems,在file_system_type組成的鏈表中根據(jù)指定的文件系統(tǒng)的名稱查看文件系統(tǒng)的類型信息。 如果在上述鏈表中找到匹配的文件系統(tǒng),則說明內(nèi)核支持該文件系統(tǒng),并已經(jīng)注冊。否則,說明該文件系統(tǒng)有可能由LKM(LinuxKernelModule)可裝載模塊支持,因此,VFS會請求內(nèi)核裝入相應(yīng)的文件系統(tǒng)模塊,此時,該文件系統(tǒng)在VFS中注冊并初始化。 1. 如果VFS仍然找到指定的文件系統(tǒng),那么將返回錯誤。 2. 然后,VFS檢驗(yàn)指定的物理塊設(shè)備是否已經(jīng)安裝。如果指定的物理塊設(shè)備已經(jīng)被安裝,那么將返回錯誤。也就是說,一個塊設(shè)備只能安裝到一個目錄,不能同時多次安裝。 3. VFS查找新文件系統(tǒng)的安裝點(diǎn)目錄的VFS索引節(jié)點(diǎn)。該VFS索引節(jié)點(diǎn)可能在索引節(jié)點(diǎn)高速緩存中,也有可能需要從安裝點(diǎn)所在的塊設(shè)備中讀取。 4. 如果該安裝點(diǎn)目錄已經(jīng)裝有其他的文件系統(tǒng),那么將返回錯誤。因?yàn)樵谕荒夸浿荒芡瑫r安裝一個文件系統(tǒng)。 5. VFS安裝代碼為新的文件系統(tǒng)分配超級塊,并將安裝信息傳遞給該文件系統(tǒng)的超級塊讀取例程。系統(tǒng)中所有的VFS超級塊保存在由super_blocks指向的super_block數(shù)據(jù)結(jié)構(gòu)指針數(shù)組中。 6. 文件系統(tǒng)的超級塊讀取例程將對應(yīng)的文件系統(tǒng)的信息映射到VFS超級塊中。如果在此過程中發(fā)生錯誤,例如所讀取的超級塊魔數(shù)和指定的文件系統(tǒng)不一致,則返回錯誤。 7. 如果成功安裝,則所有已經(jīng)安裝的文件系統(tǒng)形成如下圖所示的結(jié)構(gòu):
已注冊文件示意圖 由圖可知,每一個已經(jīng)掛裝的文件系統(tǒng)由vfsmount結(jié)構(gòu)描述。所有的vfsmount結(jié)構(gòu)形成了一個鏈表,用vfsmntlist來指向鏈表頭。這個鏈表可以稱為"已安裝文件系統(tǒng)鏈表"。系統(tǒng)中還有另外兩個指向這種結(jié)構(gòu)體的指針,vfsmnttail和mru_vfsmnt分別指向鏈表尾和最近使用過的vfsmount結(jié)構(gòu)。 fsmount結(jié)構(gòu)在include/mount.h中定義: struct vfsmount { struct dentry *mnt_mountpoint; /* dentry of mountpoint */ struct dentry *mnt_root; /* root of the mounted tree */ struct vfsmount *mnt_parent; /* fs we are mounted on */ struct list_head mnt_instances; /* other vfsmounts of the same fs */ struct list_head mnt_clash; /* those who are mounted on (other instances) of the same dentry */ struct super_block *mnt_sb; /* pointer to superblock */ struct list_head mnt_mounts; /* list of children, anchored here */ struct list_head mnt_child; /* and going through their mnt_child */ atomic_t mnt_count; int mnt_flags; char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */ struct list_head mnt_list; uid_t mnt_owner; }; 每個vfsmount結(jié)構(gòu)包含該文件系統(tǒng)所在的塊設(shè)備號、文件系統(tǒng)安裝點(diǎn)的目錄名稱,以及指向?yàn)樵撐募到y(tǒng)分配的VFS超級塊的指針。而VFS超級塊中則包含描述文件系統(tǒng)的file_system_type結(jié)構(gòu)指針和該文件系統(tǒng)根結(jié)點(diǎn)指針。 下面三個函數(shù)是用來操作已安裝文件系統(tǒng)鏈表的,它們都在fs/super.c中實(shí)現(xiàn): lookup_vfsmnt():在鏈表中尋找指定設(shè)備號的vfsmnt結(jié)構(gòu),成功則返回指向該結(jié)構(gòu)的指針,否則返回0。 add_vfsmnt():在鏈表尾加入一個vfsmnt結(jié)構(gòu),返回指向該結(jié)構(gòu)的指針。 remove_vfsmnt():從鏈表中移走指定設(shè)備號的vfsmnt結(jié)構(gòu),并釋放其所占有的內(nèi)核內(nèi)存空間。該函數(shù)無返回值。 3 文件系統(tǒng)的卸載 當(dāng)文件系統(tǒng)被卸載的時候,系統(tǒng)將檢查在該文件系統(tǒng)上是否有正被使用。如果有文件正在使用,則不能被卸載。如果該文件系統(tǒng)中的文件或者目錄正在使用,則VFS索引節(jié)點(diǎn)高速緩存中可能包含相應(yīng)的VFS索引節(jié)點(diǎn),檢查代碼將在索引節(jié)點(diǎn)高速緩存中,根據(jù)文件系統(tǒng)所在的設(shè)備標(biāo)識符,查找是否有來自該文件系統(tǒng)的VFS索引節(jié)點(diǎn),如果有而且使用計(jì)數(shù)大于0,則說明該文件系統(tǒng)正在被使用。因此,該文件系統(tǒng)不能被卸載。 否則,將查看對應(yīng)的VFS超級塊,如果該文件系統(tǒng)的VFS超級塊標(biāo)志為“臟”,那么必須將超級塊信息寫回磁盤。 上述過程結(jié)束后,對應(yīng)的VFS超級塊被釋放,vfsmount數(shù)據(jù)結(jié)構(gòu)將從vfsmntlist鏈表中斷開并釋放。 (四) VFS 數(shù)據(jù)結(jié)構(gòu)分析 現(xiàn)在我們已經(jīng)大致了解了VFS操作的基本過程。下面我們分析一下在VFS中使用的幾個重要的數(shù)據(jù)結(jié)構(gòu),它們是VFS實(shí)現(xiàn)的核心,更是與邏輯文件系統(tǒng)交互的接口,因此必須進(jìn)行詳細(xì)的分析。 1 VFS超級塊及其操作 許多邏輯文件系統(tǒng)都有超級塊結(jié)構(gòu),超級塊是這些文件系統(tǒng)中最重要的數(shù)據(jù)結(jié)構(gòu),用來描述整個文件系統(tǒng)的信息,是一個全局的數(shù)據(jù)結(jié)構(gòu)。MINIX、EXT2等都有自己的超級塊,VFS也有超級塊,但和邏輯文件系統(tǒng)的超級塊不同,VFS超級塊是存在于內(nèi)存中的結(jié)構(gòu),它在邏輯文件系統(tǒng)安裝時建立,并且在文件系統(tǒng)卸載時自動刪除,因此,VFS對于每一個邏輯文件系統(tǒng),都有一個對應(yīng)的VFS超級塊。 VFS超級塊在include/fs/fs.h中定義,即數(shù)據(jù)結(jié)構(gòu)super_block,該結(jié)構(gòu)主要定義如下: struct super_block { struct list_head s_list; /* Keep this first */ kdev_t s_dev; unsigned long s_blocksize; unsigned char s_blocksize_bits; unsigned char s_lock; unsigned char s_dirt; unsigned long long s_maxbytes; /* Max file size */ struct file_system_type *s_type; struct super_operations *s_op; struct dquot_operations *dq_op; unsigned long s_flags; unsigned long s_magic; struct dentry *s_root; wait_queue_head_t s_wait; struct list_head s_dirty; /* dirty inodes */ struct list_head s_files; struct block_device *s_bdev; struct list_head s_mounts; /* vfsmount(s) of this one */ struct quota_mount_options s_dquot; /*Diskquota specific options */
union { struct minix_sb_info minix_sb; struct ext2_sb_info ext2_sb; …… …… void *generic_sbp; } u; struct semaphore s_vfs_rename_sem; /*Kludge */ struct semaphore s_nfsd_free_path_sem; }; 下面對該結(jié)構(gòu)的主要域進(jìn)行一個簡單的分析: s_list:所有已裝載文件系統(tǒng)的雙向鏈表(參考 linux/list.h)。 s_dev:裝載該文件系統(tǒng)的設(shè)備(可以是匿名設(shè)備)標(biāo)識號,舉例來說,對于/dev/hda1,其設(shè)備標(biāo)識號為ox301。 s_blocksize:該文件系統(tǒng)的基本數(shù)據(jù)塊的大小。以字節(jié)為單位,并且必須是2的n次方。 s_blocksize_bits:塊大小所占的位數(shù),即log2(s_blocksize)。 s_lock:用來指出當(dāng)前超級塊是否被鎖住。 s_wait:這是一個等待隊(duì)列,其中的進(jìn)程都在等待該超級塊的s_lock。 s_dirt:這是一個標(biāo)志位。當(dāng)超級塊被改變時,將置位;當(dāng)超級塊被寫入設(shè)備時,將清位。(當(dāng)文件系統(tǒng)被卸載或者調(diào)用sync 時,有可能會將超級塊寫入設(shè)備。) s_type:指向文件系統(tǒng)的file_system_type結(jié)構(gòu)。 s_op:指向一個超級塊操作集super_operations,我們將在后面進(jìn)行討論。 dq_op:指向一個磁盤限額(DiscQuota)操作集。 s_flags:這是一組操作權(quán)限標(biāo)志,它將與索引節(jié)點(diǎn)的標(biāo)志進(jìn)行邏輯或操作,從而確定某一特定的行為。這里有一個標(biāo)志,可以應(yīng)用于整個文件系統(tǒng),就是MS_RDONLY。一個設(shè)置了如此標(biāo)志的文件系統(tǒng)將被以只讀的方式裝載,任何直接或者間接的寫操作都被禁止,包括超級塊中裝載時間和文件訪問時間的改變等等。 s_root:這是一個指向dentry結(jié)構(gòu)的指針。它指向該文件系統(tǒng)的根。通常它是由裝載文件系統(tǒng)的根結(jié)點(diǎn)(root inode)時創(chuàng)建的,并將它傳遞給d_alloc_root。這個dentry將被mount命令加入到dcache中。 s_dirty:“臟”索引節(jié)點(diǎn)的鏈表。當(dāng)一個索引節(jié)點(diǎn)被mark_inode_dirty標(biāo)志為“臟”時,該索引節(jié)點(diǎn)將被放入到這個鏈表中;當(dāng)sync_inode被調(diào)用時,這個鏈表中的所有索引節(jié)點(diǎn)將被傳遞給該文件系統(tǒng)的write_inode方法。 s_files:該文件系統(tǒng)所有打開文件的鏈表。 u.generic_sbp:在聯(lián)合結(jié)構(gòu)u中,包括了一個文件系統(tǒng)特定的超級塊信息,在上面的結(jié)構(gòu)中,我們可以看到有minix_sb 和ext2_sb 等等結(jié)構(gòu)。這些信息是編譯時可知的信息,對于那些當(dāng)作模塊裝載的文件系統(tǒng),則必須分配一個單獨(dú)的結(jié)構(gòu),并且將地址放入u.generic_sbp中。 s_vfs_rename_sem:這個信號量可以在整個文件系統(tǒng)的范圍內(nèi)使用,當(dāng)重命名一個目錄的時候,將使用它來進(jìn)行鎖定。這是為了防止把一個目錄重命名為它自己的子目錄。當(dāng)重命名的目標(biāo)不是目錄時,則不使用該信號量。 針對上面的超級塊,定義了一組方法,也叫作操作,在結(jié)構(gòu)super_operations中: struct super_operations { void (*read_inode) (struct inode *); void (*read_inode2) (struct inode *, void *) ; void (*dirty_inode) (struct inode *); void (*write_inode) (struct inode *, int); void (*put_inode) (struct inode *); void (*delete_inode) (struct inode *); void (*put_super) (struct super_block *); void (*write_super) (struct super_block *); void (*write_super_lockfs) (struct super_block *); void (*unlockfs) (struct super_block *); int (*statfs) (struct super_block *, struct statfs *); int (*remount_fs) (struct super_block *, int *, char *); void (*clear_inode) (struct inode *); void (*umount_begin) (struct super_block *); }; 因此在實(shí)現(xiàn)實(shí)現(xiàn)自己的邏輯文件系統(tǒng)時,我們必須提供一套自己的超級塊操作函數(shù)。對這些函數(shù)的調(diào)用都來自進(jìn)程正文(process context),而不是來自在中斷例程或者bottom half,并且,所有的方法調(diào)用時,都會使用內(nèi)核鎖,因此,操作可以安全地阻塞,但我們也要避免并發(fā)地訪問它們。 根據(jù)函數(shù)的名字,我們可以大概地了解其功能,下面簡單地介紹一下: read_inode:該方法是從一個裝載的文件系統(tǒng)中讀取一個指定的索引節(jié)點(diǎn)。它由get_new_inode調(diào)用,而get_new_inode則由fs/inode.c中的iget調(diào)用。一般來說,文件系統(tǒng)使用iget來讀取特定的索引節(jié)點(diǎn)。 write_inode:當(dāng)一個文件或者文件系統(tǒng)要求sync時,該方法會被由mark_inode_dirty標(biāo)記為“臟”的索引節(jié)點(diǎn)調(diào)用,用來確認(rèn)所有信息已經(jīng)寫入設(shè)備。 put_inode:如果該函數(shù)被定義了,則每當(dāng)一個索引節(jié)點(diǎn)的引用計(jì)數(shù)減少時,都會被調(diào)用。這并不意味著該索引節(jié)點(diǎn)已經(jīng)沒人使用了,僅僅意味著它減少了一個用戶。要注意的是,put_inode在i_count減少之前被調(diào)用,所以,如果put_inode想要檢查是否這是最后一個引用,則應(yīng)檢查i_count是否為1。大多數(shù)文件系統(tǒng)都會定義該函數(shù),用來在一個索引節(jié)點(diǎn)的引用計(jì)數(shù)減少為0之前做一些特殊的工作。 delete_inode:如果被定義,則當(dāng)一個索引節(jié)點(diǎn)的引用計(jì)數(shù)減少至0,并且鏈接計(jì)數(shù)(i_nlink)也是0的時候,便調(diào)用該函數(shù)。以后,這個函數(shù)有可能會與上一個函數(shù)合并。 notify_change:當(dāng)一個索引節(jié)點(diǎn)的屬性被改變時,會調(diào)用該函數(shù)。它的參數(shù)struct iattr *指向一個新的屬性組。如果一個文件系統(tǒng)沒有定義該方法(即NULL),則VFS會調(diào)用例程fs/iattr.c:inode_change_ok,該方法實(shí)現(xiàn)了一個符合POSIX標(biāo)準(zhǔn)的屬性檢驗(yàn),然后VFS會將該索引節(jié)點(diǎn)標(biāo)記為“臟”。如果一個文件系統(tǒng)實(shí)現(xiàn)了自己的notify_change方法,則應(yīng)該在改變屬性后顯式地調(diào)用mark_inode_dirty(inode)方法。 put_super:在umount(2)系統(tǒng)調(diào)用的最后一步,即將入口從vfsmntlist中移走之前,會調(diào)用該函數(shù)。該函數(shù)調(diào)用時,會對super_block上鎖。一般來說,文件系統(tǒng)會針對這個裝載實(shí)例,釋放特有的私有資源,比如索引節(jié)點(diǎn)位圖,塊位圖。如果該文件系統(tǒng)是由動態(tài)裝載模塊實(shí)現(xiàn)的,則一個buffer header將保存該super_block,并且減少模塊使用計(jì)數(shù)。 write_super:當(dāng)VFS決定要將超級塊寫回磁盤時,會調(diào)用該函數(shù)。有三個地方會調(diào)用它:fs/buffer.c:fs_fsync,fs/super.c:sync_supers和fs/super.c:do_umount,顯然只讀文件系統(tǒng)不需要這個函數(shù)。 statfs:這個函數(shù)用來實(shí)現(xiàn)系統(tǒng)調(diào)用statfs(2),并且如果定義了該函數(shù),會被fs/open.c:sys_statfs調(diào)用,否則將返回ENODEV錯誤。 remountfs:當(dāng)文件系統(tǒng)被重新裝載時,也就是說,當(dāng)mount(2)系統(tǒng)調(diào)用的標(biāo)志MS_REMOUNT被設(shè)置時,會調(diào)用該函數(shù)。一般用來在不卸載文件系統(tǒng)的情況下,改變不同的裝載參數(shù)。比如,把一個只讀文件系統(tǒng)變成可寫的文件系統(tǒng)。 clear_inode:可選方法。當(dāng)VFS清除索引節(jié)點(diǎn)的時候,會調(diào)用該方法。當(dāng)一個文件系統(tǒng)使用了索引節(jié)點(diǎn)結(jié)構(gòu)中的generic_ip域,向索引節(jié)點(diǎn)增加了特別的(使用kmalloc動態(tài)分配的)數(shù)據(jù)時,便需要此方法來做相應(yīng)的處理。 2 VFS的文件及其操作 文件對象使用在任何需要讀寫的地方,包括通過文件系統(tǒng),或者管道以及網(wǎng)絡(luò)等進(jìn)行通訊的對象。 文件對象和進(jìn)程關(guān)系緊密,進(jìn)程通過文件描述符(file descriptors)來訪問文件。文件描述符是一個整數(shù),linux通過fs.h中定義的NR_OPEN來規(guī)定每個進(jìn)程最多同時使用的文件描述符個數(shù): #define NR_OPEN (1024*1024) 一共有三個與進(jìn)程相關(guān)的結(jié)構(gòu),第一個是files_struct,在include/linux/sched.h中定義,主要是一個fd數(shù)組,數(shù)組的下標(biāo)是文件描述符,其內(nèi)容就是對應(yīng)的下面將要介紹的file結(jié)構(gòu)。 另外一個結(jié)構(gòu)是fs_struct,主要有兩個指針,pwd指向當(dāng)前工作目錄的索引節(jié)點(diǎn);root指向當(dāng)前工作目錄所在文件系統(tǒng)的根目錄的索引節(jié)點(diǎn)。 最后一個結(jié)構(gòu)是file結(jié)構(gòu),定義它是為了保證進(jìn)程對文件的私有記錄,以及父子進(jìn)程對文件的共享,這是一個非常巧妙的數(shù)據(jù)結(jié)構(gòu)。我們將在下面進(jìn)行詳細(xì)的分析。 上述結(jié)構(gòu)與進(jìn)程的關(guān)系如下圖所示:
仔細(xì)分析其聯(lián)系,對于我們理解進(jìn)程對文件的訪問操作很有幫助。 結(jié)構(gòu)file定義在linux/fs.h中: struct file { struct list_head f_list; struct dentry *f_dentry; struct vfsmount *f_vfsmnt; struct file_operations *f_op; atomic_t f_count; unsigned int f_flags; mode_t f_mode; loff_t f_pos; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error;
unsigned long f_version;
/* needed for tty driver, and maybe others */ void *private_data; }; 下面對其作一個簡單的分析: f_list:該域?qū)⑽募溄拥酱蜷_的文件鏈表中,鏈表由超級塊中的s_files開始。 f_dentry:該域指向該文件的索引節(jié)點(diǎn)的dcache入口。如果文件的索引節(jié)點(diǎn)不在普通的文件系統(tǒng)中,而是諸如管道pipe之類的對象,那么,dentry將是一個由d_alloc_root創(chuàng)建的root dentry。 f_vfsmnt:該域指向該文件所在文件系統(tǒng)的vfsmount結(jié)構(gòu)。 f_op:指向應(yīng)用于文件的操作集。 f_count:引用該文件的計(jì)數(shù)。是用戶進(jìn)程的引用數(shù)加上內(nèi)部的引用數(shù)。 f_flags:該域存儲了進(jìn)程對該文件的訪問類型,比如O_NONBLOCK,O_APPEND等等。有些標(biāo)志比如O_EXCL,O_CREAT等等,只在打開文件的時候使用,因此并不存儲在f_flags中。 f_mode:對文件的操作標(biāo)志,只讀,只寫,以及讀寫。 f_pos:該域存儲了文件的當(dāng)前位置。 f_reada, f_ramax, f_raend, f_ralen, f_rawin:這五個域用來跟蹤對文件的連續(xù)訪問,并決定預(yù)讀多少內(nèi)容。 f_owner:該結(jié)構(gòu)存儲了一個進(jìn)程id,以及當(dāng)特定事件發(fā)生在該文件時發(fā)送的一個信號,比如當(dāng)有新數(shù)據(jù)到來的時候等等。 f_uid, f_gid:打開該文件的進(jìn)程的uid和gid,沒有實(shí)際的用途。 f_version:用來幫助底層文件系統(tǒng)檢查cache的狀態(tài)是否合法。當(dāng)f_pos變化時,它的值就會發(fā)生變化。 private_data:這個域被許多設(shè)備驅(qū)動所使用,有些特殊的文件系統(tǒng)為每一個打開的文件都保存一份額外的數(shù)據(jù)(如coda),也會使用這個域。 下面我們看一看針對文件的操作,在file結(jié)構(gòu)中,有一個指針指向了一個文件操作集file_operations,它在linux/fs.h中被定義: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 這些操作用來將VFS對file結(jié)構(gòu)的操作轉(zhuǎn)化為邏輯文件系統(tǒng)處理相應(yīng)操作的函數(shù)。因此,要了解一個邏輯文件系統(tǒng),就要從這些接口函數(shù)入手。下面對這些操作進(jìn)行一個簡單的分析: llseek:該函數(shù)用來實(shí)現(xiàn)lseek系統(tǒng)調(diào)用。如果它沒有定義,則缺省執(zhí)行fs/read_write.c中的default_llseek函數(shù)。它將更新fs_pos域,并且,也有可能會改變f_reada和f_version域。 read:該函數(shù)用來實(shí)現(xiàn)read系統(tǒng)調(diào)用,同時也支持其他諸如裝載可執(zhí)行文件等等操作。 write:該方法用來寫文件。但它并不關(guān)心數(shù)據(jù)是否真正寫入到設(shè)備,只將數(shù)據(jù)放入隊(duì)列中。 readdir:該函數(shù)從一個假定為目錄的文件讀取目錄結(jié)構(gòu),并且使用回調(diào)函數(shù)filldir_t將其返回。當(dāng)readdir到達(dá)目錄的結(jié)尾處時,它會返回0。 poll:該函數(shù)用來實(shí)現(xiàn)select和poll系統(tǒng)調(diào)用。 ioctl:該函數(shù)實(shí)現(xiàn)專門的ioctl功能。如果一個ioctl請求不在標(biāo)準(zhǔn)請求中(FIBMAP,F(xiàn)IGETBSZ,F(xiàn)IONREAD),那么該請求將傳遞給底層的文件實(shí)現(xiàn)。 mmap:該例程用來實(shí)現(xiàn)文件的內(nèi)存映射。它通常使用generic_file_map來實(shí)現(xiàn)。使用它的任務(wù)會被檢驗(yàn)是否允許作映射,并且會設(shè)置vm_area_struct中的vm_ops。 open:如果該方法被定義,那么當(dāng)一個新的文件在索引節(jié)點(diǎn)上被打開時,會調(diào)用它。它可以做一些打開文件所必須的設(shè)置。在許多文件系統(tǒng)上,都不需要它。一個例外是coda,它需要在打開時試圖獲得本地緩存的文件。 flush:當(dāng)一個文件描述符被關(guān)閉時,會調(diào)用該函數(shù)。由于此時可能有其他的描述符在該文件上被打開,因此,它并不意味著該文件被最終關(guān)閉。目前在文件系統(tǒng)中,只有NFS的客戶端定義了該方法。 release:當(dāng)文件的最后一個句柄被關(guān)閉時,release將被調(diào)用。它會做一些必要的清理工作。該函數(shù)不能向任何方面返回錯誤值,因此應(yīng)該將其定義為void。 fsync:該方法用來實(shí)現(xiàn)fsync和fdatasync系統(tǒng)調(diào)用(它們一般是相同的)。它將一直等到所有對該文件掛起的寫操作全部成功寫到設(shè)備后才返回。fsync可以部分地通過generic_buffer_fdatasync實(shí)現(xiàn),這個函數(shù)將索引節(jié)點(diǎn)映射的頁面中所有標(biāo)記為臟的緩沖區(qū),全部寫回。 fasync:該方法在一個文件的FIOASYNC標(biāo)志被改變的時候被調(diào)用。它的int類型的參數(shù)包含了該標(biāo)志位的新值。目前還沒有文件系統(tǒng)實(shí)現(xiàn)該方法。 lock:該方法允許一個文件服務(wù)提供額外的POSIX鎖。它不被FLOCK類型的鎖使用,它對于網(wǎng)絡(luò)文件系統(tǒng)比較有用。 3 VFS索引節(jié)點(diǎn)及其操作 Linux維護(hù)了一個活動的及最近使用過的索引節(jié)點(diǎn)的高速緩存(cache)。有兩種方法來訪問這些索引節(jié)點(diǎn)。第一種是通過dcache,我們將在下一節(jié)介紹。在dcache中的每一個dentry都指向一個索引節(jié)點(diǎn),并且因此而將索引節(jié)點(diǎn)維護(hù)在緩存中。第二種方法是通過索引節(jié)點(diǎn)的哈希表。每一個索引節(jié)點(diǎn)都被基于該文件系統(tǒng)超級塊的地址和索引節(jié)點(diǎn)的編號,被哈希為一個8位的數(shù)字。所有擁有同樣哈希值的索引節(jié)點(diǎn)通過雙項(xiàng)鏈表被鏈接在一起。 通過哈希表訪問是通過函數(shù)iget而實(shí)現(xiàn)的。iget只被個別的文件系統(tǒng)實(shí)現(xiàn)所調(diào)用(當(dāng)索引節(jié)點(diǎn)不再dcache中而進(jìn)行查找的時候)。 下面我們來分析索引節(jié)點(diǎn)inode的結(jié)構(gòu),在include/linux/fs.h中有inode的定義: struct inode { struct list_head i_hash; struct list_head i_list; struct list_head i_dentry;
struct list_head i_dirty_buffers;
unsigned long i_ino; atomic_t i_count; kdev_t i_dev; umode_t i_mode; nlink_t i_nlink; uid_t i_uid; gid_t i_gid; kdev_t i_rdev; loff_t i_size; time_t i_atime; time_t i_mtime; time_t i_ctime; unsigned long i_blksize; unsigned long i_blocks; unsigned long i_version; unsigned short i_bytes; struct semaphore i_sem; struct semaphore i_zombie; struct inode_operations *i_op; struct file_operations *i_fop; struct super_block * i_shadow; struct inode_shadow_operations * i_shadow_op; struct super_block *i_sb; wait_queue_head_t i_wait; struct file_lock *i_flock; struct address_space *i_mapping; struct address_space i_data; struct dquot *i_dquot[MAXQUOTAS]; struct pipe_inode_info *i_pipe; struct block_device *i_bdev;
unsigned long i_dnotify_mask; /* Directory notify events */ struct dnotify_struct *i_dnotify; /* for directory notifications */
unsigned long i_state;
unsigned int i_flags; unsigned char i_sock;
atomic_t i_writecount; unsigned int i_attr_flags; __u32 i_generation; union { struct minix_inode_info minix_i; struct ext2_inode_info ext2_i; ……… (略) struct proc_inode_info proc_i; struct socket socket_i; struct usbdev_inode_info usbdev_i; struct supermount_inode_info supermount_i; void *generic_ip; } u; }; 下面我們對它所一個分析,在上面的結(jié)構(gòu)中,大部分字段的意義都很明顯,因此我們將對一些特殊的字段(針對linux)和一些特殊的地方進(jìn)行分析。 i_hash:i_hash將所有擁有相同哈希值的索引節(jié)點(diǎn)鏈接在一起。哈希值基于超級塊結(jié)構(gòu)的地址和索引節(jié)點(diǎn)的索引號。 i_list:i_list用來將索引節(jié)點(diǎn)鏈接到不同的狀態(tài)上。inode_in_use鏈表將正在使用的未改變的索引節(jié)點(diǎn)鏈接在一起,inode_unused將未使用的索引節(jié)點(diǎn)鏈接在一起,而superblock->s_dirty維護(hù)指定文件系統(tǒng)內(nèi)所有標(biāo)記為“臟”的索引節(jié)點(diǎn)。 i_dentry:i_dentry鏈表中,鏈接了所有引用該索引節(jié)點(diǎn)的dentry結(jié)構(gòu)。它們通過dentry中的d_alias鏈接在一起。 i_version:它被文件系統(tǒng)用來記錄索引節(jié)點(diǎn)的改變。一般來說,i_version被設(shè)置為全局變量event的值,然后event回自增。有時候,文件系統(tǒng)的代碼會把i_version的當(dāng)前值分配給相關(guān)的file結(jié)構(gòu)中的f_version,在隨后file結(jié)構(gòu)的應(yīng)用中,它可以被用來高速我們,inode是否被改變了,如果需要的話,在file結(jié)構(gòu)中緩存的數(shù)據(jù)要被刷新。 i_sem:這個信號燈用來保護(hù)對inode的改變。所有對inode的非原子操作代碼,都要首先聲明該信號燈。這包括分配和銷毀數(shù)據(jù)塊,以及通過目錄進(jìn)行查找等等操作。并且,不能對只讀操作聲明共享鎖。 i_flock:它指向在該inode上加鎖的file_lock結(jié)構(gòu)鏈表。 i_state:對于2.4內(nèi)核來說,共有六種可能的inode狀態(tài):I_DIRTY_SYNC, I_DIRTY_DATASYNC, I_DIRTY_PAGES, I_LOCK, I_FREEING和 I_CLEAR。所有臟節(jié)點(diǎn)在相應(yīng)超級塊的s_dirty鏈表中,并且在下一次同步請求時被寫入設(shè)備。在索引節(jié)點(diǎn)被創(chuàng)建,讀取或者寫入的時候,會被鎖住,即I_LOCK狀態(tài)。當(dāng)一個索引節(jié)點(diǎn)的引用計(jì)數(shù)和鏈接計(jì)數(shù)都到0時,將被設(shè)置為I_CLEAR狀態(tài)。 i_flags:i_flags對應(yīng)于超級塊中的s_flags,有許多標(biāo)記可以被系統(tǒng)范圍內(nèi)設(shè)置,也可以針對每個索引節(jié)點(diǎn)設(shè)置。 i_writecount:如果它的值為正數(shù),那么它就記錄了對該索引節(jié)點(diǎn)有寫權(quán)限的客戶(文件或者內(nèi)存映射)的個數(shù)。如果是負(fù)數(shù),那么該數(shù)字的絕對值就是當(dāng)前VM_DENYWRITE映射的個數(shù)。其他情況下,它的值為0。 i_attr_flags:未被使用。 最后要注意的是,在linux 2.4中,inode結(jié)構(gòu)中新增加了一項(xiàng),就是struct file_operations *i_fop,它指向索引節(jié)點(diǎn)對應(yīng)的文件的文件操作集,而原來它放在inode_operations中(即inode結(jié)構(gòu)的另一個項(xiàng)目struct inode_operations *i_op之中),現(xiàn)在它已經(jīng)從inode_operations中移走了,我們可以從下面對inode_operations結(jié)構(gòu)的分析中看到這一點(diǎn)。 下面我們分析一下對于inode進(jìn)行操作的函數(shù)。所有的方法都放在inode_operations結(jié)構(gòu)中,它在include/linux/fs.h中被定義: struct inode_operations { int (*create) (struct inode *,struct dentry *,int); struct dentry * (*lookup) (struct inode *,struct dentry *); 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 *,int); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,int,int); int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *); int (*readlink) (struct dentry *, char *,int); int (*follow_link) (struct dentry *, struct nameidata *); void (*truncate) (struct inode *); int (*permission) (struct inode *, int); int (*revalidate) (struct dentry *); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (struct dentry *, struct iattr *); }; 同樣,我們對這些方法做一個簡單的分析。 create:這個方法,以及下面的8個方法,都只在目錄索引節(jié)點(diǎn)中被維護(hù)。 當(dāng)VFS想要在給定目錄創(chuàng)建一個給定名字(在參數(shù)dentry中)的新文件時,會調(diào)用該函數(shù)。VFS將提前確定該名字并不存在,并且作為參數(shù)的dentry必須為負(fù)值(即其中指向inode的指針為NULL,根據(jù)include/dcache.h中的定義,其注釋為“NULL is negative”)。 如果create調(diào)用成功,將使用get_empty_inode從cache中得到一個新的空索引節(jié)點(diǎn),填充它的內(nèi)容,并使用insert_inode_hash將其插入到哈希表中,使用mark_inode_dirty標(biāo)記其為臟,并且使用d_instantiate將其在dcache中實(shí)例化。 int參數(shù)包含了文件的mode并指定了所需的許可位。 lookup:該函數(shù)用來檢查是否名字(由dentry提供)存在于目錄(由inode提供)中,并且如果存在的話,使用d_add更新dentry。 link:該函數(shù)用來將一個名字(由第一個dentry提供)硬鏈接到在在指定目錄(由參數(shù)inode提供)中的另一個名字(由第二個dentry參數(shù)提供)。 unlink:刪除目錄中(參數(shù)inode指定)的名字(由參數(shù)dentry提供)。 symlink:創(chuàng)建符號鏈接。 mkdir:根據(jù)給定的父節(jié)點(diǎn),名字和模式,創(chuàng)建一個目錄。 rmdir:移除指定的目錄(如果為空目錄),并刪除(d_delete)dentry。 mknod:根據(jù)給定的父節(jié)點(diǎn),名字,模式以及設(shè)備號,創(chuàng)建特殊的設(shè)備文件,然后使用d_instantiate將新的inode在dentry中實(shí)例化。 rename:重命名。所有的檢測,比如新的父節(jié)點(diǎn)不能是舊名字的孩子等等,都已經(jīng)在調(diào)用前被完成。 readlink:通過dentry參數(shù),讀取符號鏈接,并且將其拷貝到用戶空間,最大長度由參數(shù)int指定。 permission:在該函數(shù)中,可以實(shí)現(xiàn)真正的權(quán)限檢查,與文件本身的mode無關(guān)。 4 VFS名字以及dentry 根據(jù)我們上面的介紹,可以看出,文件和索引節(jié)點(diǎn)的聯(lián)系非常緊密,而在文件和索引節(jié)點(diǎn)之間,是通過dentry結(jié)構(gòu)來聯(lián)系的。 VFS層處理了文件路徑名的所有管理工作,并且在底層文件系統(tǒng)能夠看到它們之前,將其轉(zhuǎn)變?yōu)閐cache中的入口(entry)。唯一的一個例外是對于符號鏈接的目標(biāo),VFS將不加改動地傳遞給底層文件系統(tǒng),由底層文件系統(tǒng)對其進(jìn)行解釋。 目錄高速緩存dcache由許多dentry結(jié)構(gòu)組成。每一個dentry都對應(yīng)文件系統(tǒng)中的一個文件名,并且與之聯(lián)系。每一個dentry的父節(jié)點(diǎn)都必須存在于dcache中。同時,dentry還記錄了文件系統(tǒng)的裝載關(guān)系。 dcache是索引節(jié)點(diǎn)高速緩存的管理者。不論何時,只要在dcache中存在一個入口,那么相應(yīng)的索引節(jié)點(diǎn)一定在索引節(jié)點(diǎn)高速緩存中。換句話說,如果一個索引節(jié)點(diǎn)在高速緩存中,那么它一定引用dcache中的一個dentry。 下面我們來分析一下dentry的結(jié)構(gòu),以及在dentry上的操作。在include/linux/dcache.h中,由其定義: struct dentry { atomic_t d_count; unsigned int d_flags; struct inode * d_inode; /* Where the name belongs to - NULL is negative */ struct dentry * d_parent; /* parent directory */ struct list_head d_vfsmnt; struct list_head d_hash; /* lookup hash list */ struct list_head d_lru; /* d_count = 0 LRU list */ struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ struct list_head d_alias; /* inode alias list */ struct qstr d_name; unsigned long d_time; /* used by d_revalidate */ struct dentry_operations *d_op; struct super_block * d_sb; /* The root of the dentry tree */ unsigned long d_reftime; /* last time referenced */ void * d_fsdata; /* fs-specific data */ unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ }; 在該結(jié)構(gòu)的注釋中,大部分域的含義已經(jīng)非常的清楚,下面我再簡單地介紹一下。 d_flags:在目前,只有兩個可取值,而且都是給特殊的文件系統(tǒng)使用的,它們是DCACHE_AUTOFS_PENDING和DCACHE_NFSFS_RENAMED,因此,在這里我們可以暫時忽略它。 d_inode:它簡單地指向與該名字聯(lián)系的索引節(jié)點(diǎn)。這個域可以是NULL,它標(biāo)明這是一個負(fù)入口(negative entry),暗示著該名字并不存在。 d_hash:這是一個雙向鏈表,將所有擁有相同哈希值的入口鏈接在一起。 d_lru:它提供了一個雙向鏈表,鏈接高速緩存中未被引用的葉節(jié)點(diǎn)。這個鏈表的頭是全局變量dentry_unused,按照最近最少使用的順序存儲。 d_child:這是一個容易讓人誤會的名字,其實(shí),該鏈表鏈接d_parent的所有子節(jié)點(diǎn),因此把它稱為d_sibling(同胞)更恰當(dāng)一些。 d_subdirs:該鏈表將該dentry的所有子節(jié)點(diǎn)鏈接在一起,所以,它實(shí)際上是它子節(jié)點(diǎn)的d_child鏈表的鏈表頭。這個名字也容易產(chǎn)生誤會,因?yàn)樗淖庸?jié)點(diǎn)不僅僅包括子目錄,也可以是文件。 d_alias:由于文件(以及文件系統(tǒng)的其他一些對象)可能會通過硬鏈接的方法,擁有多個名字,因此有可能會有多個dentry指向同一個索引節(jié)點(diǎn)。在這種情況下,這些dentry將通過d_alias鏈接在一起。而inode的i_dentry就是該鏈表的頭。 d_name:該域包含了這個入口的名字,以及它的哈希值。它的子域name有可能會指向該dentry的d_iname域(如果名字小于等于16個字符),否則的話,它將指向一個單獨(dú)分配出來的字符串。 d_op:指向dentry的操作函數(shù)集。 d_sb:指向該dentry對應(yīng)文件所在的文件系統(tǒng)的超級塊。使用d_inode->i_sb有相同的效果。 d_iname:它存儲了文件名的前15個字符,目的是為了方便引用。如果名字適合,d_name.name將指向這里。
下面我們再看一下對dentry的操作函數(shù),同樣在include/linux/dcache.h中有dentry_operations的定義: struct dentry_operations { int (*d_revalidate)(struct dentry *, int); int (*d_hash) (struct dentry *, struct qstr *); int (*d_compare) (struct dentry *, struct qstr *, struct qstr *); int (*d_delete)(struct dentry *); void (*d_release)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); }; 我們再簡單地介紹一下: d_revalidate:這個方法在entry在dcache中做路徑查找時調(diào)用,目的是為了檢驗(yàn)這個entry是否依然合法。如果它依舊可以被信賴,則返回1,否則返回0。 d_hash:如果文件系統(tǒng)沒有提供名字驗(yàn)證的規(guī)則,那么這個例程就被用來檢驗(yàn)并且返回一個規(guī)范的哈希值。 d_compare:它被用來比較兩個qstr,來看它們是否是相同的。 d_delete:當(dāng)引用計(jì)數(shù)到0時,在這個dentry被放到dentry_unused鏈表之前,會調(diào)用該函數(shù)。 d_release:在一個dentry被最終釋放之前,會調(diào)用該函數(shù)。 d_iput:如果定義了該函數(shù),它就被用來替換iput,來dentry被丟棄時,釋放inode。它被用來做iput的工作再加上其他任何想要做的事情。
(五) 總結(jié) 在上面的部分中,我們對VFS進(jìn)行了一個大概的分析。了解了文件系統(tǒng)的注冊,安裝,以及卸載的過程,并對VFS對邏輯文件系統(tǒng)的管理,尤其是接口部分的數(shù)據(jù)結(jié)構(gòu),進(jìn)行了詳細(xì)的分析。 proc文件系統(tǒng)作為一個特殊的邏輯文件系統(tǒng),其實(shí)現(xiàn)也遵循VFS接口,因此,根據(jù)上面對VFS的分析,我們可以基本確定對proc文件系統(tǒng)進(jìn)行分析的步驟。
|