ORANGEFS¶
OrangeFS 是一个 LGPL 用户空间横向扩展并行存储系统。它非常适合 HPC、大数据、流媒体视频、基因组学、生物信息学所面临的大规模存储问题。
Orangefs,最初称为 PVFS,由 Walt Ligon 和 Eric Blumer 于 1993 年首次开发,作为并行虚拟机 (PVM) 的并行文件系统,是 NASA 研究并行程序 I/O 模式的资助项目的一部分。
Orangefs 的功能包括
在多个文件服务器之间分配文件数据
支持多个客户端同时访问
使用本地文件系统和访问方法在服务器上存储文件数据和元数据
用户空间实现易于安装和维护
直接 MPI 支持
无状态
邮件列表存档¶
http://lists.orangefs.org/pipermail/devel_lists.orangefs.org/
邮件列表提交¶
文档¶
在单个服务器上运行 ORANGEFS¶
OrangeFS 通常在具有多个服务器和客户端的大型安装中使用,但是可以在单个机器上运行完整的文件系统,用于开发和测试。
在 Fedora 上,安装 orangefs 和 orangefs-server
dnf -y install orangefs orangefs-server
/etc/orangefs/orangefs.conf 中有一个示例服务器配置文件。如有必要,将 localhost 更改为您的主机名。
要生成一个用于运行 xfstests 的文件系统,请参阅下文。
/etc/pvfs2tab 中有一个示例客户端配置文件。它是一行。取消注释,并在必要时更改主机名。这控制使用 libpvfs2 的客户端。这不控制 pvfs2-client-core。
创建文件系统
pvfs2-server -f /etc/orangefs/orangefs.conf
启动服务器
systemctl start orangefs-server
测试服务器
pvfs2-ping -m /pvfsmnt
启动客户端。模块必须在此之前编译或加载
systemctl start orangefs-client
挂载文件系统
mount -t pvfs2 tcp://127.0.0.1:3334/orangefs /pvfsmnt
用户空间文件系统源¶
http://www.orangefs.org/download
2.9.3 之前的 Orangefs 版本与内核客户端的上游版本不兼容。
在单个服务器上构建 ORANGEFS¶
如果无法从发行版包安装 OrangeFS,则可以从源代码构建它。
如果您不介意将内容散落在 /usr/local 中,则可以省略 --prefix。从 2.9.6 版本开始,OrangeFS 默认使用 Berkeley DB,我们可能很快会将默认值更改为 LMDB。
./configure --prefix=/opt/ofs --with-db-backend=lmdb --disable-usrint
make
make install
通过运行 pvfs2-genconfig 并指定目标配置文件来创建 orangefs 配置文件。 Pvfs2-genconfig 将提示您完成整个过程。通常,采用默认值即可,但是在出现该问题时,应使用服务器的主机名,而不是“localhost”
/opt/ofs/bin/pvfs2-genconfig /etc/pvfs2.conf
创建 /etc/pvfs2tab 文件(localhost 即可)
echo tcp://127.0.0.1:3334/orangefs /pvfsmnt pvfs2 defaults,noauto 0 0 > \
/etc/pvfs2tab
如果需要,创建您在 tab 文件中指定的挂载点
mkdir /pvfsmnt
引导服务器
/opt/ofs/sbin/pvfs2-server -f /etc/pvfs2.conf
启动服务器
/opt/ofs/sbin/pvfs2-server /etc/pvfs2.conf
现在服务器应该正在运行。 Pvfs2-ls 是一个简单的测试,用于验证服务器是否正在运行
/opt/ofs/bin/pvfs2-ls /pvfsmnt
如果一切正常,请加载内核模块并启动客户端核心
/opt/ofs/sbin/pvfs2-client -p /opt/ofs/sbin/pvfs2-client-core
挂载您的文件系统
mount -t pvfs2 tcp://`hostname`:3334/orangefs /pvfsmnt
运行 xfstests¶
使用 xfstests 使用临时文件系统非常有用。这只能通过一台服务器完成。
在服务器配置文件 /etc/orangefs/orangefs.conf 中制作 FileSystem 部分的第二个副本。将 Name 更改为 scratch。将 ID 更改为与第一个 FileSystem 部分的 ID 不同的值(2 通常是一个不错的选择)。
然后有两个 FileSystem 部分:orangefs 和 scratch。
此更改应在创建文件系统之前进行。
pvfs2-server -f /etc/orangefs/orangefs.conf
要运行 xfstests,请创建 /etc/xfsqa.config
TEST_DIR=/orangefs
TEST_DEV=tcp://127.0.0.1:3334/orangefs
SCRATCH_MNT=/scratch
SCRATCH_DEV=tcp://127.0.0.1:3334/scratch
然后可以运行 xfstests
./check -pvfs2
选项¶
接受以下挂载选项
- acl
允许在文件和目录上使用访问控制列表。
- intr
内核客户端和用户空间文件系统之间的一些操作可以中断,例如调试级别的更改和可调参数的设置。
- local_lock
从“此”内核的角度启用 posix 锁定。默认的 file_operations 锁操作是返回 ENOSYS。如果文件系统挂载了 -o local_lock,则启用 Posix 锁定。未来正在开发分布式锁定。
调试¶
如果您希望特定源文件(例如 inode.c)中的调试 (GOSSIP) 语句转到 syslog
echo inode > /sys/kernel/debug/orangefs/kernel-debug
不进行调试(默认)
echo none > /sys/kernel/debug/orangefs/kernel-debug
来自多个源文件的调试
echo inode,dir > /sys/kernel/debug/orangefs/kernel-debug
所有调试
echo all > /sys/kernel/debug/orangefs/kernel-debug
获取所有调试关键字的列表
cat /sys/kernel/debug/orangefs/debug-help
内核模块和用户空间之间的协议¶
Orangefs 是一个用户空间文件系统和一个关联的内核模块。 从现在开始,我们将 Orangefs 的用户空间部分简称为“用户空间”。 Orangefs 源自 PVFS,用户空间代码仍然使用 PVFS 来表示函数和变量名称。 用户空间 typedef 了许多重要的结构。 内核模块中的函数和变量名称已转换为“orangefs”,并且 Linux 编码风格避免使用 typedef,因此与用户空间结构对应的内核模块结构不会被 typedef。
内核模块实现了一个伪设备,用户空间可以从中读取和写入。 用户空间还可以通过 ioctl 操作伪设备来操作内核模块。
Bufmap¶
在启动时,用户空间会分配两个页面大小对齐 (posix_memalign) 的 mlocked 内存缓冲区,一个用于 IO,一个用于 readdir 操作。 IO 缓冲区为 41943040 字节,readdir 缓冲区为 4194304 字节。 每个缓冲区都包含逻辑块或分区,并且每个缓冲区的指针都会添加到其自己的 PVFS_dev_map_desc 结构中,该结构还描述了其总大小以及分区的大小和数量。
IO 缓冲区的 PVFS_dev_map_desc 结构的指针通过 ioctl 发送到内核模块中的映射例程。 该结构使用 copy_from_user 从用户空间复制到内核空间,并用于初始化内核模块的“bufmap”(struct orangefs_bufmap),然后其中包含
refcnt - 引用计数器
desc_size - PVFS2_BUFMAP_DEFAULT_DESC_SIZE (4194304) - IO 缓冲区的分区大小,表示文件系统的块大小,并用于超级块中的 s_blocksize。
desc_count - PVFS2_BUFMAP_DEFAULT_DESC_COUNT (10) - IO 缓冲区中分区的数量。
desc_shift - log2(desc_size),用于超级块中的 s_blocksize_bits。
total_size - IO 缓冲区的总大小。
page_count - IO 缓冲区中 4096 字节页面的数量。
page_array - 指向
page_count * (sizeof(struct page*))
字节的 kcalloced 内存的指针。此内存通过对 get_user_pages 的调用用作指向 IO 缓冲区中每个页面的指针数组。desc_array - 指向
desc_count * (sizeof(struct orangefs_bufmap_desc))
字节的 kcalloced 内存的指针。此内存将进一步初始化user_desc 是内核的 IO 缓冲区 ORANGEFS_dev_map_desc 结构的副本。 user_desc->ptr 指向 IO 缓冲区。
pages_per_desc = bufmap->desc_size / PAGE_SIZE offset = 0 bufmap->desc_array[0].page_array = &bufmap->page_array[offset] bufmap->desc_array[0].array_count = pages_per_desc = 1024 bufmap->desc_array[0].uaddr = (user_desc->ptr) + (0 * 1024 * 4096) offset += 1024 . . . bufmap->desc_array[9].page_array = &bufmap->page_array[offset] bufmap->desc_array[9].array_count = pages_per_desc = 1024 bufmap->desc_array[9].uaddr = (user_desc->ptr) + (9 * 1024 * 4096) offset += 1024buffer_index_array - 一个 desc_count 大小的整数数组,用于指示 IO 缓冲区的哪些分区可供使用。
buffer_index_lock - 一个自旋锁,用于在更新期间保护 buffer_index_array。
readdir_index_array - 一个五 (ORANGEFS_READDIR_DEFAULT_DESC_COUNT) 个元素的整数数组,用于指示 readdir 缓冲区的哪些分区可供使用。
readdir_index_lock - 一个自旋锁,用于在更新期间保护 readdir_index_array。
操作¶
当内核模块需要与用户空间通信时,它会构建一个“op”(struct orangefs_kernel_op_s)。op 的一部分包含“upcall”,它表示对用户空间的请求。 op 的一部分最终包含“downcall”,它表示请求的结果。
slab 分配器用于保留方便的 op 结构缓存。
在 init 时,内核模块定义并初始化一个请求列表和一个正在进行中的哈希表,以跟踪在任何给定时间正在进行的所有操作。
Ops 是有状态的
- 未知
op 刚刚初始化
- 等待中
op 位于 request_list 上(向上绑定)
- 正在进行
op 正在进行中(等待 downcall)
- 已服务
op 具有匹配的 downcall;好的
- 已清除
由于客户端核心在服务 op 之前不干净地退出,op 必须启动一个计时器
- 放弃
提交者已放弃等待它
当某些任意用户空间程序需要在 Orangefs 上执行文件系统操作(readdir、I/O、创建等)时,会初始化一个 op 结构,并用一个不同的 ID 号标记。 填充 op 的 upcall 部分,并将 op 传递给“service_operation”函数。
Service_operation 将 op 的状态更改为“waiting”,将其放在请求列表中,并通过等待队列向 Orangefs file_operations.poll 函数发出信号。 用户空间正在轮询伪设备,因此意识到需要读取的 upcall 请求。
当触发 Orangefs file_operations.read 函数时,将搜索请求列表以查找似乎准备好处理的 op。 从请求列表中删除 op。 来自 op 的标记和填充的 upcall 结构 copy_to_user 返回到用户空间。
如果这些(和一些其他协议)copy_to_user 失败,则将 op 的状态设置为“waiting”,并将 op 添加回请求列表。 否则,将 op 的状态更改为“in progress”,并在其标记上对其进行哈希处理,并将其放在 in_progress 哈希表中标记哈希到的索引处的列表末尾。
当用户空间组装好对上调的响应后,它会将包含区分标签的响应写回伪设备,通过一系列 io_vecs。 这会触发 Orangefs 的 file_operations.write_iter 函数,找到带有相关标签的操作,并将其从 in_progress 哈希表中移除。 只要操作的状态不是“已取消”或“已放弃”,其状态就会被设置为“已服务”。 file_operations.write_iter 函数返回到等待的 vfs,并通过 wait_for_matching_downcall 返回到 service_operation。
服务操作会将其下调部分(对上调的响应)填充完整后返回给其调用者。
“客户端核心”是内核模块和用户空间之间的桥梁。 客户端核心是一个守护进程。 客户端核心有一个相关的看门狗守护进程。 如果客户端核心被信号通知死亡,看门狗守护进程会重启客户端核心。 即使客户端核心“立即”重启,在这样的事件中,客户端核心也会有一段时间是死亡的。 死亡的客户端核心无法被 Orangefs 的 file_operations.poll 函数触发。 在“死亡期”内通过 service_operation 的操作可能会在等待队列中超时,并且会尝试回收它们一次。 显然,如果客户端核心死亡时间过长,尝试使用 Orangefs 的任意用户空间进程会受到负面影响。 无法服务的等待操作将从请求列表中移除,并将其状态设置为“已放弃”。 无法服务的正在进行的操作将从 in_progress 哈希表中移除,并将其状态设置为“已放弃”。
Readdir 和 I/O 操作在其有效负载方面是非典型的。
readdir 操作使用两个预先分配的预分区内存缓冲区中较小的那个。 readdir 缓冲区仅对用户空间可用。 内核模块在启动 readdir 操作之前获取一个空闲分区的索引。 用户空间将结果存入索引分区,然后将其写回 pvfs 设备。
io(读和写)操作使用两个预先分配的预分区内存缓冲区中较大的那个。 IO 缓冲区可以从用户空间和内核模块访问。 内核模块在启动 io 操作之前获取一个空闲分区的索引。 内核模块将写入数据存入索引分区,以供用户空间直接使用。 用户空间将读取请求的结果存入索引分区,以供内核模块直接使用。
对内核请求的响应都打包在 pvfs2_downcall_t 结构体中。 除了其他几个成员之外,pvfs2_downcall_t 还包含一个结构体联合,每个结构体都与特定的响应类型相关联。
联合之外的几个成员是
int32_t type
操作的类型。
int32_t status
操作的返回代码。
int64_t trailer_size
除非是 readdir 操作,否则为 0。
char *trailer_buf
初始化为 NULL,在 readdir 操作期间使用。
对于任何特定的响应,都会填充联合内部的相应成员。
- PVFS2_VFS_OP_FILE_IO
填充 pvfs2_io_response_t
- PVFS2_VFS_OP_LOOKUP
填充 PVFS_object_kref
- PVFS2_VFS_OP_CREATE
填充 PVFS_object_kref
- PVFS2_VFS_OP_SYMLINK
填充 PVFS_object_kref
- PVFS2_VFS_OP_GETATTR
填充 PVFS_sys_attr_s(内核不需要的大量内容)当对象是符号链接时,用链接目标填充字符串。
- PVFS2_VFS_OP_MKDIR
填充 PVFS_object_kref
- PVFS2_VFS_OP_STATFS
用无用的信息填充 pvfs2_statfs_response_t <g>。 我们很难及时了解有关分布式网络文件系统的这些统计信息。
- PVFS2_VFS_OP_FS_MOUNT
填充 pvfs2_fs_mount_response_t,它就像一个 PVFS_object_kref,只是其成员顺序不同,“__pad1”被替换为“id”。
- PVFS2_VFS_OP_GETXATTR
填充 pvfs2_getxattr_response_t
- PVFS2_VFS_OP_LISTXATTR
填充 pvfs2_listxattr_response_t
- PVFS2_VFS_OP_PARAM
填充 pvfs2_param_response_t
- PVFS2_VFS_OP_PERF_COUNT
填充 pvfs2_perf_count_response_t
- PVFS2_VFS_OP_FSKEY
填充 pvfs2_fs_key_response_t
- PVFS2_VFS_OP_READDIR
将表示 pvfs2_readdir_response_t 所需的一切信息塞入上调中指定的 readdir 缓冲区描述符中。
用户空间在 /dev/pvfs2-req 上使用 writev() 将响应传递给内核端发出的请求。
包含以下内容的 buffer_list
指向内核请求的已准备好的响应的指针(struct pvfs2_downcall_t)。
并且,在 readdir 请求的情况下,还包含一个指向包含目标目录中对象描述符的缓冲区的指针。
...被发送到执行 writev 的函数(PINT_dev_write_list)。
PINT_dev_write_list 有一个本地 iovec 数组:struct iovec io_array[10];
io_array 的前四个元素像这样为所有响应初始化
io_array[0].iov_base = address of local variable "proto_ver" (int32_t)
io_array[0].iov_len = sizeof(int32_t)
io_array[1].iov_base = address of global variable "pdev_magic" (int32_t)
io_array[1].iov_len = sizeof(int32_t)
io_array[2].iov_base = address of parameter "tag" (PVFS_id_gen_t)
io_array[2].iov_len = sizeof(int64_t)
io_array[3].iov_base = address of out_downcall member (pvfs2_downcall_t)
of global variable vfs_request (vfs_request_t)
io_array[3].iov_len = sizeof(pvfs2_downcall_t)
Readdir 响应像这样初始化第五个元素 io_array
io_array[4].iov_base = contents of member trailer_buf (char *)
from out_downcall member of global variable
vfs_request
io_array[4].iov_len = contents of member trailer_size (PVFS_size)
from out_downcall member of global variable
vfs_request
Orangefs 利用 dcache 以避免向用户空间发送冗余请求。 我们使用 orangefs_inode_getattr 使对象 inode 属性保持最新。 Orangefs_inode_getattr 使用两个参数来帮助它决定是否更新 inode:“new”和“bypass”。 Orangefs 将私有数据保存在对象的 inode 中,其中包括一个短超时值 getattr_time,它允许 orangefs_inode_getattr 的任何迭代知道自上次更新 inode 以来已经过了多长时间。 当对象不是新的 (new == 0) 并且未设置 bypass 标志 (bypass == 0) 时,如果 getattr_time 没有超时,orangefs_inode_getattr 会返回而不更新 inode。 每次更新 inode 时,getattr_time 都会更新。
创建新对象(文件、目录、符号链接)包括评估其路径名,从而导致该对象的否定目录项。 分配一个新的 inode 并将其与 dentry 相关联,将其从否定 dentry 变为“富有成效的社会成员”。 Orangefs 使用 new_inode()
从 Linux 获取新的 inode,并通过 d_instantiate()
将该对发送回 Linux,从而将 inode 与 dentry 相关联。
对对象路径名的评估解析为其对应的 dentry。 如果没有对应的 dentry,则会在 dcache 中为其创建一个。 每当 dentry 被修改或验证时,Orangefs 都会在 dentry 的 d_time 中存储一个短超时值,并且 dentry 将在该时间内被信任。 Orangefs 是一个网络文件系统,对象可能会与任何特定的 Orangefs 内核模块实例进行带外更改,因此信任 dentry 是有风险的。 信任 dentry 的替代方法是始终从用户空间获取所需的信息 - 至少访问客户端核心,可能还需要访问服务器。 从 dentry 获取信息很便宜,而从用户空间获取信息相对昂贵,因此有动机尽可能使用 dentry。
超时值 d_time 和 getattr_time 是基于节拍数的,并且代码旨在避免节拍数环绕问题
"In general, if the clock may have wrapped around more than once, there
is no way to tell how much time has elapsed. However, if the times t1
and t2 are known to be fairly close, we can reliably compute the
difference in a way that takes into account the possibility that the
clock may have wrapped between times."
来自讲师 Andy Wang 的课程笔记