一 .VFS 中的目錄項對象
1.爲了方便查找,VFS引入了 目錄 項,每個dentry代表路徑中的一個特定部分。目錄項也可包括安裝點。
2.目錄項對象由dentry結構體表示 ,定義在文件linux/dcache.h 頭文件中。
89struct dentry {
90 atomic_t d_count; //使用計數
91 unsigned int d_flags; //目錄項標時
92 spinlock_t d_lock; //單目錄鎖
93 int d_mounted; //目錄項的安裝點
94 struct inode *d_inode; //與該目錄項相關聯的索引節點
95
96 /*
97 * The next three fields are touched by __d_lookup. Place them here
98 * so they all fit in a cache line.
99 */
100 struct hlist_node d_hash; //散列表
101 struct dentry *d_parent; //父目錄項
102 struct qstr d_name; //目錄項名可快速查找
103
104 struct list_head d_lru; // 未使用目錄以LRU 算法鏈接的鏈表
105 /*
106 * d_child and d_rcu can share memory
107 */
108 union {
109 struct list_head d_child; /* child of parent list */
110 struct rcu_head d_rcu;
111 } d_u;
112 struct list_head d_subdirs; //該目錄項子目錄項所形成的鏈表
113 struct list_head d_alias; //索引節點別名鏈表
114 unsigned long d_time; //重新生效時間
115 const struct dentry_operations *d_op; // 操作目錄項的函數
116 struct super_block *d_sb; //目錄項樹的根
117 void *d_fsdata; //具體文件系統的數據
118
119 unsigned char d_iname[DNAME_INLINE_LEN_MIN]; //短文件名
120};
1>索引節點中的i_dentry指向了它目錄項,目錄項中的d_alias,d_inode又指會了索引節點對象,目錄項中的d_sb又指回了超級塊對象。
2>我們可以看到不同於VFS 中的索引節點對象和超級塊對象,目錄項對象中沒有對應磁盤的數據結構,所以說明目錄項對象並沒有真正標存在磁盤上,那麼它也就沒有髒標誌位。
3>目錄項的狀態(被使用,未被使用和負狀態)
a.它們是靠d_count的值來進行區分的,當d_count爲正值說明目錄項處於被使用狀態。當d_count=0時表示該目錄項是一個未被使用的目錄項, 但其d_inode指針仍然指向相關的的索引節點。該目錄項仍然包含有效的信息,只是當前沒有人引用他。d_count=NULL表示負(negative)狀態,與目錄項相關的inode對象不復存在(相應的磁盤索引節點可能已經被刪除),dentry對象的d_inode 指針爲NULL。但這種dentry對象仍然保存在dcache中,以便後續對同一文件名的查找能夠快速完成。這種dentry對象在回收內存時將首先被釋放。
4> d_subdirs:如果當前目錄項是一個目錄,那麼該目錄下所有的子目錄形成一個鏈表。該字段是這個鏈表的表頭;
d_child:如果當前目錄項是一個目錄,那麼該目錄項通過這個字段加入到父目錄的d_subdirs鏈表當中。這個字段中的next和prev指針分別 指向父目錄中的另外兩個子目錄;
d_alias:一個inode可能對應多個目錄項,所有的目錄項形成一個鏈表。inode結構中的i_dentry即爲這個鏈表的頭結點。當前目錄項以這個字段處於i_dentry鏈表中。www.linuxidc.com該字段中的prev和next指針分別指向與該目錄項同inode的其他兩個(如果有的話)目錄項
3.dentry和inode的區別:
inode(可理解爲ext2 inode)對應於物理磁盤上的具體對象,dentry是一個內存實體,其中的d_inode成員指向對應的inode。也就是說,一個inode可以在運行的時候鏈接多個dentry,而d_count記錄了這個鏈接的數量。
4.dentry與dentry_cache
dentry_cache簡稱dcache,中文名稱是目錄項高速緩存,是Linux爲了提高目錄項對象的處理效率而設計的。它主要由兩個數據結構組成:
1>哈希鏈表dentry_hashtable:dcache中的所有dentry對象都通過d_hash指針域鏈到相應的dentry哈希鏈表中。
2>未使用的dentry對象鏈表dentry_unused:dcache中所有處於unused狀態和negative狀態的dentry對象都通過其d_lru指針域鏈入dentry_unused鏈表中。該鏈表也稱爲LRU鏈表。
目錄項高速緩存dcache是索引節點緩存icache的主控器(master),也即 dcache中的dentry對象控制着icache中的inode對象的生命期轉換。無論何時,只要一個目錄項對象存在於dcache中(非 negative狀態),則相應的inode就將總是存在,因爲 inode的引用計數i_count總是大於0。當dcache中的一個dentry被釋放時,針對相應inode對象的iput()方法就會被調用。
5對目錄項進行操作的一組函數叫目錄項操作表,由dentry_operation結構描述。它可以在 include/linux/dcache.h 中查到
134struct dentry_operations {
135 int (*d_revalidate)(struct dentry *, struct nameidata *);
136 int (*d_hash) (struct dentry *, struct qstr *);
137 int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
138 int (*d_delete)(struct dentry *);
139 void (*d_release)(struct dentry *);
140 void (*d_iput)(struct dentry *, struct inode *);
141 char *(*d_dname)(struct dentry *, char *, int);
142};
a.int d_reavlidate(struct dentry *dentry ,int flags) 該函數判斷目錄對象是否有效。VFS準備從dcache中使用一個目錄項時,會調用該函數.
b.int d_hash(struct dentry *dentry ,struct qstr *name):該目錄生成散列值,當目錄項要加入到散列表時,VFS要調用此函數。
c.int d_compare( struct dentry *dentry, struct qstr *name1, struct qstr *name2) 該函數來比較name1和name2這兩個文件名。使用該函數要加dcache_lock鎖。
d.int d_delete(struct dentry *dentry):當d_count=0時,VFS調用次函數。使用該函數要叫 dcache_lock鎖。
e.void d_release(struct dentry *dentry):當該目錄對象將要被釋放時,VFS調用該函數。
f.void d_iput(struct dentry *dentry,struct inode *inode)當一個目錄項丟失了其索引節點時,VFS就掉用該函數。
二.VFS中的文件對象
1.文件對象表示進程已經打開的文件 在內存中的表示,該對象不是物理上的文件。它是由相應的open()系統調用創建,由close()系統調用銷燬。多個進程可以打開和操作同一個文件,所以同一個文件也可能存在多個對應的文件對象。
2一個文件對應的文件對象不是唯一的,但對應的索引節點和超級塊對象是唯一的。
3.file結構中保存了文件位置,此外,還把指向該文件索引節點的指針也放在其中。file結構形成一個雙鏈表,稱爲系統打開文件表 。它的定義在 include/linux/fs.h 中可以看到
909struct file {
910 /*
911 * fu_list becomes invalid after file_free is called and queued via
912 * fu_rcuhead for RCU freeing
913 */
914 union {
915 struct list_head fu_list; //每個文件系統中被打開的文件都會形成一個雙鏈表
916 struct rcu_head fu_rcuhead;
917 } f_u;
918 struct path f_path;
919#define f_dentry f_path.dentry // 與該文件對應的dentry
920#define f_vfsmnt f_path.mnt
921 const struct file_operations *f_op; //指向文件操作表的指針
922 spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
923#ifdef CONFIG_SMP
924 int f_sb_list_cpu;
925#endif
926 atomic_long_t f_count; //文件對象的使用計數
927 unsigned int f_flags; //打開文件時所指定的標誌
928 fmode_t f_mode; //文件的訪問模式
929 loff_t f_pos; //文件當前的位移量
930 struct fown_struct f_owner
931 const struct cred *f_cred;
932 struct file_ra_state f_ra; //預讀狀態
933
934 u64 f_version; //版本號
935#ifdef CONFIG_SECURITY
936 void *f_security; //安全模塊
937#endif
938 /* needed for tty driver, and maybe others */
939 void *private_data; //tty設備hook
940
941#ifdef CONFIG_EPOLL
942 /* Used by fs/eventpoll.c to link all the hooks to this file */
943 struct list_head f_ep_links;
944#endif /* #ifdef CONFIG_EPOLL */
945 struct address_space *f_mapping; //頁緩存映射
946#ifdef CONFIG_DEBUG_WRITECOUNT
947 unsigned long f_mnt_write_state;
948#endif
949};
1>文件對象實際上沒有對應的磁盤數據,所以在結構體中沒有代表其對象是否爲髒,是否需要寫回磁盤的標誌。文件對象 通過f_path.dentry指針指向相關的目錄項對象。目錄項會指向相關的索引節點,索引節點會記錄文件是否是髒的。
2>fu_list:每個文件系統中以被打開的文件都會形成一個雙聯表,這個雙聯表的頭結點存放在超級塊的s_files字段中。該字段的prev和next指針分別指向在鏈表中與當前文件結構體相鄰的前後兩個元素.
Tiger-John說明:
file結構中主要保存了文件位置,此外還把指向該文件索引節點的指針也放在其中。---》有人就問了,問什麼不直接把文件位置存放在索引節點中呢?
因爲:Linux中的文件是能夠共享的,假如把文件位置存放在索引節點中,當有兩個或更多個進程同時打開一個文件時,它們將去訪問同一個索引節點,那麼一個進程的lseek操作將影響到另一個進程的讀操作,這顯然是致命的錯誤。
4>對文件進行操作的一組函數叫文件操作表,由file_operations結構定義:可以在include/linux/fs.h 中查看
1488struct file_operations {
1489 struct module *owner;
1490 loff_t (*llseek) (struct file *, loff_t, int);
1491 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1492 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1493 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1494 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1495 int (*readdir) (struct file *, void *, filldir_t);
1496 unsigned int (*poll) (struct file *, struct poll_table_struct *);
1497 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1498 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1499 int (*mmap) (struct file *, struct vm_area_struct *);
1500 int (*open) (struct inode *, struct file *);
1501 int (*flush) (struct file *, fl_owner_t id);
1502 int (*release) (struct inode *, struct file *);
1503 int (*fsync) (struct file *, int datasync);
1504 int (*aio_fsync) (struct kiocb *, int datasync);
1505 int (*fasync) (int, struct file *, int);
1506 int (*lock) (struct file *, int, struct file_lock *);
1507 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1508 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1509 int (*check_flags)(int);
1510 int (*flock) (struct file *, int, struct file_lock *);
1511 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1512 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1513 int (*setlease)(struct file *, long, struct file_lock **);
1514};
owner:用於指定擁有這個文件操作結構體的模塊,通常取THIS_MODULE;
llseek:用於設置文件的偏移量。第一個參數指明要操作的文件,第二個參數爲偏移量,第三個參數爲開始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。
read:從文件中讀數據。第一個參數爲源文件,第二個參數爲目的字符串,第三個參數指明欲讀數據的總字節數,第四個參數指明從源文件的某個偏移量處開始讀數據。由系統調用read()調用;
write:往文件裏寫數據。第一個參數爲目的文件,第二個參數源字符串,第三個參數指明欲寫數據的總字節數,第四個參數指明從目的文件的某個偏移量出開始寫數據。由系統調用write()調用;
mmap:將指定文件映射到指定的地址空間上。由系統調用mmap()調用;
open:打開指定文件,並且將這個文件和指定的索引結點關聯起來。由系統調用open()調用;
release:釋放以打開的文件,當打開文件的引用計數(f_count)爲0時,該函數被調用;
fsync():文件在緩衝的數據寫回磁盤
3.用戶打開文件表
系統中的每一個進程都有自己的一組打開的文件 ,像根文件系統,當前目工作目錄,安裝點等。有四個數據結構將VFS層和系統的進程緊密聯繫在一起,它們分別是:files_struct,fs_struct, file_system_type 和namespace結構體。
我們先看兩個圖:
此圖來自陳莉君老師 博客,版權歸屬於陳莉君老師。
1.>文 件描述符是用來描述打開的文件的。每個進程用一個files_struct結構來記錄文件描述符的使用情況,這個結構稱爲用戶打開文件表。它是進程的私有數據。
a.files_struct 結構體定義在文件 include/linux/fdtable.h該結構體由進程描述符中的files 域指向。所有與每個進程(per-process)相關的信息如打開的文件及文件描述符都包含在其中。
44struct files_struct {
45 /*
46 * read mostly part
47 */
48 atomic_t count; //共享該表的進程數
49 struct fdtable *fdt;
50 struct fdtable fdtab; //定義了文件的一些屬性
51 /*
52 * written part on a separate cache line in SMP
53 */
54 spinlock_t file_lock ____cacheline_aligned_in_smp;
55 int next_fd; //下一個文件描述符
56 struct embedded_fd_set close_on_exec_init; //*exec()關閉的文件描述符
57 struct embedded_fd_set open_fds_init; //文件描述符的初始集合
58 struct file * fd_array[NR_OPEN_DEFAULT]; //默認的文件對象數組
59};
a.看看 struct fdtable結構:
32struct fdtable {
33 unsigned int max_fds; //文件對象的上限
34 struct file ** fd; //全部文件對象數組
35 fd_set *close_on_exec; //*exec()關閉的文件描述符
36 fd_set *open_fds; //指向打開文件的描述符
37 struct rcu_head rcu;
38 struct fdtable *next; //指向該鏈表的下一個對象
39};
40
b.fd數組指針指向已打開的文件對象鏈表,默認情況下,指向fd_arry數組。因爲NR_OPEN_DEFAULT等於32,所以該數組可以容納32個文件對象。如果一個進程所打開的文件對象超過32個。內核將分配一個新數組,並且將fd指向它。
c.對於在fd數組中有入口地址的每個文件來說,數組的索引就是文件描述符。通常,數組的第一個元素(索引爲0)表示進程的標準輸入文件,數組的第二個元素(索引爲1)是進程的標準輸出文件,數組的第三個元素(索引爲2)是進程的標準錯誤文件)
2.>fs_struct 結構
a.fs_struct 結構描述進程與文件系統的關係
b.我們來深入分析其代碼,它的定義在 include/linux/fs_struct.h,
6struct fs_struct {
7 int users;
8 spinlock_t lock; //保護該結構體的鎖
9 int umask; //默認的文件訪問權限
10 int in_exec;
11 struct path root, pwd;
12};
看一下struct path 結構的定義 :
7struct path {
8 struct vfsmount *mnt;
9 struct dentry *dentry;
10}
可以看到struct path 封裝了vfsmount 和dentry;,所以struct path root,pwd包含了當前進程的當前工作目錄和根目錄以及根目錄安裝點對象和pwd安裝點對象。
3>file_system_type結構體
在Linux中,用file_system_type來描述各種特定文件系統類型,比如ext3。也就是說Linux支持的所有文件系統類型都分別唯一的對應一個file_system_type結構
a.它的定義在 include/linux/fs.h中。
1736struct file_system_type {
1737 const char *name; //文件系統的類型名
1738 int fs_flags; //文件系統類型標誌
1739 int (*get_sb) (struct file_system_type *, int, //文件系統讀入其超級塊的函數指針
1740 const char *, void *, struct vfsmount *);
1741 void (*kill_sb) (struct super_block *); //該函數用來終止訪問超級塊
1742 struct module *owner; //通常設置爲宏THIS_MODLUE,用以確定是否把文件系統作爲模塊安裝
1743 struct file_system_type * next;
1744 struct list_head fs_supers;
1745
1746 struct lock_class_key s_lock_key;
1747 struct lock_class_key s_umount_key;
1748 struct lock_class_key s_vfs_rename_key;
1749
1750 struct lock_class_key i_lock_key;
1751 struct lock_class_key i_mutex_key;
1752 struct lock_class_key i_mutex_dir_key;
1753 struct lock_class_key i_alloc_sem_key;
1754};
tiger-john說明:
1>name:文件系統的名字,不能爲空;
2>get_sb:在安裝文件系統時,調用此指針所指函數以在磁盤中獲取超級塊;
3>kill_sb:卸載文件文件系統時候,調用此指針所指函數以進行一些清理工作;
4>owner:如果一個文件系統以模塊的形式加載到內核,則該字段用來說明哪個模塊擁有這個結構。一般爲THIS_MODULE;
5>next:所有的文件系統類型結構形成一個鏈表,該鏈表的頭指針爲全局變量file_systems(struct file_system_type *file_systems)。這個字段指向鏈表中下一個文件系統類型結構;
6>fs_supers:同一個文件系統類型下的所有超級塊形成一個雙聯表,這個字段是這個雙聯表的頭結點。超級塊之間通過s_instances字段相互鏈接.
4>vfsmount結構體
a.當文件系統被實際安裝時,將有一個vfsmount 結構體在安裝點被創建。該結構體用來代表文件系統的實例即代表一個安裝點。
b.vfsmount結構體被定義在 include/linux/mount.h中
36/*
37 * MNT_SHARED_MASK is the set of flags that should be cleared when a
38 * mount becomes shared. Currently, this is only the flag that says a
39 * mount cannot be bind mounted, since this is how we create a mount
40 * that shares events with another mount. If you add a new MNT_*
41 * flag, consider how it interacts with shared mounts.
42 */
43#define MNT_SHARED_MASK (MNT_UNBINDABLE)
44#define MNT_PROPAGATION_MASK (MNT_SHARED | MNT_UNBINDABLE)
45
46
47#define MNT_INTERNAL 0x4000
48
49struct vfsmount {
50 struct list_head mnt_hash; //散列表
51 struct vfsmount *mnt_parent; //指向上一層安轉點的指針
52 struct dentry *mnt_mountpoint; //安裝點的目錄項
53 struct dentry *mnt_root; //安裝樹的根
54 struct super_block *mnt_sb; //指向超級塊的指針
55 struct list_head mnt_mounts; //子鏈表
56 struct list_head mnt_child; / /通過mnt_child進行遍歷
57 int mnt_flags; //安裝標誌
58 /* 4 bytes hole on 64bits arches without fsnotify */
59#ifdef CONFIG_FSNOTIFY
60 __u32 mnt_fsnotify_mask;
61 struct hlist_head mnt_fsnotify_marks;
62#endif
63 const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
64 struct list_head mnt_list;
65 struct list_head mnt_expire ; /* link in fs-specific expiry list */
66 struct list_head mnt_share; /* circular list of shared mounts */
67 struct list_head mnt_slave_list;/* list of slave mounts */
68 struct list_head mnt_slave; /* slave list entry */
69 struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
70 struct mnt_namespace *mnt_ns; /* containing namespace */
71 int mnt_id; /* mount identifier */
72 int mnt_group_id; /* peer group identifier */
73 /*
74 * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
75 * to let these frequently modified fields in a separate cache line
76 * (so that reads of mnt_flags wont ping-pong on SMP machines)
77 */
78 atomic_t mnt_count; //使用計數
79 int mnt_expiry_mark; /* true if marked for expiry */
80 int mnt_pinned;
81 int mnt_ghosts;
82#ifdef CONFIG_SMP
83 int __percpu *mnt_writers;
84#else
85 int mnt_writers;
86#endif
87};
a.vfsmount 結構還保存了在安裝時指定的標誌信息,該信息存儲在mmt_flags中。
MNT_NOSUID:禁止該文件系統的可執行文件設置setuid和setgid標誌
MNT_NODEV:禁止訪問該文件系統上的設備文件
MNT_NOEXEC:禁止執行該文件系統上的可執行文件
Tiger-John說明:
在管理員安裝一些不是很安全的移動設備時,這些標誌很有用。
b.爲了對系統中的所有安裝點進行快速查找,內核把它們按哈希表來組織,mnt_hash就是形成哈希表的隊列指針。
c.mnt_mountpoint是指向安裝點dentry結構的指針。而dentry指針指向安裝點所在目錄樹中根目錄的dentry結構。
d.mnt_parent是指向上一層安裝點的指針。如果當前的安裝點沒有上一層安裝點(如根設備),則這個指針爲NULL。同時,vfsmount結構中還有mnt_mounts和mnt_child兩個隊列頭,只要上一層vfsmount結構存在,就把當前vfsmount結構中mnt_child鏈入上一層vfsmount結構的mnt_mounts隊列中。這樣就形成一顆設備安裝的樹結構,從一個vfsmount結構的mnt_mounts隊列開始,可以找到所有直接或間接安裝在這個安裝點上的其他設備。如圖8.2。
e.mnt_sb指向所安裝設備的超級塊結構super_block。
f.mnt_list是指向vfsmount結構所形成鏈表的頭指針。
我們來看個圖:
本篇文章來源於 Linux公社網站(www.linuxidc.com) 原文鏈接:http://www.linuxidc.com/Linux/2011-02/32127p2.htm