Linux文件系统学习

做项目不能太功利了,每次都是现学现卖,项目做完了落得两手空,还是要脚踏实地的学!做项目重在过程而不是结果啊!

参考书:《深入理解Linux内核》,《Linux内核源码剖析》,《Linux内核精解析》

linux虚拟文件系统(VitrulFileSystem)简介

VFS只存在于内存中,它在系统启动时被创建,系统关闭时注销。
VFS的作用就是屏蔽各类文件系统的差异,给用户、应用程序、甚至Linux其他管理模块提供统一的接口集合。
管理VFS数据结构的组成部分主要包括超级块和inode。

VFS是物理文件系统与服务之间的一个接口层,它对Linux的每个文件系统的所有细节进行抽象,使得不同的文件系统在Linux核心以及系统中运行的进程看来都是相同的。
严格的说,VFS并不是一种实际的文件系统。它只存在于内存中,不存在于任何外存空间。VFS在系统启动时建立,在系统关闭时消亡。

VFS使Linux同时安装、支持许多不同类型的文件系统成为可能。VFS拥有关于各种特殊文件系统的公共界面,当某个进程发布了一个面向文件的系统调用时,内核将调用VFS中对应的函数,这个函数处理一些与物理结构无关的操作,并且把它重定向为真实文件系统中相应的函数调用,后者用来处理那些与物理结构相关的操作。

下图就是逻辑上对VFS及其下层实际文件系统的组织图,可以看到用户层只能与VFS打交道,而不能直接访问实际的文件系统,比如EXT2、EXT3、PROC,换句话说,就是用户层不用也不能区别对待这些真正的文件系统,就是不能像打开大部分文件系统下的“文件”一样打开socket,它只能被创建,而且内核中对其有特殊性处理。

VFS主要要了解superblock,inode,dentry几个关键信息点

superblock(超级块对象)

存放已安装文件系统的有关信息。也就是一个superblock对应一个文件系统,每个分区下可挂在多个文件系统,每个superblock中有自己独立的inode链表,所以每个superblock的inode编号是相同的。且superblock是inode编号为0的节点。

file(文件对象)

存放打开文件与进程之间进行交互的有关信息。

dentry(目录项对象)

不是只对应目录文件,每个文件也对应一个目录项,存放目录项与对应文件进行链接的有关信息。

1
2
3
4
5
6
7
8
9
10
11
/*内核版本---3.2.16*/
struct dentry {
struct inode * d_inode; //与文件名关联的索引节点
struct dentry * d_parent; //父目录的目录项对象
struct list_head d_child; //父目录中目录项对象的链表的指针
struct list_head d_subdirs;//对目录而言,表示子目录目录项对象的链表
… …
struct qstr d_name;//文件名
struct super_block * d_sb;//文件的超级块对象
unsigned char d_iname [DNAME_INLINE_LEN];//存放短文件名
};

inode(索引节点对象)

inode是最小的文件单元,对文件的操作实际上是对inode的操作。文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个sector组成一个block。文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,VFS会专门在硬盘上开辟一个inode区,用来存储所有inode(所以每个分区的inode数是固定的,可以用“df -i”查看所有命令。)每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。inode中包含了block块的头结点,可以找到所有数据区的block。

sector->block->inode

1
2
3
4
5
6
7
8
9
10
11
12
struct inode{
unsigned long i_ino; //inode编号
umode_t i_mode; //inode的权限
uid_t i_uid; //inode拥有者的id
gid_t i_gid; //inode所属的群组id
off_t i_size; //inode所代表的档案大小
… …
time_t i_atime; //最近一次存取时间
time_t i_mtime; //最近一次修改时间
unsigned short i_bytes; //inode节点的大小
struct super_block * s_type; //所属文件系统
}

superblock的链接方式:

vfs对象链接图:

文件读取过程
从用户的观点来看,文件被组织在一个树结构的命名空间中,在Linux中,每个目录被看作一个文件,可以包含若干文件和其他的子目录.一旦目录项被读入内存,虚拟文件系统就把它转换成基于dentry结构体的一个目录项对象,dentry数据结构点定义在内核文include\linux\dcache.h中,不同的内核版本在结构上面略有不同!每个虚拟且存在的dentry对象都对应一个inode节点来存储文件数据的更多信息,inode结构的在include\linux\fs.h定义

在读取一个文件时,是没有dentry结构的,(VFS也是存储在内存中的,在系统启动时创建,只有inode是存储在磁盘上的)所以访问的时候是边访问边创建dentry。读取文件总是从根目录开始读取,每一个目录或者文件,在VFS中,都是一个文件对象,每一个文件对象都有唯一的一个inode与之对应。根目录的inode号为0,在superblock里,可以很快根据inode号索引到具体的inode,因此读取到的第一个inode就是根目录的。读取到了该目录后,内核对象会为该文件对象建立一个dentry,并将其缓存起来,方便下一次读取时直接从内存中取。而目录本身也是一个文件,目录文件的内容即是该目录下的文件的名字与inode号,目录文件的内容就像一张表,记录的文件名与其inode no.之间的映射关系。根据路径即可找到当前需要读取的下一级文件的名字和inode,同时继续为该文件建立dentry,dentry结构是一种含有指向父节点和子节点指针的双向结构,多个这样的双向结构构成一个内存里面的树状结构,也就是文件系统的目录结构在内存中的缓存了。有了这个缓存,我们在访问文件系统时,通常都非常快捷。

Linux目录树对应的数据结构示意图
链接方式比较独特请注意:父目录通过d_subdirs指向子目录链表,然后子目录通过d_child连接起来.子目录通过d_parent指针指向其父目录:

软硬链接

其实我们在操作系统用户层面操作的就是硬链接,在用户界面删除一个文件实际上是删除一个硬链接,当一个inode的硬链接技术为0时,系统就会回收这个inode号码,这个inode的block就可以被新文件覆盖。所以我们创建一个硬链接不会占用磁盘容量。创建硬链接就是创建一个dentry。
软连接就可以理解为windows下的快捷方式(直接ln -s创建的软连接是相对路径,移动后就不能用了,要移动的话必须要使用绝对路径来生成)

linux的树结构与链表

linux 内核链表为双向循环链表,且每个节点对象的成员有一个list_head变量即有前后两个指针,指向前后节点对象的list_head变量的指针,参照上图)

1
2
3
struct list_head {
struct list_head *next, *prev;
};

dentry打印目录下所有文件

根据当前目录(current变量)一直向上找找到根目录,再向下找,找到目标目录,并遍历。这里就比较坑了,同一层目录链表的尾指针并没有指向第一个文件,而是指向了上一级目录,而上一级目录指向了本级目录的第一个节点,打印了一下午没有打印出来,网上还没有资料,关键时刻还是要看源码啊!从mkdir()函数一路找下去,找到dentry的连接函数,观察链接过程,如下所示第46行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)  
{
struct dentry *dentry;
char *dname;

dentry = kmem_cache_alloc(dentry_cache, GFP_KERNEL);
if (!dentry)
return NULL;

if (name->len > DNAME_INLINE_LEN-1) {
dname = kmalloc(name->len + 1, GFP_KERNEL);
if (!dname) {
kmem_cache_free(dentry_cache, dentry);
return NULL;
}
} else {
dname = dentry->d_iname;
}
dentry->d_name.name = dname;

dentry->d_name.len = name->len;
dentry->d_name.hash = name->hash;
memcpy(dname, name->name, name->len);
dname[name->len] = 0;

atomic_set(&dentry->d_count, 1);
dentry->d_flags = DCACHE_UNHASHED;
spin_lock_init(&dentry->d_lock);
dentry->d_inode = NULL;
dentry->d_parent = NULL;
dentry->d_sb = NULL;
dentry->d_op = NULL;
dentry->d_fsdata = NULL;
dentry->d_mounted = 0;
INIT_HLIST_NODE(&dentry->d_hash);
INIT_LIST_HEAD(&dentry->d_lru);
INIT_LIST_HEAD(&dentry->d_subdirs);
INIT_LIST_HEAD(&dentry->d_alias);

if (parent) {
dentry->d_parent = dget(parent);
dentry->d_sb = parent->d_sb;
} else {
INIT_LIST_HEAD(&dentry->d_u.d_child);
}

spin_lock(&dcache_lock);
if (parent)
/*就是这个地方,看指向的位置,指向的不是下一个节点,而是父节点啊!*/
list_add(&dentry->d_u.d_child, &parent->d_subdirs);
/*******************************************************/
dentry_stat.nr_dentry++;
spin_unlock(&dcache_lock);

return dentry;
}

由此可写出打印指定目录下所有文件代码片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
printk("a dentry name : %s\n",a_dentry->d_name.name);  
struct dentry *a_parent_dentry = a_dentry->d_parent;
printk(KERN_EMERG"根目录名称: %s\n",a_parent_dentry->d_name.name);

struct list_head *plist;
struct dentry *sub_dentry;

list_for_each(plist, &(a_parent_dentry->d_subdirs))
{
sub_dentry = list_entry(plist, struct dentry, d_u.d_child);
if(sub_dentry)
{
printk(KERN_EMERG"subdirs_dentry %s\n",sub_dentry->d_name.name);
printk(KERN_EMERG"sub_dentry %ld\n",sub_dentry);
}
}

本文标题:Linux文件系统学习

文章作者:Hengliy

发布时间:2017年09月21日 - 19:09

最后更新:2018年02月24日 - 01:02

原始链接:http://hengliy.github.io/2017/09/21/LINUX文件系统学习/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。