LWN:对user-space pages进行明确的pin操作!

关注了就能看到更多这么棒的文章哦~

Explicit pinning of user-space pages

By Jonathan Corbet
December 13, 2019

原文来自:https://lwn.net/Articles/807108/

get_user_pages()和它所导致的kernel问题,已经在LWN上有过多篇介绍了,看这里:https://lwn.net/Kernel/Index/#Memory_management-get_user_pages 。简单来说,get_user_pages()是用来把user-space pages给钉在memory里面,供其他人(除了拥有它以外的其他人)来进行某种操作。这些操作可能会让kernel里其他一些以为对这个page有独占访问权限的部分碰到一些意外情况。John Hubbard提出了一组patch,虽然没有解决所有问题,但也是建立了一个解决这类问题的通用框架。

简要来说,get_user_pages()相关的问题有两种形式。一种是当kernel以为这个page里的内容不会改变的时候,某个外设却在这里进行了写入操作。另一种情况,如果这个memory是位于文件系统管理的persistent-memory设备上的情况,把page pin到memory的话,文件系统就不能对跟这个page有关的memory进行layout调整了。这个问题目前的解决办法是不允许在persistent-memory设备上进行这种非易失性page相关的pin操作,不过确实有些应用场景里面真的需要这样做,所以还是需要更好的解决方案。

这里部分问题来自get_user_pages()不会对pin到RAM的page进行任何专门记录。虽然会增长引用计数来确保不会被挤出memory,不过这样pin进内存的page跟其他那些途径pin进来的page完全无法区分。这样一来,我们虽然可以知道某个page是否有引用,却没有办法知道page是否是因为DMA I/O等目的pin进来的。

Hubbard的patch set就是针对这一点的。首先引入了一些新的内部函数来作为get_user_pages()系列函数的替代:

    long pin_user_pages(unsigned long start, unsigned long nr_pages,
		    	unsigned int gup_flags, struct page **pages,
		    	struct vm_area_struct **vmas);
    long pin_user_pages_remote(struct task_struct *tsk, struct mm_struct *mm,
			       unsigned long start, unsigned long nr_pages,
			       unsigned int gup_flags, struct page **pages,
			       struct vm_area_struct **vmas, int *locked);
    int pin_user_pages_fast(unsigned long start, int nr_pages,
			    unsigned int gup_flags, struct page **pages);

在调用者看来,这些新的函数跟之前的get_user_pages()系列没有什么不同。所以切换起来很容易,只要替换一下调用的函数名即可。用这种方法pin进来的page在释放的时候必须要用新增的unpin_user_page()和unpin_user_pages()函数,这些是Hubbard在2019年上半年加进来替代put_user_page()的。

开发者该如何选择get_user_pages()和pin_user_pages()呢?在patch里增加的文档里面有描述。简单来说,如果pin page的目的仅仅是为了访问这些page里的内容,那么应该使用pin_user_pages()。如果是为了操作page structure而不是访问page内容本身,那么应给使用get_user_pages()。

新增的函数会告知kernel这次调用的目的,不过大家肯定还想知道如何记录管理这些pin进来的page。肯定需要某种引用计数,因为某个page可能会被pin多次,那么直到最后一个用户也调用unpin_user_pages()来释放后才会解除pin状态。理论上来说应该把引用计数放到struct page里面,不过有个小问题:这个struct里面的内容已经塞得很满了,又不能增大这个结构。

最终选择的方案是继续利用page现有的引用计数。调用get_user_pages()会导致引用计数加一从而完成pin的操作。而调用pin_user_pages()则会给它加GUP_PIN_COUNTING_BIAS,在这组patch的第23个patch里面定义为1024。Kernel代码可以通过调用page_dma_pinned()来确定某个page是否是这样pin进来的,其实只要简单检查page的引用计数是否超过了1024。

这样使用引用计数的话会有一些需要注意的地方。如果某个page通过正常方式增加引用计数而超过了1024的话,就会被误认为是pin进来做DMA的。patch set里也承认有这个行为,不过并不认为是一个问题,因为这样导致的误报并不会影响系统的行为。还有一个可能影响更大的问题,这个引用计数只有21个bit的空间,这意味着关于pin本身的引用计数就只有11个bit可用了。对多数情况来说应该够用了,不过如果是在pin一个compound page的时候,在每次pin一个tail page的时候都要把第一个page再pin一次。一个1GB的compound page包含256个4KB page,这样一来,这个page只要被pin 8次就会导致引用计数溢出了。

Hubbard认为解决方法是让get_user_pages()系列函数都要能认识huge page,如此一来引用计数就只需要增加一次就好了。不过这需要做不少改动,需要一些时间,因此没有包含在这组patch set里面,毕竟目前已经有了25个patch,改动已经很大了。

还有一点需要注意:kernel应该如何处理这样pin进来的page呢?也就是Hubbard所问的:“碰到这种情况之后该如何处理,留待后续patch吧”。比如说可以按Ira Weiny提出的layout lease方案,通过提供一个机制来让那些长期保持pin状态的page在有需要的时候可以unpin掉。目前还不清楚应该如何实现这个机制,可以说get_user_pages()的完整解决方案还需要不少时间。预计在2020 Linux Storage, Filesystem, and Memory-Management Summit峰会上会是一个热门议题。

至少kernel目前已经能够利用这个机制来识别并跟踪pinned page了,这是我们朝正确方向迈出的一小步。这些patch已经经过了多轮迭代,可能很快会合入Andrew Morton的-mm tree了。这样很可能会合入5.6 kernel中。

全文完

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

你可能感兴趣的:(LWN:对user-space pages进行明确的pin操作!)