文件系统
Linux内核的应用层是以 文件系统 为核心展开,以文件系统作为整个内核应用层的核心,理由如下:
--文件系统本身具有重大作用:
分布式文件系统的广泛应用让文件系统成为当前内核应用的热门
--文件系统在整个内核架构中具有基础架构性质:
字符设备、块设备 这些设备驱动 需要依靠文件系统来实现。设备管理的基础架构也要依靠文件系统(sysfs)。设备和驱动 是国内当前在内核层面应用,也是国内底层开发中应用最多的方面。
从文件系统入手,掌握基本概念和实现架构后,可以从稳健系统引出设备文件的概念,设备文件可以引申到 字符设备 和 块设备,这样从 文件系统 过度到 设备管理。
设备管理包含 设备驱动, 设备驱动要用到中断,设备里面的块设备又控制了 通用块层 和 I/O调度。
而文件系统向外引申又和网络的socket联系。
深入文件系统的的代码,可以了解到内存的页面管理。
从文件系统出发,层次推进基本囊括了内核的应用层的重要概念和架构。
2.1 文件系统的基本概念
2.1.1 VFS
VFS------ VIrtual File System 虚拟文件管理系统
linux内核通过虚拟文件系统(VFS)管理文件系统;
VFS是linux内核文件系统的一个极其重要的基础设施,VFS为 所有的文件系统提供统一的接口,对每个文件系统的访问都需要通过VFS定义的接口来实现。同时,VFS也是一个极其重要的架构,所有的linux文件系统都必须按照VFS定义的方式来实现;
VFS存在于内存中,将硬盘上的文件系统抽象到内存中。
VFS定义了几个重要结构:
dentry、inode、super_block,通过这些结构将真实的硬盘文件系统抽象到内存。
通过管理dentry、inode、super_block这几个对象就可以完成对文件系统的一些操作-----合适的时候仍需要将内存数据写入到硬盘
2.1.2 超级块super_block
超级块suoer_block代表整个文件系统本身。
超级块是对应文件系统本身的控制块结构【可参考ext2文件系统的超级块结构】。
超级块保存了文件系统设定的文件块大小,超级块的操作函数,文件系统内所有的inode也要链接到超级块的表头。
对于一个具体文件系统的控制块可能还含有另外的信息,通过超级块对象,可以找到这些必要的信息。
超级快的内容需要读取具体文件系统在硬盘上的超级块获得,所以超级块是具体文本系统超级块的内存抽象。
超级块对象整个结构很庞大庞杂,以下是超级块的简化后的定义:
---------------------------------------------------------------------------------------
struct super_block{
unsigned longs_blocksize;
unsigned chars_blocksize_bit;
......
unsigned long longs_maxbytes;
struct file_system_type*s_type;
struct super_operations*s_op;
unsigned longs_magic;
struct dentry*s_root;
struct list_heads_inodes;
struct list_heads_dirty;
struct block_device*s_bdev;
void*s_fs_info;
}
从两方面了解超级结构快的作用:
1)超级块结构给出了文件系统的全局信息
*s_blocksize ---指定了文件系统的大小
*s_maxbytes ---指定文件系统中最大文件的尺寸
*s_type ---指向file_system_type结构的指针
*s_magic ---魔术数字,每个文件系统都有一个魔术数字
*s_root ---指向文件 系统根dentry的指针
超级块还定义了一些链表头:
*s_inode ---指向文件系统内所有的iNode,通过他可以遍历inode对象
*s_dirty ---指向所有dirty的inode对象
*s_block ---指向文件系统存在的块设备指针
2)超级块结构包含一些函数的指针
-------super_operation提供了最重要的超级块操作。
例如super_operation的成员函数read_inode提供读取inode信息的功能。
每个具体的文件系统需要提供这个函数来实现对inode信息的读取,例如ext2文件系统--具体函数是ext2_read_inode。
(我们可以理解为‘VFS提供了架构,具体文件系统按照VFS的架构实现’)
目录项dentry
----文件和目录一般按照树状结构保存。目录项【dentry】就反应了文件系统的这种树状关系。
---在VFS中,目录本身就是一个文件;
---------------------------------------------------------------------------
struct dentry {
..../省略dentry锁、标志等代码/
struct inode *d_inode;
struct hlist_node d_hash;
struct dentry *d_parent;
struct qstr d_name;
/* d_child and d_rcu can share memory*/
union {
struct list_head_d d_child;
struct rcu_head d_rcu;
} d_u;
struct list_headd_subdirs;
struct dentry_operations*d_op;
struct super_block*d_sb;
int d_mounted;
}
---------------------------------------------------------------------------
对dentry的解释如下:
索引节点inode
---inode代表一个文件。inode保存了一个文件的大小、创建时间、文件的块大小等参数,以及文件的读写函数、文件的读写缓存等信息。
---文件只有一个inode,可以有多个dentry【指向文件的路径可以多个(考虑文件的链接)】。
inode和dentry分别代表文件通用的两个部分
inode结构定义十分庞大,简化重点的几个结构成员如下:
--------------------------------------------------------------------------------------
struct inode {
struct list_head i_list;
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
loff_t i_size;
unsigned int i_blkbits;
struct inode_operations *i_op;
const struct file_operation *i_fop;
struct address_space *i_mapping;
struct block_device *i_bdev;
......
};
--------------------------------------------------------------------------------------
inode 结构解释:
--i_list : 链接描述inode当前状态的链表
--i_sb_list :链接到超级块中的inode链表
***当创建一个新的inode的时候,成员i_list链接到inode_in_use这个链表【表示inode在使用状态】,同时成员i_sb_list链接到文件系统超级块的s_inode链表头。
--i_dentry :一个文件有多个dentry,这些dentry都连接到成员i_dentry这个链表头
------i_ino :inode的号
------i_count :inode的引用计数
------i_size :以字节为单位的文件长度
-i_blkbits :文件块的位数
文件
文件对象的作用是描述 进程 和 文件 交互 的 关系;
硬盘上并不存在一个文件结构
进程打开一个文件,内核就动态创建一个文件对象
同一个文件,在不同进程中有不同的文件对象
文件的结构定义如下代码清单:
-------------------------------------------------------------------------
struct file {
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
const struct file_operation *f_op;
.......
loff_t f_ops;
struct fown_struct f_owner;
usigned int f_uid,
struct file_ra_state f_ra;
struct address_space *f_mapping;
}
-------------------------------------------------------------------------
--f_ra : 用于文件预读的设置---在第10章继续分析文件
注释:
文件预读:
--对于文件的预读,Linux内核提供了预读策略------比要求读的长度多读一些,存储在page cache里,后续读如果是顺序的,马上可以利用page cache的数据返回,不必再次读硬盘。对于硬盘这种慢速设备来说,利用缓存数据可以大大提升I/O传输效率。
内核提供默认的预读参数,代码清单如下:
--------------------------------------------------------------------------------------------------------------
struct backing_dev_info default_backing_dev_info = {
.ra_page=(VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE,
.state=0,
.capabilities=BDI_CAP_MAP_COPY,
.unplug_io_fn=default_unplug_io_fn,
}
--------------------------------------------------------------------------------------------------------------
linux-2.6.39\mm\backing-dev.c
struct backing_dev_info default_backing_dev_info = {
.name = "default",
.ra_pages = VM_MAX_READAHEAD * 1024 / PAGE_CACHE_SIZE,
.state = 0,
.capabilities = BDI_CAP_MAP_COPY,
};
VM_MAX_READAHEAD 默认设置为128KB,即默认读取页面是32个4KB的页面。
Linux内核会根据文件读取是否顺序启动预读参数和设置预读窗口,对于连续的顺序读,会尽量多读一些内容填充page cache。
注--这部分的内容在readahead,c文件里面。文件不大,比较孤立,不涉及太多关联的知识点,可以分析一下预读代码