Add some documentation

This commit is contained in:
Justine Tunney 2024-07-19 02:53:07 -07:00
parent 567d8fe32d
commit 1ff037df3c
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
5 changed files with 177 additions and 22 deletions

View file

@ -544,8 +544,13 @@ static void *__mmap(char *addr, size_t size, int prot, int flags, int fd,
int gransz = __gransize;
// validate arguments
if (((uintptr_t)addr & (gransz - 1)) || //
!size || (uintptr_t)addr + size < size)
if ((uintptr_t)addr & (gransz - 1))
addr = NULL;
if (!addr && (flags & (MAP_FIXED | MAP_FIXED_NOREPLACE)))
return (void *)eperm();
if ((intptr_t)addr < 0)
return (void *)enomem();
if (!size || (uintptr_t)addr + size < size)
return (void *)einval();
if (size > WINMAXX)
return (void *)enomem();
@ -732,13 +737,100 @@ static void *__mremap(char *old_addr, size_t old_size, size_t new_size,
return res;
}
/**
* Creates memory mapping.
*
* The mmap() function is used by Cosmopolitan's malloc() implementation
* to obtain new memory from the operating system. This function is also
* useful for establishing a mapping between a file on disk and a memory
* address, which avoids most need to call read() and write(). It is how
* executables are loaded into memory, for instance, in which case pages
* are loaded lazily from disk by the operating system.
*
* The `addr` parameter may be zero. This lets the implementation choose
* an available address in memory. OSes normally pick something randomly
* assigned, for security. Most OSes try to make sure subsequent mapping
* requests will be adjacent to one another. More paranoid OSes may have
* different mappings be sparse, with unmapped content between them. You
* may not use the `MAP_FIXED` parameter to create a memory map at NULL.
*
* The `addr` parameter may be non-zero, in which case Cosmopolitan will
* give you a mapping at that specific address if it's available. When a
* mapping already exists at the requested address then another one will
* be chosen automatically. On most OSes the newly selected address will
* be as close-by as possible, but that's not guaranteed. If `MAP_FIXED`
* is also supplied in `flags` then this hint is taken as mandatory, and
* existing mappings at the requested interval shall be auto-unmapped.
*
* The `size` parameter is implicitly rounded up to the system page size
* reported by getpagesize() and sysconf(_SC_PAGESIZE). Your extra bytes
* will be zero-initialized.
*
* The returned address will always be aligned, on the system allocation
* granularity. This value may be obtained from getgransize() or calling
* sysconf(_SC_GRANSIZE). Granularity is always greater than or equal to
* the page size. On some platforms, i.e. Windows, it may be larger than
* the page size.
*
* The `prot` value specifies the memory protection of the mapping. This
* may be `PROT_NONE` to disallow all access otherwise it's a bitwise or
* of the following constants:
*
* - `PROT_READ` allows read access
* - `PROT_WRITE` allows write access
* - `PROT_EXEC` allows execute access
*
* Some OSes (i.e. OpenBSD) will raise an error if both `PROT_WRITE` and
* `PROT_EXEC` are requested. You may still modify executable memory but
* you must use mprotect() to transition between the two states. On some
* OSes like MacOS ARM64, you need to pass the `MAP_JIT` flag to get RWX
* memory, which is considered zero on other OSes.
*
* The lower bits of the `flags` parameter specify the `MAP_TYPE`, which
* may be:
*
* - `MAP_PRIVATE` for non-shared and copy-on-write mappings
* - `MAP_SHARED` for memory that may be shared between processes
*
* Your `fd` argument specifies the file descriptor of the open file you
* want to map. This parameter is ignored when `MAP_ANONYMOUS` is passed
* via `flags`.
*
* Your `off` argument specifies the offset into a, file at which mapped
* memory shall begin. It must be aligned to the allocation granularity,
* which may be obtained from getgransize() or sysconf(_SC_GRANSIZE).
*
* The `MAP_FIXED_NOREPLACE` flag may be passed in `flags` which has the
* same behavior as `MAP_FIXED` except it raises `EEXIST` when a mapping
* already exists on the requested interval.
*
* The `MAP_CONCEAL` flag may be passed to prevent a memory mapping from
* appearing in core dumps. This is currently supported on BSD OSes, and
* is ignored on everything else.
*/
void *mmap(void *addr, size_t size, int prot, int flags, int fd, int64_t off) {
void *res = __mmap(addr, size, prot, flags, fd, off);
STRACE("mmap(%p, %'zu, %s, %s, %d, %'ld) → %p% m", addr, size,
DescribeProtFlags(prot), DescribeMapFlags(flags), fd, off, res);
STRACE("mmap(%p, %'zu, %s, %s, %d, %'ld) → %p% m (%'zu bytes total)", addr,
size, DescribeProtFlags(prot), DescribeMapFlags(flags), fd, off, res,
__maps.pages * __pagesize);
return res;
}
/**
* Changes memory mapping.
*
* This system call lets you move memory without copying it. It can also
* be used to shrink memory mappings.
*
* This system call is supported on Linux and NetBSD. It's used by Cosmo
* Libc's realloc() implementation under the hood.
*
* The `flags` parameter may have:
*
* - `MREMAP_MAYMOVE` to allow relocation
* - `MREMAP_FIXED` in which case an additional parameter is taken
*
*/
void *mremap(void *old_addr, size_t old_size, size_t new_size, int flags, ...) {
va_list ap;
void *new_addr = 0;
@ -748,11 +840,19 @@ void *mremap(void *old_addr, size_t old_size, size_t new_size, int flags, ...) {
va_end(ap);
}
void *res = __mremap(old_addr, old_size, new_size, flags, new_addr);
STRACE("mremap(%p, %'zu, %'zu, %s, %p) → %p% m", old_addr, old_size, new_size,
DescribeMremapFlags(flags), new_addr, res);
STRACE("mremap(%p, %'zu, %'zu, %s, %p) → %p% m (%'zu bytes total)", old_addr,
old_size, new_size, DescribeMremapFlags(flags), new_addr, res,
__maps.pages * __pagesize);
return res;
}
/**
* Removes memory mapping.
*
* The `size` parameter is implicitly rounded up to the page size.
*
* @return 0 on success, or -1 w/ errno.
*/
int munmap(void *addr, size_t size) {
int rc = __munmap(addr, size);
STRACE("munmap(%p, %'zu) → %d% m", addr, size, rc);

View file

@ -223,7 +223,6 @@ syscon mmap MAP_TYPE 15 15 15 15 15 15 15 15 # mask for type
syscon mmap MAP_FIXED 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 # unix consensus; openbsd appears to forbid; faked nt
syscon mmap MAP_FIXED_NOREPLACE 0x08000000 0x08000000 0x00004000 0x00004000 0x08000000 0x08000000 0x08000000 0x08000000 # handled and defined by cosmo runtime; 0x100000 on linux 4.7+; MAP_FIXED|MAP_EXCL on FreeBSD
syscon mmap MAP_ANONYMOUS 0x00000020 0x00000020 0x00001000 0x00001000 0x00001000 0x00001000 0x00001000 0x00000020 # bsd consensus; faked nt
syscon mmap MAP_GROWSDOWN 0x00000100 0x00000100 0 0 0 0 0 0 # use MAP_STACK; abstracted by MAP_STACK; may be passed to __sys_mmap() for low-level Linux fiddling
syscon mmap MAP_LOCKED 0x00002000 0x00002000 0 0 0 0 0 0
syscon mmap MAP_NORESERVE 0x00004000 0x00004000 0x00000040 0x00000040 0 0 0x00000040 0 # Linux calls it "reserve"; NT calls it "commit"? which is default?
syscon mmap MAP_POPULATE 0x00008000 0x00008000 0 0 0x00040000 0 0 0 # MAP_PREFAULT_READ on FreeBSD; can avoid madvise(MADV_WILLNEED) on private file mapping

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon mmap,MAP_GROWSDOWN,0x00000100,0x00000100,0,0,0,0,0,0

View file

@ -22,6 +22,7 @@
#include "libc/errno.h"
#include "libc/intrin/maps.h"
#include "libc/limits.h"
#include "libc/literal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/stdio/rand.h"
@ -146,7 +147,6 @@ TEST(mmap, hint) {
TEST(mprotect, punchHoleAndFillHole) {
char *p;
int count = __maps.count;
int gransz = getgransize();
// obtain memory
ASSERT_NE(MAP_FAILED,
@ -223,16 +223,17 @@ TEST(mmap, testMapFile_fdGetsClosed_makesNoDifference) {
TEST(mmap, fileOffset) {
int fd;
char *map;
int offset_align = IsWindows() ? gransz : getpagesize();
ASSERT_NE(-1, (fd = open("foo", O_CREAT | O_RDWR, 0644)));
EXPECT_NE(-1, ftruncate(fd, offset_align * 2));
EXPECT_NE(-1, pwrite(fd, "hello", 5, offset_align * 0));
EXPECT_NE(-1, pwrite(fd, "there", 5, offset_align * 1));
EXPECT_NE(-1, ftruncate(fd, gransz * 2));
EXPECT_NE(-1, pwrite(fd, "hello", 5, gransz * 0));
EXPECT_NE(-1, pwrite(fd, "there", 5, gransz * 1));
EXPECT_NE(-1, fdatasync(fd));
ASSERT_NE(MAP_FAILED, (map = mmap(NULL, offset_align, PROT_READ, MAP_PRIVATE,
fd, offset_align)));
ASSERT_SYS(EINVAL, MAP_FAILED,
mmap(NULL, gransz, PROT_READ, MAP_PRIVATE, fd, gransz - 1));
ASSERT_NE(MAP_FAILED,
(map = mmap(NULL, gransz, PROT_READ, MAP_PRIVATE, fd, gransz)));
EXPECT_EQ(0, memcmp(map, "there", 5), "%#.*s", 5, map);
EXPECT_NE(-1, munmap(map, offset_align));
EXPECT_NE(-1, munmap(map, gransz));
EXPECT_NE(-1, close(fd));
}
@ -263,6 +264,67 @@ TEST(mmap, ziposCannotBeShared) {
close(fd);
}
TEST(mmap, misalignedAddr_justIgnoresIt) {
char *p;
ASSERT_NE(MAP_FAILED, (p = mmap((void *)1, 1, PROT_READ,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)));
munmap(p, 1);
}
TEST(mmap, nullFixed_isNotAllowed) {
ASSERT_SYS(
EPERM, MAP_FAILED,
mmap(0, 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
ASSERT_SYS(EPERM, MAP_FAILED,
mmap(0, 1, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE, -1, 0));
}
static int GetBitsInAddressSpace(void) {
int i;
void *ptr;
uint64_t want;
for (i = 0; i < 40; ++i) {
want = UINT64_C(0x8123000000000000) >> i;
if (want > UINTPTR_MAX)
continue;
if (msync((void *)(uintptr_t)want, 1, MS_ASYNC) == 0 || errno == EBUSY)
return 64 - i;
ptr = mmap((void *)(uintptr_t)want, 1, PROT_READ,
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
if (ptr != MAP_FAILED) {
munmap(ptr, 1);
return 64 - i;
}
}
abort();
}
TEST(mmap, negative_isNotAllowed) {
ASSERT_SYS(ENOMEM, MAP_FAILED,
mmap((void *)-(70 * 1024 * 1024), 1, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
}
TEST(mmap, pml5t) {
switch (GetBitsInAddressSpace()) {
case 56:
ASSERT_EQ((void *)0x00fff2749119c000,
mmap((void *)0x00fff2749119c000, 1, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
munmap((void *)0x00fff2749119c000, 1);
break;
case 48:
ASSERT_EQ((void *)0x0000f2749119c000,
mmap((void *)0x0000f2749119c000, 1, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0));
munmap((void *)0x0000f2749119c000, 1);
break;
default:
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// zipos NON-SHARED READ-ONLY FILE MEMORY

View file

@ -505,11 +505,7 @@ FORCEINLINE int win32munmap(void* ptr, size_t size) {
* Define CALL_MREMAP
*/
#if HAVE_MMAP && HAVE_MREMAP
#ifdef MREMAP
#define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv))
#else /* MREMAP */
#define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv))
#endif /* MREMAP */
#define CALL_MREMAP(addr, osz, nsz, mv) ({ int olderr = errno; void *res = mremap((addr), (osz), (nsz), (mv)); if (res == MAP_FAILED) errno = olderr; res; })
#else /* HAVE_MMAP && HAVE_MREMAP */
#define CALL_MREMAP(addr, osz, nsz, mv) MAP_FAILED
#endif /* HAVE_MMAP && HAVE_MREMAP */