From ee7f55c9c0ce2b165af603242e679d723bc9d71b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Sun, 1 Nov 2026 03:47:04 +0900 Subject: [PATCH] 0.20.0 --- openkal 0.19.0: kal_task_cores from the kernel's topology, kal_task_set_priority as the scheduling policy kal_task_cores counts the distinct (package, core) pairs sysfs states for the processors in the affinity mask, on every architecture the kernel supports, and says nothing (zero) where a processor's topology cannot be read. kal_task_set_priority sets the calling thread's policy: SCHED_OTHER, SCHED_BATCH, SCHED_IDLE; raising it again may be refused (RLIMIT_NICE), which is kal_err_permission. Measured: 24 cores of 32 processors; the conformance suite of 0.19.0 holds in all 272 observations. Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com> --- README.md | 4 +-- mcpp.toml | 4 +-- src/sys.h | 4 +-- src/task.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4e7d276..93c6458 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface. ```toml [dependencies] -openkal = "0.17.0" +openkal = "0.19.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.19.0" +openkal-linux = "0.20.0" ``` ## Why it does not use a C library diff --git a/mcpp.toml b/mcpp.toml index 11326a5..ce464e7 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.19.0" +version = "0.20.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" @@ -64,7 +64,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.18.0" +openkal = "0.19.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/sys.h b/src/sys.h index e1a01a3..a90bc86 100644 --- a/src/sys.h +++ b/src/sys.h @@ -107,7 +107,7 @@ enum : okl_long { nr_renameat = 264, nr_readlinkat = 267, nr_dup3 = 292, nr_execveat = 322, nr_close_range = 436, nr_prlimit64 = 302, nr_dup2 = 33, nr_utimensat = 280, nr_symlinkat = 266, nr_fstatfs = 138, - nr_getrandom = 318, + nr_getrandom = 318, nr_sched_setscheduler = 144, // openkal 0.15: the region a context stands on is measured by asking the // kernel which pages are mapped. The floor comes from the limit the kernel // itself enforces, which `nr_prlimit64' above already names. @@ -203,7 +203,7 @@ enum : okl_long { nr_close_range = 436, nr_prlimit64 = 261, nr_dup2 = -1, nr_fcntl = 25, nr_prctl = 167, nr_sched_getaffinity = 123, nr_getppid = 173, nr_arch_prctl = -1, nr_utimensat = 88, nr_symlinkat = 36, nr_fstatfs = 44, - nr_getrandom = 278, + nr_getrandom = 278, nr_sched_setscheduler = 119, // openkal 0.15: the region a context stands on is measured by asking the // kernel which pages are mapped. The floor comes from the limit the kernel // itself enforces, which `nr_prlimit64' above already names --- and the two diff --git a/src/task.cpp b/src/task.cpp index 2c24e7c..a9e1db2 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -536,6 +536,41 @@ int start(void (*entry)(void*), void* arg, okl_uptr want, kal_task* out) { } // namespace +namespace { + +// A small number from a file of the kernel's: what /sys answers is one line. +bool read_number(const char* path, okl_long& out) { + const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast(path), + okl::o_rdonly | okl::o_cloexec); + if (okl::failed(fd)) return false; + char text[32]; + const okl_long n = okl::sys(okl::nr_read, fd, reinterpret_cast(text), static_cast(sizeof text)); + okl::sys(okl::nr_close, fd); + if (okl::failed(n) || n <= 0) return false; + okl_long v = 0; + bool any = false; + for (okl_long i = 0; i < n && text[i] >= '0' && text[i] <= '9'; ++i) { v = v * 10 + (text[i] - '0'); any = true; } + out = v; + return any; +} + +// `/sys/devices/system/cpu/cpu/topology/'. +bool cpu_topology(unsigned long cpu, const char* leaf, okl_long& out) { + char path[96]; + unsigned long at = 0; + for (const char* c = "/sys/devices/system/cpu/cpu"; *c; ++c) path[at++] = *c; + char digits[24]; + int d = 0; + do { digits[d++] = static_cast('0' + cpu % 10); cpu /= 10; } while (cpu); + while (d) path[at++] = digits[--d]; + for (const char* c = "/topology/"; *c; ++c) path[at++] = *c; + for (const char* c = leaf; *c; ++c) { if (at + 1 >= sizeof path) return false; path[at++] = *c; } + path[at] = 0; + return read_number(path, out); +} + +} // namespace + extern "C" { int kal_task_start(void (*entry)(void*), void* arg, kal_task* out) { @@ -696,7 +731,53 @@ int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) { // convention, and so does the block this implementation builds. Clause 7.10. kal_uintptr kal_task_props(void) { return KAL_TASK_PROP_PREEMPTIVE | KAL_TASK_PROP_PARALLEL - | KAL_TASK_PROP_WAIT_TIMEOUT | KAL_TASK_PROP_THREAD_LOCAL; + | KAL_TASK_PROP_WAIT_TIMEOUT | KAL_TASK_PROP_THREAD_LOCAL + | KAL_TASK_PROP_PRIORITY; +} + +// THE CORES OF THE PROCESSORS THIS PROGRAM MAY RUN ON. The kernel states each +// processor's package and core in sysfs on every architecture it supports; two +// processors that share both are one core. A processor whose topology cannot +// be read makes the answer "cannot say" rather than a guess. +kal_uintptr kal_task_cores(void) { + unsigned long mask[128] = { 0 }; + const okl_long n = okl::sys(okl::nr_sched_getaffinity, 0, static_cast(sizeof mask), + reinterpret_cast(mask)); + if (okl::failed(n) || n <= 0) return 0; + constexpr unsigned kMax = 1024; + okl_long seen[kMax]; + unsigned count = 0; + const unsigned long bits = static_cast(n) * 8; + constexpr unsigned long kWord = 8 * sizeof(unsigned long); + for (unsigned long cpu = 0; cpu < bits; ++cpu) { + if (!((mask[cpu / kWord] >> (cpu % kWord)) & 1ul)) continue; + okl_long package = 0, core = 0; + if (!cpu_topology(cpu, "physical_package_id", package) || !cpu_topology(cpu, "core_id", core)) return 0; + const okl_long key = (package << 32) | core; + bool known = false; + for (unsigned i = 0; i < count && !known; ++i) known = seen[i] == key; + if (known) continue; + if (count == kMax) return 0; + seen[count++] = key; + } + return count; +} + +// THE CALLING THREAD'S SCHEDULING POLICY: the kernel's default, its batch +// policy, and its idle policy, which runs only when nothing else wants the +// processor. An unprivileged thread may lower its policy and may be refused +// raising it again (RLIMIT_NICE), which is kal_err_permission. +int kal_task_set_priority(kal_uintptr priority) { + okl_long policy; + switch (priority) { + case KAL_TASK_PRIORITY_DEFAULT: policy = 0; break; // SCHED_OTHER + case KAL_TASK_PRIORITY_LOW: policy = 3; break; // SCHED_BATCH + case KAL_TASK_PRIORITY_BACKGROUND: policy = 5; break; // SCHED_IDLE + default: return kal_err_invalid; + } + int param = 0; // struct sched_param: the static priority, 0 for these policies + const okl_long r = okl::sys(okl::nr_sched_setscheduler, 0, policy, reinterpret_cast(¶m)); + return okl::failed(r) ? okl::translate(r) : kal_ok; } }