Skip to content

Commit b5308ca

Browse files
committed
0.16.0 --- the region a context stands on, measured rather than inferred
`kal_task_stack` reports the stack of the CALLING context. Every context this implementation creates runs on a region it allocated, so the pair is recorded when the context begins and returned as it stands; the context the program was started on has no such record, and its region is measured. RLIMIT_STACK ALONE IS NOT THE ANSWER, AND BELIEVING IT WAS WOULD BE THE DEFECT THIS EXISTS TO REMOVE. The limit is a policy, and the floor the kernel enforces is `mapping_end - RLIMIT_STACK` --- the end of the MAPPING, which is not the address the program's first instruction sees. `setup_arg_pages` places the arguments at the top of the mapping and moves the mapping's start down by a random shift, so a bound computed from a stack pointer begins below the real floor. Below the real floor is where this system puts the program's own libraries, so the range would contain another mapping: a caller that laid a guard at its base, or measured the room above it, would be measuring a region the kernel will not grow the stack into. So the mapping is measured with `mincore` --- an exponential-then-bisection search outward from a local of the caller --- and the floor is the higher of the two bounds the kernel itself applies: `mapping_end - RLIMIT_STACK`, read with `prlimit64`, and one guard gap above the nearest mapping below. BOTH BOUNDS WERE MEASURED ON 6.8.0 BEFORE BEING WRITTEN DOWN. A descending write stopped at exactly `mapping_end - RLIMIT_STACK`, with one page below it unwritable and one page above it writable; with one page mapped by the test itself inside the reservation, the same write stopped at exactly that page's end plus 256 pages. THE GUARD GAP IS A KERNEL GLOBAL THIS IMPLEMENTATION CANNOT READ, and the default is therefore what is applied. A kernel booted with a larger gap stops the stack above the base reported here --- the region is reported larger than the kernel will grow it, which is the direction a caller must not be wrong in, and the reason the default is applied rather than a smaller number. A kernel booted with a smaller gap is reported a region smaller than it could have, which is the harmless direction. Both are stated at the code. An environment that will not answer --- `mincore` refused by a filter, say --- is reported as the kernel's condition rather than as a range, because a range derived from a refusal is a range derived from nothing. It is not reported as "unsupported": clause 6.1 forbids a provided interface to answer that way. Where the program carries a runtime of its own, that runtime owns the stacks and is asked instead (`pthread_getattr_np`), which is the same answer a program above openkal would get for itself. That is the one arrangement in which this implementation may take a C library's name, and the file says so. The conformance suite observes containment --- the reported region contains a local of the context that asked, for the first context and for a started one, and the answer does not change while the context runs --- and this package's own tests observe the same property, because it is the one both of the wrong answers mcpplibs/openkal-musl#49 removed had violated.
1 parent 3493ed4 commit b5308ca

5 files changed

Lines changed: 398 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface.
55

66
```toml
77
[dependencies]
8-
openkal = "0.14.1"
8+
openkal = "0.15.0"
99

1010
[target.'cfg(os = "linux")'.dependencies]
11-
openkal-linux = "0.15.1"
11+
openkal-linux = "0.16.0"
1212
```
1313

1414
## Why it does not use a C library
@@ -45,6 +45,46 @@ second and third.
4545
All eight. `tools/check-surface.sh --complete` in the specification package
4646
compares the exported names against `SURFACE.txt`.
4747

48+
## The region a context stands on
49+
50+
`kal_task_stack` reports the stack of the calling context, and this
51+
implementation answers it from two different sources because the two
52+
arrangements are told apart by who chose the stack.
53+
54+
Where this implementation creates the context it also allocates the region, so
55+
the pair is recorded when the context begins and returned as it stands. That is
56+
exact and costs nothing.
57+
58+
The context the program was started on has no such record, and its region is
59+
measured. `RLIMIT_STACK` alone is not the answer: the limit is a policy and the
60+
floor the kernel enforces is `mapping_end - RLIMIT_STACK`, where the mapping's
61+
end is not the address the program's first instruction sees --- the kernel
62+
places the arguments at the top of the mapping and moves the mapping's start
63+
down by a random shift. A region computed from a stack pointer alone therefore
64+
begins below the real floor, and below the real floor is where this system puts
65+
the program's own libraries.
66+
67+
So the mapping is found by asking the kernel which pages are mapped, and the
68+
floor is the higher of the two bounds the kernel itself applies: the limit
69+
above, and one guard gap above the nearest mapping below, because the kernel
70+
refuses to grow the stack to within `stack_guard_gap` of another mapping.
71+
72+
The gap is a kernel global this implementation cannot read, and the default of
73+
256 pages is what is applied. A kernel booted with a larger gap stops the stack
74+
above the base reported here, which is the direction a caller must not be wrong
75+
in; a kernel booted with a smaller one is reported a region smaller than it
76+
could have, which is the harmless direction.
77+
78+
Both bounds were measured rather than derived, on 6.8.0: a descending write
79+
stopped at exactly `mapping_end - RLIMIT_STACK`, with one page below it
80+
unwritable and one page above it writable; and with a page mapped by the test
81+
itself inside the reservation, the same write stopped at exactly that page's end
82+
plus 256 pages.
83+
84+
A program that carries a runtime of its own answers from that runtime instead
85+
(`pthread_getattr_np`), because in that arrangement the runtime is what owns the
86+
stacks and is what a program above openkal would ask for itself.
87+
4888
## Conformance
4989

5090
The suite lives in the specification package and is the same suite every

‎mcpp.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-linux"
4-
version = "0.15.1"
4+
version = "0.16.0"
55
description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one."
66
license = "Apache-2.0"
77

@@ -63,7 +63,7 @@ provides-interfaces = [
6363
]
6464

6565
[dependencies]
66-
openkal = "0.14.1"
66+
openkal = { git = "https://github.com/mcpplibs/openkal.git", branch = "openkal-0.15.0" }
6767

6868
# The package contributes definitions and no modules. The interface it
6969
# implements is declared by the specification package, which this package

‎src/sys.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ enum : okl_long {
100100
nr_close_range = 436, nr_prlimit64 = 302,
101101
nr_dup2 = 33, nr_utimensat = 280, nr_symlinkat = 266, nr_fstatfs = 138,
102102
nr_getrandom = 318,
103+
// openkal 0.15: the region a context stands on is measured by asking the
104+
// kernel which pages are mapped. The floor comes from the limit the kernel
105+
// itself enforces, which `nr_prlimit64' above already names.
106+
nr_mincore = 27,
103107
// openkal 0.13: whether a node may be started
104108
nr_fchmodat = 268,
105109
// openkal 0.11: the directory a started program runs in, and the unit it joins
@@ -189,6 +193,12 @@ enum : okl_long {
189193
nr_prctl = 167, nr_sched_getaffinity = 123, nr_getppid = 173,
190194
nr_arch_prctl = -1, nr_utimensat = 88, nr_symlinkat = 36, nr_fstatfs = 44,
191195
nr_getrandom = 278,
196+
// openkal 0.15: the region a context stands on is measured by asking the
197+
// kernel which pages are mapped. The floor comes from the limit the kernel
198+
// itself enforces, which `nr_prlimit64' above already names --- and the two
199+
// numbers differ from the other architecture's because this one takes the
200+
// architecture-independent table for everything it can.
201+
nr_mincore = 232,
192202
// openkal 0.13: whether a node may be started
193203
nr_fchmodat = 53,
194204
// openkal 0.11: the directory a started program runs in, and the unit it joins

0 commit comments

Comments
 (0)