在 Linux 系統中,一切皆文件,除了一般所說的狹義的文件(文本文件和二進制文件) 之外,目錄、設備、套接字和管道等都是文件。html
文件系統在不一樣的上下文中有不一樣的含義:node
Linux文件系統的架構以下圖所示,分爲用戶空間、內核空間和硬件3個層面: linux
注意上圖中方塊對齊關係,不少時候咱們分不清內核文件系統中 「cache」 和 「buffer」 的區別,畢竟二者均可以翻譯爲 「緩存區」,可是從圖中,就能夠很清晰的看出所謂的 「cache」 其實指的就是圖中的 「頁緩存」 它是針對文件來講的,除了 「DAX"(直接訪問方式的設備)它不使用 「緩存」,其餘的閃存類,塊設備類設備都會使用到 「頁緩存」 也就是 「cache」,而 「buffer」 其實指的就是圖中的 「塊緩存」 它是針對塊設備的。虛擬文件系統中各數據結構之間的關係: 算法
應用程序能夠直接使用內核提供的系統調用訪問文件:編程
應用程序也可使用 glibc 庫封裝的標準 IO 流函數訪問文件,標準流提供了緩衝區, 目的是儘量減小調用 read 和 write 的次數,提升性能 glic 庫封裝的標準流函數以下所示:數組
外部存儲設備分爲塊設備、閃存和 NVDIMM 設備 3 類。 塊設備主要有如下兩種。緩存
閃存(Flash Memory)的主要特色以下。服務器
10^4~10^3
, NAND 閃存的擦除塊的最大擦除次數是 10^3~10^6
。閃存按存儲結構分爲 NAND 閃存和 NOR 閃存,二者的區別以下。網絡
爲何要針對閃存專門設計文件系統?主要緣由以下。數據結構
機械硬盤和 NAND 閃存的主要區別以下。
NVDIMM(Nonn-Volatile DIMM,非易失性內存:DIMM 是 Dual-Inline-Memory-Modules 的縮寫,表示雙列直插式存儲模塊,是內存的一種規格)設備把 NAND 閃存、內存和超級 電容集成到一塊兒,訪問速度和內存同樣快,而且斷電之後數據不會丟失。在斷電的瞬間, 超級電容提供電力,把內存中的數據轉移到 NAND 閃存。
在內核的目錄 fs 下能夠看到,內核支持多種文件系統類型。爲了對用戶程序提供統一的 文件操做接口,爲了使不一樣的文件系統實現可以共存,內核實現了一個抽象層,稱爲虛擬文件 系統( Virtual File System,VFS),也稱爲虛擬文件系統切換( Virtual Filesystem Switch,VFS) 文件系統分爲如下 4 種。
內核把閃存稱爲存儲技術設備( Memory Technology Device,MTD),爲全部閃存實現 了統一的 MTD 層,每種閃存須要實現本身的驅動程序。
針對 NVDIMM 設備,文件系統須要實現 DAX(Direct Access直接訪問:X 表明 eXciting,沒有意義,只是爲了讓名字看起來酷),繞過頁緩存和塊設備層,把 NVDIMM 設備裏面的內存直接映射到進程或內核的虛擬地址空間。
libnvdimm 子系統提供對 3 種 NVDIMM 設備的支持:持久內存(persistent memory,PMEM) 模式的 NVDIMM 設備,塊設備(block,BLK)模式的 NVDIMM 設備,以及同時支持PMEM 和 BLK 兩種訪問模式的 NVDIMM 設備。PMEM 訪問模式是把 NVDIMM 設備看成內存,BLK 訪問模式是把 NVDIMM 設備看成塊設備。每種 NVDIMM 設備須要實現本身的驅動程序。
雖然不一樣文件系統類型的物理結構不一樣,可是虛擬文件系統定義了一套統一的數據結構。
An individual dentry usually has a pointer to an inode. Inodes are filesystem objects such as regular files, directories, FIFOs and other beasts. They live either on the disc (for block device filesystems) or in the memory (for pseudo filesystems). Inodes that live on the disc are copied into the memory when required and changes to the inode are written back to disc. A single inode can be pointed to by multiple dentries (hard links, for example, do this).
單個 dentry 一般具備指向 inode 的指針。 inode 是文件系統對象,例如常規文件,目錄,FIFO 和其餘類型。 它們存在於 disc(用於塊設備文件系統)或在內存中(用於僞文件系統)中。 存在於 disc 上的 inode 在須要時被複制到內存中,而且對 inode 的更改將被寫回到 disc。 單個 inode 能夠由多個 dentry 指向(硬連接,例如,這樣作)。
注意: 這段話有三個重點:一是 inode 是用來表示文件系統對象,有些地方說是用來表明「文件」,這也沒錯,但卻沒有說這個「文件」不只僅是指常規文件,它還包括 目錄,FIFO(管道文件),socket,塊設備,字符設備,符號連接一共七種類型文件,因此很容易產生誤解,理解這點很重要。 二是 inode 的存儲位置。三是一個 inode 或者說「文件」 能夠被多個 dentry 指向,這裏還特地提了是硬連接,其實也就說明硬連接實際上是建立了一個 dentry ,而這個 dentry 的 d_inode 指向了同一個 inode,而對於符號連接它就是一個「文件」 也就是表示它有一個對應的 inode,而這個 inode 裏面的內容是指向了另一個 inode 的路徑。
To look up an inode requires that the VFS calls the lookup() method of the parent directory inode. This method is installed by the specific filesystem implementation that the inode lives in. Once the VFS has the required dentry (and hence the inode), we can do all those boring things like open(2) the file, or stat(2) it to peek at the inode data. The stat(2) operation is fairly simple: once the VFS has the dentry, it peeks at the inode data and passes some of it back to userspace.
要查找 inode, VFS 須要調用父目錄 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系統實現安裝。 一旦 VFS 有了目標 dentry(間接可以得到 inode),咱們能夠作全部那些無聊的事情,好比 open(2) 文件,或者 stat(2) 查看 inode 數據。stat(2) 操做至關簡單:一旦 VFS 具備 dentry, 就能夠獲取 inode 的數據,並將其中一些傳遞迴用戶空間。
注意: 這段話的一個重點是咱們是使用父目錄 inode 對應的 lookup()
方法來查找 inode 的,也就是說在查找當前 inode 的時候,已經確保了父目錄是沒問題,但有時候在查找後也會再次檢查父目錄有沒有發生改變。
文件系統的第一塊是超級塊,用來描述文件系統的整體信息。當咱們把文件系統掛載 到內存中目錄樹的一個目錄下時,就會讀取文件系統的超級塊,在內存中建立超級塊的副 本:結構體 super_block,主要成員以下: [include/linux/fs.h]
struct super_block {
struct list_head s_list; // Keep this first
dev_t s_dev; // search index; _not_ kdev_t
unsigned char s_blocksize_bits;
unsigned long s_blocksize;
loff_t s_maxbytes; // Max file size
struct file_system_type* s_type;
const struct super_operations* s_op;
...
unsigned long s_flags;
unsigned long s_iflags; // internal SB_I_* flags
unsigned long s_magic;
struct dentry* s_root;
...
struct hlist_bl_head s_anon; // anonymous dentries for (nfs) exporting
struct list_head s_mounts; // list of mounts; _not_ for fs use
struct block_device* s_bdev;
struct backing_dev_info* s_bdi;
struct mtd_info* s_mtd;
struct hlist_node s_instances;
...
void* s_fs_info; // Filesystem private info
...
};
複製代碼
超級塊操做集合的數據結構是結構體 super_operations,主要成員以下: [include/linux/fs.h]
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
int (*remount_fs2) (struct vfsmount *, struct super_block *, int *, char *);
void *(*clone_mnt_data) (void *);
void (*copy_mnt_data) (void *, void *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_options2)(struct vfsmount *,struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
複製代碼
一個文件系統,只有掛載到內存中目錄樹的一個目錄下,進程才能訪問這個文件系統 每次掛載文件系統,虛擬文件系統就會建立一個掛載描述符: mount 結構體。掛載描述符 用來描述文件系統的一個掛載實例,同一個存儲設備上的文件系統能夠屢次掛載,每次掛 載到不一樣的目錄下。 [include/linux/fs.h]
struct mount {
struct hlist_node mnt_hash;
struct mount *mnt_parent;
struct dentry *mnt_mountpoint;
struct vfsmount mnt;
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
struct list_head mnt_list;
...
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
...
};
複製代碼
假設咱們把文件系統 2 掛載到目錄 「/a」 下,目錄 a 屬於文件系統 1。目錄 a 稱爲掛載 點,文件系統 2 的 mount 實例是文件系統 1 的 mount 實例的孩子,文件系統 1 的 mount 實 是文件系統 2 的 mount 實例的父親。
struct vfsmount{
struct dentry *mnt_root;
struct super_block *mnt_sb;
int mnt_flags;
}
複製代碼
mnt_root 指向文件系統 2 的根目錄,mnt_sb 指向文件系統 2 的超級塊。struct mountpoint{
struct hlist_node m_hash;
struct dentry *m_dentry;
struct hlist_head m_list;
int m_count;
}
複製代碼
m_dentry 指向做爲掛載點的目錄, m_list 用來把同一個掛載點下的全部掛載描述符鏈 接起來。爲何同一個掛載點下會有多個掛載描述符?這和掛載命名空間有關。由於每種文件系統的超級塊的格式不一樣,因此每種文件系統須要向虛擬文件系統註冊 文件系統類型 file_system_type,而且實現 mount 方法用來讀取和解析超級塊。結構體 file_system_type 以下: [include/linux/fs.h]
struct file_system_type {
const char *name;
int fs_flags;
#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_USERNS_DEV_MOUNT 16 /* A userns mount does not imply MNT_NODEV */
#define FS_USERNS_VISIBLE 32 /* FS must already be visible */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
struct dentry *(*mount2) (struct vfsmount *, struct file_system_type *, int,
const char *, void *);
void *(*alloc_mnt_data) (void);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct hlist_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
複製代碼
在文件系統中,每一個文件對應一個索引節點,索引節點描述兩類信息。
每一個索引節點有一個惟一的編號。 當內核訪問存儲設備上的一個文件時,會在內存中建立索引節點的一個副本:結構體 inode,主要 成員以下: [include/linux/fs.h]
struct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
...
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
unsigned int i_blkbits;
enum rw_hint i_write_hint;
blkcnt_t i_blocks;
...
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
...
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
u64 i_version;
atomic_t i_count;
atomic_t i_dio_count;
atomic_t i_writecount;
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
};
...
void *i_private; /* fs or device private pointer */
};
複製代碼
文件分爲如下幾種類型。
字符設備文件、塊設備文件、命名管道和套接字是特殊的文件,這些文件只有索引節點, 沒有數據。字符設備文件和塊設備文件用來存儲設備號,直接把設備號存儲在索引節點中。
內核支持兩種連接。
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*follow_link) (struct dentry *, void **);
int (*permission) (struct inode *, int);
int (*permission2) (struct vfsmount *, struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
void (*put_link) (struct inode *, void *);
int (*create) (struct inode *,struct dentry *, umode_t, bool);
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 *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
int (*rename2) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*setattr2) (struct vfsmount *, struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*removexattr) (struct dentry *, const char *);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
複製代碼
lookup 方法用來在一個目錄下查找文件。 系統調用 open 和 creat 調用 create 方法來建立普通文件,系統調用 link 調用 link 方法 來建立硬連接,系統調用 symlink 調用 symlink 方法來建立符號連接,系統調用 mkdir 調用 mkdir 方法來建立目錄,系統調用 mknod 調用 mknod 方法來建立字符設備文件、塊設備文件、 命名管道和套接字。 系統調用 unlink 調用 unlink 方法來刪除硬連接,系統調用 rmdir 調用 rmdir 方法來刪除目錄。 系統調用 rename 調用 rename 方法來給文件換一個名字。 系統調用 chmod 調用 setattr 方法來設置文件的屬性,系統調用 stat 調用 getattr 方法來建立目錄讀取文件的屬性。 系統調用 listxattr 調用 listxattr 方法來列出文件的全部的擴展屬性。
文件系統把目錄當作文件,這種文件的數據是有目錄項組成的,每一個目錄項存儲一個子目錄或文件的名稱以及對應的索引節點號。 當內核訪問存儲設備上的一個目錄項時,會在內存中建立目錄項的一個副本:結構體 dentry,主要成員以下: [include/linux/dcache.h]
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is * negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
/* Ref lookup also touches following */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
struct list_head d_lru; /* LRU list */
struct list_head d_child; /* child of parent list */
struct list_head d_subdirs; /* our children */
/* * d_alias and d_rcu can share memory */
union {
struct hlist_node d_alias; /* inode alias list */
struct rcu_head d_rcu;
} d_u;
};
複製代碼
以文件 「/a/c.txt」 爲例,目錄項和索引節點的關係以下:
目錄項操做集合的數據結構是結構體 dentry_operations,其代碼以下:
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *, const struct dentry *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
struct inode *(*d_select_inode)(struct dentry *, unsigned);
struct dentry *(*d_real)(struct dentry *, struct inode *);
void (*d_canonical_path)(const struct path *, struct path *);
} ____cacheline_aligned;
複製代碼
參考 vfs.txt 文檔:
d_revalidate: called when the VFS needs to revalidate a dentry. This is called whenever a name look-up finds a dentry in the dcache. Most local filesystems leave this as NULL, because all their dentries in the dcache are valid. Network filesystems are different since things can change on the server without the client necessarily being aware of it. This function should return a positive value if the dentry is still valid, and zero or a negative error code if it isn't. d_revalidate may be called in rcu-walk mode (flags & LOOKUP_RCU).If in rcu-walk mode, the filesystem must revalidate the dentry without blocking or storing to the dentry, d_parent and d_inode should not be used without care (because they can change and, in d_inode case, even become NULL under us). If a situation is encountered that rcu-walk cannot handle, return -ECHILD and it will be called again in ref-walk mode.
當 VFS 須要驗證一個 dentry 有效性時會被調用。在 dcache 中找到 name 對應的 dentry 的時候就被調用。大多數本地文件系統直接返回 NULL,由於這類文件系統在 dcache 中全部的 dentry 都是有效的。可是網絡文件系統不同,由於事情會在服務器上已經發生了,可是客戶端殊不知道。這個函數應該返回一個正值來表示該 dentry 依然是有效的,0 或者 負數來表示錯誤。
d_automount
: called when an automount dentry is to be traversed (optional).This should create a new VFS mount record and return the record to the caller. The caller is supplied with a path parameter giving the automount directory to describe the automount target and the parent VFS mount record to provide inheritable mount parameters. NULL should be returned if someone else managed to make the automount first. If the vfsmount creation failed, then an error code should be returned. If-EISDIR
is returned, then the directory will be treated as an ordinary directory and returned to pathwalk to continue walking.
當碰到一個自動掛載 automount 目錄項時,d_automount
會被調用。(可選的)。它應該建立一個新的 VFS 掛載記錄,而且把這個記錄返回給調用者。爲調用者提供了一個 path
參數,該參數給出了automount
目錄(用於描述 automount
目標)和父 VFS 掛載記錄(以提供可繼承的掛載參數)。若是已經有其餘調用者 在該 automount
作了處理,那麼返回 NULL。若是 vfsmount
建立失敗那麼返回錯誤碼。若是返回了 -EISDIR
,那麼該目錄被當成普通目錄而且返回到路徑查找流程繼續下一步查找。
If a vfsmount is returned, the caller will attempt to mount it on the mountpoint and will remove the vfsmount from its expiration list in the case of failure. The vfsmount should be returned with 2 refs on it to prevent automatic expiration - the caller will clean up the additional ref.
若是返回了 vfsmount
,那麼調用者將會嘗試把它掛載在該掛載點上。而且若是掛載失敗,則會將 vfsmount
從它的 expiration 列表中移除。vfsmount
在返回時應該帶有兩個引用(指向它本身的引用)用來防止 自動卸載(過時)。調用者會清除附加的引用。
This function is only used if
DCACHE_NEED_AUTOMOUNT
is set on the dentry. This is set by__d_instantiate()
ifS_AUTOMOUNT
is set on the inode being added.
只有 dentry 被設置了 DCACHE_NEED_AUTOMOUNT
標誌,該函數纔會被使用。若是 inode 的 S_AUTOMOUNT
被設置了,那麼能夠經過 __d_instantiate()
函數來設置 DCACHE_NEED_AUTOMOUNT
標誌。
d_manage
: called to allow the filesystem to manage the transition from a dentry (optional). This allows autofs, for example, to hold up clients waiting to explore behind a 'mountpoint' whilst letting the daemon go past and construct the subtree there. 0 should be returned to let the calling process continue. -EISDIR can be returned to tell pathwalk to use this directory as an ordinary directory and to ignore anything mounted on it and not to check the automount flag. Any other error code will abort pathwalk completely.
d_manage
容許文件系統來管理一個 dentry 的轉換。(可選的)。它容許使用 autofs
。例如:掛起一個 等待去探索某 mountpoint
下路徑的客戶端,與此同時讓守護進程經過該掛載點而且在其下構建子樹。返回 0 表示讓掛起的客戶端繼續執行。返回 -EISDIR
,表示路徑查找進程應該把該目錄當作普通目錄處理,而且忽略在其上掛載的任何東西,以及不會去檢查 automou
標誌。其它任何錯誤碼錶示應該中斷本次路徑查找。
If the 'rcu_walk' parameter is true, then the caller is doing a pathwalk in RCU-walk mode. Sleeping is not permitted in this mode, and the caller can be asked to leave it and call again by returning
-ECHILD
.-EISDIR
may also be returned to tell pathwalk to ignore d_automount or any mounts.
若是 'rcu_walk' 參數爲 true,那麼調用者的路徑查找處於 RCU 模式。這種模式下是不容許睡眠,因此 調用者被要求離開該函數而且返回 -ECHILD
。一樣的,返回 -EISDIR
告訴當前路徑查找忽略 d_automount
或者其餘任何掛載。
This function is only used if
DCACHE_MANAGE_TRANSIT
is set on the dentry being transited from.
該函數只有當前遍歷的 dentry 被設置了 DCACHE_MANAGE_TRANSIT
標誌是才能使用。
d_hash: called when the VFS adds a dentry to the hash table. The first dentry passed to d_hash is the parent directory that the name is to be hashed into.
VFS 添加一個 dentry 到散列表的時候被調用。第一個參數是父 dentry。
d_compare 用來比較兩個目錄項的文件系統。 d_delete 用來在目錄項的引用計數減到 0 時判斷是否能夠釋放目錄項的內存。 d_release 用來在釋放目錄項的內存以前調用。 d_iput 用來釋放目錄項關聯的索引節點。
當進程打開一個文件的時候,虛擬文件系統就會建立文件的一個打開實例:file 結構體,主要成員以下:
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/* * Protects f_ep_links, f_flags. * Must not be taken from IRQ context. */
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
複製代碼
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
複製代碼
mnt 指向文件所屬文件系統的掛載描述符的成員 mnt,dentry 是文件對應的目錄項。文件的打開實例和索引節點的關係如圖:
進程描述符有兩個文件系統相關的成員:成員 fs 指向進程的文件系統信息結構體,主要是進程的根目錄和當前工做目錄;成員 files 指向打開文件表。[include/linux/sched.h]
struct task_struct{
...
struct fs_struct *fs;
struct files_struct *files;
...
}
複製代碼
文件系統信息結構體的主要成員以下:
[include/linux/fs_struct.h]
struct fs_struct{
...
struct path root, pwd;
}
複製代碼
成員 root 存儲進程的根目錄,成員 pwd 存儲進程的當前工做目錄。 假設首先調用系統調用 chroot,把目錄「/a」 設置爲進程的根目錄,而後建立子進程,子進程繼承 父進程的文件系統信息,那麼把子進程能看到的目錄範圍限制爲以目錄 「/a」 爲根的子樹。但子進程打開文件 「/b.txt」(文件路徑是絕對路徑,以「/」開頭)時,真實的文件路徑是 「/a/b.txt」。 假設調用系統調用 chdir,把目錄 「/c」 設置爲進程的當前工做目錄,當子進程打開文件 「d.txt」 (文件路徑是相對路徑,不以「/」開頭)時,真實的文件路徑是 「/c/d.txt」。
打開文件表也稱爲文件描述符表,數據結構以下圖:
結構體 files_struct 是打開文件表的包裝器,主要成員以下:[include/linux/fdtable.h]
struct files_struct {
/* * read mostly part */
atomic_t count;
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
/* * written part on a separate cache line in SMP */
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
複製代碼
成員 count 是結構體 files_struct 的引用計數。 成員 fdt 指向打開文件表。 當進程剛剛建立的時候,成員 fdt 指向成員 fdtab,運行一段時間後,進程打開的文件數量超過了 NR_OPEN_DEFAULT,就會擴大打開文件表,從新分配 fdtable 結構體,成員 fdt 指向新的 fdtable 結構體。 打開文件表的數據以下: [include/linux/fdtable]
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
複製代碼
成員 max_fds 是打開文件表的當前大小,即成員 fd 指向的 file 指針數組的大小。隨着進程 打開文件的數量增長,打開文件表逐步擴大。 成員 fd 指向 file 指針數組。當進程調用 open 打開文件的時候,返回的文件描述符是 file 指針 數組的索引。 成員 close_on_exec 指向一個位圖,指示在指向 execve() 以裝載新程序的時候須要關閉哪些文件 描述符。 成員 open_fds 指向文件描述符位圖,指示哪些文件描述符被分配。
The VFS implements the open(2), stat(2), chmod(2), and similar system calls. The pathname argument that is passed to them is used by the VFS to search through the directory entry cache (also known as the dentry cache or dcache). This provides a very fast look-up mechanism to translate a pathname (filename) into a specific dentry. Dentries live in RAM and are never saved to disc: they exist only for performance.
VFS實現 open(2)、stat(2)、chmod(2) 和相似的系統調用。傳遞給這些函數的路徑名參數被 VFS 用來在目錄項緩存(也稱爲 dentry 緩存或 dcache)進行搜索目錄項。這提供了一個很是快速的查找機制,將路徑名(filename)轉換爲一個特定的 dentry。這些 dentry 存在內存中,並無保存到磁盤:它們的存在於只是爲了提升性能。
The dentry cache is meant to be a view into your entire filespace. As most computers cannot fit all dentries in the RAM at the same time, some bits of the cache are missing. In order to resolve your pathname into a dentry, the VFS may have to resort to creating dentries along the way, and then loading the inode. This is done by looking up the inode.
dentry 緩存是指向整個文件空間的視圖。因爲大多數計算機不能同時在 RAM 中容納全部的 dentry,緩存中的一些位丟失了。爲了將路徑名解析爲 dentry, VFS 可能不得不在此過程當中建立 dentry,而後加載 inode。這是經過查找 inode 完成的。
有幾個文件系統用來維護目錄項的函數:
dget:對一個已經存在的 dentry 打開一個新的句柄(handle)(只是增長 dentry 的使用計數)。
dput: 對一個 dentry 關閉一個句柄(減小使用計數)。若是使用計數變爲 0,而且這個 dentry 仍是它父目錄的 hash 表中,那麼 "d_delete" 方法被調用,用來檢查它是否應該被緩存。若是 不該該被緩存,或者該 dentry 沒有被 hash,那麼刪除它。不然把該已緩存的 dentry 移動到 LRU 鏈表中,未來當內存不夠時能夠回收它。
d_drop:它用來從父目錄的 hash 表中 unhashes 一個 dentry。若是此時它的使用計數(usage count) 已經降到 0,隨後調用 dput() 來釋放這個(deallocate) dentry,;
d_delete:刪除一個 dentry。若是它沒有其餘開放的引用,那麼它變成一個 negative dentry, 而後 d_iput() 被調用。若是有其餘引用,那麼調用 d_drop。
d_add: 增長一個 dentry 到它的父目錄的 hash 列表中,而後調用 d_instantiate()。
d_instantiate: 增長一個 dentry 到相應 inode 的 alise hash 鏈表中,而且更新 d_inode
成員。 inode 的 i_count
成員應該被 「set/incremented」。若是該 dentry 的 inode 指針爲空, 那麼該 dentry 稱爲 "negative dentry"。該函數一般在爲 "negative dentry" 建立一個 inode 時被調用。
d_lookup: 給定一個父目錄和路徑名稱份量,而後查找相應的 dentry。它從緩存(decahe)的散列表中 經過給定的名稱查找相應的子項。若是查找到了,增長引用計數並返回 dentry。當完成使用後, 調用者必須調用 dput() 來釋放這個 dentry。
由於每種文件系統的超級塊的格式不一樣,因此每種文件系統須要向虛擬文件系統註冊文件系統類型 file_system_type, 實現 mount 方法用來讀取和解析超級塊。 函數 register_filesystem 用來註冊文件系統類型: int register_filesystem(struct file_system_type *fs);
函數 unregister_filesystem 用來註銷文件系統類型: int unregister_filesystem(struct file_system_type *fs);
管理員能夠執行命令 「cat/proc/filesystems」 來查看已經註冊的文件系統類型。
最後給出兩張數據結構圖:第一張基於 Linux 4.4,後一張基於 Linux 3.x 版本。圖片較大,可先點擊放大後再右鍵「在新標籤頁中打開圖片」,可查看高清圖。
參考:
《Linux 內核深度解析》餘華兵 著