MMap and hugetlbfs

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Mar 14 10:04:28 UTC 2024


Hi Cleber,
This is more a general question on FileChannel, so I'm adding nio-dev in 
CC, as someone there might be able to help more.

Cheers
Maurizio

On 13/03/2024 23:09, Cleber Muramoto wrote:
> Are there plans to encapsulate offset/page boundary computation in 
> FileChannel.map for files under a hugetlbfs?
>
> The problem is that, unlike files in "regular" file systems with 4K 
> pages, *ftruncate* can only be called with multiples of page size, 
> otherwise it will fail and set errno to EINVAL, which is translated in 
> the JNI land into an IOException with a not very informative message: 
> "Invalid argument".
>
> With hugetlbfs, we also have to manually compute page positions, since 
> FileDispatcher::allocationGranularity() will "always" report 4K and we 
> have to call *mmap* with, e.g. 2M page alignment:
>
> ----------
> import static java.nio.file.StandardOpenOption.*;
>
> import java.io.*;
> import java.lang.foreign.*;
> import java.nio.channels.*;
> import java.nio.channels.FileChannel.*;
> import java.nio.file.*;
>
> public class TestHugeTLBFS {
>
>   static final long REGULAR_PS = 4096L;
>   static final long HUGE_PS = 1024 * 1024 * 2L;
>   static final StandardOpenOption[] OPTS = { CREATE, WRITE, READ };
>
>   public static void main(String[] args) throws IOException {
>     var base = Paths.get("/var/lib/hugetlbfs/global/pagesize-2MB");
>
>     var tmp = Files.createTempFile(base, "test", ".bin");
>
>     var a = Arena.ofShared();
>
>     MemorySegment ms = null;
>
>     try (var fc = FileChannel.open(tmp, OPTS)) {
>       // fails in truncate (length is not a multiple of HUGE_PS)
>       ms = fc.map(MapMode.READ_WRITE, 0, HUGE_PS - 1, a);
>     } catch (IOException e) {
>       e.printStackTrace();
>     }
>
>     assert ms == null : "Worked?!";
>
>     try (var fc = FileChannel.open(tmp, OPTS)) {
>       // fails in mmap (computed offset is 4K aligned, but it's not 
> 2MB aligned)
>       ms = fc.map(MapMode.READ_WRITE, HUGE_PS + 3 * REGULAR_PS, 
> HUGE_PS - 3 * REGULAR_PS, a);
>     } catch (IOException e) {
>       e.printStackTrace();
>     }
>
>     assert ms == null : "Worked?!";
>
>     try (var fc = FileChannel.open(tmp, OPTS)) {
>       // This works, because the aligned offset ends up 2MB aligned
>       ms = fc.map(MapMode.READ_WRITE, HUGE_PS + 19, HUGE_PS -  19, a);
>     } catch (IOException e) {
>       throw new UncheckedIOException(e);
>     }
>
>     ms.set(ValueLayout.JAVA_INT_UNALIGNED, 0, 42);
>     a.close();
>
>     try (var fc = FileChannel.open(tmp, OPTS)) {
>       ms = fc.map(MapMode.READ_WRITE, HUGE_PS + 19 , HUGE_PS - 19, a = 
> Arena.ofShared());
>     } catch (IOException e) {
>       throw new UncheckedIOException(e);
>     }
>
>     assert ms.get(ValueLayout.JAVA_INT_UNALIGNED, 0) == 42;
>     a.close();
>   }
> }
> ----------
>
> To satisfy the alignment constraints for both offset and length, the 
> offset has to be rounded down to the beginning of a page boundary and 
> the length must be compensated taking into account the aligned start 
> offset, more or less like:
>
> MemorySegment map(Path path, long offset, long length, long pageSize) {
>     var start = offset;
>     var len = length;
>     var truncLen = start + len;
>
>     if (truncLen % pageSize != 0) {
>       // round down to start of a page offset -> offset - (offset % 
> pageSize)
>       start = alignDown(offset, pageSize);
>       var end = offset + length;
>       len = end - start;
>       truncLen = start + len;
>
>       if (truncLen % pageSize != 0) {
>         // round up to a multiple of pageSize: length -> pageSize * 
> (length / pageSize + ((length % pageSize == 0) ? 0 : 1))
>         len = alignUp(len, pageSize);
>         truncLen = start + len;
>
>         assert truncLen % pageSize == 0 : "Sanity";
>       }
>     }
>
>     try (var fc = FileChannel.open(path,...)) {
>       var segment = fc.map(mode, path, start, len, Arena.ofSomething());
>
>       if (start != offset || len != length) {
>         segment = segment.asSlice(offset - start, length);
>       }
>
>       return segment;
>     }
> }
>
> It would be nice to have this somehow handled by FileChannel.
>
> An overload with a user-defined page size might not be ideal, but it 
> might be the cheapest way to bypass the (incorrect) 
> FileDispatcher::allocationGranularity() value to enforce correct 
> alignment constraints.
>
> Regards
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/nio-dev/attachments/20240314/5bb75c10/attachment.htm>


More information about the nio-dev mailing list