|
|
Log in / Subscribe / Register

Improving support for large, contiguous allocations

By Jonathan Corbet
May 1, 2018

LSFMM
Allocating chunks of memory that are both large and physically contiguous has long been a difficult thing to do in the kernel. But there are times where there is no alternative. Two sessions in the memory-management track of the 2018 Linux Storage, Filesystem, and Memory-Management Summit explored ways of making those allocations more reliable. It turns out that some use cases have a rather larger value of "large" than others.

The contiguous memory allocator

Allocating large, physically contiguous blocks of memory becomes increasingly difficult over the life of the system as memory gets more fragmented. Most kernel code goes out of its way to avoid such allocations, but there are places where they are necessary; for such situations, the contiguous memory allocator (CMA) can be used.

In her session on CMA, Fedora kernel maintainer Laura Abbott said that it relies on the kernel's "page block" mechanism, and it needs both reclaim and compaction to work [Laura Abbott] properly. That is because CMA counts on being able to move other allocations out of the way to create large blocks when the need arises. Page blocks are a relatively small management unit in most kernels, being the same size as the smallest huge page by default. Some trouble comes up on ARM systems, though, when 64KB pages are in use; at that page size, a page block holds 512MB. CMA requires regions to be page-block aligned, but creating a 512MB block for CMA on a memory-constrained system like a Raspberry Pi does not work well.

The current workarounds are to either do without CMA, or to use 4KB pages, neither of which is entirely appealing. Using smaller pages, in particular, would require Fedora to ship two kernels using different page sizes, and distributors will go far out of their way to avoid supporting multiple kernels if at all possible. Matthew Wilcox said that it might be interesting to add the ability for the kernel to automatically choose the page size when it boots, but that would not be a straightforward thing to implement.

One possible solution is to tie CMA to the ZONE_MOVABLE zone, which also would solve a number of accounting problems. CMA would still be bound by the page-block size, though. There was some talk of maybe reducing the page-block size, which would address this problem, but it might come at the cost of support for huge pages. Nonetheless, changing the size of page blocks is easily supported in the kernel now, so it might be the cleanest path to a short-term solution. At the end of the session, though, Michal Hocko noted that the page-block size is an arbitrary software construct; if page-block alignment requirements are causing trouble, "we've done it to ourselves", he said.

Going larger

The following session, led by Mike Kravetz, made it clear that "large" is a relative term. While CMA tends to be used for allocations measured in megabytes, he is working with a Mellanox RDMA controller that performs best when given a 2GB physically contiguous buffer to work with. While CMA allocations are performed within the kernel, this buffer is allocated in user space and registered with the driver. Needless to say, there can be some challenges involved in making it possible for that allocation to succeed.

The hugetlbfs mechanism supports "gigantic page" allocations; it can provide 1GB pages on the x86 architecture. Given the tricks involved in making that work, Kravetz said, the code that does [Mike Kravetz] this in hugetlbfs probably shouldn't be there. But it is a useful technique, and other use cases, such as Intel's patch set that goes under the concise name of "resource director technology cache pseudo locking", are starting to pop up as well.

Kravetz has been looking into alternatives. One of those was to add a MAP_CONTIG option to the mmap() system call that would cause the new mapping to be populated with physically contiguous pages. That turns out to give user space too much control over how allocations are made, though.

So he is looking instead at adding a new allocation function called find_alloc_contig_pages(). This function, meant to be called from within the driver, replicates much of the behavior found in the hugetlbfs allocator. It requires that the system support page migration so that the huge pages can be cleared if needed. The memory it allocates is in the movable zone, which may well be a problem for buffers meant to be used with peripheral devices.

The actual use case for this functionality lies in the Oracle database, which needs to register global areas for RDMA transfers. For hardware performance, a 2GB physically contiguous area is preferred. The CPU, though, needs to see this area as 2MB huge pages, which perform better than 1GB gigantic pages on current hardware. This memory could be allocated with hugetlbfs, Kravetz said, or the kernel could just allocate contiguous areas at boot.

He was seemingly hoping for some guidance from the developers on which of these approaches might be the least ugly. The session ran out of time, though, without a definitive conclusion on the best way to obtain this kind of memory allocation.

Index entries for this article
KernelMemory management/Large allocations
ConferenceStorage, Filesystem, and Memory-Management Summit/2018


to post comments

Improving support for large, contiguous allocations

Posted May 3, 2018 0:32 UTC (Thu) by thoughtpolice (subscriber, #87455) [Link] (1 responses)

The size of "huge" indeed varies. I had a similar problem at $WORK very recently. We have FPGAs which can DMA to arbitrary memory over a bus. On an ARM system we use CMA with a driver to allocate a small range of space (~100MB), which works fine and is just about the right size. (The page block issue brought up by Laura seemingly hasn't been a problem for us, but our ARM systems have a bit more headroom than an RPi.)

On x86 this approach didn't work so nicely, though (presumably due to kernel shenanigans/virtualization issues I didn't have time to figure out), so we went with the huge page approach: mmap(2) an anonymous 1GB huge page and simply mlockall it into place. You can reserve a couple 1G huge pages at boot to make fragmentation less of a problem. Then you use /proc/self/pagemap in order to find the physical page backing that memory (and make sure it's really 1G-aligned) and pass it to the hardware in whatever manner, which can then write back to it using PCIe DMA.

There are some significant annoyances with this, though, the main one being that we don't actually *need* 1GB buffers. 2MB huge pages are a little too small for us -- they fundamentally could work but would make data management more annoying. So it's a "huge allocation" that doesn't really need to be that huge.

An alternative approach pointed out to me by Luke Gorrie (of Snabb Switch, and who I got this trick from originally) indicated that using VFIO drivers, it should be possible to 'stitch' together a set of non-contiguous, physical 2MB pages into a virtualized "physical" region that the IOMMU presents to the PCIe device as continuous, almost like using VFIO to approximate a limited form of scatter-gather DMA. This would be the best of both worlds for us I think: we have a contiguous mmap()'d region from userspace, physically the buffers can be anywhere, but from the hardware they're a single physical region, and without too much overhead waste.

Has anyone attempted something like this before? I'd be interested in anyone's thoughts, here.

Improving support for large, contiguous allocations

Posted May 4, 2018 16:56 UTC (Fri) by imMute (guest, #96323) [Link]

At $MY_WORK I did something similar to you. We too have an FPGA with a DMA engine with access to a PCIe bus. The DMA engine can do scatter-gather, but each chunk has a maximum size of of 1GB - 1 (literally one byte below 1GB). I wrote a kernel mode driver than controls the DMA engine. It uses get_user_pages() to lock the pages in RAM. It uses none of the DMA infrastructure provided by the kernel. Two reasons for that: I didn't know about them at the time, and now that I know about them it would be more work to go back and rewrite the working driver. The engine is perfectly capable of scatter-gather, but for various (bad) reasons, the driver has a limited number of descriptors it can use for each transfer. Not wanting to waste precious RAM, I settled on using hugetlbfs to make the memory physically contiguous. The driver figures out when the pages are contiguous and merges descriptors where it can. Looking back, I would probably write the driver a bit differently, but at this point it's not terribly high on the priority list.

The biggest thing that pushed me into making a driver (rather than doing it in userspace with UIO like the rest of our FPGA registers) was the in ability to see the physical address from userspace. I like your trick with `/proc/self/pagemap`, but I think there may be a problem with it. What happens if the userspace process crashes while a DMA transaction is in process? Wouldn't the kernel free up the locked pages while the DMA engine is using them?

I like the idea of using the IOMMU to make scatter-gather possible for engines that don't implement it themselves. I would think there's a possible performance bottleneck there (akin to TLB cache misses in the CPUs MMU) but it would have to be measured before considered a problem.

Improving support for large, contiguous allocations

Posted May 9, 2018 11:50 UTC (Wed) by mina86 (guest, #68442) [Link]

For anyone interested in CMA some more:

CMA needs page-block-aligned regions because a page block is the smallest unit that can have it’s migration type set⁰. Importantly though, allocations within a CMA area do not need to be page-block-aligned and can be as small as one page. As a consequence one can easily create a 64 MiB-large CMA area, mark 50 MiB of it occupied¹ and result would be a 14 MiB CMA area.

The only unresolved problem is that the whole 64 MiB would be marked as MIGRATE_CMA which means that only movable pages can be allocated from it. To solve that page block size would have to be reduced, but IIRC the caveat here is that a lot of code (including page allocator) assume pageblock_order ≥ HUGETLB_PAGE_ORDER².

⁰ CMA uses this to make sure that only movable pages are allocated within CMA area so that they can be moved when CMA needs them. It also locks a page block during allocation so page allocator cannot grab pages from it.

¹ Internally CMA uses simple occupancy bitmap; setting a string of ones in it shouldn’t be a problem for anyone.

² Page allocator groups free pages by page block thus a page spanning multiple blocks would probably break something.


Copyright © 2018, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds