Pages v0.1.0
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.
PageRead
pub const PageRead: uint32 = 1;
The page may be read.
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.
PageExecute
pub const PageExecute: uint32 = 4;
The page may be executed.
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.
Cases
| Name | Value | Description |
|---|---|---|
InvalidLength | 1 | The length was zero, or too large for the address space to hold. |
InvalidAddress | 2 | The address was not page-aligned, or names nothing this process may operate on. |
OutOfMemory | 3 | The system has no storage to back the request. |
PermissionDenied | 4 | The system refused the protection asked for, which is usually write and execute together. |
Unsupported | 5 | The combination is not supported on this system. |
Failed | 6 | The 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.
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.
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.
Parameters
| Name | Type | Description |
|---|---|---|
length | uint | how many bytes of address space to take, rounded up to whole pages by the system |
protection | uint32 | what 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.
Parameters
| Name | Type | Description |
|---|---|---|
address | *var opaque | a base address PageAllocate returned |
length | uint | the 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.
Parameters
| Name | Type | Description |
|---|---|---|
address | *var opaque | a page-aligned address |
length | uint | how many bytes, rounded up to whole pages |
protection | uint32 | what 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.
Parameters
| Name | Type | Description |
|---|---|---|
length | uint | the byte count to round up |
Returns: the rounded length