Linux 文件系统中元数据使用计数的机制(1)使用计数增加
- UID
- 1066743
|
Linux 文件系统中元数据使用计数的机制(1)使用计数增加
概述元数据是一个文件系统的重要部分。很多书籍和文章都介绍过 dentry 和 inode 在 Linux 中的作用和机制,但却很少有文献涉及到它们的使用计数( usage counter )。使用计数的机制看似很简单:使用了一个元数据就递增,用完了就递减。但在这句简单的描述后面,具体的过程到底是如何进行的呢?这实际上贯穿了整个元数据的操作以及元数据在内存中的管理。了解这部分的机制,是一个很有意思的过程,可以让你看到 Linux 严谨缜密的思路,可以深入认识 Linux 文件系统的运行机制。这部分内容也是构建分布式文件系统所必须的知识。
本文仍然从两方面来介绍使用计数:增加和减少。最后再看一下在分布式环境中有哪些变化。
这里所引用的代码依据的是 Linux 内核 2.6.20 的版本。
使用计数的增加创建操作元数据的创建主要可以分为对文件的创建和对目录的创建。不管是文件还是目录,它们都对应同样的元数据结构,在内存中都有 inode 和 dentry 。
下面我们分别看一下主要的两个创建操作:创建文件和创建目录。
(1)创建文件
创建文件是通过系统调用 sys_open() ,并设置 O_CREATE 标志位来实现的。其调用过程如下:
1
| sys_open() > do_sys_open() > do_filp_open() > open_namei()
|
在 open_namei() 中,会创建出 dentry 和 inode 结构。先看关于 dentry 的路径:
1
| open_namei() > lookup_hash() > __lookup_hash()
|
这里会分成3种情况:
- 在 dcache 中查找: __lookup_hash() > cached_lookup() > d_lookup() > __d_lookup()
- 分配新的 dentry: __lookup_hash() > d_alloc() > atomic_set(&dentry -> d_count, 1);
- 在具体文件系统中查找: __lookup_hash() > i_op -> lookup()
和查找有关的内容我们在后面介绍,这里只看创建,也就 d_alloc() ,它会分配一个新的 dentry 结构,在分配的过程中,就会把 dentry 的使用计数初始化为1。在 d_alloc() 中,还会通过函数 dget() 递增父目录的使用计数,这是为了防止父目录在该 dentry 删除前被删除。(“/”除外,它没有父目录):
1
| d_alloc() > dget(parent) > atomic_inc(&dentry->d_count);
|
我们再看关于 inode 的路径:
1
| open_namei() > open_namei_create() > vfs_create() > i_op->create()
|
最终会调用具体文件系统的 create 函数。这里以 Ext2 为例,其调用过程如下:
1
2
| ext2_create() > ext2_new_inode() > new_inode() >
alloc_inode() > atomic_set(&inode->i_count, 1);
|
具体文件系统在分配 inode 结构的时候,会通过初始化把 inode 的 i_count 域置为1。同时还把 inode 的 i_nlink 域置为1,这个域表示 inode 的 hard link 的数目,其值会被写入到具体文件系统的磁盘中。
总结一下,通过创建操作,会在内存中建立起 dentry 和 inode 结构,并且会把它们的使用计数都初始化为1。
(2)创建目录
创建目录和创建文件是类似的,这里我们简单看一下调用的路径就清楚了。
创建目录是通过系统调用 sys_mkdir() 来实现的。关于 dentry 的路径如下:
1
| sys_mkdir() > sys_mkdirat() > lookup_create() > lookup_hash() > __lookup_hash()
|
可以看出,这与前面“创建文件”中介绍的是一样的。
关于 inode 的路径如下:
1
| sys_mkdir() > sys_mkdirat() > vfs_mkdir() > i_op->mkdir()
|
最终会调用具体文件系统的 mkdir 函数。这里以 Ext2 为例,其调用过程如下:
1
| ext2_mkdir() > ext2_new_inode() > new_inode() > alloc_inode() > atomic_set()
|
可以看出,这与前面“创建文件”中介绍的也是一样的。
由此也可以看出,从内存中的元数据结构来看,Linux对文件和目录的管理是一样的。
查找操作创建了一个对象(文件或目录)后,要使用这个对象,就必须先进行查找。查找操作是元数据使用的关键操作,基本上所有元数据操作都会以查找操作为起始,因为只有找到了元数据才能进一步对其进行操作。即使对于创建操作,一开始也要进行查找,只不过因为要创建的对象还不存在,所以会查找失败,然后才进行创建。
查找操作的入口函数是 __link_path_walk() ,其调用过程如下:
1
| __link_path_walk() > do_lookup()
|
到了这里,要做的事情主要是在内存中查找相应文件所对应的 dentry 结构。这会分为两种情况:
(1)该 dentry 结构在内存中
此时,通过哈希就可以获取该 dentry 结构,并将其使用计数递增。
1
| do_lookup() > __d_lookup() > atomic_inc(&dentry->d_count)
|
(2)该 dentry 结构不在内存中
此时,该 dentry 结构可能从来就没在内存中建立起来,或者在内存中存在过,但已经从 LRU 队列 dentry_unused 中被换出内存。无论如何,都需要从磁盘读取元数据,在内存中建立起 dentry 和 inode 结构。这时所进行的步骤是:
首先在内存中分配一个dentry结构:
1
| do_lookup() > real_lookup() > d_alloc() > atomic_set(&dentry->d_count, 1);
|
这里的 d_alloc() 和前面“创建操作”介绍的一样,会把 dentry 的使用计数初始化为1,并将其父目录的使用计数通过 dget() 递增。
分配了 dentry 结构后,就要从磁盘找出对应的元数据。这个过程因文件系统而异,所以通过父节点的 inode -> i_op 里的函数来进行。
1
| real_lookup() > i_op->lookup()
|
这里以 Ext2 为例,调用的是 ext2_create() ,过程如下:
1
2
3
| (1) ext2_lookup() > iget(dir->i_sb, ino);
(2) ext2_lookup() > d_splice_alias() > __d_find_alias() > __dget_locked() >
atomic_inc(&dentry->d_count);
|
前者调用 iget() ,首先通过 ino 在 inode cache 中查找 inode ,如果找到就返回并增加其引用计数;如果没有找到,就分配一个新的(调用 alloc_inode() ,会把使用计数初始化为1,参照前面“创建操作”),并从磁盘读入相应索引节点,在内存中建立起 inode 结构。
后者则把 dentry 与 inode 结构绑定,并递增了 dentry 的使用计数。
总结一下,查找操作的主要过程就是在内存中查找 dentry 结构,如果找到就递增其使用计数;如果找不到就到磁盘中去取,并在内存建立 dentry 和 inode 结构,同时将它们的使用计数初始化为1。因此查找操作都会增加 dentry 的使用计数,或者递增,或者初始化为1。
元数据操作对使用计数的运用这里我们举例说明元数据操作对 dentry 使用计数的运用,让大家对其有个比较具体的认识和感觉。
元数据操作的实质就是对元数据进行使用。那么,要使用某个元数据时,必须在内存中为其建立相应的结构,即 inode 和 dentry 。但并不是所有的元数据每时每刻都会有对应的结构在内存中,只有需要时才会建立这些结构,并且在特定的时候又会被换出内存。那么如何管理内存元数据结构的使用,从而决定其何时在内存中,何时被换出,这就是通过 dentry 的使用计数来实现的。
下面我们以两个常见的元数据操作为例,来看 Linux 如何管理内存元数据结构的使用。
(1) getattr 操作
Linux 内核中有很多操作都会调用到 getattr ,我们举其中的一个来说明:sys_stat() > vfs_stat_fd() 。
函数 vfs_stat_fd() 比较短,我们将其内容都列出来:
1
2
3
4
5
6
7
8
9
10
11
12
| int vfs_stat_fd(int dfd, char __user *name, struct kstat *stat)
{
struct nameidata nd;
int error;
error = __user_walk_fd(dfd, name, LOOKUP_FOLLOW, &nd);
if (!error) {
error = vfs_getattr(nd.mnt, nd.dentry, stat);
path_release(&nd);
}
return error;
}
|
这里先调用了 __user_walk_fd() ,这个函数继续走下去的路径是:
1
| __user_walk_fd() > do_path_lookup() > link_path_walk() > __link_path_walk()
|
可以看出, __link_path_walk() 就是前面介绍过的查找操作。如果成功返回,就会增加 dentry 的使用计数,否则就不增加。而如果查找成功,就进行具体的 getattr 的工作,调用的是 vfs_stat_fd() 的主体函数 vfs_getattr() 。这之后,会调用 path_release() ,这个函数的路径是:
1
| path_release(&nd) > dput(nd->dentry)
|
函数 dput() 会将 dentry 的使用计数减少,这个函数我们将在后面详细介绍。
总结一下, getattr 操作首先要查找元数据,找到后,就增加 dentry 的使用计数,只要 dentry 的使用计数不为0,它就会存在于 dcache 中,而不会被换出内存。当 getattr 的主要操作步骤完成后,就会减少 dentry 的使用计数,表明 getattr 操作已经完成,不再需要使用这个 dentry 了。
(2) link 操作
下面再看一个操作。 Link 操作用于创建一个对象链接。其调用路径为:
1
| sys_link() > sys_linkat()
|
接下来可以分为七个部分:
1
2
3
4
5
6
7
8
9
| (1) error = __user_walk_fd(olddfd, oldname,
flags & AT_SYMLINK_FOLLOW ? LOOKUP_FOLLOW : 0,
&old_nd);
(2) error = do_path_lookup(newdfd, to, LOOKUP_PARENT, &nd);
(3) new_dentry = lookup_create(&nd, 0);
(4) error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry);
(5) dput(new_dentry);
(6) path_release(&nd);
(7) path_release(&old_nd);
|
第1步中, __user_walk_fd() 会查找要被链接的文件,这和前面 getattr 中的函数一样,会把这个文件对应的 dentry 的使用计数进行递增。它和第7步中的 path_release() 对应。
第2步中, do_path_lookup() 会查找要创建的链接的父目录,它同样会进行查找操作,递增 dentry 的使用计数。它和第6步中的 path_release() 对应。
第3步中, lookup_create() 会创建链接对象的 dentry 结构,这和前面“创建目录”中介绍的函数一样。它和第5步中的 dput() 对应。
这里我们再次看到,一个元数据操作中都会先查找涉及到的元数据,并增加其 dentry 的使用计数,然后在该操作结束的时候递减这些使用计数。
对于 link 操作,我们还要讲讲它的主体函数,也就是 vfs_link() ,其路径为:
1
| vfs_link() > i_op->link()
|
以 Ext2 为例,调用的是 ext2_link() ,过程如下:
1
2
| (1) ext2_link() > inode_inc_link_count() > inc_nlink()
(2) ext2_link() > atomic_inc(&inode->i_count);
|
先看前者,这里的 inode 结构对应的是被链接的文件, ext2_link() 会递增该 inode 的 i_nlink ,前面说过,这个域表示 inode 的 hard link 的数目,因为我们对这个文件建立了一个新的链接,所以会对这个域进行递增。
后者的 inode 结构依然对应的是被链接的文件, ext2_link() 会递增 inode 的使用计数。到目前位置,我们看到,各个操作对元数据使用计数的运用主要都是针对 dentry ,而很少针对 inode 。这是因为 dentry 主要用于内存中目录树结构的表示,而查找操作主要就是针对目录树结构来进行的,因此它频繁地对 dentry 的使用计数进行操作。对于 inode 的使用计数,它主要表示这个元数据的存在,因此一般只在创建这个 inode 的时候以及删除的时候才会用到。我们知道一个 inode 可以对应多个 dentry ,这是因为一个文件可以有多个链接,所以当多了一个链接时,就要递增 inode 的使用计数。 |
|
|
|
|
|