linux内核探秘4---文件系统(最简单的文件系统aufs)
2014-10-12 21:27
399 查看
最近一周报名的内核课程讲到了文件系统这一章,老师布置了2个作业:
1.复习文件系统中超级块的创建过程
2.复习文件系统中文件的创建过程
老师给了一个最简单的aufs文件系统的例子,里面注册了aufs, 然后调用kern_mount对文件系统进行挂载,然后创建了1个目录,3个文件,总体来说实现了最简单的
文件系统的功能,作业主要是为了熟悉超级块的创建(这也涉及到了mount的过程), 文件的创建(了解inode和dentry).
在做作业之前,先把课程视频学习了,然后网上找资料学习,这篇文章主要是根据网上的几篇比较牛的文章进行了整合,有些也是直接搬过来了,整合之后的过程更加清楚。
在基础知识之后,我也贴出了作业代码流程,有些代码还没怎么看懂(所以注释还没写)。。等我请教了别人之后,再来添加。
一、基本概念
1.一块磁盘(块设备),首先要按照某种文件系统(如 NTFS)格式进行格式化,然后才能在其上进行创建目录、保存文件等操作。在 Linux 中,有“安装(mount)”文件系统和“卸载(unmount)”文件系统的概念。一块经过格式化的“块设备”(不管是刚刚格式化完的,没有创建任何名录和文件;还是已经创建了目录和文件),只有先被“安装”,才能融入 Linux 的文件系统中,用户才可以在它上面进行正常的文件操作。
2. Linux 把目录或普通文件,统一看成“目录节点”。通常一个“目录节点”具有两个重要属性:名称以及磁盘上实际对应的数据。“符号链接”是一种特殊的目录节点,它只有一个名称,没有实际数据。这个名称指向一个实际的目录节点。
3.“接口结构”:在 内核代码中,经常可以看到一种结构,其成员全部是函数指针,例如:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
4. 虚拟文件系统
Linux 通过虚拟文件系统 (VFS) 来支持不同的具体的文件系统,那么 VFS 到底是什么?
从程序员的角度看,VFS 就是一套代码框架(framework),它将用户与具体的文件系统隔离开来,使得用户能够通过这套框架,以统一的接口在不同的具体的文件系统上进行操作。
这套框架包括:
l 为用户提供统一的文件和目录的操作接口,如 open, read, write
l 抽象出文件系统共有的一些结构,包括“目录节点”inode、“超级块”super_block 等。
l 面向具体的文件系统,定义一系列统一的操作“接口”, 如 file_operations, inode_operations, dentry_operation,具体的文件系统必须提供它们的实现。
l 提供一套机制,让具体的文件系统融入 VFS 框架中,包括文件系统的“注册”和“安装”
l 实现这套框架逻辑的核心代码
二、核心数据结构
1.inode 和 file_operations
先看一下文件在内存和磁盘上是如何描述的?每个文件至少有一个数据结构存放该文件的信息,例如大小,创建时间,修改时间、uid、gid 等,这个数据结构就是inode.
l inode 描述了一个目录节点物理上的属性.
l 本来inode中应该包含文件名称等信息,但是由于符号链接的存在,导致一个文件可能存在多个文件名称,所以把和文件名称相关的信息从inode中隔离出来,放入另外一个数据结构中dentry,由dentry结构中的d_inode指向对应的inode(所以寻找inode的过程实际上就是寻找dentry的过程). 所以一个文件可以有两个数据结构表示inode和dentry.
l 对于文件的操作,通过inode中的i_fop来指向file_operations(上面的一个数据结构)中的操作,不同的文件系统会实现具体的细节。
2. 目录节点入口(根据路径名寻找目标文件--简单版)
在Linux中目录也被作为文件看待,只是目录是一种比较特殊的文件。其特殊之处在于文件的内容是该目录中文件和子目录的dentry的描述符,通过这些dentry的描述符可以找到文件或子目录的dentry,进而找到相应的inode。
目录内容(文件和子目录dentry的描述符)---》文件或目录的dentry--->inode(文件信息)
下面我们看看如果根据绝对路径寻找一个文件/tmp/temp/abc的:
l 首先找到根文件系统的根目录文件(/)的 dentry 和 inode
l 由这个 inode 提供的操作接口 i_op->lookup(),找到下一层节点tmp的 dentry 和 inode
l 由 ‘tmp’ 的 inode 找到 ‘temp’ 的 dentry 和 inode
l 最后由 ‘temp’ 的 inode 找到 ‘abc’ 的 dentry 和 inode
通过相对路径找到文件/tmp/temp/abc的过程:
假如我们目前的工作目录为/tmp/temp/dir_a 中,比如我们通过拷贝命令拷贝该文件:cp ../abc ./ 如何通过相对路径寻找文件呢?
dentry这个数据结构的成员,其中有一个是d_parent,数据结构定义如下
struct dentry { 删除了无关的成员
struct dentry *d_parent; /* parent directory */
struct inode *d_inode; /* Where the name belongs to - NULL is * negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
}
d_parent指向了本目录的父目录的dentry,这样就在通过“..”时就是通过该指针找到的父目录dentry,找到父目录inode,进而找到父目录下的所有文件的信息。
Dentry , inode,和文件操作file_operations的关系:
3. Super_block和super_operations
一个存放在磁盘上的文件系统如 EXT2 等,在它的格式中通常包括一个“超级块”或者“控制块”的部分,用于从整体上描述文件系统,例如文件系统的大小、是否可读可写等等。
虚拟文件系统VFS中也通过“超级块”这种概念来描述文件系统整体的信息,对应的结构是 struct super_block。super_block 除了要记录文件大小、访问权限等信息外,更重要的是提供一个操作“接口”super_operations。
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*read_inode) (struct inode *);
void (*read_inode2) (struct inode *, void *) ;
void (*dirty_inode) (struct inode *);
void (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);
void (*delete_inode) (struct inode *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*sync_fs) (struct super_block *);
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);
struct dentry * (*fh_to_dentry)(struct super_block *sb, __u32 *fh, int len, int fhtype, int parent);
int (*dentry_to_fh)(struct dentry *, __u32 *fh, int *lenp, int need_parent);
int (*show_options)(struct seq_file *, struct vfsmount *);
};
三、安装文件系统
在此之前,先大概了解一下linux文件系统的结构
1.linux文件系统的组织形式
Linux中使用树来组织文件系统。整个文件系统构成了一颗树,这棵树以/为根。整个系统有且只有这一颗文件树。这棵树描述了文件系统的拓扑结构,没有任何文件系统的类型信息。
2.mount机制
linux使用mount机制扩展文件系统,使不同类型的文件系统可以挂载在系统的文件树的任何位置。mount机制使文件树有了类型属性,支持了不同类型的文件系统的挂载。
如图所示。
可以看到如果不考虑挂载点,整个文件系统就是一棵树,如果考虑了挂载点,这棵树原来是嫁接而成的,可以包含各种不同种类的文件系统。正如桥片扩展了总线一样,挂载点扩展了文件树,和扩展总线不同的是,任意目录都可以是挂载点,但是不是任意芯片都是可以作为桥的。(网桥/交换机扩展以太网也是一样的道理,和mount机制更加类似一些,因为你只需要插入一个多端口网卡就可以作为一个网桥了。)
3、mount机制的好处
mount可以屏蔽文件系统的类型,所有类型的文件系统共享一棵树,但是实现却可以不相同。用户进程可以使用相同的系统调用接口访问所有的文件系统,而不必在意访问的文件是什么类型的。mount机制使一棵文件树得以多样化,然而又可以向用户屏蔽这种多样化。这种多样化是通过vfs实现的。
4、单棵树组织+mount扩展
Linux的文件系统和磁盘并不绑定,文件系统就是一棵树,是一个虚拟的概念,没有介质,没有容量,没有读写规则,只有在挂载(mount)发生的时候,也就是具体的文件系统挂载的时候,某个挂载点才和介质建立联系,然而此时对于文件系统来讲仍然没有容量的概念,容量仅仅是挂载于此目录的磁盘设备的属性,如果磁盘空间满了,仍然可以通过在此磁盘的一个目录上mount一个新的文件系统来解决,而新的文件系统在一块新的磁盘上。
所以:
l 一个经过格式化的块设备,只有安装后,才能融入 Linux 的 VFS 之中。
l 安装一个文件系统,必须指定一个目录作为安装点。
l 一个设备可以同时被安装到多个目录上。
l 如果某个目录下原来有一些文件和子目录,一旦将一个设备安装到目录下后,则原有的文件和子目录消失。因为这个目录已经变成了一个安装点。
l 一个目录节点下可以同时安装多个设备。
安装一个文件系统,需要“被安装设备”, “安装点”(一个已经存在的目录节点)。可是文件系统要先安装后使用。因此,要使用 /mnt/win 这个“安装点”,必然要求它所在文件系统已也经被安装。也就是说,安装一个文件系统,需要另外一个文件系统已经被安装。
那么:最顶层的文件系统是如何被安装的?
最顶层文件系统的时候是被安装在“根安装点”上的,而根安装点不属于任何文件系统,它对应的 dentry 、inode 是由内核在初始化阶段凭空构造出来的。最顶层的文件系统叫做“根文件系统”。Linux 在启动的时候,要求用户必须指定一个“根设备”,内核在初始化阶段,将“根设备”安装到“根安装点”上,从而有了根文件系统。这样,文件系统才算准备就绪。此后,用户就可以通过 mount 命令来安装新的设备。(所以:由于linux的树形文件系统是完全抽象的,因此它不和任何介质进行绑定,仅存在于内核当中,内核只要起来,这个虚拟的树就存在了,只是此时只有树根,然而linux此时却可以挂载任意类型的文件系统到这个树根,这样就可以实现很方便的定制,linux可以在initrd中挂载任意文件系统到树根,这是因为内核和文件系统是分离的概念,内核启动并不依赖任何文件系统。)
2.mount设备(文件系统)
我们通过mount命令向Linux系统mount了一个设备。其实该命令触发了两个过程,一个是文件系统注册过程(当然,如果文件系统已注册过的话,就不需要该步骤了),另一个才是真正意义上的mount设备的过程。
文件系统注册过程
Linux内核是可加载的,许多模块式可选的,只有真正需要使用时才加载他们。文件系统注册过程就是把对应某类型文件系统相关的模块加载到内核,并创建相关的数据结构。每个文件系统模块都有一个初始化例程,它的作用就是VFS中进行注册,即填写一个叫做file_system_type的数据结构。所有已注册的文件系统的file_system_type结构形成一个链表,我们把这个链表称为注册链表。
每个设备在mount时都要搜索该注册链表,选择适合自己设备文件系统的一项,并从中取出read_super()函数获取设备的超级块(存储在具体设备上,记录存储设备各种信息的一个存储块),并解析其内容。因为每种类型文件系统的超级块的格式不同,并且各自有特定的信息,每种文件系统必须使用对应的解析函数,否则内核就因为不认识该文件系统而无法完成安装。这就是注册文件系统的意义所在。
struct file_system_type {
const char *name;
int fs_flags;
struct super_block *(*read_super) (struct super_block *, void *, int);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers;
};
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
if (!fs)
return -EINVAL;
if (fs->next)
return -EBUSY;
INIT_LIST_HEAD(&fs->fs_supers);
write_lock(&file_systems_lock);
p = find_filesystem(fs->name);
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
这个结构中最关键的就是 read_super() 这个函数指针,在文件系统向内核注册后,需要read_super()来为文件系统创建并设置 super_block 。因为前面说过,在安装一个文件系统的时候,需要为“被安装设备”创建一个 super_block,并设置它。而不同的具体的文件系统的 super_block 有自己特定的信息,因此要求具体的文件系统首先向内核注册,并提供 read_super() 的实现。
2.安装连接件 vfsmount
将安装点 和安装设备连接起来的mount过程是vfsmount来完成的
1、 创建一个设备的 vfsmount
2、为“被安装设备”创建一个 super_block,并由具体的文件系统来设置这个 super_block。在super_block中包含了该类型设备操作的各种接口的结构成员s_op,类型为super_operations。
3、 为被安装设备的根目录节点创建 dentry
4、 为被安装设备的根目录节点创建 inode, 并由 super_block->s_op->read_inode() 来设置此 inode
5、 将 super_block 与“被安装设备“根目录节点 dentry 关联起来
6、 将 super_block中的s_root与“被安装设备”的根目录节点 dentry 关联起来
如上图所示,在linux2.4.30中有三条链表,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks,挂接点结构vfsmount的链表头为vfsmntlist。
在Linux3.3.5中只有两条链表结构,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks。数据结构vfsmount 的结构定义还存在,但已经没有了mnt_list成员了。
所以从上面的3-5知道,对于安装点目录来说,它比较特殊,一方面它在原来的文件系统中是一个普通的目录文件,而在即将挂载的文件系统而言,它又是一个挂载根目录,所以它的dentry中有d_sb(超级块成员,即文件系统),d_sb中有“struct dentry
*s_root;”,s_root就是指向“/dev/sda1挂载设备(文件系统)”的dentry---即新的挂载目录的dentry。
3.根据路径名寻找目标节点的 dentry(复杂版,含有mount点)----看上图
例如要打开 /mnt/win/dir1/abc 这个文件,其中win和dir1是mount点
l 首先找到根文件系统的根目录节点 dentry 和 inode
l inode 提供的操作接口 i_op->lookup(),找到下一层节点 mnt的 dentry 和 inode
l 由 ‘mnt’ 的 inode 找到 ‘win’ 的 dentry 和 inode
l 由于 ‘win’ 是个“安装点”,因此需要找到“被安装设备”/dev/sda1 根目录节点的 dentry 和 inode。需要找到vfsmountB来完成任务。“win”的dentry中有d_sb(超级块成员),d_sb中有“struct dentry
*s_root;”,s_root就是指向“/dev/sda1”的dentry。
l 然后由 /dev/sda1 根目录节点的 inode 负责找到下一层节点dir1的 dentry 和 inode
l 由于 dir1 是个“安装点”,因此需要借助dir1的dentry->d_sb->s_root找到 /dev/sda2 的根目录节点 dentry 和 inode
l 最后由这个 inode 负责找到 ‘abc’ 的 dentry 和 inode
可以看到,整个寻找过程是一个递归的过程。
完成寻找后,内存中结构如下,其中红色线条是寻找目标节点的路径
四、进程打开文件
一个文件可以被多次打开,并且多个进程对一个文件的访问权限可能不同,因此打开方式就会不同(只读、读写、可执行)。而dentry 和 inode 只能描述一个物理的文件,无法描述“打开”这个概念。因此有必要引入 file 结构,来描述一个“被打开的文件”。每打开一个文件,就创建一个 file 结构。
file 结构中包含以下信息:
打开这个文件的进程的 uid,pid
打开的方式
读写的方式
当前在文件中的位置
实际上,打开文件的过程正是建立file, dentry, inode 之间的关联的过程。如下图
2.文件的读写
文件一旦被打开,数据结构之间的关系已经建立,后面对文件的读写以及其它操作都变得很简单。就是根据 fd 找到 file 结构,然后找到 dentry 和 inode,最后通过 inode->i_fop 中对应的函数进行具体的读写等操作即可。
2.一个进程可以打开多个文件,每打开一个文件,创建一个 file 结构。所有的 file 结构的指针保存在一个数组中。而文件描述符正是这个数组的下标。例如fopen函数返回一个文件描述符fd, 用户仅仅看到一个“整数”,实际底层对应着的是 file, dentry, inode 等复杂的数据结构。
files_struct 用于管理这个“打开文件”表(一个files*的指针数组)。
struct files_struct {
atomic_t count;
rwlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; // 这个就是“打开文件”表
};
task_struct 中通过成员 files 与 files_struct 关联起来。
task_struct 中与文件系统相关的还有另外一个成员 fs,它指向一个 fs_struct 。
(个人觉得root和pwd主要是跟绝对路径和相对路径查找时候提供最初dentry)
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
其中:
root 指向此进程的“根目录”,通常就是“根文件系统”的根目录 dentry
pwd 指向此进程当前所在目录的 dentry
rootmnt :指向“安装”根文件系统时创建的那个 vfsmount
pwdmnt:指向“安装”当前工作目录所在文件系统时创建的那个 vfsmount
这两个域用于初始化 nameidata 结构。
Ps: 通过 task_struct->fs->root,就可以找到“根文件系统”的根目录 dentry
进程与文件系统关系图:
1. 复习文件系统超级块的创建过程
首先先看下超级块的定义:
/*
*超级块对象有super_block结构体表示,定义在文件linux/fs.h中
*
*/
struct super_block {
struct list_head s_list; /* 指向超级块链表的指针 */
dev_t s_dev; /* 设备标识符 */
unsigned long s_blocksize; /* 以字节为单位的块大小 */
unsigned long s_old_blocksize; /* 以位为单位的旧的块大小 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned char s_dirt; /* 修改脏标志 */
unsigned long long s_maxbytes; /* 文件大小上限 */
struct file_system_type s_type; /* 文件系统类型 */
struct super_operations s_op; /* 超级块方法 */
struct dquot_operations *dq_op; /* 磁盘限额方法 */
struct quotactl_ops *s_qcop; /* 限额控制方法 */
struct export_operations *s_export_op; /* 到处方法 */
unsigned long s_flags; /* 挂载标志 */
unsigned long s_magic; /* 文件系统魔数 */
struct dentry *s_root; /* 目录挂载点 */
struct rw_semaphore s_umount; /* 卸载信号量 */
struct semaphore s_lock; /* 超级块信号量 */
int s_count; /* 引用计数 */
int s_syncing; /* 文件系统同步标志 */
int s_need_sync_fs; /* 尚未同步标志 */
atomic_t s_active; /* 活动引用计数 */
void *s_security; /* 安全模块 */
struct list_head s_dirty; /* 脏节点链表 */
struct list_head s_io; /* 回写链表 */
struct hlist_head s_anon; /* 匿名目录项 */
struct list_head s_files; /* 被分配文件链表 */
struct block_device *s_bdev; /* 相关块设备 */
struct list_head s_instances; /* 该类型文件系统的超级块链表 */
struct quota_info s_dquot; /* 限额相关选项 */
char s_id[32]; /* 文本名字 */
void *s_fs_info; /* 文件系统特殊信号 */
struct semaphore s_vfs_rename_sem; /* 重命名信号量 */
};
Super_block是跟文件系统有关的,每个文件系统都会创建这样一个超级块,记录设备有关的信息,比如设备号,文件系统类型,super_block是挂载文件系统的时候创建分配的,所以里面也包含了跟mount相关的信息,比如目录挂载点s_root, 挂载标志等,里面有两个比较重要的双向链表需要注意:
Struct list_head s_list; ----这个链表连接了系统中所有文件系统的超级块
Struct list_head s_instances; ----这个链表连接了某个特定文件系统的超级块
在超级块创建后初始化的时候,需要将super_block的这两个成员添加add_list()到这 super_blocks和type->fs_supers这两个链表里面。
从下面这个图可以清楚地表示出不同链表与super_block 和 file_system_type等的关系:
File_sytem_type里面的fs_super 链表是该文件系统类型的所有super_block链表,每个节点是super_block的s_instance
Super_blcok里面的s_type又指示了是哪种文件系统类型
在mount时候创建的vfsmount结构里面mnt_sb又指向了对应的super_block结构。
实际上mount的过程是:创建vfsmount结构体,创建super_block(首先在该文件系统类型file_system_type里面找到fs_supers,一个个找s_instances, 通过list_for_entry找到每个super_block,通过super_block里面的bdev来判断是否跟现在需要mount的设备一样,如果一样的话,就直接使用该super_block,如果没有找到的话,就新建super_block), 创建完之后给super_block赋值,将设备和mount相关的信息填充进去,同时将s_type赋值为该文件系统类型,然后将s_instance添加到fs_super链表里,将s_list添加到super_blocks链表里面。
然后我们再来详细看看创建超级块的过程,这要从文件系统初始化的时候说起。
从
static int __init aufs_init(void)
{
int retval;
struct dentry *pslot;
retval = register_filesystem(&au_fs_type);
if (!retval) {
aufs_mount = kern_mount(&au_fs_type);
if (IS_ERR(aufs_mount)) {
printk(KERN_ERR "aufs: could not mount!\n");
unregister_filesystem(&au_fs_type);
return retval;
}
}
pslot = aufs_create_dir("woman star",NULL);
aufs_create_file("lbb", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("fbb", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("ljl", S_IFREG | S_IRUGO, pslot, NULL, NULL);
pslot = aufs_create_dir("man star",NULL);
aufs_create_file("ldh", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("lcw", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("jw", S_IFREG | S_IRUGO, pslot, NULL, NULL);
return retval;
}
首先需要注册文件系统,在系统中注册文件系统,就是在file_system这个结构体链表里面(内核维护)查找,如果没有的话,就把这个文件系统类型添加到这个链表里面,同时将fs_super初始化为空。
然后kern_mount来完成文件系统mount过程:
在2.6.18里面是这样的过程,其他版本不太一样,但是过程都是一样的:
struct vfsmount *kern_mount(struct file_system_type *type)
{
return vfs_kern_mount(type, 0, type->name, NULL);
}EXPORT_SYMBOL(kern_mount);
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct vfsmount *mnt;
char *secdata = NULL;
mnt = alloc_vfsmnt(name);
if (!mnt)
goto out;
if (data) {
secdata = alloc_secdata();
if (!secdata)
goto out_mnt;
error = security_sb_copy_data(type, data, secdata);
if (error)
goto out_free_secdata;
}
error = type->get_sb(type, flags, name, data, mnt);
if (error < 0)
goto out_free_secdata;
error = security_sb_kern_mount(mnt->mnt_sb, secdata);
if (error)
goto out_sb;
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
up_write(&mnt->mnt_sb->s_umount);
free_secdata(secdata);
return mnt;
}
1.alloc_vfsmnt()是分配并且初始化跟该设备相关的vfsmount结构体,vfsmount是mount过程完成的关键,它将设备和目录连接起来,vfsmount里面的成员也基本上都是一些mount点,mount父目录,超级块指针(通过它可以指向对应的设备的super_block),递归的mount目录等等。
struct vfsmount {
struct list_head mnt_hash;
struct vfsmount *mnt_parent; /* fs we are mounted on */
struct dentry *mnt_mountpoint; /* dentry of mountpoint */
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
atomic_t mnt_count;
int mnt_flags;
int mnt_expiry_mark; /* true if marked for expiry */
char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
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 vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
struct namespace *mnt_namespace; /* containing namespace */
int mnt_pinned;
};
2. type->get_sb(type, flags, name, data, mnt); 这里调用了file_system_type里面的成员.get_sb=get_sb。
在这个函数里面主要是调用了:
get_sb_single(fs_type, flags, data, fill_super, mnt);
int get_sb_single(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int),
struct vfsmount *mnt)
{
struct super_block *s;
int error;
s = sget(fs_type, compare_single, set_anon_super, NULL); //获得超级块
if (IS_ERR(s))
return PTR_ERR(s);
if (!s->s_root) { //如果超级块里对应的目录为空,那么就给它填充必要的信息(创建dentry和inode)
s->s_flags = flags;
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
up_write(&s->s_umount);
deactivate_super(s);
return error;
}
s->s_flags |= MS_ACTIVE;
}
do_remount_sb(s, flags, data, 0);
return simple_set_mnt(mnt, s);
}EXPORT_SYMBOL(get_sb_single)
Sget()函数主要是返回超级块的:
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
void *data)
{
struct super_block *s = NULL;
struct list_head *p;
int err;
retry:
spin_lock(&sb_lock);
if (test) list_for_each(p, &type->fs_supers) { // 遍历该文件系统类型上的超级块s_instance
struct super_block *old;
old = list_entry(p, struct super_block, s_instances); //通过s_instance找到超级块
if (!test(old, data)) //判断该超级块里面的设备是否与待mount的设备一样
continue;
if (!grab_super(old))
goto retry;
if (s)
destroy_super(s);
return old;
}
if (!s) {
spin_unlock(&sb_lock);
s = alloc_super(type);
if (!s)
return ERR_PTR(-ENOMEM);
goto retry;
}
err = set(s, data);
if (err) {
spin_unlock(&sb_lock);
destroy_super(s);
return ERR_PTR(err);
}
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id));
list_add_tail(&s->s_list, &super_blocks);
list_add(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
return s;
}
http://blog.chinaunix.net/uid-12567959-id-160991.html 这篇文章分配超级块过程分析的很清楚
2.复习文件系统中创建文件的过程
在课程里面讲到:创建目录和文件总结:创建dentry 、创建inode
一个文件是由inode和dentry表示的,从aufs这个文件中创建文件ldn为例子,从aufu_create_file()入手:
struct dentry *aufs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
struct file_operations *fops)
{
struct dentry *dentry = NULL;
int error;
printk("aufs: creating file '%s'\n",name);
error = aufs_create_by_name(name, mode, parent, &dentry);
if (error) {
dentry = NULL;
goto exit;
}
if (dentry->d_inode) {
if (data)
dentry->d_inode->u.generic_ip = data;
if (fops)
dentry->d_inode->i_fop = fops;
}
exit:
return dentry;
}
可以知道是通过aufs_create_by_name()来创建inode,通过传入了dentry的地址,从前面的知识可知道,应该是给dentry赋值,将文件名字之类的信息存进去,同时将d_inode指向文件的inode.
static int aufs_create_by_name(const char *name, mode_t mode,
struct dentry *parent,
struct dentry **dentry)
{
int error = 0;
/* If the parent is not specified, we create it in the root.
* We need the root dentry to do this, which is in the super
* block. A pointer to that is in the struct vfsmount that we
* have around.
*/
if (!parent ) {
if (aufs_mount && aufs_mount->mnt_sb) {
parent = aufs_mount->mnt_sb->s_root;
}
}
if (!parent) {
printk("Ah! can not find a parent!\n");
return -EFAULT;
}
*dentry = NULL;
mutex_lock(&parent->d_inode->i_mutex);
*dentry = lookup_one_len(name, parent, strlen(name));
if (!IS_ERR(dentry)) {
if ((mode & S_IFMT) == S_IFDIR)
error = aufs_mkdir(parent->d_inode, *dentry, mode);
else
error = aufs_create(parent->d_inode, *dentry, mode);
} else
error = PTR_ERR(dentry);
mutex_unlock(&parent->d_inode->i_mutex);
return error;
}
Lookup_one_len()在linux/fs/iname.c里面,通过文件名字,长度的判断是否合法,然后根据传入的根目录dentry来确定dentry.
struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
{
struct qstr this;
unsigned int c;
int err;
WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
this.name = name;
this.len = len;
this.hash = full_name_hash(name, len);
if (!len)
return ERR_PTR(-EACCES);
if (unlikely(name[0] == '.')) {
if (len < 2 || (len == 2 && name[1] == '.'))
return ERR_PTR(-EACCES);
}
while (len--) {
c = *(const unsigned char *)name++;
if (c == '/' || c == '\0')
return ERR_PTR(-EACCES);
}
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
if (base->d_flags & DCACHE_OP_HASH) {
int err = base->d_op->d_hash(base, &this);
if (err < 0)
return ERR_PTR(err);
}
err = inode_permission(base->d_inode, MAY_EXEC);
if (err)
return ERR_PTR(err);
return __lookup_hash(&this, base, 0);
}
EXPORT_SYMBOL(lookup_one_len);
参考文章:http://itlab.idcquan.com/linux/administer/784553.html inode和超级块详解
http://blog.chinaunix.net/uid-12567959-id-160990.html 挂载普通文件系统
http://blog.sina.com.cn/s/blog_70b6ff960101am5z.html 路径名解析过程中使用的各种hash函数
http://dato0123.iteye.com/blog/1260053 sysfs创建目录
/article/1349552.html 初窥ext2/ext3文件系统
http://blog.chinaunix.net/uid-27052262-id-3259907.html
http://blog.csdn.net/rstevens/article/details/1824785
http://www.2cto.com/os/201110/108845.html
/article/1394935.html
/article/2693535.html
1.复习文件系统中超级块的创建过程
2.复习文件系统中文件的创建过程
老师给了一个最简单的aufs文件系统的例子,里面注册了aufs, 然后调用kern_mount对文件系统进行挂载,然后创建了1个目录,3个文件,总体来说实现了最简单的
文件系统的功能,作业主要是为了熟悉超级块的创建(这也涉及到了mount的过程), 文件的创建(了解inode和dentry).
在做作业之前,先把课程视频学习了,然后网上找资料学习,这篇文章主要是根据网上的几篇比较牛的文章进行了整合,有些也是直接搬过来了,整合之后的过程更加清楚。
在基础知识之后,我也贴出了作业代码流程,有些代码还没怎么看懂(所以注释还没写)。。等我请教了别人之后,再来添加。
一、基本概念
1.一块磁盘(块设备),首先要按照某种文件系统(如 NTFS)格式进行格式化,然后才能在其上进行创建目录、保存文件等操作。在 Linux 中,有“安装(mount)”文件系统和“卸载(unmount)”文件系统的概念。一块经过格式化的“块设备”(不管是刚刚格式化完的,没有创建任何名录和文件;还是已经创建了目录和文件),只有先被“安装”,才能融入 Linux 的文件系统中,用户才可以在它上面进行正常的文件操作。
2. Linux 把目录或普通文件,统一看成“目录节点”。通常一个“目录节点”具有两个重要属性:名称以及磁盘上实际对应的数据。“符号链接”是一种特殊的目录节点,它只有一个名称,没有实际数据。这个名称指向一个实际的目录节点。
3.“接口结构”:在 内核代码中,经常可以看到一种结构,其成员全部是函数指针,例如:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};
4. 虚拟文件系统
Linux 通过虚拟文件系统 (VFS) 来支持不同的具体的文件系统,那么 VFS 到底是什么?
从程序员的角度看,VFS 就是一套代码框架(framework),它将用户与具体的文件系统隔离开来,使得用户能够通过这套框架,以统一的接口在不同的具体的文件系统上进行操作。
这套框架包括:
l 为用户提供统一的文件和目录的操作接口,如 open, read, write
l 抽象出文件系统共有的一些结构,包括“目录节点”inode、“超级块”super_block 等。
l 面向具体的文件系统,定义一系列统一的操作“接口”, 如 file_operations, inode_operations, dentry_operation,具体的文件系统必须提供它们的实现。
l 提供一套机制,让具体的文件系统融入 VFS 框架中,包括文件系统的“注册”和“安装”
l 实现这套框架逻辑的核心代码
二、核心数据结构
1.inode 和 file_operations
先看一下文件在内存和磁盘上是如何描述的?每个文件至少有一个数据结构存放该文件的信息,例如大小,创建时间,修改时间、uid、gid 等,这个数据结构就是inode.
l inode 描述了一个目录节点物理上的属性.
l 本来inode中应该包含文件名称等信息,但是由于符号链接的存在,导致一个文件可能存在多个文件名称,所以把和文件名称相关的信息从inode中隔离出来,放入另外一个数据结构中dentry,由dentry结构中的d_inode指向对应的inode(所以寻找inode的过程实际上就是寻找dentry的过程). 所以一个文件可以有两个数据结构表示inode和dentry.
l 对于文件的操作,通过inode中的i_fop来指向file_operations(上面的一个数据结构)中的操作,不同的文件系统会实现具体的细节。
2. 目录节点入口(根据路径名寻找目标文件--简单版)
在Linux中目录也被作为文件看待,只是目录是一种比较特殊的文件。其特殊之处在于文件的内容是该目录中文件和子目录的dentry的描述符,通过这些dentry的描述符可以找到文件或子目录的dentry,进而找到相应的inode。
目录内容(文件和子目录dentry的描述符)---》文件或目录的dentry--->inode(文件信息)
下面我们看看如果根据绝对路径寻找一个文件/tmp/temp/abc的:
l 首先找到根文件系统的根目录文件(/)的 dentry 和 inode
l 由这个 inode 提供的操作接口 i_op->lookup(),找到下一层节点tmp的 dentry 和 inode
l 由 ‘tmp’ 的 inode 找到 ‘temp’ 的 dentry 和 inode
l 最后由 ‘temp’ 的 inode 找到 ‘abc’ 的 dentry 和 inode
通过相对路径找到文件/tmp/temp/abc的过程:
假如我们目前的工作目录为/tmp/temp/dir_a 中,比如我们通过拷贝命令拷贝该文件:cp ../abc ./ 如何通过相对路径寻找文件呢?
dentry这个数据结构的成员,其中有一个是d_parent,数据结构定义如下
struct dentry { 删除了无关的成员
struct dentry *d_parent; /* parent directory */
struct inode *d_inode; /* Where the name belongs to - NULL is * negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */
}
d_parent指向了本目录的父目录的dentry,这样就在通过“..”时就是通过该指针找到的父目录dentry,找到父目录inode,进而找到父目录下的所有文件的信息。
Dentry , inode,和文件操作file_operations的关系:
3. Super_block和super_operations
一个存放在磁盘上的文件系统如 EXT2 等,在它的格式中通常包括一个“超级块”或者“控制块”的部分,用于从整体上描述文件系统,例如文件系统的大小、是否可读可写等等。
虚拟文件系统VFS中也通过“超级块”这种概念来描述文件系统整体的信息,对应的结构是 struct super_block。super_block 除了要记录文件大小、访问权限等信息外,更重要的是提供一个操作“接口”super_operations。
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*read_inode) (struct inode *);
void (*read_inode2) (struct inode *, void *) ;
void (*dirty_inode) (struct inode *);
void (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);
void (*delete_inode) (struct inode *);
void (*put_super) (struct super_block *);
void (*write_super) (struct super_block *);
int (*sync_fs) (struct super_block *);
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*clear_inode) (struct inode *);
void (*umount_begin) (struct super_block *);
struct dentry * (*fh_to_dentry)(struct super_block *sb, __u32 *fh, int len, int fhtype, int parent);
int (*dentry_to_fh)(struct dentry *, __u32 *fh, int *lenp, int need_parent);
int (*show_options)(struct seq_file *, struct vfsmount *);
};
三、安装文件系统
在此之前,先大概了解一下linux文件系统的结构
1.linux文件系统的组织形式
Linux中使用树来组织文件系统。整个文件系统构成了一颗树,这棵树以/为根。整个系统有且只有这一颗文件树。这棵树描述了文件系统的拓扑结构,没有任何文件系统的类型信息。
2.mount机制
linux使用mount机制扩展文件系统,使不同类型的文件系统可以挂载在系统的文件树的任何位置。mount机制使文件树有了类型属性,支持了不同类型的文件系统的挂载。
如图所示。
可以看到如果不考虑挂载点,整个文件系统就是一棵树,如果考虑了挂载点,这棵树原来是嫁接而成的,可以包含各种不同种类的文件系统。正如桥片扩展了总线一样,挂载点扩展了文件树,和扩展总线不同的是,任意目录都可以是挂载点,但是不是任意芯片都是可以作为桥的。(网桥/交换机扩展以太网也是一样的道理,和mount机制更加类似一些,因为你只需要插入一个多端口网卡就可以作为一个网桥了。)
3、mount机制的好处
mount可以屏蔽文件系统的类型,所有类型的文件系统共享一棵树,但是实现却可以不相同。用户进程可以使用相同的系统调用接口访问所有的文件系统,而不必在意访问的文件是什么类型的。mount机制使一棵文件树得以多样化,然而又可以向用户屏蔽这种多样化。这种多样化是通过vfs实现的。
4、单棵树组织+mount扩展
Linux的文件系统和磁盘并不绑定,文件系统就是一棵树,是一个虚拟的概念,没有介质,没有容量,没有读写规则,只有在挂载(mount)发生的时候,也就是具体的文件系统挂载的时候,某个挂载点才和介质建立联系,然而此时对于文件系统来讲仍然没有容量的概念,容量仅仅是挂载于此目录的磁盘设备的属性,如果磁盘空间满了,仍然可以通过在此磁盘的一个目录上mount一个新的文件系统来解决,而新的文件系统在一块新的磁盘上。
所以:
l 一个经过格式化的块设备,只有安装后,才能融入 Linux 的 VFS 之中。
l 安装一个文件系统,必须指定一个目录作为安装点。
l 一个设备可以同时被安装到多个目录上。
l 如果某个目录下原来有一些文件和子目录,一旦将一个设备安装到目录下后,则原有的文件和子目录消失。因为这个目录已经变成了一个安装点。
l 一个目录节点下可以同时安装多个设备。
安装一个文件系统,需要“被安装设备”, “安装点”(一个已经存在的目录节点)。可是文件系统要先安装后使用。因此,要使用 /mnt/win 这个“安装点”,必然要求它所在文件系统已也经被安装。也就是说,安装一个文件系统,需要另外一个文件系统已经被安装。
那么:最顶层的文件系统是如何被安装的?
最顶层文件系统的时候是被安装在“根安装点”上的,而根安装点不属于任何文件系统,它对应的 dentry 、inode 是由内核在初始化阶段凭空构造出来的。最顶层的文件系统叫做“根文件系统”。Linux 在启动的时候,要求用户必须指定一个“根设备”,内核在初始化阶段,将“根设备”安装到“根安装点”上,从而有了根文件系统。这样,文件系统才算准备就绪。此后,用户就可以通过 mount 命令来安装新的设备。(所以:由于linux的树形文件系统是完全抽象的,因此它不和任何介质进行绑定,仅存在于内核当中,内核只要起来,这个虚拟的树就存在了,只是此时只有树根,然而linux此时却可以挂载任意类型的文件系统到这个树根,这样就可以实现很方便的定制,linux可以在initrd中挂载任意文件系统到树根,这是因为内核和文件系统是分离的概念,内核启动并不依赖任何文件系统。)
2.mount设备(文件系统)
我们通过mount命令向Linux系统mount了一个设备。其实该命令触发了两个过程,一个是文件系统注册过程(当然,如果文件系统已注册过的话,就不需要该步骤了),另一个才是真正意义上的mount设备的过程。
文件系统注册过程
Linux内核是可加载的,许多模块式可选的,只有真正需要使用时才加载他们。文件系统注册过程就是把对应某类型文件系统相关的模块加载到内核,并创建相关的数据结构。每个文件系统模块都有一个初始化例程,它的作用就是VFS中进行注册,即填写一个叫做file_system_type的数据结构。所有已注册的文件系统的file_system_type结构形成一个链表,我们把这个链表称为注册链表。
每个设备在mount时都要搜索该注册链表,选择适合自己设备文件系统的一项,并从中取出read_super()函数获取设备的超级块(存储在具体设备上,记录存储设备各种信息的一个存储块),并解析其内容。因为每种类型文件系统的超级块的格式不同,并且各自有特定的信息,每种文件系统必须使用对应的解析函数,否则内核就因为不认识该文件系统而无法完成安装。这就是注册文件系统的意义所在。
struct file_system_type {
const char *name;
int fs_flags;
struct super_block *(*read_super) (struct super_block *, void *, int);
struct module *owner;
struct file_system_type * next;
struct list_head fs_supers;
};
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
if (!fs)
return -EINVAL;
if (fs->next)
return -EBUSY;
INIT_LIST_HEAD(&fs->fs_supers);
write_lock(&file_systems_lock);
p = find_filesystem(fs->name);
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
这个结构中最关键的就是 read_super() 这个函数指针,在文件系统向内核注册后,需要read_super()来为文件系统创建并设置 super_block 。因为前面说过,在安装一个文件系统的时候,需要为“被安装设备”创建一个 super_block,并设置它。而不同的具体的文件系统的 super_block 有自己特定的信息,因此要求具体的文件系统首先向内核注册,并提供 read_super() 的实现。
2.安装连接件 vfsmount
将安装点 和安装设备连接起来的mount过程是vfsmount来完成的
1、 创建一个设备的 vfsmount
2、为“被安装设备”创建一个 super_block,并由具体的文件系统来设置这个 super_block。在super_block中包含了该类型设备操作的各种接口的结构成员s_op,类型为super_operations。
3、 为被安装设备的根目录节点创建 dentry
4、 为被安装设备的根目录节点创建 inode, 并由 super_block->s_op->read_inode() 来设置此 inode
5、 将 super_block 与“被安装设备“根目录节点 dentry 关联起来
6、 将 super_block中的s_root与“被安装设备”的根目录节点 dentry 关联起来
如上图所示,在linux2.4.30中有三条链表,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks,挂接点结构vfsmount的链表头为vfsmntlist。
在Linux3.3.5中只有两条链表结构,文件系统类型结构file_system_type的链表头为file_systems,超级块结构super_block的链表头为super_blocks。数据结构vfsmount 的结构定义还存在,但已经没有了mnt_list成员了。
所以从上面的3-5知道,对于安装点目录来说,它比较特殊,一方面它在原来的文件系统中是一个普通的目录文件,而在即将挂载的文件系统而言,它又是一个挂载根目录,所以它的dentry中有d_sb(超级块成员,即文件系统),d_sb中有“struct dentry
*s_root;”,s_root就是指向“/dev/sda1挂载设备(文件系统)”的dentry---即新的挂载目录的dentry。
3.根据路径名寻找目标节点的 dentry(复杂版,含有mount点)----看上图
例如要打开 /mnt/win/dir1/abc 这个文件,其中win和dir1是mount点
l 首先找到根文件系统的根目录节点 dentry 和 inode
l inode 提供的操作接口 i_op->lookup(),找到下一层节点 mnt的 dentry 和 inode
l 由 ‘mnt’ 的 inode 找到 ‘win’ 的 dentry 和 inode
l 由于 ‘win’ 是个“安装点”,因此需要找到“被安装设备”/dev/sda1 根目录节点的 dentry 和 inode。需要找到vfsmountB来完成任务。“win”的dentry中有d_sb(超级块成员),d_sb中有“struct dentry
*s_root;”,s_root就是指向“/dev/sda1”的dentry。
l 然后由 /dev/sda1 根目录节点的 inode 负责找到下一层节点dir1的 dentry 和 inode
l 由于 dir1 是个“安装点”,因此需要借助dir1的dentry->d_sb->s_root找到 /dev/sda2 的根目录节点 dentry 和 inode
l 最后由这个 inode 负责找到 ‘abc’ 的 dentry 和 inode
可以看到,整个寻找过程是一个递归的过程。
完成寻找后,内存中结构如下,其中红色线条是寻找目标节点的路径
四、进程打开文件
一个文件可以被多次打开,并且多个进程对一个文件的访问权限可能不同,因此打开方式就会不同(只读、读写、可执行)。而dentry 和 inode 只能描述一个物理的文件,无法描述“打开”这个概念。因此有必要引入 file 结构,来描述一个“被打开的文件”。每打开一个文件,就创建一个 file 结构。
file 结构中包含以下信息:
打开这个文件的进程的 uid,pid
打开的方式
读写的方式
当前在文件中的位置
实际上,打开文件的过程正是建立file, dentry, inode 之间的关联的过程。如下图
2.文件的读写
文件一旦被打开,数据结构之间的关系已经建立,后面对文件的读写以及其它操作都变得很简单。就是根据 fd 找到 file 结构,然后找到 dentry 和 inode,最后通过 inode->i_fop 中对应的函数进行具体的读写等操作即可。
2.一个进程可以打开多个文件,每打开一个文件,创建一个 file 结构。所有的 file 结构的指针保存在一个数组中。而文件描述符正是这个数组的下标。例如fopen函数返回一个文件描述符fd, 用户仅仅看到一个“整数”,实际底层对应着的是 file, dentry, inode 等复杂的数据结构。
files_struct 用于管理这个“打开文件”表(一个files*的指针数组)。
struct files_struct {
atomic_t count;
rwlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; // 这个就是“打开文件”表
};
task_struct 中通过成员 files 与 files_struct 关联起来。
task_struct 中与文件系统相关的还有另外一个成员 fs,它指向一个 fs_struct 。
(个人觉得root和pwd主要是跟绝对路径和相对路径查找时候提供最初dentry)
struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
其中:
root 指向此进程的“根目录”,通常就是“根文件系统”的根目录 dentry
pwd 指向此进程当前所在目录的 dentry
rootmnt :指向“安装”根文件系统时创建的那个 vfsmount
pwdmnt:指向“安装”当前工作目录所在文件系统时创建的那个 vfsmount
这两个域用于初始化 nameidata 结构。
Ps: 通过 task_struct->fs->root,就可以找到“根文件系统”的根目录 dentry
进程与文件系统关系图:
1. 复习文件系统超级块的创建过程
首先先看下超级块的定义:
/*
*超级块对象有super_block结构体表示,定义在文件linux/fs.h中
*
*/
struct super_block {
struct list_head s_list; /* 指向超级块链表的指针 */
dev_t s_dev; /* 设备标识符 */
unsigned long s_blocksize; /* 以字节为单位的块大小 */
unsigned long s_old_blocksize; /* 以位为单位的旧的块大小 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned char s_dirt; /* 修改脏标志 */
unsigned long long s_maxbytes; /* 文件大小上限 */
struct file_system_type s_type; /* 文件系统类型 */
struct super_operations s_op; /* 超级块方法 */
struct dquot_operations *dq_op; /* 磁盘限额方法 */
struct quotactl_ops *s_qcop; /* 限额控制方法 */
struct export_operations *s_export_op; /* 到处方法 */
unsigned long s_flags; /* 挂载标志 */
unsigned long s_magic; /* 文件系统魔数 */
struct dentry *s_root; /* 目录挂载点 */
struct rw_semaphore s_umount; /* 卸载信号量 */
struct semaphore s_lock; /* 超级块信号量 */
int s_count; /* 引用计数 */
int s_syncing; /* 文件系统同步标志 */
int s_need_sync_fs; /* 尚未同步标志 */
atomic_t s_active; /* 活动引用计数 */
void *s_security; /* 安全模块 */
struct list_head s_dirty; /* 脏节点链表 */
struct list_head s_io; /* 回写链表 */
struct hlist_head s_anon; /* 匿名目录项 */
struct list_head s_files; /* 被分配文件链表 */
struct block_device *s_bdev; /* 相关块设备 */
struct list_head s_instances; /* 该类型文件系统的超级块链表 */
struct quota_info s_dquot; /* 限额相关选项 */
char s_id[32]; /* 文本名字 */
void *s_fs_info; /* 文件系统特殊信号 */
struct semaphore s_vfs_rename_sem; /* 重命名信号量 */
};
Super_block是跟文件系统有关的,每个文件系统都会创建这样一个超级块,记录设备有关的信息,比如设备号,文件系统类型,super_block是挂载文件系统的时候创建分配的,所以里面也包含了跟mount相关的信息,比如目录挂载点s_root, 挂载标志等,里面有两个比较重要的双向链表需要注意:
Struct list_head s_list; ----这个链表连接了系统中所有文件系统的超级块
Struct list_head s_instances; ----这个链表连接了某个特定文件系统的超级块
在超级块创建后初始化的时候,需要将super_block的这两个成员添加add_list()到这 super_blocks和type->fs_supers这两个链表里面。
从下面这个图可以清楚地表示出不同链表与super_block 和 file_system_type等的关系:
File_sytem_type里面的fs_super 链表是该文件系统类型的所有super_block链表,每个节点是super_block的s_instance
Super_blcok里面的s_type又指示了是哪种文件系统类型
在mount时候创建的vfsmount结构里面mnt_sb又指向了对应的super_block结构。
实际上mount的过程是:创建vfsmount结构体,创建super_block(首先在该文件系统类型file_system_type里面找到fs_supers,一个个找s_instances, 通过list_for_entry找到每个super_block,通过super_block里面的bdev来判断是否跟现在需要mount的设备一样,如果一样的话,就直接使用该super_block,如果没有找到的话,就新建super_block), 创建完之后给super_block赋值,将设备和mount相关的信息填充进去,同时将s_type赋值为该文件系统类型,然后将s_instance添加到fs_super链表里,将s_list添加到super_blocks链表里面。
然后我们再来详细看看创建超级块的过程,这要从文件系统初始化的时候说起。
从
static int __init aufs_init(void)
{
int retval;
struct dentry *pslot;
retval = register_filesystem(&au_fs_type);
if (!retval) {
aufs_mount = kern_mount(&au_fs_type);
if (IS_ERR(aufs_mount)) {
printk(KERN_ERR "aufs: could not mount!\n");
unregister_filesystem(&au_fs_type);
return retval;
}
}
pslot = aufs_create_dir("woman star",NULL);
aufs_create_file("lbb", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("fbb", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("ljl", S_IFREG | S_IRUGO, pslot, NULL, NULL);
pslot = aufs_create_dir("man star",NULL);
aufs_create_file("ldh", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("lcw", S_IFREG | S_IRUGO, pslot, NULL, NULL);
aufs_create_file("jw", S_IFREG | S_IRUGO, pslot, NULL, NULL);
return retval;
}
首先需要注册文件系统,在系统中注册文件系统,就是在file_system这个结构体链表里面(内核维护)查找,如果没有的话,就把这个文件系统类型添加到这个链表里面,同时将fs_super初始化为空。
然后kern_mount来完成文件系统mount过程:
在2.6.18里面是这样的过程,其他版本不太一样,但是过程都是一样的:
struct vfsmount *kern_mount(struct file_system_type *type)
{
return vfs_kern_mount(type, 0, type->name, NULL);
}EXPORT_SYMBOL(kern_mount);
vfs_kern_mount(struct file_system_type *type, int flags, const char *name, void *data)
{
struct vfsmount *mnt;
char *secdata = NULL;
mnt = alloc_vfsmnt(name);
if (!mnt)
goto out;
if (data) {
secdata = alloc_secdata();
if (!secdata)
goto out_mnt;
error = security_sb_copy_data(type, data, secdata);
if (error)
goto out_free_secdata;
}
error = type->get_sb(type, flags, name, data, mnt);
if (error < 0)
goto out_free_secdata;
error = security_sb_kern_mount(mnt->mnt_sb, secdata);
if (error)
goto out_sb;
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
up_write(&mnt->mnt_sb->s_umount);
free_secdata(secdata);
return mnt;
}
1.alloc_vfsmnt()是分配并且初始化跟该设备相关的vfsmount结构体,vfsmount是mount过程完成的关键,它将设备和目录连接起来,vfsmount里面的成员也基本上都是一些mount点,mount父目录,超级块指针(通过它可以指向对应的设备的super_block),递归的mount目录等等。
struct vfsmount {
struct list_head mnt_hash;
struct vfsmount *mnt_parent; /* fs we are mounted on */
struct dentry *mnt_mountpoint; /* dentry of mountpoint */
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
atomic_t mnt_count;
int mnt_flags;
int mnt_expiry_mark; /* true if marked for expiry */
char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
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 vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
struct namespace *mnt_namespace; /* containing namespace */
int mnt_pinned;
};
2. type->get_sb(type, flags, name, data, mnt); 这里调用了file_system_type里面的成员.get_sb=get_sb。
在这个函数里面主要是调用了:
get_sb_single(fs_type, flags, data, fill_super, mnt);
int get_sb_single(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int),
struct vfsmount *mnt)
{
struct super_block *s;
int error;
s = sget(fs_type, compare_single, set_anon_super, NULL); //获得超级块
if (IS_ERR(s))
return PTR_ERR(s);
if (!s->s_root) { //如果超级块里对应的目录为空,那么就给它填充必要的信息(创建dentry和inode)
s->s_flags = flags;
error = fill_super(s, data, flags & MS_SILENT ? 1 : 0);
if (error) {
up_write(&s->s_umount);
deactivate_super(s);
return error;
}
s->s_flags |= MS_ACTIVE;
}
do_remount_sb(s, flags, data, 0);
return simple_set_mnt(mnt, s);
}EXPORT_SYMBOL(get_sb_single)
Sget()函数主要是返回超级块的:
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
void *data)
{
struct super_block *s = NULL;
struct list_head *p;
int err;
retry:
spin_lock(&sb_lock);
if (test) list_for_each(p, &type->fs_supers) { // 遍历该文件系统类型上的超级块s_instance
struct super_block *old;
old = list_entry(p, struct super_block, s_instances); //通过s_instance找到超级块
if (!test(old, data)) //判断该超级块里面的设备是否与待mount的设备一样
continue;
if (!grab_super(old))
goto retry;
if (s)
destroy_super(s);
return old;
}
if (!s) {
spin_unlock(&sb_lock);
s = alloc_super(type);
if (!s)
return ERR_PTR(-ENOMEM);
goto retry;
}
err = set(s, data);
if (err) {
spin_unlock(&sb_lock);
destroy_super(s);
return ERR_PTR(err);
}
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id));
list_add_tail(&s->s_list, &super_blocks);
list_add(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
return s;
}
http://blog.chinaunix.net/uid-12567959-id-160991.html 这篇文章分配超级块过程分析的很清楚
2.复习文件系统中创建文件的过程
在课程里面讲到:创建目录和文件总结:创建dentry 、创建inode
一个文件是由inode和dentry表示的,从aufs这个文件中创建文件ldn为例子,从aufu_create_file()入手:
struct dentry *aufs_create_file(const char *name, mode_t mode,
struct dentry *parent, void *data,
struct file_operations *fops)
{
struct dentry *dentry = NULL;
int error;
printk("aufs: creating file '%s'\n",name);
error = aufs_create_by_name(name, mode, parent, &dentry);
if (error) {
dentry = NULL;
goto exit;
}
if (dentry->d_inode) {
if (data)
dentry->d_inode->u.generic_ip = data;
if (fops)
dentry->d_inode->i_fop = fops;
}
exit:
return dentry;
}
可以知道是通过aufs_create_by_name()来创建inode,通过传入了dentry的地址,从前面的知识可知道,应该是给dentry赋值,将文件名字之类的信息存进去,同时将d_inode指向文件的inode.
static int aufs_create_by_name(const char *name, mode_t mode,
struct dentry *parent,
struct dentry **dentry)
{
int error = 0;
/* If the parent is not specified, we create it in the root.
* We need the root dentry to do this, which is in the super
* block. A pointer to that is in the struct vfsmount that we
* have around.
*/
if (!parent ) {
if (aufs_mount && aufs_mount->mnt_sb) {
parent = aufs_mount->mnt_sb->s_root;
}
}
if (!parent) {
printk("Ah! can not find a parent!\n");
return -EFAULT;
}
*dentry = NULL;
mutex_lock(&parent->d_inode->i_mutex);
*dentry = lookup_one_len(name, parent, strlen(name));
if (!IS_ERR(dentry)) {
if ((mode & S_IFMT) == S_IFDIR)
error = aufs_mkdir(parent->d_inode, *dentry, mode);
else
error = aufs_create(parent->d_inode, *dentry, mode);
} else
error = PTR_ERR(dentry);
mutex_unlock(&parent->d_inode->i_mutex);
return error;
}
Lookup_one_len()在linux/fs/iname.c里面,通过文件名字,长度的判断是否合法,然后根据传入的根目录dentry来确定dentry.
struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
{
struct qstr this;
unsigned int c;
int err;
WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
this.name = name;
this.len = len;
this.hash = full_name_hash(name, len);
if (!len)
return ERR_PTR(-EACCES);
if (unlikely(name[0] == '.')) {
if (len < 2 || (len == 2 && name[1] == '.'))
return ERR_PTR(-EACCES);
}
while (len--) {
c = *(const unsigned char *)name++;
if (c == '/' || c == '\0')
return ERR_PTR(-EACCES);
}
/*
* See if the low-level filesystem might want
* to use its own hash..
*/
if (base->d_flags & DCACHE_OP_HASH) {
int err = base->d_op->d_hash(base, &this);
if (err < 0)
return ERR_PTR(err);
}
err = inode_permission(base->d_inode, MAY_EXEC);
if (err)
return ERR_PTR(err);
return __lookup_hash(&this, base, 0);
}
EXPORT_SYMBOL(lookup_one_len);
参考文章:http://itlab.idcquan.com/linux/administer/784553.html inode和超级块详解
http://blog.chinaunix.net/uid-12567959-id-160990.html 挂载普通文件系统
http://blog.sina.com.cn/s/blog_70b6ff960101am5z.html 路径名解析过程中使用的各种hash函数
http://dato0123.iteye.com/blog/1260053 sysfs创建目录
/article/1349552.html 初窥ext2/ext3文件系统
http://blog.chinaunix.net/uid-27052262-id-3259907.html
http://blog.csdn.net/rstevens/article/details/1824785
http://www.2cto.com/os/201110/108845.html
/article/1394935.html
/article/2693535.html
相关文章推荐
- Linux最简单的文件系统aufs剖析详解
- aufs简单的文件系统
- 实现一个简单的aufs文件系统
- 一个简单的系统配置文件使用DTD和XSD两种方式描述
- Linux内核裁减及根文件系统定制
- Linux内核裁减及根文件系统定制(一)
- Linux内核裁减及根文件系统定制(二)
- Linux基础-文件系统简单命令
- 用busybox做一个简单的文件系统
- LINUX内核和文件系统
- C#监视文件系统如此简单
- 简单说点对文件系统、分层驱动、文件读写的理解
- 今天刚发现的好东西!让系统自动查找删除文件的办法!(简单!!!!)
- Linux内核具体体系结构之虚拟文件系统
- Linux内核移植和根文件系统制作
- 一个简单文件系统的实现
- 简单文件系统的实现
- Solaris下利用/proc文件系统特性简单构建log系统
- 简单文件系统
- 【WEISS算法书】书上简单的文件系统