自 2.5.0 以来的更改:¶
---
推荐
新的辅助函数:sb_bread(),sb_getblk(),sb_find_get_block(),set_bh(),sb_set_blocksize() 和 sb_min_blocksize()。
使用它们。
(sb_find_get_block() 取代了 2.4 的 get_hash_table())
---
推荐
新的方法:->alloc_inode() 和 ->destroy_inode()。
删除 inode->u.foo_inode_i
声明
struct foo_inode_info {
/* fs-private stuff */
struct inode vfs_inode;
};
static inline struct foo_inode_info *FOO_I(struct inode *inode)
{
return list_entry(inode, struct foo_inode_info, vfs_inode);
}
使用 FOO_I(inode) 而不是 &inode->u.foo_inode_i;
添加 foo_alloc_inode() 和 foo_destroy_inode() - 前者应分配 foo_inode_info 并返回 ->vfs_inode 的地址,后者应释放 FOO_I(inode)(请参阅树内文件系统示例)。
在您的 super_operations 中将它们设为 ->alloc_inode 和 ->destroy_inode。
请记住,现在您需要在调用 iget_locked()
和解锁 inode 之间显式初始化私有数据。
在某个时候,这将成为强制性的。
强制性
foo_inode_info 应该始终通过 alloc_inode_sb() 而不是 kmem_cache_alloc()
或 kmalloc()
来分配,以便正确设置 inode 回收上下文。
---
强制性
file_system_type 方法的更改(->read_super 到 ->get_sb)
->read_super() 不再存在。DECLARE_FSTYPE 和 DECLARE_FSTYPE_DEV 也是如此。
将您的 foo_read_super() 转换为一个函数,该函数在成功时返回 0,在发生错误时返回负数(-EINVAL,除非您有更具信息性的错误值要报告)。将其称为 foo_fill_super()。现在声明
int foo_get_sb(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data, struct vfsmount *mnt)
{
return get_sb_bdev(fs_type, flags, dev_name, data, foo_fill_super,
mnt);
}
(或者类似地使用 s/bdev/nodev/ 或 s/bdev/single/,具体取决于文件系统的类型)。
用显式初始化器替换 DECLARE_FSTYPE...,并将 ->get_sb 设置为 foo_get_sb。
---
强制性
锁定更改:->s_vfs_rename_sem 仅由跨目录重命名获取。很可能不需要更改任何内容,但是如果您依赖于重命名之间的全局互斥以实现某些内部目的 - 您需要更改您的内部锁定。否则,互斥保证保持不变(即父级和受害者被锁定等)。
---
信息性
现在我们有 ->lookup() 和目录删除(通过 ->rmdir() 和 ->rename())之间的互斥。如果您过去需要该互斥并通过内部锁定来执行(大多数文件系统不在乎) - 您可以放宽您的锁定。
---
强制性
->lookup()、->truncate()、->create()、->unlink()、->mknod()、->mkdir()、->rmdir()、->link()、->lseek()、->symlink()、->rename() 和 ->readdir() 现在在没有 BKL 的情况下调用。在入口处获取它,在返回时释放它 - 这将保证您过去拥有的相同锁定。如果您的方法或其部分不需要 BKL - 更好的是,现在您可以移动 lock_kernel() 和 unlock_kernel(),以便它们可以保护需要保护的内容。
---
强制性
BKL 也从 sb 操作周围移动。BKL 应该已移动到各个 fs sb_op 函数中。如果您不需要它,请将其删除。
---
信息性
由调用者完成检查 ->link() 目标是否不是目录。请随意删除它...
---
信息性
->link() 调用者在我们要链接到的对象上持有 ->i_mutex。您的一些问题可能已经结束...
---
强制性
新的 file_system_type 方法 - kill_sb(superblock)。如果您正在转换现有文件系统,请根据 ->fs_flags 设置它
FS_REQUIRES_DEV - kill_block_super
FS_LITTER - kill_litter_super
neither - kill_anon_super
FS_LITTER 已消失 - 只需从 fs_flags 中删除它。
---
强制性
FS_SINGLE 已消失(实际上,这发生在 ->get_sb() 进入时 - 并且没有记录 ;-/)。只需从 fs_flags 中删除它(并查看 ->get_sb() 条目以了解其他操作)。
---
强制性
->setattr() 现在在没有 BKL 的情况下调用。调用者_始终_持有 ->i_mutex,因此请注意 ->setattr() 可能使用的 ->i_mutex 获取代码。notify_change()
的调用者现在需要 ->i_mutex。
---
推荐
新的 super_block 字段 struct export_operations *s_export_op
用于显式支持导出,例如通过 NFS。该结构在其在 include/linux/fs.h 中的声明中以及在 使文件系统可导出 中有完整记录。
简而言之,它允许定义 decode_fh 和 encode_fh 操作来编码和解码文件句柄,并允许文件系统使用 decode_fh 的标准辅助函数,并为此辅助函数提供特定于文件系统的支持,特别是 get_parent。
计划一旦代码稳定下来,这将是导出的必需项。
强制性
s_export_op 现在是导出文件系统所必需的。isofs、ext2、ext3、fat 可以用作非常不同的文件系统的示例。
---
强制性
iget4() 和 read_inode2 回调已被 iget5_locked()
取代,后者具有以下原型
struct inode *iget5_locked(struct super_block *sb, unsigned long ino,
int (*test)(struct inode *, void *),
int (*set)(struct inode *, void *),
void *data);
当 inode 编号不足以标识实际文件对象时,可以使用“test”作为附加函数。“set”应该是一个非阻塞函数,用于初始化新创建的 inode 的那些部分,以允许测试函数成功。“data”作为不透明值传递给 test 和 set 函数。
当 inode 由 iget5_locked()
创建时,它将返回并设置 I_NEW 标志,并且仍将被锁定。然后,文件系统需要完成初始化。一旦初始化了 inode,就必须通过调用 unlock_new_inode()
来解锁它。
文件系统负责在适当的时候设置(并可能测试)i_ino。还有一个更简单的 iget_locked 函数,它只接受超级块和 inode 编号作为参数,并为您进行测试和设置。
例如
inode = iget_locked(sb, ino);
if (inode->i_state & I_NEW) {
err = read_inode_from_disk(inode);
if (err < 0) {
iget_failed(inode);
return err;
}
unlock_new_inode(inode);
}
请注意,如果设置新 inode 的过程失败,则应在 inode 上调用 iget_failed()
以使其失效,并且应将适当的错误传递回调用者。
---
推荐
->getattr() 最终被使用。请参阅 nfs、minix 等中的实例。
---
强制性
->revalidate() 已消失。如果您的文件系统有它 - 提供 ->getattr() 并让它调用您作为 ->revlidate() 的任何内容 +(对于具有 ->revalidate() 的符号链接)在 ->follow_link()/->readlink() 中添加调用。
---
强制性
->d_parent 更改不再受 BKL 保护。如果满足以下至少一个条件,则读取访问是安全的
文件系统没有跨目录 rename()
我们知道父级已被锁定(例如,我们正在查看 ->lookup() 参数的 ->d_parent)。
我们从 ->rename() 中调用。
持有子级的 ->d_lock
审核您的代码并在需要时添加锁定。请注意,即使在旧树中,任何不受上述条件保护的位置也是有风险的 - 您一直依赖于 BKL,这很容易导致错误。旧树有相当多的此类漏洞 - 对 ->d_parent 的不受保护的访问会导致从 oops 到静默内存损坏的任何问题。
---
强制性
FS_NOMOUNT 已消失。如果您使用它 - 只需在标志中设置 SB_NOUSER(请参阅 rootfs 以了解一种解决方案,以及 bdev/socket/pipe 以了解另一种解决方案)。
---
推荐
使用 bdev_read_only(bdev) 而不是 is_read_only(kdev)。后者仍然存在,但仅由于 drivers/s390/block/dasd.c 中的混乱。一旦修复了 is_read_only(),它就会消失。
---
强制性
->permission() 现在在没有 BKL 的情况下调用。在入口处获取它,在返回时释放它 - 这将保证您过去拥有的相同锁定。如果您的方法或其部分不需要 BKL - 更好的是,现在您可以移动 lock_kernel() 和 unlock_kernel(),以便它们可以保护需要保护的内容。
---
强制性
->statfs() 现在在没有 BKL 持有的情况下调用。BKL 应该已移动到各个 fs sb_op 函数中,在这些函数中,尚不清楚移除它是否安全。如果您不需要它,请将其删除。
---
强制性
is_read_only() 已消失;请改用 bdev_read_only()。
---
强制性
destroy_buffers() 已消失;请改用 invalidate_bdev()。
---
强制性
fsync_dev() 已消失;请改用 fsync_bdev()。注意:lvm 中断是故意的;一旦 struct block_device * 以合理的方式在该代码中传播,修复将变得微不足道;在那之前,什么也做不了。
强制性
从 ->write_begin 和 ->direct_IO 的错误退出时的块截断从通用方法(block_write_begin、cont_write_begin、nobh_write_begin、blockdev_direct_IO*)移动到调用者。请查看 ext2_write_failed 和调用者以获取示例。
强制性
->truncate 已消失。整个截断序列需要在 ->setattr 中实现,对于实现磁盘大小更改的文件系统,这是强制性的。首先复制旧的 inode_setattr 和 vmtruncate,然后重新排序 vmtruncate + foofs_vmtruncate 序列,使其按照使用 block_truncate_page 或类似辅助函数清零块的顺序、大小更新和最终的磁盘截断(不应失败)的顺序排列。setattr_prepare(过去是 inode_change_ok)现在包括 ATTR_SIZE 的大小检查,并且必须在 ->setattr 的开头无条件调用。
强制性
->clear_inode() 和 ->delete_inode() 已被移除;应使用 ->evict_inode() 代替。无论 inode 是否还有剩余的链接,只要 inode 被驱逐,就会调用此方法。调用者不会驱逐页缓存或 inode 关联的元数据缓冲区;该方法必须使用 truncate_inode_pages_final()
来清除这些。调用者确保在调用 ->evict_inode() 时(或之后)不会有针对该 inode 的异步回写操作正在运行。
->drop_inode() 现在返回 int;它在最后一个 iput()
时被调用,此时 inode->i_lock 被持有,并且如果文件系统希望删除 inode,则返回 true。和以前一样,generic_drop_inode() 仍然是默认方法,并且已进行了相应的更新。generic_delete_inode() 也仍然存在,它只是简单地返回 1。请注意,所有实际的驱逐工作都是由调用者在 ->drop_inode() 返回后完成的。
和以前一样,每次调用 ->evict_inode() 时都必须精确地调用一次 clear_inode() (就像以前每次调用 ->delete_inode() 一样)。与以前不同的是,如果您正在使用 inode 关联的元数据缓冲区 (例如 mark_buffer_dirty_inode()),则您有责任在调用 clear_inode() 之前调用 invalidate_inode_buffers()。
注意:在 ->write_inode() 的开头检查 i_nlink 并如果为零就退出的做法是不够的,而且从来都不够。最终的 unlink() 和 iput()
可能发生在 inode 正在执行 ->write_inode() 的过程中;例如,如果您盲目地释放磁盘上的 inode,则可能会在 ->write_inode() 正在写入它时进行此操作。
---
强制性
.d_delete()
现在仅建议 dcache 是否缓存未引用的目录项,并且现在仅在目录项的引用计数变为 0 时调用。即使在引用计数变为 0 时,它也必须能够容忍被调用 0 次、1 次或多次(例如,常量、幂等)。
---
强制性
.d_compare() 的调用约定和锁定规则已发生重大更改。请阅读 Linux 虚拟文件系统概述 中的更新文档(并查看其他文件系统的示例)以获取指导。
---
强制性
.d_hash() 的调用约定和锁定规则已发生重大更改。请阅读 Linux 虚拟文件系统概述 中的更新文档(并查看其他文件系统的示例)以获取指导。
---
强制性
dcache_lock 已被移除,取而代之的是细粒度锁。有关使用哪些锁来替换 dcache_lock 以保护特定内容的详细信息,请参阅 fs/dcache.c。在大多数情况下,文件系统只需要 ->d_lock,它可以保护给定目录项的所有 dcache 状态。
---
强制性
如果文件系统可以通过 rcu-walk 路径遍历访问其 inode (基本上,如果该文件在 vfs 命名空间中可能有一个路径名),则必须使用 RCU 释放 inode。
即使 i_dentry 和 i_rcu 在一个联合中共享存储空间,我们也会在 inode_init_always() 中初始化前者,所以只需在回调中保持原样即可。以前有必要在那里清理它,但从 3.2 版本开始不再需要这样做了。
---
推荐
vfs 现在尝试在“rcu-walk 模式”下进行路径遍历,从而避免了目录项和 inode 上的原子操作和可伸缩性危害(请参阅 路径名查找)。d_hash 和 d_compare 的更改(如上所述)是支持此功能所需的更改示例。对于更复杂的文件系统回调,vfs 会在文件系统调用之前退出 rcu-walk 模式,因此不需要对文件系统进行更改。但是,这样做代价很高,并且会失去 rcu-walk 模式的优势。我们将开始添加具有 rcu-walk 感知的文件系统回调,如下所示。文件系统应尽可能利用此功能。
---
强制性
d_revalidate 是在每个路径元素上进行的回调(如果文件系统提供此回调),它需要退出 rcu-walk 模式。现在可以在 rcu-walk 模式下调用此回调 (nd->flags & LOOKUP_RCU)。如果文件系统无法处理 rcu-walk,则应返回 -ECHILD。有关更多详细信息,请参阅 Linux 虚拟文件系统概述。
permission 是一个 inode 权限检查,在路径遍历中,它会在许多或所有目录 inode 上调用(以检查执行权限)。现在它必须具有 rcu-walk 感知 (mask & MAY_NOT_BLOCK)。有关更多详细信息,请参阅 Linux 虚拟文件系统概述。
---
强制性
在 ->fallocate() 中,您必须检查传入的模式选项。如果您的文件系统不支持空洞穿孔(释放文件中间的空间),如果模式中设置了 FALLOC_FL_PUNCH_HOLE,则必须返回 -EOPNOTSUPP。目前,您只能将 FALLOC_FL_PUNCH_HOLE 与 FALLOC_FL_KEEP_SIZE 一起设置,因此即使在穿孔文件的末尾时,i_size 也不应更改。
---
强制性
->get_sb() 已被移除。切换为使用 ->mount()。通常,只需从调用 get_sb_
... 切换到 mount_
... 并更改函数类型即可。如果您是手动进行此操作,只需从设置 ->mnt_root 为某个指针切换到返回该指针即可。如果发生错误,则返回 ERR_PTR(...)。
---
强制性
->permission() 和 generic_permission()
丢失了 flags 参数;我们不是传递 IPERM_FLAG_RCU,而是将 MAY_NOT_BLOCK 添加到 mask 中。
generic_permission()
也丢失了 check_acl 参数;ACL 检查已移至 VFS,文件系统需要提供一个非 NULL 的 ->i_op->get_inode_acl 来从磁盘读取 ACL。
---
强制性
如果您实现自己的 ->llseek(),则必须处理 SEEK_HOLE 和 SEEK_DATA。您可以通过返回 -EINVAL 来处理此问题,但是以某种方式支持它会更好。通用处理程序假定整个文件都是数据,并且文件的末尾有一个虚拟空洞。因此,如果提供的偏移量小于 i_size 且指定了 SEEK_DATA,则返回相同的偏移量。如果偏移量为真且给定 SEEK_HOLE,则返回文件末尾。如果偏移量大于或等于 i_size,则在任何一种情况下都返回 -ENXIO。
强制性
如果您有自己的 ->fsync(),则必须确保调用 filemap_write_and_wait_range()
,以便正确同步所有脏页。您还必须记住 ->fsync() 不再使用 i_mutex 持有,因此如果需要 i_mutex 锁定,则必须确保自己获取并释放它。
---
强制性
d_alloc_root() 已被移除,它曾经导致许多因代码误用而导致的错误。替代方法:d_make_root(inode)。成功时,d_make_root(inode) 将分配并返回一个使用传入的 inode 实例化的新目录项。失败时,将返回 NULL 并删除传入的 inode,因此在所有情况下都会消耗对 inode 的引用,并且失败处理无需对 inode 执行任何清理操作。如果将 NULL inode 传递给 d_make_root(inode),它将返回 NULL,并且也不需要进一步的错误处理。典型用法如下
inode = foofs_new_inode(....);
s->s_root = d_make_root(inode);
if (!s->s_root)
/* Nothing needed for the inode cleanup */
return -ENOMEM;
...
---
强制性
女巫死了!好吧,至少死了 2/3。->d_revalidate() 和 ->lookup() 不再接受 struct nameidata;只接受 flags。
---
强制性
->create() 不接受 struct nameidata *
;与前两个不同,它会获得一个“是 O_EXCL 或等效项吗?”的布尔参数。请注意,本地文件系统可以忽略此参数 - 它们保证该对象不存在。而远程/分布式文件系统可能会关心它...
---
强制性
FS_REVAL_DOT 已被移除;如果您以前使用过它,请在您的目录项操作中添加 ->d_weak_revalidate()。
---
强制性
vfs_readdir() 已被移除;切换为使用 iterate_dir() 代替
---
强制性
->readdir() 现在已被移除;切换为使用 ->iterate_shared()
强制性
vfs_follow_link 已被移除。对于普通符号链接,文件系统必须使用来自 ->follow_link 的 nd_set_link,对于魔术 /proc/<pid> 样式的链接,必须使用 nd_jump_link。
---
强制性
iget5_locked()
/ilookup5()
/ilookup5_nowait()
的 test() 回调以前在使用 ->i_lock 和 inode_hash_lock 持有的情况下调用;前者不再使用,因此请验证您的回调是否不依赖于它(树内的任何实例都没有)。当然,inode_hash_lock 仍然持有,因此它们仍然相对于从 inode 哈希中删除的操作进行序列化,并且相对于 iget5_locked()
的 set() 回调进行序列化。
---
强制性
d_materialise_unique() 已被移除;d_splice_alias()
现在可以满足您的一切需求。请记住它们的参数顺序相反 ;-/
---
强制性
f_dentry 已被移除;请使用 f_path.dentry,或者,更好的是,看看是否可以完全避免使用它。
---
强制性
永远不要直接调用 ->read() 和 ->write();请使用 __vfs_{read,write} 或包装器;而不是检查 ->write 或 ->read 是否为 NULL,请在 file->f_mode 中查找 FMODE_CAN_{WRITE,READ}。
---
强制性
请不要对 ->read/->write 使用 new_sync_{read,write};请将其保留为 NULL。
---
- 强制性
->aio_read/->aio_write 已被移除。请使用 ->read_iter/->write_iter。
---
推荐
对于嵌入式(“快速”)符号链接,只需将 inode->i_link 设置为符号链接主体所在的位置,并使用 simple_follow_link() 作为 ->follow_link()。
---
强制性
->follow_link() 的调用约定已更改。我们不是返回 cookie 并使用 nd_set_link() 存储要遍历的主体,而是返回要遍历的主体并使用显式 void ** 参数存储 cookie。根本不会传递 nameidata - nd_jump_link() 不需要它,并且 nd_[gs]et_link() 已被移除。
---
强制性
->put_link() 的调用约定已更改。它获取 inode 而不是目录项,根本不获取 nameidata,并且仅在 cookie 为非 NULL 时调用。请注意,链接主体不再可用,因此如果您需要它,请将其存储为 cookie。
---
强制性
任何可能使用 page_follow_link_light/page_put_link() 的符号链接,必须在任何操作其页缓存之前调用 inode_nohighmem(inode)。高内存页不应出现在此类符号链接的页缓存中。这包括符号链接创建期间可能进行的任何预先播种。page_symlink() 将遵循映射 gfp 标志,因此一旦您完成 inode_nohighmem(),就可以安全使用,但如果您手动分配并插入页面,请确保使用正确的 gfp 标志。
---
强制性
->follow_link() 已被 ->get_link() 替换;API 相同,只是
->get_link() 将 inode 作为单独的参数传递
->get_link() 可以在 RCU 模式下调用 - 在这种情况下,会传递 NULL dentry
---
强制性
->get_link() 现在获得 struct delayed_call *done
,并且应该执行 set_delayed_call(),而不是以前设置的 *cookie
。
->put_link() 已被删除 - 只需将析构函数提供给 ->get_link() 中的 set_delayed_call() 即可。
---
强制性
->getxattr() 和 xattr_handler.get() 将 dentry 和 inode 分开传递。dentry 可能尚未附加到 inode,因此请不要在实例中使用其 ->d_inode。理由:!@#!@# security_d_instantiate()
需要在我们将 dentry 附加到 inode 之前调用。
---
强制性
符号链接不再是唯一在 inode 回收时 i_bdev/i_cdev/i_pipe/i_link 联合体未清零的 inode。因此,您不能假设 ->destroy_inode() 中 ->i_nlink 的非 NULL 值意味着它是一个符号链接。现在确实需要检查 ->i_mode。在树内,我们不得不修复过去使用这种快捷方式的 shmem_destroy_callback();请注意,因为这种快捷方式不再有效。
---
强制性
->i_mutex 现在被 ->i_rwsem 替换。inode_lock() 等函数的工作方式与以前相同 - 它们只是独占地获取锁。但是, ->lookup() 可能会在父目录被共享锁定的情况下被调用。它的实例必须不
单独使用 d_instantiate) 和
d_rehash()
- 请改用d_add()
或d_splice_alias()
。单独使用
d_rehash()
- 请改用调用 d_add(new_dentry, NULL)。在极少数情况下,如果由于某种原因需要排除对文件系统数据结构的(只读)访问,请自行安排。树内的文件系统都没有这种需求。
依赖于在 dentry 被送入
d_add()
或d_splice_alias()
之后 ->d_parent 和 ->d_name 不会更改。同样,树内实例都没有依赖于这一点。
我们保证在同一个目录中对相同名称的查找不会并行发生(“相同”是指您的 ->d_compare() 的意义)。现在,对同一目录中不同名称的查找可以并行发生。
---
强制性
添加了 ->iterate_shared()。仍然提供 struct file
级别的互斥(以及它与同一个 struct file
上的 lseek 之间的互斥),但如果您的目录被多次打开,您可以并行调用这些方法。当然,仍然提供该方法与所有目录修改方法之间的互斥。
如果您有任何被 ->iterate_shared() 修改的每个 inode 或每个 dentry 的内核数据结构,您可能需要一些东西来序列化对它们的访问。如果您进行 dcache 预播种,您需要为此切换到 d_alloc_parallel();请查看树内的示例。
---
强制性
没有 O_CREAT 的 ->atomic_open() 调用可能会并行发生。
---
强制性
->setxattr() 和 xattr_handler.set() 将 dentry 和 inode 分开传递。xattr_handler.set() 获得了 inode 所在挂载的用户命名空间,因此文件系统可以相应地进行 i_uid 和 i_gid 的 ID 映射。dentry 可能尚未附加到 inode,因此请不要在实例中使用其 ->d_inode。理由:!@#!@# security_d_instantiate()
需要在我们将 dentry 附加到 inode 之前调用,并且 !@#!@##!@$!$#!@#$!@$!@$ smack ->d_instantiate()
不仅使用 ->getxattr(),还使用 ->setxattr()。
---
强制性
->d_compare() 不再将父目录作为单独的参数传递。如果您使用它来查找涉及的 struct super_block,dentry->d_sb 也能很好地工作;如果更复杂,请使用 dentry->d_parent。请注意,不要假设多次获取它会产生相同的值 - 在 RCU 模式下,它可能会在您不知情的情况下发生变化。
---
强制性
->rename() 添加了一个 flags 参数。文件系统未处理的任何标志都应导致返回 EINVAL。
---
推荐
->readlink 对于符号链接是可选的。除非文件系统需要为 readlink(2) 伪造某些东西,否则不要设置。
---
强制性
->getattr() 现在传递一个 struct path,而不是单独的 vfsmount 和 dentry,并且现在有 request_mask 和 query_flags 参数来指定 statx 请求的字段和同步类型。不支持任何 statx 特定功能的文件系统可以忽略新参数。
---
强制性
->atomic_open() 的调用约定已更改。已删除 int *opened
以及 FILE_OPENED/FILE_CREATED。取而代之的是 FMODE_OPENED/FMODE_CREATED,在 file->f_mode 中设置。此外,“调用 finish_no_open(),自行打开”这种情况的返回值已变为 0,而不是 1。由于 finish_no_open() 本身现在返回 0,因此 ->atomic_open() 实例的这一部分不需要任何更改。
---
强制性
alloc_file() 现在已变为静态;应使用两个包装器代替。alloc_file_pseudo(inode, vfsmount, name, flags, ops) 用于需要创建 dentry 的情况;这是大多数旧 alloc_file() 用户的情况。调用约定:成功后,返回对新的 struct file
的引用,并且调用者对 inode 的引用被它所取代。失败时,返回 ERR_PTR()
,并且不影响任何调用者的引用,因此调用者需要删除它持有的 inode 引用。alloc_file_clone(file, flags, ops) 不影响任何调用者的引用。成功后,您将获得一个新的 struct file
,该结构与原始文件共享挂载/dentry,失败后 - ERR_PTR()
。
---
强制性
->clone_file_range() 和 ->dedupe_file_range 已被 ->remap_file_range() 替换。有关更多信息,请参阅 Linux 虚拟文件系统概述。
---
推荐
执行以下等效操作的 ->lookup() 实例
if (IS_ERR(inode))
return ERR_CAST(inode);
return d_splice_alias(inode, dentry);
无需费心检查 - 当给定 ERR_PTR(...) 作为 inode 时,d_splice_alias()
将执行正确的操作。此外,将 NULL inode 传递给 d_splice_alias()
也会执行正确的操作(等效于 d_add(dentry, NULL); return NULL;),因此这种特殊情况也不需要单独处理。
---
强烈建议
将 ->destroy_inode() 的 RCU 延迟部分放入一个新方法 - ->free_inode()。如果 ->destroy_inode() 变为空 - 那就更好了,直接将其删除即可。同步工作(例如,无法从 RCU 回调中完成的操作,或任何我们想要堆栈跟踪的 WARN_ON())可能可以移动到 ->evict_inode();但是,这仅适用于不需要平衡 ->alloc_inode() 完成的操作的事情。也就是说,如果它是清理在内核 inode 的生命周期中可能累积的内容,则 ->evict_inode() 可能适合。
inode 销毁规则
如果 ->destroy_inode() 非 NULL,则会调用它
如果 ->free_inode() 非 NULL,则会由
call_rcu()
调度NULL ->destroy_inode 和 NULL ->free_inode 的组合被视为 NULL/free_inode_nonrcu,以保留兼容性。
请注意,回调(无论是通过 ->free_inode() 还是 ->destroy_inode() 中的显式 call_rcu()
)不 与超级块销毁排序;事实上,超级块和所有相关的结构可能已经消失。文件系统驱动程序保证仍然存在,但仅此而已。在回调中释放内存是可以的;执行更多操作是可能的,但需要非常谨慎,最好避免。
---
强制性
DCACHE_RCUACCESS 已被删除;默认情况下,dentry 释放具有 RCU 延迟。DCACHE_NORCU 选择退出,并且只有 d_alloc_pseudo() 可以这样做。
---
强制性
d_alloc_pseudo() 是内部专用的;alloc_file_pseudo() 之外的用法非常可疑(并且在模块中不起作用)。这种用法很可能拼写错误为 d_alloc_anon()。
---
强制性
[本应在 2016 年添加] 尽管 finish_open() 中存在过时的注释,但在 ->atomic_open() 实例中的失败退出不应该 fput() 文件,无论如何。一切都由调用者处理。
---
强制性
clone_private_mount()
现在返回一个长期挂载,因此其结果的正确析构函数是 kern_unmount() 或 kern_unmount_array()。
---
强制性
不允许使用零长度的 bvec 段,它们必须在传递给迭代器之前被过滤掉。
---
强制性
对于基于 bvec 的迭代器,bio_iov_iter_get_pages()
现在不复制 bvec,而是使用提供的 bvec。任何发出 kiocb-I/O 的人都应该确保 bvec 和页面引用保持不变,直到 I/O 完成,即直到 ->ki_complete() 被调用或返回非 -EIOCBQUEUED 代码。
---
强制性
mnt_want_write_file()
现在只能与 mnt_drop_write_file() 配对,而之前它可以与 mnt_drop_write()
配对。
---
强制性
iov_iter_copy_from_user_atomic() 已被移除;请使用 copy_page_from_iter_atomic()。区别在于 copy_page_from_iter_atomic() 会推进迭代器,并且之后你不需要 iov_iter_advance()。但是,如果你决定只使用部分获取的数据,则应该使用 iov_iter_revert()。
---
强制性
file_open_root() 的调用约定已更改;现在它接受 struct path * 而不是单独传递 mount 和 dentry。对于过去传递 <mnt, mnt->mnt_root> 对(即给定挂载的根)的调用者,提供了一个新的辅助函数 - file_open_root_mnt()。内部用户已调整。
---
强制性
no_llseek 已被移除;不要将 .llseek 设置为该值 - 只需将其保留为 NULL 即可。“该文件是否有 llseek(2),或者是否应该以 ESPIPE 失败”的检查应通过查看 file->f_mode 中的 FMODE_LSEEK 来完成。
---
强制性
filldir_t(readdir 回调)的调用约定已更改。现在它返回 bool 而不是返回 0 或 -E...。false 表示“不再继续”(如 -E... 过去那样),true 表示“继续”(如旧调用约定中的 0)。理由:调用者从来不关心特定的 -E... 值。-> iterate_shared() 实例根本不需要更改,树中的所有 filldir_t 实例都已转换。
---
强制性
->tmpfile() 的调用约定已更改。它现在接受一个 struct file
指针而不是 struct dentry 指针。d_tmpfile() 也进行了类似更改,以简化调用者。传递的文件处于非打开状态,并且在成功时必须在返回之前打开(例如,通过调用 finish_open_simple())。
---
强制性
->huge_fault 的调用约定已更改。它现在接受页面顺序而不是 enum page_entry_size,并且可以在不持有 mmap_lock 的情况下调用。所有内部用户都经过了审核,似乎不依赖于持有 mmap_lock,但外部用户应该自行验证。如果他们确实需要它,他们可以返回 VM_FAULT_RETRY,以便在持有 mmap_lock 的情况下调用。
---
强制性
打开块设备和匹配或创建超级块的顺序已更改。
旧的逻辑首先打开块设备,然后尝试根据块设备指针找到合适的超级块进行重用。
新的逻辑首先尝试根据设备号找到合适的超级块,然后再打开块设备。
由于打开块设备不能在 s_umount 下进行,因为存在锁排序要求,因此在打开块设备时现在会放弃 s_umount,并在调用 fill_super() 之前重新获取。
在旧的逻辑中,并发的挂载程序会在文件系统类型的超级块列表中找到超级块。由于块设备的第一个打开者会持有 s_umount,因此他们会等待,直到超级块诞生或由于初始化失败而被丢弃。
由于新的逻辑放弃了 s_umount,并发的挂载程序可以获取 s_umount 并会自旋。相反,现在使用显式的等待-唤醒机制使它们等待,而无需持有 s_umount。
---
强制性
块设备的持有者现在是超级块。
块设备的持有者过去是 file_system_type,这并不是特别有用。如果不匹配存储在超级块中的设备指针,就无法从块设备转到拥有该设备的超级块。此机制仅适用于单个设备,因此块层无法找到任何其他设备的拥有超级块。
在旧的机制中,对于竞速的 mount(2) 和 umount(2),重用或创建超级块依赖于 file_system_type 作为持有者。但是,这方面的文档严重不足。
任何设法获取现有超级块的活动引用的并发挂载程序都会被强制等待,直到超级块准备就绪,或者直到超级块从文件系统类型的超级块列表中删除。如果超级块已准备就绪,则调用者将简单地重用它。
如果挂载程序在
deactivate_locked_super()
之后但在超级块从文件系统类型的超级块列表中删除之前出现,则挂载程序将等待,直到超级块关闭,重用块设备并分配新的超级块。如果挂载程序在
deactivate_locked_super()
之后且在超级块已从文件系统类型的超级块列表中删除之后出现,则挂载程序将重用块设备并分配新的超级块(bd_holder 指针可能仍设置为文件系统类型)。
由于块设备的持有者是 file_system_type,因此任何并发挂载程序都可以打开同一 file_system_type 的任何超级块的块设备,而不会因块设备仍被另一个超级块使用而面临 EBUSY 风险。
使超级块成为块设备的所有者会改变这一点,因为持有者现在是唯一的超级块,因此与其关联的块设备不能被并发挂载程序重用。因此,(2)中的并发挂载程序在尝试打开其持有者是不同超级块的块设备时可能会突然看到 EBUSY。
因此,新的逻辑会等待,直到超级块和设备在 ->kill_sb() 中关闭。从文件系统类型的超级块列表中删除超级块的操作现在被移动到设备关闭之后的较晚时间点。
任何设法获取现有超级块的活动引用的并发挂载程序都会被强制等待,直到超级块准备就绪,或者直到超级块和所有设备在 ->kill_sb() 中关闭。如果超级块已准备就绪,则调用者将简单地重用它。
如果挂载程序在
deactivate_locked_super()
之后但在超级块从文件系统类型的超级块列表中删除之前出现,则会强制挂载程序等待,直到超级块和设备在 ->kill_sb() 中关闭,并且超级块从文件系统类型的超级块列表中删除。挂载程序将分配一个新的超级块并获取块设备的所有权(块设备的 bd_holder 指针将设置为新分配的超级块)。这种情况现在被折叠到(2)中,因为超级块保留在文件系统类型的超级块列表中,直到所有设备在 ->kill_sb() 中关闭。换句话说,如果超级块不再位于文件系统类型的超级块列表中,则它已放弃所有关联的块设备的所有权(bd_holder 指针为 NULL)。
由于这是一个 VFS 级别的更改,它对文件系统没有实际影响,只是所有文件系统都必须使用提供的 kill_litter_super()、kill_anon_super() 或 kill_block_super() 辅助函数之一。
---
强制性
锁排序已更改,以便 s_umount 再次高于 open_mutex。所有在 open_mutex 下获取 s_umount 的地方都已修复。
---
强制性
export_operations ->encode_fh() 不再具有编码 FILEID_INO32_GEN* 文件句柄的默认实现。使用默认实现的文件系统可以使用通用辅助函数 generic_encode_ino32_fh()
显式编码。
---
强制性
如果 ->rename() 在跨目录移动时更新 .. 需要排除目录修改,请不要在你的 ->rename() 中锁定相关的子目录 - 现在由调用者完成 [该项应该在 28eceeda130f “fs: Lock moved directories” 中添加]。
---
强制性
在同一目录下的 ->rename() 中,.. 的(同义反复)更新不受任何锁保护;如果旧的父目录与新的父目录相同,则不要执行此操作。我们真的不能在同一目录下的重命名中锁定两个子目录 - 否则会死锁。
---
强制性
如果 lock_rename() 和 lock_rename_child() 的参数没有共同的祖先,它们可能会在跨目录情况下失败。在这种情况下,将返回 ERR_PTR(-EXDEV),并且不获取任何锁。内部用户已更新;外部用户将需要这样做。
---
强制性
锚定在父 dentry 中的子列表现在变成了 hlist。字段名称已更改(->d_children/->d_sib 而不是 ->d_subdirs/->d_child 用于锚点/条目),因此任何受影响的地方都会立即被编译器捕获。
---
强制性
->d_delete()
实例现在为持有 ->d_lock 且 refcount 等于 0 的 dentry 调用。它们不允许放弃/重新获取 ->d_lock。内部实例都没有执行任何此类操作。请确保你的实例没有...
---
强制性
->d_prune() 实例现在在没有持有父级的 ->d_lock 的情况下调用。dentry 本身上的 ->d_lock 仍然持有;如果需要每个父级的排除项(内部实例都没有),请使用你自己的自旋锁。
->d_iput() 和 ->d_release() 在被移除出父目录的子目录列表之前会被调用。此时,受害者 dentry 仍然在父目录的子目录列表中,它仍然是未哈希的、标记为已删除等等,只是尚未从父目录的 ->d_children 中移除。
任何遍历子目录列表的代码都需要注意可能会在那里看到的半删除状态的 dentry;对这些 dentry 执行 ->d_lock 操作会看到它们为负数、未哈希且引用计数为负,这意味着大多数内核用户在没有任何调整的情况下也会正确处理这种情况。
---
推荐
块设备冻结和解冻操作已移至 holder 操作中。
在此更改之前,get_active_super() 只能找到主块设备的超级块,即存储在 sb->s_bdev 中的那个。现在,块设备冻结操作适用于给定超级块拥有的任何块设备,而不仅仅是主块设备。get_active_super() 辅助函数和 bd_fsfreeze_sb 指针已被移除。
---
强制性
set_blocksize() 现在接受已打开的 struct file
,而不是 struct block_device,并且必须以独占方式打开。