Pages v0.1.0

Source
Address space in whole pages from the operating system, with a protection.

PageNone

pub const PageNone: uint32 = 0;

What may be done with a page, as a set of flags to combine with |. A protection of PageNone reserves the address space and refuses every access to it, which is how a guard page is made. Combining write and execute is refused outright by some systems and by policy on others, so a caller that needs both should write through one protection and execute through another.

Source

PageRead

pub const PageRead: uint32 = 1;

The page may be read.

Source

PageWrite

pub const PageWrite: uint32 = 2;

The page may be written. Implies readability on every supported system: none of them can make a page write-only, whatever their flags suggest.

Source

PageExecute

pub const PageExecute: uint32 = 4;

The page may be executed.

Source

PageError

pub enum PageError: uint8 {
    InvalidLength = 1,
    InvalidAddress = 2,
    OutOfMemory = 3,
    PermissionDenied = 4,
    Unsupported = 5,
    Failed = 6
}

Why a page operation did not do what was asked. Copy. One set of reasons across four systems that report failure three different ways: an errno, a GetLastError code, and a sentinel address. A reason the mapping does not cover arrives as Failed, which is not a promise that nothing went wrong — only that this module could not name it. Every case is a failure: a call that succeeds reports its success channel instead.

Source

Cases

NameValueDescription
InvalidLength1The length was zero, or too large for the address space to hold.
InvalidAddress2The address was not page-aligned, or names nothing this process may operate on.
OutOfMemory3The system has no storage to back the request.
PermissionDenied4The system refused the protection asked for, which is usually write and execute together.
Unsupported5The combination is not supported on this system.
Failed6The system reported a failure this module has no name for.

MinimumPageSize

pub const MinimumPageSize: uint = 4096;

The smallest page any supported system uses, in bytes. Four kibibytes, which is the page size on seven of the eight supported target cells and the floor on the eighth: Apple Silicon uses sixteen, and a Linux kernel may be configured for sixteen or sixty-four, but none of them goes below this. It is a bound, not an answer. PageAlign rounds to it because rounding to a divisor of the real page size is always safe — the system rounds again, up to whatever its pages actually are — and because asking the system on every call would put a file read or a system call inside an allocator's inner loop. A caller that needs the real granularity asks PageSize once and keeps the answer.

Source

PageSize

pub func PageSize() -> uint ! PageError

Asks the system how large a page actually is, in bytes. The answer is always a power of two, and is the granularity every call in this module rounds a length up to. It varies by machine and not only by system: Apple Silicon uses sixteen kibibytes where Apple's x86-64 uses four, and a Linux kernel may be configured for either or for sixty-four. Each system is asked the way it wants to be asked, by the package that owns it — GetSystemInfo on Windows, getpagesize on Darwin, the hw.pagesize sysctl on FreeBSD, and /proc/self/auxv on Linux, whose kernel publishes the value in the auxiliary vector rather than behind a system call. Nothing is captured at process start and no libc is called. Reports Unsupported when the system would not answer — on Linux, a container built without /proc is the ordinary way that happens. A failure is reported rather than a guess substituted, because a caller that is asking this instead of using MinimumPageSize is asking for the real number, and a wrong real number is worse than a refusal it can handle. Allocates nothing. Runs in constant time on three systems and performs one open, read and close on Linux, so a caller that needs the size more than once should keep it.

Source

Returns: the page size in bytes, or the reason the system would not answer

PageAllocate

pub func PageAllocate(
    length: uint,
    protection: uint32
) -> (*var opaque) ! PageError

Takes length bytes of address space with the given protection, and reports where it landed. length is rounded up to whole pages by the system. protection is a combination of the Page* flags, and PageNone is legal — it takes the space and refuses every access to it. The pages are zero-filled on every supported system. Nothing else about their contents is promised. A length of zero is refused with InvalidLength rather than succeeding with nothing, because a zero-length mapping is a caller's arithmetic error far more often than an intention. Runs in constant time as far as this module is concerned; the system may take longer. Allocates nothing beyond what it is asked for. The pages are the caller's until PageRelease takes exactly this length back. Releasing a different length, or an address this did not return, is undefined on Windows and silently partial elsewhere.

Source

Parameters

NameTypeDescription
lengthuinthow many bytes of address space to take, rounded up to whole pages by the system
protectionuint32what may be done with the pages, as Page* flags

Returns: the base address of the pages, or the reason the address space was not taken

PageRelease

pub func PageRelease(
    address: *var opaque,
    length: uint
) -> ! PageError

Gives back length bytes of address space at address. address must be a base address PageAllocate returned, and length the length it was given. Succeeds when the system accepted the release. A length of zero is refused with InvalidLength, and a null address with InvalidAddress, rather than either being quietly ignored: both are arithmetic errors at the caller far more often than intentions. Every pointer into the range becomes invalid, and the address space may be handed to the next allocation at once. Windows requires the exact base address and ignores the length entirely; the other systems accept a sub-range and release only that, so a wrong length there silently releases the wrong amount.

Source

Parameters

NameTypeDescription
address*var opaquea base address PageAllocate returned
lengthuintthe length it was given

Returns: nothing on success, or the reason the system refused the release

PageProtect

pub func PageProtect(
    address: *var opaque,
    length: uint,
    protection: uint32
) -> ! PageError

Changes what may be done with length bytes at address. address must be page-aligned and length is rounded up to whole pages. Succeeds when the system accepted the change. The change takes effect at once, so a thread touching the range concurrently faults rather than finishing what it started. Nothing here checks that the range was ever allocated. Asking for write and execute together is refused on Apple Silicon without an entitlement and by policy on hardened systems elsewhere, which arrives as PermissionDenied.

Source

Parameters

NameTypeDescription
address*var opaquea page-aligned address
lengthuinthow many bytes, rounded up to whole pages
protectionuint32what may be done with the pages afterwards

Returns: nothing on success, or the reason the system refused the change

PageAlign

pub func PageAlign(length: uint) -> uint

Rounds length up to a whole number of MinimumPageSize blocks. It rounds to the bound rather than to the system's real page size, which is a deliberate difference from what the name suggests and the reason MinimumPageSize is public. Rounding to a divisor of the real size cannot make an allocation wrong — every call in this module hands the length to a system that rounds again, up to whatever its pages actually are — and it keeps a file read out of an allocator's inner loop. On a machine with larger pages the result is a length the system will round further, which costs nothing. A caller that needs the length to be a whole number of real pages asks PageSize once, keeps the answer, and rounds with AlignUp itself. Returns the rounded length, or length itself when rounding would pass the end of the range — which a caller sizing an allocation should notice, since the system will refuse such a length anyway. Cannot fail. Runs in constant time and allocates nothing.

Source

Parameters

NameTypeDescription
lengthuintthe byte count to round up

Returns: the rounded length