ORANGEFS

OrangeFS是一个LGPL用户空间横向扩展的并行存储系统。它非常适合HPC,大数据,流视频,基因组学,生物信息学面临的大型存储问题。

Orangefs,最初称为PVFS,于1993年由Walt Ligon和Eric Blumer首次开发,作为并行虚拟机(PVM)的并行文件系统,作为NASA授予的研究并行程序I/O模式的资金的一部分。

Orangefs的特性包括

  • 在多个文件服务器之间分配文件数据

  • 支持多个客户端同时访问

  • 使用本地文件系统和访问方法在服务器上存储文件数据和元数据

  • 用户空间实现易于安装和维护

  • 直接MPI支持

  • 无状态

邮件列表存档

http://lists.orangefs.org/pipermail/devel_lists.orangefs.org/

邮件列表提交

devel@lists.orangefs.org

文档

http://www.orangefs.org/documentation/

在单服务器上运行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://localhost: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://localhost: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部分的第二个副本。 将名称更改为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://localhost:3334/orangefs
SCRATCH_MNT=/scratch
SCRATCH_DEV=tcp://localhost: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结构中,该结构还描述了其总大小,以及分区的大小和数量。

通过ioctl将指向IO缓冲区的PVFS_dev_map_desc结构的指针发送到内核模块中的映射例程。 该结构使用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 += 1024
    
  • buffer_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结构缓存方便。

在初始化时,内核模块定义并初始化一个请求列表和一个in_progress哈希表,以跟踪在任何给定时间正在进行的所有操作。

操作是有状态的

  • 未知
    • 操作刚刚初始化

  • 等待
    • 操作位于request_list上(向上绑定)

  • 进行中
    • 操作正在进行中(等待downcall)

  • 已服务
    • 操作具有匹配的downcall; 好的

  • 已清除
    • 操作必须启动一个计时器,因为客户端核心在服务操作之前不干净地退出

  • 放弃
    • 提交者已放弃等待它

当一些任意用户空间程序需要在Orangefs上执行文件系统操作(readdir,I/O,create等)时,初始化一个op结构并标记一个区分ID号。 填写op的upcall部分,并将op传递给“service_operation”函数。

Service_operation将op的状态更改为“waiting”,将其放在请求列表中,并通过等待队列向Orangefs file_operations.poll函数发出信号。 用户空间正在轮询伪设备,因此意识到需要读取的upcall请求。

当触发Orangefs file_operations.read函数时,会在请求列表中搜索似乎已准备好处理的操作。 从请求列表中删除该操作。 op中的标记和已填写的upcall结构通过copy_to_user复制回用户空间。

如果任何这些(以及一些附加协议)copy_to_users失败,则将op的状态设置为“waiting”,并将op添加回请求列表。 否则,将op的状态更改为“in progress”,并将其哈希到其标记上,并将其放在in_progress哈希表中标记哈希到的索引处的列表末尾。

当用户空间组装好对upcall的响应时,它会将响应(包括区分标记)以一系列io_vecs的形式写回到伪设备。 这会触发Orangefs file_operations.write_iter函数来查找具有关联标记的操作并将其从in_progress哈希表中删除。 只要op的状态不是“canceled”或“given up”,其状态就会设置为“serviced”。 file_operations.write_iter函数返回到等待的vfs,并通过wait_for_matching_downcall返回到service_operation。

Service operation返回其调用方,并填写op的downcall部分(对upcall的响应)。

“client-core”是内核模块和用户空间之间的桥梁。 client-core是一个守护程序。 client-core具有关联的监视程序守护程序。 如果client-core被信号通知死亡,则监视程序守护程序会重新启动client-core。 即使client-core“立即”重新启动,在此类事件期间,client-core也会死亡一段时间。 无法通过Orangefs file_operations.poll函数触发死亡的client-core。 在“死亡时间”期间通过service_operation的操作可能会在等待队列上超时,并且会尝试回收它们。 显然,如果client-core死亡时间太长,则尝试使用Orangefs的任意用户空间进程将受到不利影响。 无法服务的等待操作将从请求列表中删除,并将其状态设置为“given up”。 无法服务的进行中操作将从in_progress哈希表中删除,并将其状态设置为“given up”。

Readdir和I/O操作在其有效负载方面是非典型的。

  • readdir操作使用两个预先分配的预先分区内存缓冲区中较小的一个。 readdir缓冲区仅适用于用户空间。 内核模块在启动readdir操作之前获取空闲分区的索引。 用户空间将结果存入索引分区中,然后将它们写回pvfs设备。

  • io(读取和写入)操作使用两个预先分配的预先分区内存缓冲区中较大的一个。 IO缓冲区可从用户空间和内核模块访问。 内核模块在启动io操作之前获取空闲分区的索引。 内核模块将写入数据存入索引分区中,以供用户空间直接使用。 用户空间将读取请求的结果存入索引分区中,以供内核模块直接使用。

对内核请求的响应都封装在pvfs2_downcall_t结构中。 除了其他一些成员外,pvfs2_downcall_t还包含一个结构联合,每个结构都与特定的响应类型相关联。

联合之外的几个成员是

int32_t 类型
  • 操作类型。

int32_t 状态
  • 操作的返回代码。

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

将所有需要的内容塞入readdir缓冲区描述符中,该描述符在upcall中指定,以表示pvfs2_readdir_response_t。

用户空间使用/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基于jiffy,并且代码旨在避免jiffy-wrap问题

"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的课程笔记