目录
1.索引节点
1.1概念和作用
作用就是索引。
索引节点inode描述两类信息。
1)文件属性,例如:创建、访问时间,文件类型文件大小、创建文件的用户标识符、指向文件操作函数集的指针、address_space对象、指向超级块的指针、所属块设备block_device。
2)文件数据的存储位置。inode并不一定直接存储数据的存储位置,也可能通过中间块存储位置。通过inode可以找到文件数据在磁盘的存储位置以及文件偏移与磁盘上数据的对应关系。
每个inode对应一个索引节点号,该节点号唯一地标识文件系统中的文件。
在struct dentry中有文件名字段和字段d_inode指针指向inode(在磁盘的目录项结构中一般有字段表示inode号),从而形成文件名和inode的对应,轻易找到inode。通过inode,我们可以方便地了解到关于文件的信息,读取文件数据。
1.2inode主要成员
结构定义在include/linux/fs.h中,主要成员如下:
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' */ 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; #ifdef CONFIG_SECURITY void *i_security; #endif /* 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 timespec64 i_atime; struct timespec64 i_mtime; struct timespec64 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; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif /* Misc */ unsigned long i_state; struct rw_semaphore i_rwsem; unsigned long dirtied_when; /* jiffies of first dirtying */ unsigned long dirtied_time_when; struct hlist_node i_hash; struct list_head i_io_list; /* backing dev IO list */ #ifdef CONFIG_CGROUP_WRITEBACK struct bdi_writeback *i_wb; /* the associated cgroup wb */ /* foreign inode detection, see wbc_detach_inode() */ int i_wb_frn_winner; u16 i_wb_frn_avg_time; u16 i_wb_frn_history; #endif struct list_head i_lru; /* inode LRU list */ struct list_head i_sb_list; struct list_head i_wb_list; /* backing dev writeback list */ union { struct hlist_head i_dentry; struct rcu_head i_rcu; }; atomic64_t 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; unsigned i_dir_seq; }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; /* all events this inode cares about */ struct fsnotify_mark_connector __rcu *i_fsnotify_marks; #endif #if IS_ENABLED(CONFIG_FS_ENCRYPTION) struct fscrypt_info *i_crypt_info; #endif void *i_private; /* fs or device private pointer */ } __randomize_layout;
i_mode是文件类型和访问权限,i_uid是创建文件的用户的标识符,i_gid是创建文件的用户所属的组标识符。
i_ino是索引节点的编号。i_size是文件长度;
i_blocks是文件的块数,即文件长度除以块长度的商;
i_bytes是文件长度除以块长度的余数;
i_blkbits是块长度以2为底的对数,块长度是2的i_blkbits次幂。
i_atime(access time)是上一次访问文件的时间,i_mtime(modified time)是上一次修改文件数据的时间,i_ctime(change time)是上一次修改文件索引节点的时间。
i_sb指向文件所属的文件系统的超级块。
i_mapping指向文件的地址空间address_space。
i_count是索引节点的引用计数,i_nlink是硬链接计数;
i_stat:索引节点状态,如果为脏,则对应的磁盘索引节点必须被更新。具体在“inode状态”一节进行描述。
i_rdev:如果文件的类型是字符设备文件或块设备文件,那么i_rdev是设备号。
i_pipe i_bdev i_cdev:
这三个字段属于同一个联合体,同时只能存在一个。i_bdev指向块设备,i_cdev指向字符设备, i_pipe 字段指向一个代表管道的数据结构。
i_hash:
如果没有读取文件,内存中是不会存放该文件的索引节点对象,而当从磁盘读取文件后会构造索引节点对象并存储在内存中,这样在后续的访问中不需要以较慢的速度频繁访问磁盘,而是直接访问内存中的索引节点对象。索引节点对象被存放在一个称为 inode_hashtable 的散列表中。散列表加快了对索引节点对象的搜索,前提是系统内核要知道索引节点号及文件所在文件系统对应的超级块对象的地址。由于散列技术可能引发冲突,所以索引节点对象包含一个 i_hash 字段,该字段包含向前和向后两个指针,分别指向散列到同一地址的前一个索引节点和后一个索引节点;该字段因此创建了由这些索引节点组成的一个双向链表。
inode加入到inode_hashtable的代码如下:
fs/inode.c ... static struct hlist_head *inode_hashtable __read_mostly; ... void __insert_inode_hash(struct inode *inode, unsigned long hashval) { /* 通过文件所在文件系统对应的超级块对象的地址和索引节点号计算哈希值找到在数组中的项 */ struct hlist_head *b = inode_hashtable + hash(inode->i_sb, hashval); spin_lock(&inode_hash_lock); spin_lock(&inode->i_lock); hlist_add_head(&inode->i_hash, b); // 插入到链表b中 spin_unlock(&inode->i_lock); spin_unlock(&inode_hash_lock); } EXPORT_SYMBOL(__insert_inode_hash);
i_dentry:
一个目录项只能对应一个索引节点,而由于索引节点可能与若干硬链接关联,所以一个索引节点可能对应多个目录项,因此需要一个链表。正在使用的目录项对象被插入一个双向链表中,链表的首元素为 i_dentry 字段,目录项中的 d_alias 字段存放链表中相邻元素的地址。
i_lru:
inode对象通过该字段链入超级块对象指向的lru链表s_inode_lru。不再被使用则被放入该链表。相关操作函数如下:
/*
* Add inode to LRU if needed (inode is unused and clean).
*
* Needs inode->i_lock held.
*/
void inode_add_lru(struct inode *inode)
{
if (!(inode->i_state & (I_DIRTY_ALL | I_SYNC |
I_FREEING | I_WILL_FREE)) &&
!atomic_read(&inode->i_count) && inode->i_sb->s_flags & SB_ACTIVE)
inode_lru_list_add(inode);
}
1.3 文件类型
文件分为以下几种类型。
(1)普通文件(regular file):就是我们通常说的文件,是狭义的文件。
(2)目录:目录是一种特殊的文件,这种文件的数据是由目录项组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号。
(3)符号链接(也称为软链接):这种文件的数据是另一个文件的路径。
(4)字符设备文件。
(5)块设备文件。
(6)命名管道(FIFO)。
(7)套接字(socket)。
字符设备文件、块设备文件、命名管道和套接字是特殊的文件,这些文件只有索引节点,没有数据。字符设备文件和块设备文件用来存储设备号,直接把设备号存储在索引节点中。
1.4 软连接和硬链接
(1)软链接,也称为符号链接,这种文件的数据是另一个文件的路径。在ocfs2文件系统中,软链接的ocfs2_dinode的id2结构为__u8 i_symlink[0],没有其他额外参数,直接存放的是ln -s命令时输入的路径名称(绝对路径或相对路径)。软链接有自己的inode,不与原文件共享inode。
(2)硬链接,相当于给一个文件取了多个名称,多个文件名称对应同一个索引节点,索引节点inode的成员i_nlink是硬链接计数,ll查看目录下文件,权限后面的数字就是硬链接数。在组织方式上看,就是多个目录项指向了同一个inode。ls -i查看各文件的inode号,可以看到硬链接和原文件inode号相同(实际上如果不看创建时间,无法区分谁是原文件谁是硬链接)。
1.5 索引节点指向的两个操作集
索引节点的成员i_op指向索引节点操作集合inode_operations,成员i_fop指向文件操作集合file_operations。两者的区别如下:
1) inode_operations用来操作目录(在一个目录下创建或删除文件)和文件属性;
2) file_operations用来访问文件的数据。在open文件时,该操作集将赋值给file对象的fop字段。
inode_operations
inode_operation(include/linux/fs.h)主要函数如下:
lookup方法用来在一个目录下查找文件。
系统调用open和creat调用create方法来创建普通文件,系统调用link调用link方法来创建硬链接,系统调用symlink调用symlink方法来创建符号链接,系统调用mkdir调用mkdir方法来创建目录,系统调用mknod调用mknod方法来创建字符设备文件、块设备文件、命名管道和套接字。
系统调用unlink调用unlink方法来删除硬链接,系统调用rmdir调用rmdir方法来删除目录。
系统调用rename调用rename方法来给文件换一个名字。
系统调用chmod调用setattr方法来设置文件的属性,系统调用stat调用getattr方法来读取文件的属性。
系统调用listxattr调用listxattr方法来列出文件的所有扩展属性。
1.6 inode状态
每个索引节点对象都会复制磁盘索引节点包含的一些数据,比如分配给文件的磁盘块数。i_state字段表示inode的值状态。有以下状态。
I_DIRTY_SYNC、I_DIRTY_DATASYNC、I_DIRTY_PAGES:该索引节点就是脏的,对应的磁盘索引节点必须被更新。I_DIRTY宏可以用来立即检查这三个标志的值。
I_LOCK:涉及的索引节点对象处于I/O传送中;
I_FREEING:索引节点对象正在被释放;
I_CLEAR:索引节点对象的内容不再有意义;
I_NEW:索引节点对象已经分配但还没有用从磁盘索引节点读取来的数据填充。
inode缓存
2. 目录项dentry
2.1 目录项概述
引用《Linux内核设计与实现》中对dentry的描述。
“VFS把目录当做文件对待,所以在路径/bin/vi中,bin和vi都属于文件——bin是特殊的目录文件而vi是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示。虽然他们可以统一由索引节点表示,但是VFS经常需要执行目录相关的操作,比如路径名查找等。路径名查找需要解析路径中的每个组成部分,不但要确保它有效,而且还需要再进一步寻找路径中的下一个部分。”
“为了方便查找操作,VFS引入了目录项的概念。每个dentry代表路径中的一个特定部分。对前一个例子来说,/、bin和vi都属于目录项对象。必须明确一点:在路径中(包括普通文件在内),每一个部分都是目录项对象。解析一个路径并遍历其分量绝非简单的演练,它是耗时的、常规的字符串比较过程,执行耗时、代码繁琐。目录项对象的引入使得这个过程更加简单”。
dentry保存了目录/文件到inode的映射,在open一个文件时,如果路径上的某个目录或者文件没有对应的dentry,则会为其建立dentry并插入dentry_hashtable缓存起来。dentry里的d_inode成员保存了该目录或文件的indoe指针。这样后面如果在lookup这个目录或文件时就可以根据目录或文件的hash值和name等在dentry_hashtable中找到对应的dentry。找到了对应的dentry后,就知道了这个目录或文件的inode,就不用再进入文件系统读磁盘去lookup这个文件,从而提高检索效率。
2.2 目录项成员
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */ // 目录项缓存标识,该文件下面有类型宏定义 /* d_flags entries */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */ // dentry创建之后,就通过d_hash链接进入dentry_hashtable对应的hash值的链表中。
struct dentry *d_parent; /* parent directory */ // 父目录的目录项对象
struct qstr d_name; // 文件名和hash值(根据其父目录的dentry和目录名/文件名两者计算)
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */ // 与目录项关联的inode
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 */ // 重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效。
void *d_fsdata; /* fs-specific data */ // 与文件系统相关的数据
union {
struct list_head d_lru; /* LRU list */ // 未使用的目录项的链表
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
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 */ // 通过这个字段链接到关联inode的i_dentry链表中
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */ // 新创建dentry后,将其放入该表中,以便并发访问的其他程序能够找到。关联inode后,就从中删除不会用了。
struct rcu_head d_rcu;
} d_u;
} __randomize_layout;
其中字段d_name类型为struct qstr,包含了文件名字符串、文件名长度和hash值(根据全路径名计算),定义如下:
/*
* "quick string" -- eases parameter passing, but more importantly
* saves "metadata" about the string (ie length and the hash).
*
* hash comes first so it snuggles against d_parent in the
* dentry.
*/
struct qstr {
union {
struct {
HASH_LEN_DECLARE; // u32 hash; u32 len
};
u64 hash_len;
};
const unsigned char *name;
};
2.3 父子目录之间的关系
(转载:VFS数据结构之(dentry)_目录项对象_指向NULL的博客-CSDN博客)
d_parent 字段表示父目录项对象。
d_subdirs 子目录项链表的头。
d_child 字段表示同一父目录中的目录项链表的指针。所以d_child 字段指向父目录项中 d_subdirs 链表的相邻元素。
例如tmp目录下有三个子目录,名字分别为test1,test2,test3。
2.4 目录项对象的三种状态
- 未使用(unused)状态:该dentry对象的引用计数lockref.count的值为0,但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。当以后再需要它时,不必再重新创建,这样路径查找更迅速。但这种dentry对象在回收内存时可能会被释放。
- 正在使用(inuse)状态:处于该状态下的dentry对象的引用计数lockref.count大于0,且其d_inode指向相关的inode对象。这种dentry对象不能被释放。
- 负(negative)状态:与目录项相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。比如,一个进程不断去尝试打开一个不存在的配置文件,内核遍历磁盘上的目录结构体去查找,这就很浪费资源。将这种负状态的dentry继续保留在缓存中,便可提高效率。这种dentry对象在回收内存时将首先被释放。
2.5 目录项高速缓存(dentry cache)
目录项对象的引入为何使这个过程更加简单?这主要得益于Linux设计与实现了目录项高速缓存(dentry cache)。如果某个文件已经被打开过,内存中就应该有该文件的dentry结构,并且该dentry被链接到dentry_hashtable中(一个数组,数组的每个元素是一个链表)。后续再访问该文件的时候,就可以直接从该hash表里面找到,避免了繁琐耗时的路径解析和读盘操作。dentry cache主要由三个数据结构组成:
1. 哈希链表数组dentry_hashtable。dcache中的所有dentry对象都通过d_hash指针域链到dentry_hashtable的其中一个链表中。
2. 未被使用的dentry对象被存放到超级块中字段s_dentry_lru指向的链表。dcache中所有处于“unused”状态和“negative”状态的dentry对象都通过其d_lru指针域链入该链表中。该链表也称为LRU(最近最少使用)链表。最近最少使用的目录项对象总是靠近链表的尾部,一旦目录项高速缓存空间变小,内核就从链表尾部删除元素,使得最近最常使用的对象得以保留
3. 被使用的dentry对象链表。每个inode可能对应多个目录项对象并通过字段i_dentry指向的链表将这些dentry链接。dentry对象中通过d_alias项链接到上述链表中。
dentry_hashtable是在dcache.c中声明的静态变量,声明如下。
static struct hlist_bl_head *dentry_hashtable __read_mostly; // 全局dentry hash表
static inline struct hlist_bl_head *d_hash(unsigned int hash)
{
return dentry_hashtable + (hash >> d_hash_shift);
}
2.6 目录项的分配和查找
2.6.1 分配接口
d_alloc()函数:为一般的目录项分配一个dentry对象。
a_alloc_root()函数:为文件系根目录分配一个dentry对象。
// 分配dentry对象,赋值name、父目录、超级块指针和操作集,将其加入到父目录的子目录项链表
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
{
struct dentry *dentry = __d_alloc(parent->d_sb, name); // 分配dentry对象,赋值name和操作集(dentry_operations)等初始化工作
if (!dentry)
return NULL;
dentry->d_flags |= DCACHE_RCUACCESS;
spin_lock(&parent->d_lock);
/*
* don't need child lock because it is not subject
* to concurrency here
*/
__dget_dlock(parent);
dentry->d_parent = parent; // 修改dentry的父目录项
list_add(&dentry->d_child, &parent->d_subdirs); // 当前目录项加入父目录的子目录项链表
spin_unlock(&parent->d_lock);return dentry;
}
EXPORT_SYMBOL(d_alloc);
2.7 目录项操作
与目录项对象关联的方法由 dentry_operations 结构描述,由 d_op 字段指向。
dcache.h
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int); // 判断目录对象是否有效。vfs准备从dcache中使用一个目录项时,会调用该函数。
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *); // 为目录项生成散列值。
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *); // 比较文件名,多数文件系统使用vfs默认的操作。需要加dcache_lock锁。
int (*d_delete)(const struct dentry *); // 当目录项对象的d_count为0时,vfs调用该函数。需要加dcache_lock锁和目录项的d_lock锁。
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *); // 当目录项对象将要被释放时,vfs调用该函数。默认情况下,它什么也不做。
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *); // 当dentry丢失了其相关的索引节点时,vfs调用该函数。在该函数中必须调用input()函数。
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *,
unsigned int, unsigned int);
} ____cacheline_aligned;struct qstr {
union {
struct {
HASH_LEN_DECLARE; // 该宏定义:u32 hash; u32 len
};
u64 hash_len;
};
const unsigned char *name;
};
3. 超级块对象
存放已安装的文件系统的信息,通常对应于磁盘超级块。
3.1 超级块数据结构
struct super_block {
struct list_head s_list; /* 指向超级块链表的指针 */
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; /* 超级块操作集 */
const struct dquot_operations *dq_op; /* 磁盘限额处理方法 */
const struct quotactl_ops *s_qcop; /* 磁盘限额管理方法 */
const struct export_operations *s_export_op; /* 网络文件系统使用的输出操作 */
unsigned long s_flags; /* 安装标志 */
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic; /* 文件系统的魔数 */
struct dentry *s_root; /* 文件系统的根目录的目录项对象 */
struct rw_semaphore s_umount; /* 卸载所用的信号量 */
int s_count; /* 引用计数器 */
atomic_t s_active; /* 次级引用计数器 */
#ifdef CONFIG_SECURITY
void *s_security; /* 指向超级块安全数据结构的指针 */
#endif
const struct xattr_handler **s_xattr; * 指向超级块扩展属性结构的指针 */
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
const struct fscrypt_operations *s_cop;
#endif
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
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; /* 用于给定文件系统类型的超级块对象链表的指针 */
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options 磁盘限额的描述符 */
struct sb_writers s_writers;
char s_id[32]; /* Informational name 包含超级块的块设备名称 */
uuid_t s_uuid; /* UUID */
void *s_fs_info; /* Filesystem private info 指向特定文件系统的超级块信息的指针 */
unsigned int s_max_links;
fmode_t s_mode;
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype;
const struct dentry_operations *s_d_op; /* default d_op for dentries 目录项操作函数集*/
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/* Being remounted read-only */
int s_readonly_remount;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
/*
* Owning user namespace and default context in which to
* interpret filesystem uids, gids, quotas, device nodes,
* xattrs and security labels.
*/
struct user_namespace *s_user_ns;
/*
* Keep the lru lists last in the structure so they always sit on their
* own individual cachelines.
*/
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp; /* 未使用的dentry链表 */
struct list_lru s_inode_lru ____cacheline_aligned_in_smp; /* 未使用的inode链表*/
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
} __randomize_layout;
3.2关键字段
(转载:VFS数据结构之(superblock )_指向NULL的博客-CSDN博客)
struct list_head s_list:
所有超级块对象都以双向循环链表的形式链接在一起。超级块全局链表中第一个元素为super_blocks,s_list 字段存放指向链表相邻元素的指针。
struct hlist_node s_instances:
用于给定文件系统类型的超级块对象链表的指针,在文件系统类型对象(file_system_type)的 fs_supers 字段表示该链表的第一个元素,s_instances 字段存放指向相邻元素的指针。注意:与 s_list 字段的区别,一个用于超级块全局链表,一个用于特定文件系统类型的超级块链表(ext2和ocfs2有各自的file_system_type对象和链表),通过下图比较进行简单说明,注意图中没有关心超级块的插入顺序,具体是头插还是尾插需要从代码中具体分析。
struct file_system_type *s_type
s_type 字段对应于实际的文件系统类型。具体的类型值由 mount 时传入的参数确定,例如:mount -t ext2 /dev/fd0 /flp。而内核中是否支持这个指定的文件系统是通过在初始化期间调用 register_filesystem() 函数来注册编译时指定的文件系统。
char s_id[32]
:
该字段在分配超级块时先被初始化为指定文件系统的名字,后面会赋值为包含超级块的块设备名称。
struct block_device *s_bdev:
指向块设备的block_device对象。
void *s_fs_info;
该字段指向具体文件系统的超级块信息。假如超级块对象指的是Ext2文件系统,该字段就指向 ext2_sb_info 数据结构,该结构包含磁盘分配位掩码和其他与VFS的通用文件模型无关的数据。任何基于磁盘的文件系统都需要访问和更改自己的磁盘分配位图,以便分配或释放磁盘块。VFS允许这些文件系统直接对内存超级块的 s_fs_info 字段进行操作,而无需访问磁盘。
int s_count
:
引用计数器。
atomic_t s_active
:
次级引用计数器,在调用 alloc_super() 分配超级块时,该字段设为1。同一个文件系统被安装多次是有可能的,不管一个文件系统被安装多少次,都仅有一个超级块对象。因此当一个有效活跃的超级块已存在链表中,同一设备再次安装时,s_active 字段递增。
struct hlist_bl_head s_roots;
在安装文件系统时,该字段指向新分配的根目录项。后续可以使用该字段判断是否是新超级块。
unsigned long s_magic;
每个特定于文件系统使用不同的魔数,因此从磁盘读取超级块后可以通过 s_magic 判断该磁盘是否是此文件系统。例如:对于Ext2 s_magic 必须与 EXT2_SUPER_MAGIC 匹配。
unsigned long s_flags:
系统调用时使用的安装标志:
MS_RDONLY:文件只能被读
MS_NOSUID:禁止setuid和setgid
MS_NODEV:禁止访问设备文件
MS_NOEXEC:不允许执行程序
MS_SYNCHRONOUS:文件和目录上的写操作是即时的
MS_REMOUNT:重新安装改变了安装标志的文件系统
MS_MANDLOCK:允许强制加锁
MS_DIRSYNC:目录上的写操作是即时的
MS_NOATIME:不更新文件访问时间
MS_NODIRATIME:不更新目录访问时间
MS_BIND:创建一个“绑定安装”,这就使得一个文件或目录在系统目录树的另外一个点上可以看得见(mount命令的bind选项)
MS_MOVE:自动把一个已安装文件系统移动到另一个安装点(mount命令move选项)
MS_REC:为目录子树递归地创建“绑定安装”
MS_VERBOSE:在安装出错时产生内核消息
3.3 超级块操作
超级块的s_op字段指向了超级块的函数操作集。
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 *);
void (*umount_begin) (struct super_block *); // 中断一个安装操作,因为相应的卸载操作已经开始(只在网络文件系统中使用)。
int (*show_options)(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 *);
};