Linux内核中的文件管理¶
本文档描述了文件 ( struct file
) 和文件描述符表 (struct files) 的锁是如何工作的。
在 2.6.12 版本之前,文件描述符表受到锁 (files->file_lock) 和引用计数 (files->count) 的保护。 ->file_lock 保护对该表所有与文件相关的字段的访问。 ->count 用于在使用 CLONE_FILES 标志克隆的任务之间共享文件描述符表。 通常,这适用于 posix 线程。 与内核中常见的引用计数模型一样,最后一个执行 put_files_struct() 的任务会释放文件描述符 (fd) 表。 文件 (struct file
) 本身使用引用计数 (->f_count) 保护。
在新的无锁文件描述符管理模型中,引用计数类似,但锁基于 RCU。 文件描述符表包含多个元素 - fd 集合(open_fds 和 close_on_exec、文件指针数组、集合和数组的大小等)。 为了使更新对无锁读取器呈现原子性,文件描述符表的所有元素都位于一个单独的结构中 - struct fdtable。 files_struct 包含一个指向 struct fdtable 的指针,通过该指针访问实际的 fd 表。 最初,fdtable 嵌入在 files_struct 本身中。 在后续的 fdtable 扩展时,会分配一个新的 fdtable 结构,并且 files->fdtab 指向新的结构。 fdtable 结构使用 RCU 释放,无锁读取器要么看到旧的 fdtable,要么看到新的 fdtable,从而使更新呈现原子性。 以下是 fdtable 结构的锁定规则 -
对 fdtable 的所有引用都必须通过 files_fdtable() 宏完成
struct fdtable *fdt; rcu_read_lock(); fdt = files_fdtable(files); .... if (n <= fdt->max_fds) .... ... rcu_read_unlock();
files_fdtable() 使用
rcu_dereference()
宏,该宏负责无锁解引用所需的内存屏障。 fdtable 指针必须在读取端临界区内读取。如上所述的 fdtable 读取必须受到
rcu_read_lock()
/rcu_read_unlock()
的保护。对于 fd 表的任何更新,都必须持有 files->file_lock。
要查找给定 fd 的文件结构,读取器必须使用 lookup_fdget_rcu() 或 files_lookup_fdget_rcu() API。 这些 API 负责由于无锁查找而产生的屏障要求。
一个例子
struct file *file; rcu_read_lock(); file = lookup_fdget_rcu(fd); rcu_read_unlock(); if (file) { ... fput(file); } ....
由于 fdtable 和 file 结构都可以无锁查找,因此必须使用
rcu_assign_pointer()
API 安装它们。 如果它们是无锁查找的,则必须使用rcu_dereference()
。 但是,建议使用 files_fdtable() 和 lookup_fdget_rcu()/files_lookup_fdget_rcu(),它们可以解决这些问题。更新时,必须在持有 files->file_lock 的情况下查找 fdtable 指针。 如果 ->file_lock 被丢弃,则另一个线程会扩展文件,从而创建一个新的 fdtable 并使之前的 fdtable 指针过时。
例如
spin_lock(&files->file_lock); fd = locate_fd(files, file, start); if (fd >= 0) { /* locate_fd() may have expanded fdtable, load the ptr */ fdt = files_fdtable(files); __set_open_fd(fd, fdt); __clear_close_on_exec(fd, fdt); spin_unlock(&files->file_lock); .....
由于 locate_fd() 可以丢弃 ->file_lock(并重新获取 ->file_lock),因此 fdtable 指针 (fdt) 必须在 locate_fd() 之后加载。
在较新的内核中,基于 rcu 的文件查找已切换为依赖 SLAB_TYPESAFE_BY_RCU 而不是 call_rcu()
。 仅使用 atomic_long_inc_not_zero()
在 rcu 下获取对相关文件的引用已不再足够,因为该文件可能已被回收,并且其他人可能已增加了引用计数。 换句话说,调用者可能会看到来自较新用户的引用计数增加。 因此,有必要在引用计数增加前后验证指针是否相同。 可以在 get_file_rcu() 和 __files_get_rcu() 中看到此模式。
此外,如果不首先在 rcu 查找下获取引用,则无法访问或检查 struct file
中的字段。 不这样做总是非常可疑的,并且它仅适用于 struct file
中的非指针数据。 使用 SLAB_TYPESAFE_BY_RCU,调用者要么首先获取引用,要么必须持有 fdtable 的 files_lock。