Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface.

```toml
[dependencies]
openkal = "0.20.0"
openkal = "0.21.0"

[target.'cfg(os = "linux")'.dependencies]
openkal-linux = "0.21.0"
openkal-linux = "0.22.0"
```

## Why it does not use a C library
Expand Down Expand Up @@ -102,6 +102,38 @@ A program that carries a runtime of its own answers from that runtime instead
(`pthread_getattr_np`), because in that arrangement the runtime is what owns the
stacks and is what a program above openkal would ask for itself.

## What 0.22 adds (openkal 0.21)

**Instructions from a file.** `kal_exec_map_part` maps the file's pages over part
of a region (`mmap` with `MAP_FIXED`, private), read, or read and executed;
`KAL_EXEC_PROP_FILE` is claimed. A volume mounted without execution reports
`kal_err_not_supported`.

**An ended program's processor time.** `kal_process_info` reports `cpu_ns` of a
started program that has ended from the same record as its peak memory
(`waitid(WNOWAIT)`'s usage), until `kal_process_close` collects it.

**A program is started without copying this one.** `kal_process_spawn` starts the
program with `clone(CLONE_VM | CLONE_VFORK)` on a stack of its own, as a C
library's `posix_spawn` does, and every request is held until it is replaced; the
started image sets each handled request to its default before it takes the
caller's mask. A copy of the address space (`clone` with `SIGCHLD` alone) copied
the page tables of the whole program and made each page it wrote afterwards fault
again: 1.4 ms in the kernel and about 1 900 faults for each worker a language
server started.

**An enquiry and an open read the name once.** `kal_fs_info` and `kal_fs_open`
check the name and terminate it for the kernel in one pass, the volume each
enquiry asks about is read without a lock (a sequence read before and after the
slots), and the answer is written straight into the caller's structure: what an
enquiry costs beyond the kernel's own fell from 28 ns to 19 ns.

**A chunk of `kal_alloc` is carved as it is used.** A free list threaded through
each 1 MiB chunk when it was mapped made the whole chunk resident; six classes in
use made 6 MiB resident in every program. A block is now carved when it is
handed out. An alignment wider than 4096 bytes is mapped, as a block of a class
is aligned only to its size or to the page.

## What 0.21 adds (openkal 0.20)

**Transfers at an offset.** `kal_fs_read_at` and `kal_fs_write_at` are the
Expand Down
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-linux"
version = "0.21.0"
version = "0.22.0"
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."
license = "Apache-2.0"

Expand Down Expand Up @@ -65,7 +65,7 @@ provides-interfaces = [
]

[dependencies]
openkal = "0.20.0"
openkal = "0.21.0"

# The package contributes definitions and no modules. The interface it
# implements is declared by the specification package, which this package
Expand Down
38 changes: 37 additions & 1 deletion src/exec.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "sys.h"
#include "handle.h"
#include <openkal/exec.h>
#include <openkal/map.h>

namespace okl { okl_ulong auxval(okl_ulong key); }

Expand Down Expand Up @@ -114,6 +116,40 @@ int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) {

kal_uintptr kal_exec_granularity(void) { return page(); }

// A PART THAT IS A FILE'S (0.21): the file's pages mapped over the part,
// private, and executable where asked. MAP_FIXED replaces what the part held in
// one call, so there is no moment at which the addresses are free for another
// mapping to take. The arguments are checked first: a refused part leaves the
// region as it was.
int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_file f, kal_u64 at,
kal_uintptr how) {
const okl_uptr g = page();
const int fd = okl::unpack(f.h);
if (region == nullptr || size == 0 || fd < 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0 || at % KAL_MAP_ALIGNMENT != 0
|| (how & ~KAL_EXEC_MAP_EXECUTE) != 0)
return kal_err_invalid;
okl::kstat st{};
const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast<okl_long>(&st));
if (okl::failed(s)) return okl::translate(s);
if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported;
const kal_u64 length = st.size > 0 ? static_cast<kal_u64>(st.size) : 0;
if (at > length || static_cast<kal_u64>(size) > length - at) return kal_err_invalid;
const okl_long prot = okl::prot_read | ((how & KAL_EXEC_MAP_EXECUTE) ? okl::prot_exec : 0);
const okl_long r = okl::sys(okl::nr_mmap,
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), prot, okl::map_private | okl::map_fixed, fd,
static_cast<okl_long>(at));
if (okl::failed(r)) {
// ENODEV: the file system does not map. EPERM: its files are not
// executed (a volume mounted noexec). The resource refuses, not the
// implementation (exec.h).
if (-r == okl::e_nodev || -r == okl::e_perm) return kal_err_not_supported;
return okl::translate(r);
}
return kal_ok;
}

void kal_exec_free(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
Expand All @@ -127,7 +163,7 @@ void kal_exec_free(void* p, kal_uintptr size) {
kal_uintptr kal_exec_props(void) {
// Executable memory is available to every artifact on this kernel: nothing
// here is granted only to a program produced in a particular way.
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE;
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE | KAL_EXEC_PROP_FILE;
}

} // extern "C"
58 changes: 42 additions & 16 deletions src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,23 +228,49 @@ kal_uintptr volume_props_of(int fd) { return volume_props(fd); }
// A volume's format does not change under a device number while it is mounted,
// so the answer is kept by the device the kernel reports for the node; a slot
// a later device needs is taken over (eight are kept).
//
// READ WITHOUT A LOCK (0.22): the slots change only when a ninth device is
// asked about, so a reader takes the sequence before and after reading them and
// reads again if a writer was between; writers take the lock. The lock taken on
// every enquiry was a fifth of what an enquiry cost beyond the kernel's own.
struct volume_answer { okl_u64 dev; kal_uintptr props; bool known; };
volume_answer g_volumes[8];
unsigned g_volume_next = 0;
volatile int g_volume_lock = 0;
unsigned g_volume_sequence = 0; // odd while a writer changes a slot

bool volume_known(okl_u64 dev, kal_uintptr& props) {
for (;;) {
const unsigned before = __atomic_load_n(&g_volume_sequence, __ATOMIC_ACQUIRE);
if (before & 1u) { okl::sys(okl::nr_sched_yield); continue; }
bool found = false;
kal_uintptr p = 0;
for (auto const& v : g_volumes)
if (__atomic_load_n(&v.known, __ATOMIC_RELAXED) && __atomic_load_n(&v.dev, __ATOMIC_RELAXED) == dev) {
p = __atomic_load_n(&v.props, __ATOMIC_RELAXED);
found = true;
break;
}
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (__atomic_load_n(&g_volume_sequence, __ATOMIC_RELAXED) != before) continue;
props = p;
return found;
}
}

kal_uintptr volume_props_on(okl_u64 dev, int fd) {
kal_uintptr props = 0;
if (volume_known(dev, props)) return props;
props = volume_props(fd);
while (__atomic_exchange_n(&g_volume_lock, 1, __ATOMIC_ACQUIRE)) okl::sys(okl::nr_sched_yield);
for (auto const& v : g_volumes)
if (v.known && v.dev == dev) {
const kal_uintptr props = v.props;
__atomic_store_n(&g_volume_lock, 0, __ATOMIC_RELEASE);
return props;
}
__atomic_store_n(&g_volume_lock, 0, __ATOMIC_RELEASE);
const kal_uintptr props = volume_props(fd);
while (__atomic_exchange_n(&g_volume_lock, 1, __ATOMIC_ACQUIRE)) okl::sys(okl::nr_sched_yield);
g_volumes[g_volume_next++ % 8] = volume_answer{ dev, props, true };
__atomic_store_n(&g_volume_sequence, g_volume_sequence + 1, __ATOMIC_RELAXED);
__atomic_thread_fence(__ATOMIC_RELEASE);
volume_answer& slot = g_volumes[g_volume_next++ % 8];
__atomic_store_n(&slot.known, false, __ATOMIC_RELAXED);
__atomic_store_n(&slot.dev, dev, __ATOMIC_RELAXED);
__atomic_store_n(&slot.props, props, __ATOMIC_RELAXED);
__atomic_store_n(&slot.known, true, __ATOMIC_RELAXED);
__atomic_store_n(&g_volume_sequence, g_volume_sequence + 1, __ATOMIC_RELEASE);
__atomic_store_n(&g_volume_lock, 0, __ATOMIC_RELEASE);
return props;
}
Expand Down Expand Up @@ -293,8 +319,8 @@ void fill_info(const okl::kstat& st, kal_u32 wanted, kal_node_info* out,
v.executable = (st.mode & 0111u) != 0 ? 1 : 0;
}

const kal_u32 n = self < sizeof v ? self : (kal_u32)sizeof v;
okl::copy(reinterpret_cast<char*>(out), reinterpret_cast<const char*>(&v), n);
if (self >= sizeof v) { *out = v; out->self_size = self; return; }
okl::copy(reinterpret_cast<char*>(out), reinterpret_cast<const char*>(&v), self);
}

// A node that refers to nothing, in the same shape.
Expand Down Expand Up @@ -453,8 +479,8 @@ int kal_fs_open_dir(kal_dir base, const char* name, kal_uintptr len, kal_dir* ou
int kal_fs_open(kal_dir base, const char* name, kal_uintptr len,
kal_uintptr flags, kal_file* out) {
const int b = okl::unpack(base.h);
if (b < 0 || out == nullptr || !okl::acceptable(name, len)) return kal_err_invalid;
okl::terminated t(name, len); if (!t.ok) return kal_err_invalid;
if (b < 0 || out == nullptr) return kal_err_invalid;
okl::accepted_name t(name, len); if (!t.ok) return kal_err_invalid;

const bool r = (flags & KAL_OPEN_READ) != 0;
const bool w = (flags & KAL_OPEN_WRITE) != 0;
Expand Down Expand Up @@ -556,8 +582,8 @@ kal_intptr kal_fs_write_at(kal_file f, const void* buf, kal_uintptr len, kal_u64
int kal_fs_info(kal_dir base, const char* name, kal_uintptr len,
kal_uintptr flags, kal_u32 wanted, kal_node_info* out) {
const int b = okl::unpack(base.h);
if (b < 0 || !info_ok(out) || !okl::acceptable(name, len)) return kal_err_invalid;
okl::terminated t(name, len); if (!t.ok) return kal_err_invalid;
if (b < 0 || !info_ok(out)) return kal_err_invalid;
okl::accepted_name t(name, len); if (!t.ok) return kal_err_invalid;
okl::kstat st{};
// RESOLVES BY DEFAULT, SO THAT ASKING AND OPENING ANSWER THE SAME QUESTION.
//
Expand Down
43 changes: 28 additions & 15 deletions src/memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ struct spin {

spin g_lock;
void* g_free[kClasses];
// The part of each class's newest chunk no block has been carved from yet.
unsigned char* g_next[kClasses];
unsigned char* g_end[kClasses];

int class_of(okl_uptr n) {
okl_uptr s = kMinBlock; int c = 0;
Expand All @@ -66,19 +69,29 @@ void unmap(void* p, okl_uptr bytes) {
okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - 1); }

// A chunk is page-aligned and a block size is a power of two no larger than a
// chunk, so a block at a multiple of its size is aligned to its size. That is
// why an alignment request is satisfied by choosing a larger class rather than
// by padding: no metadata is needed, and none is kept.
bool refill(int c) {
// chunk, so a block at a multiple of its size is aligned to its size or to the
// page, whichever is smaller. That is why an alignment request up to 4096 bytes
// (the smallest page among the environments) is satisfied by choosing a larger
// class rather than by padding: no metadata is needed, and none is kept. A
// wider alignment is mapped (below), as a block of a class would not have it.
//
// A CHUNK IS CARVED AS ITS BLOCKS ARE HANDED OUT, NOT WHEN IT IS MAPPED. A free
// list threaded through the whole chunk at once wrote a pointer into each of
// its blocks, so every page of every chunk was made resident before a block of
// it was used: measured, six classes and six mebibytes resident, 1 500 page
// faults, in each program an ok image's loader started --- three quarters of
// what such a program paid before its first instruction.
void* carve(int c) {
const okl_uptr bs = size_of(c);
auto* base = static_cast<unsigned char*>(map(kChunk));
if (!base) return false;
for (okl_uptr off = kChunk; off >= bs; off -= bs) {
auto* b = base + off - bs;
*reinterpret_cast<void**>(b) = g_free[c];
g_free[c] = b;
if (g_next[c] == g_end[c]) {
auto* base = static_cast<unsigned char*>(map(kChunk));
if (!base) return nullptr;
g_next[c] = base;
g_end[c] = base + kChunk;
}
return true;
void* b = g_next[c];
g_next[c] += bs;
return b;
}

} // namespace
Expand All @@ -90,12 +103,12 @@ void* kal_alloc(kal_uintptr size, kal_uintptr align) {
if (align < kMinBlock) align = kMinBlock;

okl_uptr want = size < align ? align : size;
if (want <= kMaxSmall) {
if (want <= kMaxSmall && align <= 4096) {
const int c = class_of(want);
g_lock.lock();
if (!g_free[c] && !refill(c)) { g_lock.unlock(); return nullptr; }
void* b = g_free[c];
g_free[c] = *reinterpret_cast<void**>(b);
if (b) g_free[c] = *reinterpret_cast<void**>(b);
else b = carve(c);
g_lock.unlock();
return b;
}
Expand Down Expand Up @@ -128,7 +141,7 @@ void kal_free(void* p, kal_uintptr size, kal_uintptr align) {
if (align < kMinBlock) align = kMinBlock;

const okl_uptr want = size < align ? align : size;
if (want <= kMaxSmall) {
if (want <= kMaxSmall && align <= 4096) {
const int c = class_of(want);
g_lock.lock();
*reinterpret_cast<void**>(p) = g_free[c];
Expand Down
Loading
Loading