【前言】
文件系统是结合了众多工程师智慧的结晶,所以在这个过程中,会出现各种各样的小技术手段来解决一些实际性问题,从而产生了很多技术词汇。
【文件系统Overview】
Linux一切皆文件:普通文件、目录文件(也就是文件夹)、设备文件、链接文件、管道文件、套接字文件(数据通信的接口)等等。引入文件系统,帮助我们屏蔽底层实现的细节(不必care扇区,cache,文件管理),我们只需通过VFS提供的fs_op就可以对文件进行对应的操作。
Android 支持的文件系统: ext4 vfat f2fs sdcardfs fuse(fuse解决了文件系统必须在内核态的难题。将文件系统的实现从内核态搬到了用户态)
Android linux 文件系统核心: VFS(1.向上提供接口,向下兼容文件系统,实现了inode/page cache等公共部分,让其他文件系统无需重复实现)
【文件系统四大对象】
1. 超级块super_block:
struct super_block {
struct list_head s_list; /* Keep this first 指向超级块链表的指针*/
dev_t s_dev; /*具体文件系统的块设备描述符*/
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; /*指向super_block操作的函数集合*/
alloc_inode () /*创建并初始化一个inode*/
write_inode() /*将inode同步到磁盘*/
sync_fs() /*同步文件系统元数据到磁盘*/
list_head s_inodes; /* all inodes */
list_head s_inodess_dirty; /* dirty inodes */
void *s_fs_info; /* Filesystem private info 具体文件系统的私有数据*/
*** }
- 超级块用来描述整个文件系统的信息
- 每个具体的文件系统都有自己的超级块
- VFS超级块是各种文件系统在安装时建立的,并在卸载时被自动删除,其数据结构是super_block
- 所有超级块对象都以双向循环链表的形式链接在一起
1.1 struct super_operations:超级块操作表
2. inode:index node
struct inode {
struct hlist_node i_hash; /* 哈希表 */
struct list_head i_list; /* 索引节点链表 */
struct list_head i_dentry; /* 目录项链表 */
unsigned long i_ino; /* 节点号 */
atomic_t i_count; /* 引用记数 */
umode_t i_mode; /* 访问权限控制 */
unsigned int i_nlink; /* 硬链接数 */
uid_t i_uid; /* 使用者id */
gid_t i_gid; /* 使用者id组 */
kdev_t i_rdev; /* 实设备标识符 */
loff_t i_size; /* 以字节为单位的文件大小 */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改(modify)时间 */
struct timespec i_ctime; /* 最后改变(change)时间 */
unsigned int i_blkbits; /* 以位为单位的块大小 */
unsigned long i_blksize; /* 以字节为单位的块大小 */
unsigned long i_version; /* 版本号 */
unsigned long i_blocks; /* 文件的块数 */
unsigned short i_bytes; /* 使用的字节数 */
spinlock_t i_lock; /* 自旋锁 */
struct rw_semaphore i_alloc_sem; /* 索引节点信号量 */
struct inode_operations *i_op; /* 索引节点操作表 */
struct file_operations *i_fop; /* 默认的索引节点操作 */
struct super_block *i_sb; /* 相关的超级块 */
struct file_lock *i_flock; /* 文件锁链表 */
//important
struct address_space *i_mapping; /* 描述页高速缓存中的页面。一个文件对应
一个address_space,一个
address_space和一个偏移量可以确定一
个页高速缓存中的页面
struct address_space i_data; /* 设备地址映射 */
struct dquot *i_dquot[MAXQUOTAS]; /* 节点的磁盘限额 */
struct list_head i_devices; /* 块设备链表 */
struct pipe_inode_info *i_pipe; /* 管道信息 */
struct block_device *i_bdev; /* 块设备驱动 */
unsigned long i_dnotify_mask; /* 目录通知掩码 */
struct dnotify_struct *i_dnotify; /* 目录通知 */
unsigned long i_state; /* 状态标志 */
unsigned long dirtied_when; /* 首次修改时间 */
unsigned int i_flags; /* 文件系统标志 */
unsigned char i_sock; /* 可能是个套接字吧 */
atomic_t i_writecount; /* 写者记数 */
void *i_security; /* 安全模块 */
__u32 i_generation; /* 索引节点版本号 */
union {
void *generic_ip; /* 文件特殊信息 */
} u;
- 文件系统处理文件所需要的所有信息都保存在称为索引节点的inode结构体中
- 同一个文件系统中,每个文件的索引节点号都是唯一的
- 与索引节点关联的方法由struct inode_operations来描述
- inode有两个设备号:i_dev(常规文件的设备号),i_rdev(某一设备的设备号)
- LInux文件系统的另外一大特色:设备即文件 —— 驱动中设备号的来源。
2.1 struct inode_operations:index node操作函数
3. dentry:目录项
struct dentry {
atomic_t d_count; //目录项对象使用计数器,可以有未使用态,使用态和负状态
unsigned int d_flags; //目录项标志
struct inode *d_inode; //与文件名关联的索引节点
struct dentry *d_parent; //父目录的目录项对象
struct list_head d_hash; //散列表表项的指针
struct list_head d_lru; //未使用链表的指针
struct list_head d_child; //父目录中目录项对象的链表的指针
struct list_head d_subdirs; //对目录而言,表示子目录目录项对象的链表
struct list_head d_alias; //相关索引节点(别名)的链表
int d_mounted; //对于安装点而言,表示被安装文件系统根项
struct qstr d_name; //文件名
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op; //目录项方法
struct super_block *d_sb; //文件的超级块对象
vunsigned long d_vfs_flags;
void *d_fsdata; //与文件系统相关的数据
unsigned char d_iname [DNAME_INLINE_LEN]; //存放短文件名
};
- 存在于内存的目录项缓存,为了提高查找性能而设计,动态生成的(哈希表,或组织为一颗树,链表)。
- 每个文件除了一个struct inode结构体外,还要一个目录项struct dentry结构,通过其d_hash域链入哈希表中
- 不管是文件夹还是文件(目录),都属于目录项,所有的目录项在一起构成一颗庞大的目录树。
- dentry对象有三种状态:被使用,未被使用和负状态。
•dentry 建立流程:
某个目录对应的dentry不在内存中,调用d_lookup函数,以父dentry和qstr类型的name为依据,来查找内存中是否已经有了对应的dentry。如无,分配一个dentry,这是d_alloc函数负责分配dentry结构体,初始化相应的变量,建立与父dentry的关系。
3.1 struct dentry_operations
4. 文件对象(file)
- 进程通过文件描述符来访问文件,由相应的open()系统调用创建, 有close()系统调用销毁。
- LInux用一个file文件对象来保存打开文件的位置,这个对象称为打开的文件描述符。
- file结构主要保存了文件位置,还把指向文件索引节点的指针也放在其中。
- file结构形成一个双链表,称为系统打开文件表。
- 只有目录项对象才表示已打开的实际文件,文件对象实际上没有对应的磁盘数据。
struct file {
union { struct list_head fu_list; //文件对象链表指针linux/include/linux/list.h struct
rcu_head fu_rcuhead; //RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制 }
f_u; struct path f_path; //包含dentry和mnt两个成员,用于确定文件路径
#define f_dentry f_path.dentry //f_path的成员之一,当前文件的dentry结构
#define f_vfsmnt f_path.mnt //表示当前文件所在文件系统的挂载根目录
const struct file_operations *f_op; //与该文件相关联的操作函数
atomic_t f_count; //文件的引用计数(有多少进程打开该文件)
unsigned int f_flags; //对应于open时指定的
flag mode_t f_mode; //读写模式:open的mod_t mode参数
off_t f_pos; //该文件在当前进程中的文件偏移量
struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。
unsigned int f_uid, f_gid; //文件所有者id,所有者组id
struct file_ra_state f_ra; //在linux/include/linux/fs.h中定义,文件预读相关
unsigned long 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; 文件的事件轮询等待者链表的头,
spinlock_t f_ep_lock;
f_ep_lock是保护f_ep_links链表的自旋锁。
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; 文件地址空间的指针
};
4.1 struct file_operations
4.2 struct files_struct
struct files_struct {
atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护该结构体的锁*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset; /*当前文件描述符的最大数*/
int next_fd; /*已分配的文件描述符加1*/
struct file ** fd; /* 指向文件对象指针数组的指针 */
fd_set *close_on_exec; /*指向执行exec()时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init; /* 执行exec()时关闭的初始文件*/
fd_set open_fds_init; /*文件描述符的初值集合*/
struct file * fd_array[32]; /* 文件对象指针的初始化数组*/
};
- 文件描述符用来描述打开的文件
- 每个进程用一个files_struct结构来记录文件描述符的使用情况
- 这个files_stuct结构称为用户打开文件表,它是进程的私有数据
4.3 struct fs_struct
//描述进程与文件系统的关系
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask; //用于为新创建的文件设置初始文件许可权限
int in_exec;
struct path root, pwd;
};
关系图:
贪婪和成本效益算法
PS:
【I/O 缓冲区】
Cache:解决的是速度不同步的问题。
- Buffer : 用于内存和硬盘的缓冲,缓冲“写”操作,保存即将要写入到磁盘上的数据。
- Cache:一般指高速缓存,用于CPU和内存之间的缓冲,解决读的问题,保存从磁盘上读出的数据。
Buffer Cache和 Page Cache
- buffer cache:块缓冲器,面向块设备(文件系统的块)。
- page cache: 页缓冲器,面向虚拟内存。已映射到内存的某些物理设备(例如磁盘)上的数据,包含来自最近访问的“文件”的整个页面。在页面I / O操作(例如read()])中,内核检查数据是否驻留在page cache中。如果数据在page cache中,则内核可以快速返回请求的页面,而不必从磁盘读取数据。