点击上方蓝色“Linux News搬运工”关注我们~
Long-term get_user_pages() and truncate(): solved at last?
By Jonathan Corbet
RDMA等技术都需要能把文件映射到内存page里。现在还有一些persistent-memory设备也需要这样的功能,这样就能把文件内容直接映射到内存,而不需要走kernel的page cache。不过,把文件直接映射到内存这件事情,跟文件系统需要管理文件在磁盘上的存放位置,这两件事情是有些冲突的,尤其是在某些情况下这个file mapping需要长时间保持住的情况(RMDA经常会采取这种策略)。这个问题很难解决,不过现在Ira Weiny提供的一组patch set(编号为V1,000,002)可能是一个比较好的方案。
这个问题,就是如何应对对文件或者persistent memory进行map(通过get_user_pages())以及文件系统管理文件存放方式的冲突,在2019 Linux Storage, Filesystem, and Memory-Management Summit上被提出。核心问题简化下来就是:如果一个进程调用了truncate()而另一个进程还一直通过get_user_pages()持有文件的一部分page,这种情况下该怎么做?假如文件系统真的truncate(截短)了文件,而不管已经map的这些page,那么肯定会出现数据损坏问题。会议上讨论过一些方案,包括要么让truncate()调用返回错误值,或者干脆取消现存的page mapping来让拥有mapping的进程在后续试图访问这些mapped page的时候收到一个SIGBUS signal。两个方案都有不少赞同者,当时没有达成一致结论。
Weiny提供的patch set采取的解决方法是前者,也就是说这个文件如果有一个长期mapping在存在的话,就会让truncate()这些操作直接失败。不过它需要在user space先做一些准备工作然后再创建mapping。这个方案的理念是,在实际工作中,不会有人真希望在某个文件在被RDMA用来传输的同时进行文件的truncate操作,这不是一个合理需求,也就不应该让这个操作正常完成。并且,确保文件系统不要损坏是一个更重要的目标,应该通知application它肯定有什么地方做错了。
Layout leases
这组patch set,却也说明了一个问题,就是在persistent storage上创建长期使用的kernel mapped页面,不是一件很容易做到的事情。任何用户进程如果希望想建立这样的mapping,首先都需要告知kernel自己对这个操作带来的负面影响负责任。这就引入了在kernel里扩展lease(租期)的机制。
进程调用fcntl()的F_SETLEASE命令来获得对某个文件的“lease"(租约)。租约不会阻止其他进程对这个文件所做的多数操作,不过在其他进程想要对文件进行更改的时候,获得租约的进程都会收到一个通知(signal形式的通知)。然后租约拥有者进程可以选择在一段有限的时间之内来完成清理工作,然后释放租约,从而允许这个文件改动继续进行。因此,租约其实并不会保证对文件的独占访问,而是会给租约拥有者进程一个时间窗口来准备处理其他独立的改动。
Kernel里会有一个"layout lease"的概念,不过并没有暴露给user space。Layout lease用在pNFS(parallel NFS,是NFS v4.1规范中的概念)上,用来在多方试图修改在存储设备上文件内容的存储格局时进行仲裁,判断谁拥有更改权限。Weiny的patch set里面,第一步就是把F_LAYOUT这种类型的租约也暴露给user space。这样进程要想把persistent storage上的某个文件映射入kernel memory,就需要利用这种类型的租约来确保其他人改动文件存储布局时不会导致数据出错。此外它还修改了XFS和ext4文件系统,确保在进行文件布局改动的操作之前先停止租约(layout lease),通知对方,然后再进行文件布局更改。
对RDMA使用场景来说,却不应该采取这样的操作(就是unmap这个文件,然后继续进行文件更改操作)。因为这里map好的memory很有可能是正在由网卡设备进行DMA操作,如果unmap的话一定会导致出错。所以这系列patch中第二个patch就加了一个F_EXCLUSIVE flag,用来表明这个租约不能破坏。这种租约的拥有者就不需要再处理其他人进行文件改动导致的通知signal了,其他人如果进行像truncate()这类操作,不会发signal了,而是会直接返回失败,错误信息为ETXTBSY。
最后一步就是改变get_user_pages(),在用FOLL_LONGTERM flag(表示这个mapping会持续向当长时间)来映射某个page之前,需要先检查是否拥有layout lease租约。因此任何application如果想要创建长期mapping的话就需要首先取得独占的租约。这个改变,可能在某种意义上来说是一个ABI change。不过幸好在打上这组patch之前,这种类型的长期mapping根本就没有支持,所以也谈不上会损害现有的application。
需要注意一点,application在map了文件之后,不再需要拥有租约,甚至都不需要继续保持文件在打开状态。如果已经拥有了独占的layout lease租约,那么所有试图修改此文件的layout的尝试都会直接失败。如果没能拿到租约而想做layout change、只要牵涉到一些已经用get_user_pages()锁定了的page,那么也会直接返回失败。也就是说,在对已经long term mapped page的文件进行truncate操作时还是有可能会成功的,只要在操作时没有人拥有独占的layout lease,并且这个truncate操作不要影响到这些long term mapped page。这样的设计主要是为了满足如下使用场景:在persistent-memory storage设备上注册一个文件用来做RDMA操作。
Accounting and the future
其他几个patch主要是用来进行统计分析的。在代码合入之后,一段时间之后肯定会有人来抱怨无法对某个文件进行truncate操作了,这是就需要知道是什么原因导致操作失败。因此/proc下面增加了一些文件节点来提供信息,描述哪些文件有page被映射在内存中,以及谁做的这个操作。
因为这个问题已经热烈讨论一两年了,因此这个patch set没有引出多少反对意见。Dave Chinner对新增用户可见layout lease的名字有些意见,不过他更希望能把代码具体行为描述清楚并敲定。大家都觉得这次的方案是可行的,因此很有可能不久后就能合入upstream主线了。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
极度欢迎将文章分享到朋友圈
热烈欢迎转载以及基于现有协议修改再创作~
长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~