openkal is a portable kernel application binary interface. This repository contains the normative specification, the declarations in both of the forms it distributes them in, and the suite an implementation runs against itself.
The specification is SPEC.md.
Declarations, and no definitions. Every function declared here is supplied by an implementation package; building this package alone produces a library with undefined references, which is the intended outcome.
| Module | Header | Interface | Class |
|---|---|---|---|
openkal.types |
openkal/types.h |
machine word, error values, endpoint | — |
openkal.version |
openkal/version.h |
what the implementation says about itself | every |
openkal.abort |
openkal/abort.h |
termination | core |
openkal.stream |
openkal/stream.h |
byte streams | core |
openkal.memory |
openkal/memory.h |
allocation, and the environment's granularity | core |
openkal.env |
openkal/env.h |
the parameters a program receives at inception | optional |
openkal.time |
openkal/time.h |
monotonic and wall time sources | optional |
openkal.fs |
openkal/fs.h |
directories and open files, relative throughout | optional |
openkal.process |
openkal/process.h |
starting a program and waiting for it | optional |
openkal.task |
openkal/task.h |
execution contexts, and the primitive they are built upon | optional |
openkal.random |
openkal/random.h |
a source of unpredictable bytes | optional |
openkal.exec |
openkal/exec.h |
a region of the address space a program may execute | optional |
openkal.terminal |
openkal/terminal.h |
an interactive stream's treatment of what is typed | optional |
openkal.net |
openkal/net.h |
a connection, and a listener for connections | optional |
openkal.datagram |
openkal/datagram.h |
a message with a boundary | optional |
openkal.space |
openkal/space.h |
an address space, and a context executing in one | optional |
openkal.timeout |
openkal/timeout.h |
a bound upon operations that would otherwise wait | optional |
There were three classes and there are two. Version 0.8 named a middle
one — standard, "an interface an implementation hosting a C library provides"
— and it was false: an implementation for a machine with firmware and no
operating system provides none of openkal.fs, openkal.process or
openkal.task, and a C library is hosted above it. Clause 6.1 makes an
interface's absence a fact a consumer learns from the linker; no class was
needed to predict it, and the prediction was wrong.
openkal.version is in the table and is not an interface: it provides no
resource, and every conforming implementation exports its two operations so that
a consumer with no linker to ask can ask before it calls.
The contract is a C application binary interface, and a C translation unit has
no import. The canonical consumer of this contract — a C library ported onto
openkal — is a C translation unit, so the declarations are distributed in both
forms.
The header is the statement. The module includes it in its global module fragment and exports the names, so a consumer that imports and a consumer that includes obtain the same entities rather than two declarations that agree today. What the module adds is not a second declaration: it is what C++ can check and C cannot — the layouts clause 5.3 freezes are asserted there, and the capability words become types that cannot be mixed.
SURFACE.txt is normative, and both forms are compared against it:
tools/check-declarations.sh |
compiles a translation unit naming every entity, with -nostdinc — because the consumer the header exists for is compiled that way |
| a test in each implementation | the same list, reached through import openkal.* |
conformance/src/declarations.c |
the same list again, compiled by every toolchain that builds the suite |
The header includes nothing. openkal must be usable on a freestanding target,
and a consumer compiled with -nostdinc has nothing to include.
A program declares two dependencies. The first fixes the version of the contract it is written against; the second selects an implementation and is ordinarily conditional on the target.
[dependencies]
openkal = "0.13.0"
[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.13.0"
[target.'cfg(os = "macos")'.dependencies]
openkal-macos = "0.10.0"
[target.'cfg(windows)'.dependencies]
openkal-windows = "0.8.0"The program imports the interface and names no implementation.
import openkal.stream;
int main() {
const char greeting[] = "hello\n";
kal::write(kal::out(), greeting, sizeof(greeting) - 1);
return 0;
}Changing the implementation is a change to one line of the manifest. The source does not change, and this property is the reason the specification exists.
A build that selects openkal resolves several layers, and each guarantees only
its own. The layer names are those of the build tool (mcpp, The Target Side).
| Layer | Supplied by | Guarantees | Does not guarantee |
|---|---|---|---|
kernel-abi = openkal |
this specification and one implementation per target | the behaviour of every kal_* operation is independent of the platform; a missing interface is a link failure; a varying property is answered by an enquiry |
which C library is present; whether a platform SDK is available; that the rest of the program is portable |
c-abi = musl |
openkal-musl |
a POSIX-shaped C environment built on kal_*; what it cannot provide it refuses and lists |
the platform's own C runtime headers (io.h, TargetConditionals.h); facilities openkal does not define (epoll, signal handlers) |
c++-abi = libc++ |
openkal-llvm-runtime |
a C++ runtime configured for that C library | |
| build tool | mcpp |
a layer supplied by the dependency graph is supplied wholly by it: the host's headers and libraries are not searched | whether a package's source compiles against that C library |
Four rules follow, and they bind packages rather than this specification.
- openkal is not openkal-musl. The headers of this package include nothing
and conflict with no platform header. A package that fails to find
winsock2.horTargetConditionals.hhas met the C library layer, and adapts oncfg(c-abi = "musl"), not onkernel-abi. - A platform dependency is permitted and is the package's own. A package
may call a platform's system interfaces, provided the headers and libraries
it needs come from the dependency graph (a feature-scoped dependency) and are
not visible to its consumers. openkal is responsible only for what crosses
kal_*. - One C runtime and one C++ runtime per image. A platform dependency means
the platform's system interfaces, across which only handles and values pass.
It does not include a static library compiled against the platform's C
runtime, nor passing an object that runtime owns (
FILE*, memory released by the other runtime,errno) across the boundary. A context created by a platform library rather than bykal_task_starthas no C library state, and code reached from such a context shall not rely on it. - Source does not identify the implementation. No macro states that a
program is built on openkal. A difference in headers is adapted to on
c-abi; a difference in facilities (for example, the absence ofepoll) is adapted to by a feature of the package, or where a manifest must select it, oncfg(all(kernel-abi = "openkal", c-abi = "musl")). Both are decided when dependencies are resolved, not when the source is compiled.
An implementation provides definitions and no modules. It reaches the declarations it is defining and exports nothing: the interface belongs to this package.
openkal-linux |
on the kernel's own system-call interface |
openkal-macos |
on the kernel's own calls, and two names no C library defines |
openkal-windows |
on Win32 and the object manager beneath it, using no C runtime symbol |
Clause 4 states how the declarations are organised, clause 6 states how the absence of an interface is expressed, and clause 7 states the requirements an implementation must satisfy.
Clause 9 has two halves, and both are here.
The artefact. tools/check-surface.sh compares an implementation's exported
names against SURFACE.txt. It detects the one freedom an implementation retains
after the language has removed the others: the addition of names.
The behaviour. conformance/ is a program an implementation
runs against itself — 193 observations across fifteen interfaces, in four kinds:
behaviour, ABI, stability and cost.
# from an implementation's working tree
bash /path/to/openkal/tools/run-conformance.sh openkal-linux . fullIt is composable, because openkal is: an implementation provides an interface in
whole or not at all, so each interface is a feature and a run reports on what was
selected. It reports three counts, and the third is the one to read —
190 held, 0 did not hold, 3 not observed — because a suite that reported only
the first two cannot distinguish an interface that behaved from one it never
examined.
It depends on openkal and the language. There is no import std: the suite must
run against an implementation in a program that carries no other runtime, and a
suite resting on facilities that implementation may be the only supplier of would
be reporting on itself.
Three compiler families across three systems, because a contract that holds only under the compiler its author used is a description of that compiler.
| Linux | macOS | Windows | |
|---|---|---|---|
| gcc | ✓ | — | ✓ (PE, GNU CRT) |
| llvm | ✓ | ✓ | ✓ (MSVC ABI) |
| msvc | — | — | ✓ |
Apache-2.0.
kit/ holds facilities composed from the interfaces this specification defines.
It is a separate package, mcpplibs/openkal-kit, and it is not part of the
specification.
The specification admits an interface only when it is a minimal capability every
kernel has and cannot be composed from the interfaces already present. That rule
is what keeps openkal implementable on a machine with firmware and nothing else,
and it leaves a gap: a program that wants to carry bytes between two of its own
contexts, or to turn "127.0.0.1:8080" into an endpoint, has an answer in POSIX
and no answer here — because both are composed rather than primitive.
That gap was being filled by the port layer. There is one port layer today and what it composes is POSIX, so a native openkal program either wrote the composition again or took a whole C library. The kit is where the composition is written once.
The contract form is what makes the two unmistakable. Clause 10 states that
openkal's contract is a C application binary interface. The kit deliberately is
not one: it is C++ modules in namespace kal::kit, and it exports no name
beginning with kal_. Measured on its objects: the defined names are C++ mangled
module initialisers such as _ZGIW7openkalW3kitW7channel, and the operations are
inline and emitted into consumers rather than exported at all. So
tools/check-surface.sh --complete does not read a program that links the kit as
an implementation which has added names — the rule that checker enforces is about
the C surface, and the kit has none.
So "is this normative" is answered by the shape of what is exported rather than by a sentence saying it is not. A sentence can be overlooked; a mangled name cannot become a C symbol.
The consequence is the one that matters. Clause 8 forbids the specification from altering a declaration it has published, which is what makes openkal safe to depend upon and what makes it the wrong place for a facility still finding its shape. The kit may evolve.