1 概述
在Linux系统中,一切皆文件,除了通常所说的狭义的文件(文本文件和二进制文件)以外;目录,设备,套接字和管道等都是文件.
1.1 用户空间层面
应用程序可以直接使用内核提供的系统调用访问文件.
(1)一个存储设备上的文件系统,只有挂载到内存中目录树的某个目录下,进程才能访问这个文件系统.系统调用mount用来把文件系统挂载到内存中目录树的某个目录下.
(2)系统调用umount用来卸载某个目录下挂载的文件系统.
(3)使用open打开文件.
(4)使用close关闭文件.
(5)使用read读文件.
(6)使用write写文件.
(7)使用lseek设备文件偏移.
(8)当我们写文件的时候,内核的文件系统模块把数据保存到页缓存中,不会立即写到存储设备.我们可以使用fsync把文件修改过的属性和数据立即写到存储设备,或者使用fdatasync把文件修改过得数据立即写到存储设备.
应用程序也可以使用glibc库封装的标准I/O流函数访问文件,标准I/O流提供了缓冲区,目的是尽可能减少调用read和write的次数,提高性能.
(1)fopen打开文件流.
(2)fclose关闭流.
(3)fread读流.
(4)fwrite写流.
(5)fseek设置文件偏移.
(6)fwrite可以把数据写到用户空间缓冲区,但不会立即写到内核.可以使用fflush冲刷流,即把写到用户空间缓冲区的数据立即写到内核.
1.2 硬件层面
外部存储设备分为块设备,闪存和NVIDIMM设备3类.
块设备主要有以下两种:
(1)机械硬盘:机械硬盘的读写单位是扇区.
(2)闪存类块设备:使用闪存作为存储介质,里面的控制器运行固化的驱动程序,驱动程序的功能之一是闪存转换层,把闪存转换为块设备,对外表现为块设备.
闪存类块设备相对机械硬盘的优势是:访问速度块,因为没有机械操作;抗振性很高,便于携带.
闪存按存储结构分为NAND闪存和NOR闪存,NOR闪存适合存储程序,一般用来存储引导程序,比如U-Boot程序;NAND闪存适合存储数据.
(3)NVDIMM:非易失性内存,设备把NAND闪存,内存和超级电容集成到一起,访问速度和内存一样快,并且断电之后数据不会丢失.
1.3 内核空间层面
在内核的目录fs下可以看到,内核支持多种文件系统类型.为了对用户程序提供统一的文件操作接口,为了使不同的文件系统实现能够共存,内核实现了一个抽象层,称为虚拟文件系统.
文件系统分为以下4种:
(1)块设备文件系统,存储设备是机械硬盘和固态硬盘等块设备,常见的块设备文件系统是EXT和btrfs.
(2)闪存文件系统,存储设备是NAND闪存和NOR闪存,常用的闪存文件系统是JFFS2和UBIFS.
(3)内存文件系统,文件在内存中,断电后文件丢失,常用的内存文件系统是tmpfs,用来创建临时文件.
(4)伪文件系统,是假的文件系统,只是为了使用虚拟文件系统的编程接口,常用的伪文件系统如:sockfs,proc,sysfs,hugetlbfs,cgroup,cgroup2.
访问外部存储设备的速度很慢,为了避免每次读写文件时,访问外部存储设备,文件系统模块为每个文件在内存中创建了一个缓存,因为缓存的单位是页,所以称为页缓存. 块设备的访问单位是块,块大小是扇区大小的整数倍.内核为所有块设备实现了统一的块设备层.为了避免每次读写都需要访问块设备,内核实现了块缓存,为每个块设备在内存中创建一个块缓存.缓存的单位是块,基于页缓存实现的.
每种块设备需要实现自己的驱动.
内核把闪存称为存储技术设备(MTD),为了所有闪存实现了统一的MTD层,每种闪存需要实现自己的驱动程序.
针对NVDIMM设备,文件系统需要实现DAX(Direct Access,直接访问),绕过页缓存和块设备层,把NVDIMM设备里面的内存直接映射到进程或内核的虚拟地址空间.
2 虚拟文件系统的数据结构
虽然不同文件系统类型的物理结构不同,但是虚拟文件系统定义了一套统一的数据结构.
(1)超级块,文件系统的第一块就是超级块,描述文件系统的总体信息,挂载文件系统的时候在内存中创建超级块的副本:结构体super_block;
(2)虚拟文件系统在内存中把目录组织为一棵树.一个文件系统,只有挂载到内核中目录树的一个目录下,进程才能访问这个文件系统.每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符mount,并且读取文件系统的超级块,在内存中创建一个副本.
(3)每种文件系统的超级块的格式不同,需要向虚拟文件系统注册文件系统类型file_system_type.并且实现mount方法用来读取和解析超级块.
(4)索引节点,每个文件对应一个索引节点,每个索引节点有一个唯一的编号.当内核访问存储设备上的一个文件时,会在内存中创建一个索引节点的副本:结构体inode.
(5)目录项,文件系统把目录看作文件的一种类型,目录的数据是由目录项组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号.当内核访问存储设备上的一个目录项时,会在内存中创建该目录项的一个副本:结构体dentry.
(6)当进程打开一个文件的时候,虚拟文件系统就会创建文件的一个打开实例file结构体,然后在进程的打开文件表中分配一个索引,这个索引称为文件描述符,最后把文件描述符和file结构体的映射添加到打开文件表中.
2.1 超级块
文件系统的第一块是超级块,用来描述文件系统的总体信息.当我们把文件系统挂载到内存中目录树的一个目录下时,就会读取文件系统的超级块,在内存中创建超级块的副本:结构体super_block,主要成员如下:
struct super_block {
struct list_head s_list; /* 用来把所有超级块的实例链接到全局链表super_blocks */
dev_t s_dev; /* 保存文件系统所在的块设备,s_dev保存设备号*/
unsigned char s_blocksize_bits;
unsigned long s_blocksize;
loff_t s_maxbytes; /*文件系统支持的最大文件长度*/
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; /*指向跟目录的结构体dentry*/
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;
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; /*用来把同一个文件系统类型的所有超级块实例链接到一起,链表的头结点是结构体file_system_type的成员fs_supers*/
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 */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; /*指向具体文件系统的私有信息*/
unsigned int s_max_links;
fmode_t s_mode;
u32 s_time_gran;
struct mutex s_vfs_rename_mutex; /* Kludge */
char *s_subtype;
char __rcu *s_options;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
atomic_long_t s_remove_count;
int s_readonly_remount;
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
int s_stack_depth;
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
};
超级块操作集合的数据结构是结构体super_operations,主要成员如下:
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 *); /*用来在索引节点的引用计数减到0时,调用*/
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 *);
};
2.2 挂载描述符
一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件才能访问这个文件系统.每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符:mount结构体.挂载描述符用来描述文件系统的一个挂载实例,同一个存储设备上的文件系统可以多次挂载,每次挂载到不同的目录下.
挂载描述符的主要成员如下:
假设我们把文件系统2挂载到目录"/a"下,目录a属于文件系统1.目录a称为挂载点,文件系统2的mount实例是文件系统1的mount实例的孩子,文件系统1的mount实例是文件系统2的mount实例的父亲.
struct mount {
struct hlist_node mnt_hash; /*用来挂载描述符加入全局散列表mount_hashtable,关键词是{父挂载点描述符,挂载点}*/
struct mount *mnt_parent; /*指向父亲,即文件系统1的mount实例*/
struct dentry *mnt_mountpoint; /*指向作为挂载点的目录,即文件系统1的目录a,目录a的dentry实例成员d_flags设置了标志位DCACHA_MOUNTED*/
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; /* 孩子链表的头结点 */
struct list_head mnt_child; /*用来加入父亲的孩子链表 */
struct list_head mnt_instance; /* 用来把挂载描述符添加到超级块的挂载实例链表中,同一个存储设备上的文件系统,可以多次挂载,每次挂载到不同的目录下*/
const char *mnt_devname; /* 指向存储设备的名称*/
struct list_head mnt_list;
struct list_head mnt_expire; /* link in fs-specific expiry list */
struct list_head mnt_share; /* circular list of shared mounts */
struct list_head mnt_slave_list;/* list of slave mounts */
struct list_head mnt_slave; /* slave list entry */
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* 指向挂载命名空间*/
struct mountpoint *mnt_mp; /* 指向挂载点 */
struct hlist_node mnt_mp_list; /* 用来把挂载描述符加入到同一个挂载点的挂载描述符链表,链表的头节点是成员mnt_mp的成员m_list */
struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
struct hlist_head mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
#endif
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
};
2.3 文件系统类型
因为每种文件系统的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型file_system_type,并且实现mount方法用来读取和解析超级块.结构体file_system_type如下:
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 *); /*用来挂载文件系统时读取并且解析超级块*/
void (*kill_sb) (struct super_block *); /*用来卸载文件系统的时候释放超级块*/
struct module *owner;
struct file_system_type * next;
struct hlist_head fs_supers; /*多个存储设备上的文件系统的类型可能相同,成员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;
};
2.4 索引节点
在文件系统中,每个文件对应一个索引节点,索引节点描述两类信息.
(1)文件的属性,也称为元数据(metadata),例如文件长度,创建文件的用户的标识符,上一次访问的时间和上一次修改的时间等等.
(2)文件数据的存储位置.每个索引节点有一个唯一的编号.
当内核访问存储设备上的一个文件时,会在内存中创建索引节点的一个副本:结构体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 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;
blkcnt_t i_blocks;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
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;
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
u64 i_version;
atomic64_t i_sequence; /* see futex */
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;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
void *i_private; /* fs or device private pointer */
};
文件分为以下几种类型:
(1)普通文件.
(2)目录,目录是一种特殊的文件,这种文件的数据是由目录项组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号.
(3)符号链接(软链接),这种文件的数据是另一个文件的路径.
(4)字符设备文件.
(5)块设备文件.
(6)命名管道(FIFO).
(7)套接字(socket).
字符设备文件,块设备文件,命名管道和套接字都是特殊的文件,这些文件只有索引节点,没有数据.字符设备文件和块设备文件用来存储设备号,直接把设备号存储在索引节点中.
内核支持两种链接:
(1)软链接,也称为符号链接,这种文件的数据是另一个文件的路径.
(2)硬连接,也相当于给一个文件取了多个名称,多个文件名称对应同一个索引节点,索引节点的成员i_nlink是硬链接计数.
索引节点的成员i_op指向索引节点操作集合inode_operations,成员i_fop指向文件操作结合file_operations.两者的区别是:inode_operations用来操作目录(比如在一个目录下创建或删除文件)和文件属性,file_operations用来访问文件的数据.
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*follow_link) (struct dentry *, void **);
int (*permission) (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 (*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和create调用create方法来创建普通文件,系统调用link调用link方法来创建硬链接,系统调用symlink调用symlink方法来创建符号链接,系统调用mkdir调用mkdir方法来创建目录,系统调用mknod调用mknod方法来创建字符设备文件,块设备文件,命名管道和套接字.
系统调用unlink调用unlink方法来删除硬链接,系统调用rmdir调用rmdir方法来删除目录,
系统调用rename调用rename方法来给文件换一个名字.
系统调用chmod调用setattr方法来设置文件的属性,系统调用stat调用getattr方法来读取文件的属性.
系统调用listxattr调用listxattr方法来列出文件的所有扩展属性.
2.5 目录项
文件系统把目录当作文件,这种文件的数据是由目录项组成的,每个目录项存储一个子目录或文件的名称以及对应的索引节点号.
当内核访问存储设备上的一个目录时,会在内存中创建目录项的一个副本:结构体dentry.
struct dentry {
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; /*父目录*/
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; /*引用计数*/
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; /*用来把本目录加入到父目录的子目录链表中*/
struct list_head d_subdirs; /*子目录链表*/
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /*用来把同一个文件的所有硬连接对应的目录项链接起来*/
struct rcu_head d_rcu;
} d_u;
};
目录项操作结合的数据结构体是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 *); /*用来在目录项的引用计数减到0时判断是否可以释放目录项的内存*/
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 *);
} ____cacheline_aligned;
2.6 文件的打开实例和打开文件表
当进程打开一个文件的时候,虚拟文件系统就会创建文件的一个打开实例: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; /*文件操作集合*/
spinlock_t f_lock;
atomic_long_t f_count; /*file结构体的引用计数*/
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;
};
进程描述符有两个文件系统相关的成员:成员fs指向进程的文件系统信息结构体,主要是进程的跟目录和当前工作目录;files指向打开文件表.
3 注册文件系统类型
因为每种文件系统的超级块的格式不同,所以每种文件系统需要向虚拟文件系统注册文件系统类型file_system_type,实现mount方法来读取和解析超级块.
函数register_filesystem用来注册文件系统类型:
int register_filesystem(struct file_system_type *);
函数**unregister_filesystem用来注销文件系统类型:
int unregister_filesystem(struct file_system_type *);
管理员可以执行命令"cat /proc/filesystem"来查看已经注册的文件系统类型.
4 挂载文件系统
虚拟文件系统在内存中把目录组织为一个树.一个文件系统,只有挂载到内存中目录树的一个目录下,进程才能访问这个文件系统.
glibc库封装了挂载文件系统的函数mount,unmount.
每次挂载文件系统,虚拟文件系统就会创建一个挂载描述符:mount结构体.挂载描述符用来描述文件系统的一个挂载实例,同一个存储设备上的文件系统可以被多次挂载,每次挂载到不同目录下.
绑定挂载,用来把目录树的一颗子树挂载到其他地方.
挂载命名空间:容器使用命名空间隔离资源,其中挂载命名空间用来隔离挂载点.每个进程都属于一个挂载命名空间.关系如下:
task_struct.nsproxy -> nsproxy.mnt_ns
标准的挂载命名空间是完全隔离的,在一个挂载命名空间中挂载或卸载一个文件系统不会影响其他挂载命名空间.
(1)共享挂载:同一个挂载点下面的所有共享挂载共享挂载/卸载事件.
(2)从属挂载:假设同一个挂载点下面同时有共享挂载和从属挂载,所有共享挂载组成一个共享对等体组,如果我们在共享对等体组中的任何一个共享挂载下面挂载或卸载文件系统,会自动传播到所有从属挂载;如果我们在任何一个从属挂载下面挂载或着卸载文件系统,则不会传播到所有共享挂载.
(3)私有挂载:私有挂载和同一个挂载点下面的所有其他挂载是完全隔离的.
(4)不可绑定挂载:不可绑定挂载是私有挂载,并且不允许被绑定挂载.
挂载根文件系统
一个文件系统,只有挂载到内存目录树的一个目录下,进程才能访问这个文件系统.第一个文件系统称为根文件系统,根文件系统无法通过mount命令或mount系统调用进行挂载.
内核有两个根文件系统:
(1)一个是隐藏的根文件系统,文件系统类型的名称是"rootfs".
(2)另一个是用户指定的根文件系统,引导内核时,通过内核参数指定,内核把这个根文件系统挂载到rootfs文件系统的根目录下.
内核初始化的时候最先挂载的根文件系统是rootfs文件系统,是一个内存文件系统,对用户隐藏.每个进程使用的标准输入,标准输出和标准错误,对应文件描述符0,1和2这三个文件描述符都对应控制台的字符设备文件"/dev/console",这个文件属于rootfs文件系统.
内核初始化时,调用函数init_rootfs以注册rootfs文件系统,然后调用函数init_mount_tree以挂载rootfs文件系统.
(1)函数init_rootfs
函数init_rootfs负责注册rootfs文件系统,其代码如下:
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int __init init_rootfs(void)
{
int err = register_filesystem(&rootfs_fs_type);
if (err)
return err;
if (IS_ENABLED(CONFIG_TMPFS) && !saved_root_name[0] &&
(!root_fs_names || strstr(root_fs_names, "tmpfs"))) {
err = shmem_init();
is_tmpfs = true;
} else {
err = init_ramfs_fs();
}
if (err)
unregister_filesystem(&rootfs_fs_type);
return err;
}
(2)函数init_mount_tree
函数init_mount_tree负责挂载rootfs文件系统.
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;
struct file_system_type *type;
type = get_fs_type("rootfs");
if (!type)
panic("Can't find rootfs type");
mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
put_filesystem(type);
if (IS_ERR(mnt))
panic("Can't create rootfs");
ns = create_mnt_ns(mnt);
if (IS_ERR(ns))
panic("Can't allocate initial namespace");
init_task.nsproxy->mnt_ns = ns;
get_mnt_ns(ns);
root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}
通过vfs_kern_mount挂载rootfs文件系统.create_mnt_ns创建第一个挂载命名空间.init_task.nsproxy->mnt_ns = ns设置0号线程的挂载命名空间.最后分别设置0号线程的当前工作目录为rootfs文件系统的根目录和把0号线程的根目录设置为rootfs文件系统的根目录.
然后通过default_rootfs在rootfs文件系统中创建必需的目录和文件,比如/dev,/dev/console,/root等.然后1号线程打开控制台的字符设备文件"/dev/console",得到文件描述符0,然后复制两次得到文件描述符1和2.最后1号线程在函数kernel_init中装载用户程序,转换成用户空间的1号进程,分叉成子进程,子进程从1号进程继承打开文件表,继承文件描述符0,1和2.
用户指定的根文件系统
引导内核时,可以使用内核参数"root"指定存储设备的名称,使用内核参数"rootfstype"指定根文件系统的类型.
(1)解析参数''root"和"rootfstype".
内核在初始化的时候,调用函数parse_args解析参数,调用参数的解析函数.
asmlinkage __visible void __init start_kernel(void)
{
...
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
...
}
参数"root"用来指定根文件系统所在的存储设备,解析函数是root_dev_setup,把设备名称保存在静态变量saved_root_name.参数"rootfstype"用来指定根文件系统的类型,解析函数是fs_names_setup,把文件系统的类型保存在静态变量root_fs_names中.
(2)函数prepare_namespace
接下来1号线程调用函数prepare_namespace以挂载根文件系统,主要代码如下:
void __init prepare_namespace(void)
{
int is_floppy;
if (root_delay) {
printk(KERN_INFO "Waiting %d sec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
wait_for_device_probe();
md_run_setup();
if (saved_root_name[0]) {
root_device_name = saved_root_name;
if (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
mount_block_root(root_device_name, root_mountflags);
goto out;
}
ROOT_DEV = name_to_dev_t(root_device_name);
if (strncmp(root_device_name, "/dev/", 5) == 0)
root_device_name += 5;
}
if (initrd_load())
goto out;
if ((ROOT_DEV == 0) && root_wait) {
printk(KERN_INFO "Waiting for root device %s...\n",
saved_root_name);
while (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
msleep(100);
async_synchronize_full();
}
is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
if (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
mount_root();
out:
devtmpfs_mount("dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
}
如果存储设备是闪存分区(设备名称以"mtd"开头)或是在闪存分区的基础上封装的UBI设备(设备名称以"ubi"开头),那么调用函数mount_block_root把根文件系统挂载到rootfs文件系统的目录"/root"下.如果存储设备是其他设备,例如机械硬盘或固态硬盘,那么调用函数mount_root,把根文件系统挂载到rootfs文件系统的目录''/root"下.之后,把根文件系统从目录"/root"移动到根目录下.最后把1号线程的根目录设置为根文件系统的根目录.