首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Linux 文件系统中元数据使用计数的机制(2)使用计数减少

Linux 文件系统中元数据使用计数的机制(2)使用计数减少

使用计数的减少dput 函数前面我们提到过,在元数据操作中,通过查找操作增加了 dentry 的使用计数,会在结尾处通过 dput() 进行递减。这里我们就来看看 dput() 的机制。
在 dput() 中,处理的步骤如下:
  • 对 dentry -> d_count 递减,如果不为0,就直接返回。
  • 判断具体文件系统是否定义了 d_op -> d_delete 这个接口函数。本地文件系统 Ext2, Ext3 都没有定义这个函数, NFS 定义了这个函数。对于该函数的具体作用我们在后面介绍。既然 Ext2 没有定义,我们就继续往下看。
  • 判断 dentry 是否从 dcache 的哈希链上移除了。(1)如果是,表示该元数据对应的对象已经被删除了,此时可以释放该元数据;(2)如果不是,表示该元数据对应的对象没有被删除,这时把 dentry 挂到 LRU 队列 dentry_unused 上,然后返回。
  • 如果进入释放元数据的那条路径,会释放 dentry 结构和 inode 结构。
其中,释放 inode 结构的调用路径是:
1
dput() > dentry_iput() > iput() > iput_final()




在 iput() 中,会递减 inode 的使用计数,如果递减完后为0,就进一步调用 iput_final()
1
iput_final() > generic_drop_inode()




在 generic_drop_inode() 中,会判断 inode -> i_nlink 的值。我们在前面说过, i_nlink 这个域表示 inode 的 hard link 数目,这里就是通过这个域来判断该 inode 能否被删除。
若 inode -> i_nlink 为0,说明没有 hard link 指向该 inode ,可以将其删除,路径为:
1
generic_drop_inode() > generic_delete_inode() > s_op->delete_inode()




若 inode -> i_nlink 不为0,说明仍有 hard link 指向该 inode ,不能将其删除,路径为:
1
generic_drop_inode() > generic_forget_inode()




释放 inode 结构的步骤就是这些。释放 dentry 的主要做的事情就是将 dentry与inode 脱离,然后释放 dentry 结构。要注意的是,每当释放了一个 dentry ,都要获取其原来父目录的 dentry ,然后又跳转到 dput() 的开头,继续对父目录的 dentry 进行释放操作。这是因为,前面也提过,每次创建一个 dentry 结构,除了增加自身的使用计数外,还会增加其父目录 dentry 的使用计数。所以当释放了一个 dentry 后也要将其父目录 dentry 使用计数递减,才能保证父目录为空时能够被释放。
unlink 函数在 dput() 中我们提到过要判断 dentry 是否从 dcache 的哈希链上移除。在本地文件系统中,当删除一个文件时,就会将其对应的 dentry 从 dcache 的哈希链上移除,这是通过 unlink() 来实现。unlink() 的流程如下:
1
sys_unlink() > do_unlinkat() >




在do_unlinkat()中,主要可以分为五个部分:
1
2
3
4
5
(1) dentry = lookup_hash(&nd);
(2) atomic_inc(&inode->i_count);
(3) vfs_unlink(nd.dentry->d_inode, dentry);
(4) dput(dentry);
(5) iput(inode);    /* truncate the inode here */




可以看出,主体函数 vfs_unlink() 前后,有配对的操作对 dentry 和 inode 进行增减。
我们先看 vfs_unlink() ,其路径为:
1
vfs_unlink() > d_delete()




在 d_delete() 中,如果 dentry 的使用计数为1,说明此时没有其他人引用该 dentry ,那么就尝试把该 dentry 的 inode 删除,这里调用的是 dentry_iput() ,这个函数已经在 dput() 那部分介绍过了。这个过程的实质就是把 dentry 转为 negative 状态,然后返回。转为 negative 的 dentry 依然在 dcache 的哈希链中,但删除操作已经完成,对应的代码为:
1
2
3
4
5
if (atomic_read(&dentry->d_count) == 1) {
    dentry_iput(dentry);
    ...
    return;
}




否则,即 dentry 的使用计数大于1(这里不可能小于1,因为 do_unlinkat() 中调用 lookup_hash() 时已经对 dentry 的使用计数进行了增加),说明有其他人引用该 dentry ,此时不能把这个 dentry 转为 negative ,那么就把这个 dentry 从 dcache 的哈希链中脱离,对应的代码为:
1
2
if (!d_unhashed(dentry))
    __d_drop(dentry);




那么什么时候删除这个 dentry 所对应的 inode 呢?大家可以回过头看一下 dput() 介绍过的内容。在 dput() 中,当 dentry 已经从 dcache 的哈希链上移除后,就会继续进行释放元数据的操作。所以只要当最后一个使用 dentry 的操作结束时调用 dput() ,就会调用 dentry_iput() 。
总结一下,删除 inode 必须由 unlink 的 d_delete 发起,如果可能就在 d_delete 中完成删除;否则就 unhash ,然后由最后一个使用者调用 dput() 删除。但只有在 d_delete 完成 unhash 之后, dput 才有可能删除 inode 。
看完主体函数 vfs_unlink() 后,我们关注一下对 inode 使用计数的增减。
在 do_unlinkat() 中,调用 vfs_unlink() 之前,递增了 inode -> i_count ,因此,如果刚进入 do_unlinkat() 时 dentry 的使用计数为1,真正的删除操作并不是在 vfs_unlink() 的 d_delete() 进行,而是在 vfs_unlink() 之后的 iput() 进行(源码里也在 iput() 旁边进行了注释)。
大家也许要问, vfs_unlink() 之后,在 iput() 之前不是还有一个 dput() 吗?那么会不会在这里就删除了 inode 呢?其实不会,我们分三种情况来讨论:
  • 如果 dentry 的使用计数为1,说明没有其他人在使用,则此前 vfs_unlink() 的 d_delete() 必然已经将 dentry 转为 negative ,但没有脱离 dcache 的哈希链,因此不会进行删除(参看 dput() 流程)。删除 inode 的时机是 do_unlinkat() 的 iput() 。
  • 如果 dentry 的使用计数大于1,说明有其他人在使用。若在 do_unlinkat() 调用 dput() 之前,其他使用者都调用了自己的 dput() ,则此时 dentry 的使用计数又变为了1。由于之前 vfs_unlink() 的 d_delete() 已经将 dentry 从哈希链上移除(但也因此没有走到 dentry_iput() 那步,没有递减 inode 的使用计数),因此在 do_unlinkat() 的 dput() 中会走到 dentry_iput() ,但由于 do_unlinkat() 一开始递增了 inode 的使用计数,所以这个 dput() 也不能删除 inode 。删除 inode 的时机仍然是 do_unlinkat() 的 iput() 。
  • 如果 dentry 的使用计数大于1,并且在 do_unlinkat() 调用 dput() 之前,其他使用者没有调用自己的 dput() ,则 do_unlinkat() 调用 dput() 递减了 dentry 的使用计数之后就直接返回了。删除 inode 的时机是其他使用者的 dput() 。
总结一下,这部分的操作比较繁杂,需要结合 dput() 和 unlink 操作一起来看,但理清思路后,也会发现其逻辑还是很清晰的,而这种机制也与使用计数的增加结合得非常好,共同构成了 Linux 文件系统管理内存元数据结构的机制。
返回列表