Skip to content

Context Switching - #28

Merged
d-tatianin merged 152 commits into
masterfrom
ctx-switching
Nov 12, 2026
Merged

d-tatianin merged 152 commits into
masterfrom
ctx-switching

Conversation

@d-tatianin

Copy link
Copy Markdown
Member

No description provided.

The spinlock, the mutex and the seqlock were in the kernel root for
lack of a home, and the reader-writer locks to come would have joined
them there. The headers move to <locking/>.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The header split going forward is going to work like the following:
- include/ -> public API
- include/private -> private API (for neighbor subsystems)
- include/private/arch -> private API that is meant to be called or
                          implemented by the architecture's "half" of
                          a generic subsystem
- include/detail -> private API, but may be leaked via the public
                    header because it needs either fields or other
                    helpers to be available inline
- include/arch/ -> public, arch-specific API
- include/arch/private -> private arch-specific API
- include/arch/detail -> the arch-specific "half" of the implementation
                         of a generic subsystem

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Leaking private headers via the public ones completely invalidates the
whole idea behind having private headers, so check that we don't do
that.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A statement expression macro must indent its body once and put the
closing }) in the first colum. Check it, and convert RUNTIME_COMPARE()
to follow this rule.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
@d-tatianin
d-tatianin force-pushed the ctx-switching branch 2 times, most recently from 1b0eb8c to 8f1ab01 Compare November 11, 2026 22:15
A DEFINE_*() or DECLARE_*() macro must leave the semicolon to its
users, so that a use reads like any other definition. Check it, and
convert DEFINE_MUTEX() and DEFINE_SPINLOCK() to follow this rule.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Nothing is declared in it anymore and both of its includes are already
reached through memory/io.h.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Callers only hold an io window and hand it back, its layout is an
implementation detail. Move it out of the public header together with
the pio address type and the io type enum, and the arch header that
defines them into arch/detail/.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Only the spin_*() helpers touch its fields, so spinlock.h keeps a
forward declaration and the definition moves out of the way.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Only the mutex_*() helpers touch its fields, so mutex.h keeps the
documented forward declaration and the definition moves out of the
way.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The initial state of each lock was written twice, in its initializer
macro and in its init function, so a new field had to be added to
both. Going forward, an init function is defined through the macro next
to it, e.g. *lock = SPINLOCK_INIT(*lock).

The time keeper passed the address of its lock and sequence count
instead of the objects, which only built because neither macro uses its
argument. Pass the objects.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
struct log_ring is defined in detail/log_ring.h, so log_ring.h never
said the type exists to its reader.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Only the subsystem and its arch half use the table entry, its flags and
the entry point of the exception handlers, so the public header has no
readers.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The statement expression declares its own ret, which shadows the ret
every caller assigns the result to. Rename it to consume_ret so that
-Wshadow can be enabled.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The old device's final reading is declared in an inner block under the
same name as the new device's. Rename it to old_cycles.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
SEQLOCK_INIT() builds a struct seqlock, but seqlock is a typedef of an
anonymous struct, so the macro fails to compile on its first use. It
also hands the whole seqlock to SEQCOUNT_INIT() and SPINLOCK_INIT()
instead of its members. Nothing uses it yet.

Name the struct like struct seqcount, drop the typedef and initialize
each member from its own field.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
do_tick() converts the cycles since the previous tick to nanoseconds,
rounds down and throws the remainder away. The keeper falls behind the
counter by up to a nanosecond per tick, and ns_since_boot() can go back
by one: a reader that sampled the counter after the tick did, but still
saw the previous base, gets the time without the rounding.

Keep the remainder in units of the device's shift and add it to the
next conversion. A device switch rounds it off once, since the next
device has a different shift.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A shadowed variable compiles silently and is easy to mistake for the
outer one. Nothing in the tree shadows anymore, so keep it that way.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
irq_* prefixed APIs are going to be reserved for actual interaction with
IRQ lines and hardware, not CPU flags. Rename to reflect that and to
avoid confusion about what this API does.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The flag was a static in the stub header, so every translation unit had
its own copy, and a lock taken with interrupts disabled in spinlock.c
left them enabled as far as the test could tell. Define it once in
kernel_helpers.cpp.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
So that generic code doesn't have to use the arch_* variant directly.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A handler interrupting the panic could log into the ring being dumped or
block on a lock the panic path holds.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Assembly is about to need MSR numbers, but msr.h is C and its digit
separators do not assemble. Keep the numbers as plain defines in a
header of their own, which msr.h includes.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Going forward, the contract with the arch-specific code is going to be
that upon calling the generic entry() per-cpu variables are already
online and usable on the BSP.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Cover locking, static initialization, try-lock and the interrupt state
around the irq-saving variants, which the tests can observe now that the
emulated interrupt state is shared.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
They were compound assignments on a gs-qualified lvalue, which only
becomes a single instruction with optimizations on. A debug build loads,
modifies and stores, so an interrupt updating the same variable in
between loses its update.

Spell them out as one instruction. The gs prefix is written explicitly
since clang drops it for a gs-qualified memory operand.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A convenience macro to walk all CPUs.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
BIT_UNIT_FIND_FIRST_SET() takes a long, so BIT_FIELD_SHIFT() would
truncate a u64 mask where long is 32-bit. Replace it with a helper for a
value of any integer type, which picks the builtin by the size of the
value.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
CPU numbers were plain u32 next to APIC IDs of the same type, and loops
over the CPUs used u32 and size_t interchangeably. Give logical CPU
numbers and counts of CPUs a type of their own, printed with %u.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The kernel parses its command line in entry.c and nowhere else, so
cmdline_parse(), cmdline_parse_tables() and struct param_table are not
something the author of a parameter uses.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Modules need parameters that do not clash with each other. The key is
module.name, so the parser needs no new table and plain parameters are
unaffected. Init parameters get no module variant: they belong to the
boot of the kernel itself and would mean nothing to a module loaded
after it. In a runtime module an init parameter is an unused entry, so
a driver that has one still builds either way.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The image's command line was fixed, so trying a parameter meant editing
the script. --cmdline extends a baremetal job's command line as well,
which makes --baremetal-cmdline redundant, so build.py drops it.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Let code outside the scheduler read how many context switches a CPU
has done without reaching into its runqueue.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Nothing exercises the scheduler with many threads. Every rocket and
particle is a thread that sleeps for a frame between draws, which keeps
thousands of threads waking 60 times a second, and the log gets switch
rate and wakeup latency every two seconds.

Exclusive with the framebuffer console and built with --config
fireworks. fireworks.runtime-seconds=<n> makes it stop and print its
totals.

The demo is a tristate module, so it can be loaded at runtime once
the kernel has a module loader.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Only kthread_stop() could wait for a kernel thread to exit, and it also
asks the thread to stop. Add kthread_wait_for_exit() for a thread that
ends on its own, and use it in kthread_stop().

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A framework for testing kernel features that require the real kernel
environment.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A case that hangs would stall the whole run. Each case now gets 30 s
unless KTEST_CASE_WITH_TIMEOUT() says otherwise, after which it fails
and its thread is left running. A result that arrives after its case
finished is reported as late and not counted.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Running every case is slow when working on one subsystem. ktest.run=
now takes a comma-separated list of suites and suite.case names, "all"
still runs everything.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A helper for starting a number of threads that all run the same function
with an index per-thread.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A run that waits for a result has to end once the kernel printed it.
qemu_watch() can now stop at the first match of a pattern, which it
looks for with the color escapes of the console removed, and returns
the match after a short drain of the remaining output.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Use download() from build_utils instead, which the toolchain build
already uses, it retries a failed download and only moves the file into
place once it is complete.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A kernel that builds can still crash early in boot. --boot-test boots it
headless and exits 0 once it idles, 1 on a panic, a BUG or an oops, and
2 when it never got that far. The output is judged by criteria.json,
like a plain baremetal run. --baremetal-criteria becomes --criteria
and picks the criteria for both.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
This boots the tests headless and exits with 0 when everything passed,
1 on a failure or a crash and 2 when the run never finished. The output
is judged by criteria-ktest.json, the same criteria as a baremetal run.
--ktest always builds the ktest preset and uses its criteria, so it
can't be combined with --config or --criteria.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A preset fixes the optimization level and whether LTO is used, so
building a preset with another combination meant editing its .config by
hand. Add --optimization-level and --lto/--no-lto to override them.

The options only apply to the build that is given them. They win over
the preset and over a value changed by hand in .config, and build.py
records the values they replace in the build directory, so that a later
build without them restores those values.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
On a cache miss, both GCC legs of the build matrix built the toolchain
at the same time. Build it once in a toolchain job that the build job
waits for, with the toolchain steps in a composite action that both
jobs use.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
A successful build doesn't mean that the kernel boots, undefined
behavior can still crash it early. Boot every kernel the job builds
with --boot-test, and rename the job to build-and-boot.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
The AST checks of the linter only see the code the linted build
compiles, and the default preset leaves out everything behind an option
it doesn't enable. Lint every preset in configs/ instead.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Cover timed sleeps, equal priority spinners sharing the CPU, wakeup
preemption by a higher priority but not an equal one, and kthread_stop()
on a sleeping and on a never started thread.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Cover hundreds of timers expiring at the same time, cancellation, a
callback rearming its own timer and the clock never stepping back
between two readers.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Cover mutual exclusion between contending threads, a waiter not starved
by a thread that keeps retaking the lock, and try-lock failing only
while the lock is held.

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Add the counterpart of str_starts_with().

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
Check each describer on various memory types:
- A function and a variable in the image
- Per-CPU variable on every CPU
- Heap object and a large allocation
- Block and a struct page
- The whole chain through %pMEM

Signed-off-by: Daniil Tatianin <99danilt@gmail.com>
@d-tatianin
d-tatianin merged commit 857b886 into master Nov 12, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant