首先介绍VFS的数据结构
VFS抽象数据结构
- 超级块(superblock): 存放文件系统控制信息
- 索引节点(inode): 存放具体文件的一般信息,用来标识存储介质上的文件
- 文件(file): 存放已打开的文件和进程之间交互的信息
- 目录项(dentry): 存放文件的名称和文件的路径
其他重要数据结构包括
- file_system_type: 用来描述不同的文件系统
- vfsmount: 描述系统中已经挂载的文件系统信息,用于对超级块和inode进行跟踪
superblock的主要数据结构
域 | 作用 |
s_list | 指向下一个superblock,所有的superblock组成一个链表,头指针保存在super_blocks变量中 |
s_fs_info | 指向特定文件系统的superblock信息,比如ext2,这个指针指向ext2_sb_info结构 |
s_dirt | 因为superblock既存在于内存中也存在于磁盘中,所以需要这个标记标示内存中的数据是否和磁盘中的同步 |
s_root | 文件系统根目录的dentry,表示这个文件系统的mount点
|
s_inodes | 指向所有inodes组成的链表的头指针 |
s_files | 指向所有file对象组成的链表的头指针 |
s_op | 超级块操作函数集,struct super_operations类型,包括alloc_inode,destroy_inode,put_super等函数,用来管理这个文件系统中的 inode 的函数 |
inode的主要数据结构
域 | 作用 |
i_dentry | 指向本inode的所有dentry对象组成的链表的头指针 |
i_ino | 指向特定文件系统的superblock信息,比如ext2,这个指针指向ext2_sb_info结构 |
i_nlink | inode的硬连接引用计数 |
i_count | 进程对inode的引用计数 |
i_uid/i_gid | 所属用户/组的id |
i_atime | inode访问时间,类型域有i_mtime和i_ctime |
i_mode | inode的权限 |
i_size | 文件大小(单位:字节) |
i_data | 数据地址索引,struct address_space结构类型 |
i_mapping | 内存索引页面,struct address_space结构类型指针,指向i_data(?) |
i_blocks | 所占的block数 |
i_bytes | 最后一个block使用了的字节数 |
i_sb_list | 指向super_block中s_inodes链表中本inode的下一个inode |
i_op | 索引节点操作函数集,inode_operations类型,定义直接在 inode 上执行的操作 |
i_cdev/i_pipe/i_bdev | 非特殊文件时,inode所存储的设备 |
i_rdev | 非特殊文件时,所存储设备的设备号 |
dentry的主要数据结构
域 | 作用 |
d_name | 文件名 |
d_inode | 指向的inode |
d_parent | 父目录的dentry对象 |
d_count | 目录项对象使用计数器 |
d_child | 对于目录来说,和本dentry相同父目录的目录dentry对象链表的头指针 |
d_subdirs | 对于目录来说,当前dentry下的子目录dentry对象链表的头指 |
d_alias | 指向相同inode的所有dentry对象构成的链表的头指针 |
d_op | 目录项操作表,dentry_operations类型 |
d_sb | 文件的超级块对象 |
file的主要数据结构
域 | 作用 |
f_dentry | 指向当前file对象关联的dentry对象 |
f_vfsmnt | |
f_pos | 文件指针 |
f_op | 定义与文件和目录相关的方法(即标准系统调用POSIX标准文件I/O管理函数),file_operations结构类型 |
file_system_type的主要数据结构
域 | 作用 |
name | 文件系统的名称 |
fs_flags | 一些特殊标记,如FS_REQUIRES_DEV |
mount | 本文件系统的挂载方法,执行kern_mount函数时调用 |
kill_sb | 卸载文件系统超级块的方法 |
next | file_system_type类型单项链表,指向其他文件系统,单链表的表头由全局变量file_systems表示 |
fs_supers | 双向链表,指向本文件系统所支持的super_block |
vfsmount的主要数据结构
域 | 作用 |
mnt_parent | 上一层挂载点 |
mnt_mounts | 子文件系统的链表头 |
mnt_child | 指向 上一层挂载点mnt_mounts形成双链表 的兄弟结点 |
mnt_mountpoint | 指向挂载点dentry结构的指针 |
mnt_root | 指向本文件系统的根路径dentry |
mnt_sb | 指向文件系统超级块 |
mnt_flags | 挂载选项,如MNT_NODEV,MNT_READONLY等 |
mnt_list | 指向vfsmount结构所形成链表的头指针(?) |
tip:通常,superblock, inode会保存至存储介质,而dentry ,file仅存在于内存中
2.文件系统初始化过程
对于特定的文件系统, 其初始化过程主要是
static struct vfsmount *fixfs_mnt;
static struct file_system_type fixfs_fs_type = {
.owner = THIS_MODULE,
.name = "fixfs",
.mount = fixfs_mount,
.kill_sb = fixfs_kill_sb,
};static int __init init_fixfs_fs(void)
{
int ret;/* fixfs specify init */
ret = register_filesystem(&fixfs_fs_type);
if (!ret) {
fixfs_mnt = kern_mount(&fixfs_fs_type);
if (IS_ERR(fixfs_mnt)) {
fixfs_mnt = NULL;
unregister_filesystem(&fixfs_fs_type);
}
}
/* error handle specs */
}static void __exit exit_fixfs_fs(void)
{
unregister_filesystem(&fixfs_fs_type);/* fixfs specify cleanup */
}module_init(init_fixfs_fs);
module_exit(exit_fixfs_fs);
需要定义的数据结构包括file_system_type,super_operations等
register_filesystem 完成文件系统的注册,即将该fixfs_fs_type挂到全局链表file_systems上
kern_mount 调用fixfs_fs_type实现的fixfs_mount方法将文件系统挂载至fixfs_mnt供内核使用
fixfs_mount 通过sget来动态创建超级块sb并挂载至全局链表super_blocks,然后调用相关函数或fill_super完成sb的初始化
tip:对于特点的文件系统一般只进行注册,kern_mount这步是不存在的,挂载过程由系统调用mount来实现(?)
3.Linux特殊文件系统
Linux特殊文件系统包括rootfs, proc, sysfs, swap, tmpfs等pseudo filesystems
它们都是基于内存的文件系统,有其特殊的用途
- rootfs初始化
vfs_caches_init() –> mnt_init() –> init_rootfs(),init_mount_tree()
这两个函数创建一个虚拟的根文件系统(基于内存),后面会指向真正的根文件系统(基于block_dev)
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;
err = bdi_init(&ramfs_backing_dev_info);
if (err)
return err;
err = register_filesystem(&rootfs_fs_type); /* 注册rootfs */
if (err)
bdi_destroy(&ramfs_backing_dev_info);
return err;
}
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
struct mnt_namespace *ns;
struct path root;mnt = do_kern_mount("rootfs", 0, "rootfs", NULL); /* 挂载rootfs */
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; /* 将rootfs的mnt和dentry信息记录到init_task的fs结构中 */
get_mnt_ns(ns);root.mnt = mnt;
root.dentry = mnt->mnt_root;set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root); /* 将rootfs设置为根文件系统 */
}
上面两个函数执行完成后,rootfs的挂载点默认为"/",init_task进程的根目录和当前目录为"/"。
以上只是完成初始化,下面介绍实际的根文件系统挂载
在Linux中,允许实际的根文件系统存放在不同的地方,如硬盘分区,软盘,NFS或保存在ramdisk中,此时需要在内核中指定变量ROOT_DEV或由bootloader来传递root提供包含根文件系统的设备号
也可以将实际的根文件系统编进内核
实际根文件系统的挂载在函数prepare_namespace中,该函数必须在do_basic_setup之后
之所以在do_basic_setup之后,因为do_basic_setup完成了模块的加载(do_initcalls),也包括设备驱动程序,这样后面在具体设备上读取根文件系统才能成功。
在do_initcalls函数会执行rootfs_initcall,
对于initrd类型(initrd机制)会调用populate_rootfs,否则调用default_rootfs
populate_rootfs主要完成initrd的检测工作,检查并解压CPIO Initrd或Initramfs或Image-Initrd
对于initramfs和cpio-initrd的,都会将文件系统解压到根文件系统,此时将不会执行prepare_namespace
default_rootfs创建目录(/dev,/root)及节点(/dev/console)
prepare_namespace首先会解析内核参数root(由bootloader传递)并生成ROOT_DEV
然后尝试加载Image-Initrd(/initrd.image)
然后会尝试加载软盘0分区上的根文件系统
最后通过mount_root依次加载NFS,软盘1分区及块设备上的根文件系统
当成功加载一种根文件系统后,会进行根文件系统的切换
当实际根文件系统成功挂载后,init_post会执行一些清理工作并执行init程序切换至用户空间,