runtime: fall back on mmap if madvise is unsupported

Since Linux 3.18, support for madvise is optional, depending on
the setting of the CONFIG_ADVISE_SYSCALLS configuration option.

The Go runtime currently assumes in several places that we 
do not unmap heap memory; that needs to remain true. So, if 
madvise is unsupported, we cannot fall back on munmap. AFAIK, 
the only way to free the pages is to remap the memory region.

For the x86, the system call mmap() is implemented by sys_mmap2() 
which calls do_mmap2() directly with the same parameters. The main 
call trace for 
mmap(v, n, PROT_READ|PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0)
is as follows:

```
do_mmap2()
    \- do_mmap_pgoff()
        \- get_unmapped_area()
        \- find_vma_prepare()
				
        // If a VMA was found and it is part of the new mmaping, remove 
        // the old mapping as the new one will cover both.
        // Unmap all the pages in the region to be unmapped.
        \- do_munmap()
				
        // Allocate a VMA from the slab allocator.
        \- kmem_cache_alloc()
				
        // Link in the new vm_area_struct.
        \- vma_link()
```

So, it's safe to fall back on mmap().
See D.2 https://www.kernel.org/doc/gorman/html/understand/understand021.html
This commit is contained in:
Lance Yang 2023-05-16 16:13:29 +08:00 committed by GitHub
parent 04e2472895
commit 179f047154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 24 additions and 8 deletions

View File

@ -36,6 +36,8 @@ func sysAllocOS(n uintptr) unsafe.Pointer {
var adviseUnused = uint32(_MADV_FREE)
const madviseUnsupported = 0
func sysUnusedOS(v unsafe.Pointer, n uintptr) {
if uintptr(v)&(physPageSize-1) != 0 || n&(physPageSize-1) != 0 {
// madvise will round this to any physical page
@ -44,17 +46,31 @@ func sysUnusedOS(v unsafe.Pointer, n uintptr) {
throw("unaligned sysUnused")
}
var advise uint32
if debug.madvdontneed != 0 {
advise := atomic.Load(&adviseUnused)
if debug.madvdontneed != 0 && advise != madviseUnsupported {
advise = _MADV_DONTNEED
} else {
advise = atomic.Load(&adviseUnused)
}
if errno := madvise(v, n, int32(advise)); advise == _MADV_FREE && errno != 0 {
// MADV_FREE was added in Linux 4.5. Fall back to MADV_DONTNEED if it is
// not supported.
switch advise {
case _MADV_FREE:
if madvise(v, n, _MADV_FREE) == 0 {
break
}
atomic.Store(&adviseUnused, _MADV_DONTNEED)
madvise(v, n, _MADV_DONTNEED)
fallthrough
case _MADV_DONTNEED:
// MADV_FREE was added in Linux 4.5. Fall back on MADV_DONTNEED if it's
// not supported.
if madvise(v, n, _MADV_DONTNEED) == 0 {
break
}
atomic.Store(&adviseUnused, madviseUnsupported)
fallthrough
case madviseUnsupported:
// Since Linux 3.18, support for madvise is optional.
// Fall back on mmap if it's not supported.
// _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE will unmap all the
// pages in the old mapping, and remap the memory region.
mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0)
}
if debug.harddecommit > 0 {