6.6实例研究,Linux的文件管理
6.6.1 Linux文件管理概述
Linux支持多种不同类型的文件系统,包括 EXT,EXT2,MINIX,UMSDOS,NCP、
ISO9660,HPFS,MSDOS,NTFS,XIA、
VFAT,PROC,NFS,SMB,SYSV、
AFFS以及 UFS等。
Linux文件系统概念演变,
Minix文件系统 (64Mb)
EXT(Extended file system)文件系统 (2Gb)
Xia文件系统 (2Gb)
EXT2(Second Extended file system)文件系统 (4Tb)
几个术语,
1 i-节点 (inode)
文件描述信息包括:文件类型,访问权限,拥有者,时间,尺寸,块指针 。
2 目录树型目录结构,这是一种存储文件目录项的文件 。 含义同 Unix。
3 链接多个文件名指向同一 inode,实现文件共享 。
4 设备文件
6.6.2 Linux文件系统的管理
Linux中,一个文件系统在物理数据组织上一般划分成引导块,超级块,inode区以及数据区 。
引导块位于文件系统开头,通常为一个扇区,存放引导程序,用于读入并启动操作系统 。
超级块由于记录文件系统的管理信息,根据特定文件系统的需要超级块中存储的信息不同 。
inode区用于登记每个文件的目录项,第一个
inode是该文件系统的根节点 。 数据区则存放文件数据或一些管理数据 。
Linux操作系统究竟支持几种不同类型的文件系统通过文件系统类型注册链表来描述的 。
向系统注册文件系统类型有两种途径,
一是在编译操作系统内核时确定,并在系统初始化时通过函数调用向注册表登记;
另一种是把文件系统当作一个模块,通过
kerneld或 insmod命令在装入该文件系统模块时向注册表登记它的类型 。
文件系统注册表的数据结构
file_systems指向文件系统注册表,每一个文件系统类型在注册表中有一个登记项,记录了该文件系统类型的名 name,支持该文件系统的设备 requires_dev,读出该文件系统在外存超级块的函数 read_super,以及注册表的链表指针 next。
函数 register_filesystem用于注册一个文件系统类型,函数 unregister_filesystem用于从注册表中卸装一个文件系统类型 。
file_system_type
name
requires_dev
read_super
next
file_systems
file_system_type
name
requires_dev
read_super
next
file_system_type
name
requires_dev
read_super
next
Linux操作系统不通过设备标识访问某个具体文件系统,而是通过 mount命令把它安装到整个文件系统树的某一个目录节点,该文件系统的所有文件和子目录就是该目录的文件和子目录,直到用
umount命令显式的撤卸该文件系统 。
当 Linux自举时,首先装入根文件系统,
然 后 根 据 /etc/fstab 中 的 登 记 项 使 用
mount命令自动逐个安装文件系统 。 此外 用 户 也 可 以 显 式 地 通 过 mount 和
umount命令安装和卸装文件系统 。
文件系统的注册和注销操作时,将在以 vfsmntlist为链表头和
vfsmnttail为链表尾的单项链表中增加或删除一个 vfsmount节点,
具体数据结构如下:
static struct vfsmount vfsmntlist = (static struct vfsmount )NULL;
static struct vfsmount *vfsmnttail = (static struct vfsmount
*)NULL; /* 尾 */
static struct vfsmount *mru_vfsmnt = (static struct vfsmount
*)NULL; /*当前 */
struct vfsmount {
kdev_t mnt_dev; /* 文件系统所在的主次设备号 */
char* mnt_devname;/*文件系统所在的设备名,*/
char* mnt_dirname;/* 安装目录名 */
unsigned int mnt_flags;/* 设备标志,如 ro */
struct semaphore mnt_sem;/* 设备有关的信号量 */
struct super_block* mnt_sb;/* 指向超级块 */
struct file* mnt_quotas[MAXQUOTAS];/*指向配额文件的指针
time_t mnt_iexp[MAXQUOTAS]; /* expiretime for inodes */
time_t mnt_bexp[MAXQUOTAS]; /* expiretime for blocks */
struct vfsmount* mnt_next; /* 后继指针 */
};
6.6.3 虚拟文件系统 VFS
虚拟文件系统 VFS是物理文件系统与服务之间的一个接口层,它对每一个具体的文件系统的所有细节进行抽象,使得 Linux用户能够用同一个接口使用不同的文件系统 。 VFS只是一种存在于内存的文件系统,在操作系统自举时建立,
在系统关闭时消亡 。
它的主要功能包括:
l 记录可用的文件系统的类型 。
l 把设备与对应的文件系统联系起来 。
l 处理一些面向文件的通用操作 。
涉及到针对具体文件系统的操作时,把它们映射到与控制文件,目录以及 inode相关的物理文件系统 。
Linux文件管理的实现层次
EXT FS
VFS inode cache
VFS directory cache
EXT2 FS MSDOS FS MINIX FS
VFS
buffer cache
buffer cache
VFS描述文件时使用超级块和 inode的方式。超级块的数据结构如下:
struct super_block {
kdev_t s_dev; /* 该文件系统的主次设备号 */
unsigned long s_blocksize; /* 块大小 */
unsigned char s_blocksize_bits; /* 以 2的幂次表示块大小 */
unsigned char s_lock; /* 锁定标志,置位表示拒绝其他进程访问 */
unsigned char s_rd_only; /* 只读标志 */
unsigned char s_dirt; /* 已修改标志 */
struct file_system_type* s_type; /* 指向文件系统类型注册表相应项 */
struct super_operations* s_op; /* 指向一组操作该文件系统的函数 */
struct dquot_operations* dq_op;
unsigned long s_flags;
unsigned long s_magic;
unsigned long s_time;
struct inode* s_covered; /* 指向安装点目录的 inode */
struct inode* s_mounted; /* 指向被安装文件系统的第一个
inode */
struct wait_queue* s_wait; /* 在该超级块上的等待队列 */
union { 各个物理文件系统超级块的结构类型 } u;
};
文件系统中的每一个子目录和文件读对应于一个唯一的 inode,它是 Linux管理文件系统的最基本单位。
inode的数据结构如下:
struct inode {
kdev_t i_dev; /* 该文件系统的主次设备号 */
umode_t i_mode; /* 文件类型以及存取权限 */
ulink_t i_nlink; /* 连接到该文件的 link数 */
uid_t i_uid;
gid_t i_gid;
kdev_t i_rdev; /* 该文件系统的主次设备号 */
off_t i_size; /* 文件长度 */
time_t i_atime,i_mtime,i_ctime;
unsigned long i_blocksize,i_blocks; /* 字节 /块为单位的文件长度 */
unsigned long i_version;
unsigned long i_nrpages; /* 文件所占的内存页数 */
struct semaphore i_sem;
struct inode_operations* i_op;/* 指向一组针对该文件的操作函数 */
struct super_block* i_sb; /* 指向内存中的 VFS超级块 */
struct wait_queue* i_wait; /* 在该文件上的等待队列 */
struct file_lock* i_flock; /* 操作该文件的文件锁链表的首地址 */
struct vm_area_struct* i_mmap;
struct page* i_pages; /* 文件所占页面构成的单向链 */
struct dquot* i_dquot[MAXQUOTAS];
struct inode *i_next,*i_prev,*i_hash_next,*i_hash_prev,*i_bound_to,*i_bound_by;
struct inode *i_mount; /* 指向下挂文件系统的 inode的根目录 */
unsigned long i_count; /* 引用计数,0表示空闲 */
unsigned short i_flags;
unsigned short i_writecount;
unsigned char i_lock; /* inode的锁定标志 */
unsigned char i_dirt; /* 已修改标志 */
unsigned char i_pipe,i_sock,i_seek,i_update,i_condemned;
union { 各个物理文件系统 inode的结构类型 } u;
};
同超级块一样,inode.u用于存储每一个特定文件系统的特定 inode。
系统所有的 inode通过 i_prev,i_next连接成双向链表,头指针是 first_inode。
每个 inode通过 i_dev和 i_ino唯一地对应到某一个设备上的某一个文件或子目录 。
i_count为 0时表明该 inode空闲,空闲的 inode总是放在 first_inode链表的前面,当没有空闲的
inode时,VFS会调用函数 grow_inodes从系统内核空间申请一个页面,并将该页面分割成若干个空闲 inode,加入 first_inode链表 。
6.6.4文件管理的缓冲机制
Linux既支持多种类型的文件系统,又保持了很高的性能 。 探究其原因,除了 VFS
以外,多种复杂的 cache起到了关键作用 。
VFS inode cache
从效率角度出发,为提高对 first_inode链表进行线性搜索的速度,VFS为已经分配的 inode构造了 cache和 hash表。
图 6-14给出了这一结构,VFS访问 inode
时,它首先根据 hash函数计算出 h,然后找到对应的 hash_table[h]指向的双向链表,通过 i_hash_next和 i_hash_prev进行查找。
updating inode
hash_table
updating inode
updating inode
inode inode inode
VFS directory cache
Linux 维 护 了 表 达 路 径 与 inode 对 应 关 系 的 VFS
directory cache 。 被访问过的目录将会被存入
directory cache,这样当同一目录被再次访问时就可以快速获得 。 数据结构如下:
struct hash_list { struct dir_cache_entry *next,*prev; };
struct dir_cache_entry {
struct hash_list h;
kdev_t dc_dev;
unsigned long dir; /* 父目录的 inode */
unsigned long version;
unsigned long ino; /* 本目录的 inode */
unsigned char name_len;
char name[DCACHE_NAME_LEN];
struct dir_cache_entry **lru_head;
struct dir_cache_entry *next_lru,*prev_lru;
};
VFS directory cache 由 level1_cache 和
level2_cache组成,头指针存放在 level1_head
和 level2_head中 。 level1_cache和 level2_cache
均采用 dir_cache_entry.next_lru 和
dir_cache_entry.prev_lru指针构成包含 128个节点的双向循环链表,使用 LRU算法增删节点 。
level1_cache和 level2_cache各有分工,新增加的目录存放在 level1_cache尾部;查询信息命中后则放在 level2_cache尾部 。 为进一步提高效率,Linux还为 level1_cache和 level2_cache
建立了 hash表 。
Buffer cache
为加快对物理设备的访问,Linux维护一组数据块缓冲区,称为 buffer cache。
Buffer cache就时文件组织中所提到的主存缓冲区,它独立于任何类型的文件系统,被所有的物理设备所共享 。
为提高访问效率,Buffer cache系统被精心设计成一个 hash表和四类 buffer链表:
l hash表,用于进一步提高访问效率 。
static struct buffer_head ** hash_table;
l 最近最少用链表,它包括 4个链表 。 lru_list[0]保存数据已经写出的干净的缓冲区; lru_list[1]保存正在进行写出操作的缓冲区; lru_list[2]保存超级块和 inode的缓冲区;
lru_list[3]保存已有新数据但尚未安排写出的缓冲区 。
static struct buffer_head *lru_list[NR_LIST];
l 空闲链表,它按照 buffer长度 ( 512,1024,2048、
4096,8192字节 ) 分成 5个链表,其中的 buffer_head节点封装了数据缓冲区 。
static struct buffer_head *free_list[NR_SIZES];
l 未使用链表,buffer_head节点未封装数据缓冲区 。
static struct buffer_head *unused_list;
l 重用链表,buffer_head节点未封装数据缓冲区 。
static struct buffer_head *reuse_list;
6.6.5 系统打开文件表系统打开文件表记录系统已经打开的文件,
用于文件的读写操作 。 系统打开文件表是一张以 file结构作为节点的双向链表,表头指针为 first_file,每个节点对应一个已打开的文件,包含了此文件的 inode,操作函数,
对文件的所有操作都离不开它 。 数据结构如下:
struct file {
mode_t f_mode;
loff_t f_pos;
unsigned short f_flags;
unsigned short f_count;
off_t f_reada;
struct file *f_next,*f_prev;
int f_owner;
struct inode *f_inode;
struct file_operations *f_op;
unsigned long f_version;
void *private_data;
};
对于每一个进程,PCB包含一个打开文件表,结构如下:
struct files_struct {
int count; /* 引用计数器 */
fd_set close_on_exec; /* 系统调用 exec时关闭的文件的屏蔽字数组 */
fd_set open_fds; /* 对所有文件描述字 fd的屏蔽字数组 */
struct file *fd[NR_OPEN]; /* 进程打开文件数组 */
};
6.6.2 EXT2文件系统
EXT( 1992年 ) 和 EXT2( 1994年 ) 是专门为 Linux设计的可扩展的文件系统 。
在 EXT2中,文件系统组织成数据块的序列,这些数据块的长度相同,块大小在创建时被固定下来 。
EXT2把它所占用的磁盘逻辑分区划分为块组,每一个块组依次包括超级块,组描述符表,块位图,inode位图,
inode表以及数据块 。
块位图集了本组各个数据块的使用情况; inode位图则记录了 inode表中 inode的使用情况 。 inode表保存了本组所有的 inode,inode用于描述文件,一个 inode对应一个文件和子目录,有一个唯一的 inode号,并记录了文件在外存的位置,存取权限,修改时间,类型等信息 。
6.6.2.1 EXT2的超级块
EXT2的超级块用来描述目录和文件在磁盘上的静态分布,包括尺寸和结构 。 每个块组都有一个超级块,一般来说只有组 0的超级块才被读入内存超级块,其它块组的超级块仅仅作为备份 。
EXT2文件系统的超级块主要包括 inode数量,
块数量,保留块数量,空闲块数量,空闲 inode
数量,第一个数据块位置,块长度,片长度,
每个块组块数,每个块组片数,每个块组 inode
数,以及安装时间,最后一次写时间,安装信息,文件系统状态信息,…,等等内容 。 具体的 EXT2外存超级块和内存超级块数据结构参见
include/linux/ext2_fs.h 中 的 结 构
ext2_super_block和结构 ext2_sb_info。
6.6.2.2 EXT2的组描述符每个块组都有一个组描述符,记录了该块组的块位图位置,inode位图位置,inode节点位置,
空闲块数,inode数,目录数等内容 。 具体的组描述符数据结构参见 include/linux/ext2_fs.h
中的结构 ext2_group_desc。
所有的组描述符一个接一个存放,构成了组描述附表 。 同超级块一样,组描述符表在每个块组中都有备份,这样,当文件系统崩溃时,可以用来恢复文件系统 。
6.6.2.3 EXT2的 inode
inode用于描述文件,一个 inode对应一个文件和子目录,有一个唯一的 inode号,
并记录了文件的类型及存取权限,用户和组标识,修改 /访问 /创建 /删除时间,
link数,文件长度和占用块数,在外存的位置,以及其他控制信息 。 具体的数据结构参见 include/linux/ext2_fs.h中的结构 ext2_inode。
6.6.2.4 EXT2的目录文件目录是用来创建和保存对文件系统中的文件的存取路径的特殊文件,它是一个目录项的列表,其中头两项是标准目录项,,”( 本目录 ) 和,,.”( 父目录 ) 。 目录项的数据结构如下:
struct ext2_dir_entry {
_u32 inode; /*
该目录项的 inode号 */
_u16 rec_len; /*
目录项长度 */
_u16 name_len; /*
文件名长度 */
char name[EXT2_NAME_LEN]; /* 文件名 */
};