Skip to content

Domain schedules#445

Open
Kswin01 wants to merge 8 commits into
seL4:mainfrom
au-ts:run_time_domains
Open

Domain schedules#445
Kswin01 wants to merge 8 commits into
seL4:mainfrom
au-ts:run_time_domains

Conversation

@Kswin01

@Kswin01 Kswin01 commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

This adds support for domain schedules to the Microkit. It defines syntax that looks like:

    <!-- This element is used to declare the list of domains, to give
         each domain a human-readable name. -->
    <domains>
        <!-- The first domain contains the Microkit Monitor PD, but can contain others. -->
        <domain name="monitor" id="0" />
        <!-- You give each domain a name and its domain id. -->
        <domain name="emitter" id="1" />
        <!-- The domain ID is optional and can be auto-assigned if desired.
             It will be assigned to the smallest unused ID, and must be less
             than KernelNumDomains. -->
        <domain name="collector" />

        <!-- This is a domain schedule. Note that the same domain can appear twice
             in the schedule.
             Also supports optional attributes "start_index" and "index_shift".
         -->
        <domain_schedule>
            <schedule_entry domain="monitor" duration="6000 us" />
            <schedule_entry domain="emitter" duration="8000 us" />
            <schedule_entry domain="collector" duration="2000 us" />
            <!-- This element is optional, and is implicitly there anyway. -->
            <schedule_end_marker />
        </domain_schedule>
    </domains>

    <protection_domain name="emitter" domain="emitter">
        <program_image path="emitter.elf" />
    </protection_domain>

The standard, non-SMP config is changed to always have the domain schedule configured. This is only a minor memory cost to users who do not want to use domains.

Depends on seL4/rust-sel4#360 being merged.

Original description

This PR adds support for defining domain schedules in Microkit, and relies on the run-time domain scheduler changes due to be merged into seL4.

It also depends on the following branch of rust-sel4: https://github.com/au-ts/rust-sel4/tree/domain_set?branch=domain_set

Changes for users

In build_sdk.py we define two new configurations, debug_domains and release_domains. These configurations build the kernel with a max of 256 domains, and 256 entries into the domain schedule. The user can change this to values as they see fit. I have separated these from the regular configs as the users may not wish to have the extra memory overhead (although mostly negligible). I can merge these configs into one if its more desirable.

A domain schedule is defined in the sdf as:

  <domain_schedule>
      <domain name="domain_1" length="1000" />
      <domain name="domain_2" length="1000" />
      <domain name="domain_3" length="2000" />
  </domain_schedule>

The length is defined in milliseconds, and reads the TIMER_FREQUENCY from the kernel config to covert to timer ticks that the kernel requires. This is the case for aarch64 and riscv64. However, on x86, we don't have a static definition of the timer frequency, and is instead set at runtime by the kernel. I'm not sure of the best way to do it here, whether we should do this conversion in the capDL initialiser instead.

The above will insert the schedule using the new DomainSet invocations, beginning at index 0 by default. We also provide capDL a start index of 0 by default.

The users can optionally set these values like so:

    <domain_schedule>
        <domain name="domain_1" length="1000" />
        <domain name="domain_2" length="1000" />
        <domain name="domain_3" length="2000" />
        <domain_start index="2" />
        <domain_idx_shift shift="10" />
    </domain_schedule>

The start index is relative to the schedule list that the user provides not an absolute index. It must be in bounds of the length of schedule that the user defines. We also can have a domain shift, which means that will start inserting the schedule at that shifted index (in the above case 10). The shift + length of the schedule must be less than the kernel configured max number of domain schedules.

We also now build one monitor per domain. The monitor is responsible for handling faults, and also making threads passive if requested. The decision to create a monitor per domain was that these faults/requests can be serviced within that domains timeslice, and not have to wait for a singular domain to be scheduled again.

@Ivan-Velickovic

Copy link
Copy Markdown
Contributor

In build_sdk.py we define two new configurations, debug_domains and release_domains. These configurations build the kernel with a max of 256 domains, and 256 entries into the domain schedule. The user can change this to values as they see fit. I have separated these from the regular configs as the users may not wish to have the extra memory overhead (although mostly negligible). I can merge these configs into one if its more desirable.

Having the extra configs doesn't scale well, it already is a bit out of hand with the SMP ones, I think the domain functionality should be part of the existing configurations.

@lsf37

lsf37 commented Mar 24, 2026

Copy link
Copy Markdown
Member

Having the extra configs doesn't scale well, it already is a bit out of hand with the SMP ones, I think the domain functionality should be part of the existing configurations.

If you're always running with NUM_DOMAINS > 1 and don't set up a schedule, you will get the default schedule that wraps around once 2^56-1 ticks have passed. On a 1 GHz timer tick that's after about 833 days. Impact is negligible, just wanted to point out that if you are using this for a very long-term deployment with super precise time requirements you would see a blip every few years.

@Kswin01
Kswin01 force-pushed the run_time_domains branch 2 times, most recently from 8c458dd to a3b6e88 Compare March 24, 2026 05:32
@Indanz

Indanz commented Mar 24, 2026

Copy link
Copy Markdown

Some comments:

  • Default of 256 domains is way too many. 4 domains would be much more sensible and reduce the performance and memory overhead enough that can you enable it always.
  • 256 entries is over the top too, but that only costs a little bit of extra memory, not performance.
  • Milliseconds for domain durations is too coarse, I think you want microseconds.
  • Domains have no SMP support, so it's either one or the other, but never both.
  • Using domain_start index="1" as an example is misleading because it gives the impression that index starts from 1 instead of 0.

When we move to a clock based MCS API instead of time based, we'll run into the same issues as for domains.

For x86, the timing info is passed on via bootinfo. The problem of that is that it's hard to use by stand-alone or library code, as it depends on the system how to get that information. That's why I proposed a new syscall to get the same info (it could take either a domain cap, or an SchedContext/SchedControl cap). Even on Arm and RISC-V it depends on the system used whether those kernel configs are passed on or not.

@lsf37

lsf37 commented Mar 25, 2026

Copy link
Copy Markdown
Member
  • Default of 256 domains is way too many. 4 domains would be much more sensible and reduce the performance and memory overhead enough that can you enable it always.

The max number of domains has no performance impact at all as long as it fits 8 bits. You can have NUM_DOMAINS=256 and only use 4 of them. I do agree that you don't want to actually use 256 domains, that would indeed have a performance impact, but there is no reason to pick a particular low number for the maximum.

  • 256 entries is over the top too, but that only costs a little bit of extra memory, not performance.

That is the only one that actually does cost a little. The kernel default is 100, but I don't think 256 is a problem.

  • Milliseconds for domain durations is too coarse, I think you want microseconds.

I agree, people have been asking for a shorter minimum duration for non-MCS as well.

@midnightveil

midnightveil commented Mar 25, 2026

Copy link
Copy Markdown
Collaborator

The max number of domains has no performance impact at all as long as it fits 8 bits. You can have NUM_DOMAINS=256 and only use 4 of them. I do agree that you don't want to actually use 256 domains, that would indeed have a performance impact, but there is no reason to pick a particular low number for the maximum.

That's not true, all the scheduling queues are duplicated per-domain, and they're often some of the largest data in the kernel image aside from kernel page table structures. Though it's probably not a performance impact but more just a memory usage one. (But yes, I broadly agree with you).

@lsf37

lsf37 commented Mar 25, 2026

Copy link
Copy Markdown
Member

The max number of domains has no performance impact at all as long as it fits 8 bits. You can have NUM_DOMAINS=256 and only use 4 of them. I do agree that you don't want to actually use 256 domains, that would indeed have a performance impact, but there is no reason to pick a particular low number for the maximum.

That's not true, all the scheduling queues are duplicated per-domain, and they're often some of the largest data in the kernel image aside from kernel page table structures. Though it's probably not a performance impact but more just a memory usage one. (But yes, I broadly agree with you).

You're right, there is indeed a memory impact.

@Kswin01

Kswin01 commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Using domain_start index="1" as an example is misleading because it gives the impression that index starts from 1 instead of 0.

I do explicitly mention just below the xml snippet:
"The above will insert the schedule using the new DomainSet invocations, beginning at index 0 by default"

And I also mention something similar in the manual.md changes in this PR. Hopefully this is enough to indicate that index starts from 0, but I'll change the example here to be clearer.

@Kswin01

Kswin01 commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

I've switched the default maximums to 64 domains, and 128 domain schedule entries. Would this be sufficient?

@Kswin01
Kswin01 force-pushed the run_time_domains branch 3 times, most recently from a791ef9 to ed650e1 Compare March 25, 2026 01:17
@Kswin01
Kswin01 force-pushed the run_time_domains branch 2 times, most recently from 1085b8d to 887c554 Compare March 25, 2026 03:16
@Indanz

Indanz commented Mar 25, 2026

Copy link
Copy Markdown

I've switched the default maximums to 64 domains, and 128 domain schedule entries. Would this be sufficient?

64 is still way too many, especially considering Microkit practically has a limit of about 64 PDs and already uses MCS, which reduces the need for domains.

I also strongly recommend reducing the number of priorities to something sane. E.g.

#define NUM_READY_QUEUES (CONFIG_NUM_DOMAINS * CONFIG_NUM_PRIORITIES)

and the same is true for ksReadyQueuesL2Bitmap.

If you reduce the number of priorities to 64 and have a default number of domains of 4, then you don't use much more memory than now without domains enabled.

@Kswin01

Kswin01 commented Mar 26, 2026

Copy link
Copy Markdown
Contributor Author

and already uses MCS, which reduces the need for domains.

I'm not so sure that MCS has much to do with reducing the need for domains. The main use case we have now is supporting ARINC like systems where domains represent independent subsystems that we want to temporally isolate from each other. We want to define a fixed schedule for these subsystems, MCS doesn't provide us that.

If you reduce the number of priorities to 64 and have a default number of domains of 4, then you don't use much more memory than now without domains enabled.

I won't comment on the reduction of priorities here as it's out of scope for this PR, but as said before the main current use case is using domains to represent independent subsystems, 4 is too limiting for that. KSU's simple examples already use ~11 domains.

I'll reduce the defaults to 32 domains and 64 domain schedule entries

@Kswin01
Kswin01 force-pushed the run_time_domains branch 2 times, most recently from 054ec74 to ba3f824 Compare March 30, 2026 04:30

@Indanz Indanz left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to make idx_shift and domain_start attributes of domain_schedule instead? Adding it as an attribute to an entry makes no sense.

And I would also make it standard integers, not some custom weird hexadecimal format (assuming that's supported) which can't easily defined with XSD and is less user friendly anyway. If you don't support that already, then perhaps don't use characters that would be valid within hexadecimal, it's ambiguous now.

@Kswin01
Kswin01 force-pushed the run_time_domains branch 2 times, most recently from 3b992bb to d0ff46d Compare March 31, 2026 02:12
Comment thread docs/manual.md Outdated
Comment thread docs/manual.md Outdated
Comment thread docs/manual.md Outdated
Comment thread docs/manual.md Outdated
Comment thread docs/manual.md Outdated
Comment thread tool/microkit/src/capdl/builder.rs Outdated
Comment thread tool/microkit/src/main.rs Outdated
Comment thread tool/microkit/src/sdf.rs Outdated
Comment thread tool/microkit/src/sdf.rs Outdated
Comment thread tool/microkit/src/sel4.rs Outdated
@midnightveil

Copy link
Copy Markdown
Collaborator

I'm inclined to leave the number of domains at the verification default of 16.

(The verification is generic over the number of domains, right, though?)

@lsf37

lsf37 commented Jun 11, 2026

Copy link
Copy Markdown
Member

I'm inclined to leave the number of domains at the verification default of 16.

(The verification is generic over the number of domains, right, though?)

Yes, it can be configured to anything <=255.

@midnightveil midnightveil self-assigned this Jun 12, 2026
midnightveil added a commit to seL4/capdl that referenced this pull request Jul 14, 2026
In the work on [RFC-20] it was proposed that it would make sense
to remove the (micro)seconds-to-ticks conversions from seL4
and migrate all of (MCS) userspace APIs to be in ticks, to make dealing
with these calculations and their trickiness to instead be done
at user-level - and the tradeoffs one has to make to be no longer
kernel policy.

This PR demonstrates implements this for the capDL initialiser.
For other cases supported by the capDL specification, such
(MCS) budgets and periods currently specified in microseconds,
the capDL initialiser should be able to paper over the change
in kernel APIs without the users needing to deal with this
distinction.

These changes are motivated by the [Domain schedules] PR to the
seL4 Microkit, which creates a capDL specification for the
(Rust version) of the capDL initialiser to use. At the moment,
this PR is unable to be consistent about the values exposed
to the user for the units of the domain schedule: whilst we can
statically determine the us->ticks conversion for ARM/RISC-V
platforms, on x86 this is a runtime-known value only. Since
the spec is packed at system-build-time, we cannot therefore
specify x86 domain schedules in a consistent set of units.

The concrete specification changes from this PR is the addition
of a 'unit' suffix inside the domain schedule syntax. The
initialiser can then support either ticks (the default without
any units) or microseconds, and it performs a runtime conversion
of this value to ticks for the domain scheduler API.

Canonically, the end marker is left without units as (0, 0), but
we permit inputs of (0, 0 ticks) or (0, 0 us) for consistency.

Note: the meaning of a kernel tick is different between MCS and
non-MCS configurations.

One consequence of taking input in 'us' is that there are cases
where the input is unrepresentable in 'ticks'; and so this
introduces the possiblity of failure in the initialiser that
is unchecked at build-time.

For non-MCS, as kernel ticks are an integer multiple of 1 ms,
the conversion from us to ticks will fail if the conversion is
not exact. This is easy to avoid, one has to specify a 'us' value
that is a multiple of (CONFIG_TIMER_TICK_MS * 1000), something
which does not differ per platform.

For MCS, the kernel tick reflects the underlying timer frequency
(for the domain scheduler). Thus, there exists many frequencies
for which it is impossible to be exact in the conversion from
us to ticks. (those frequencies above 1 MHz, which is most
of them on modern 64-bit platforms). We guarantee a conversion
that is accurate to the nearest tick value, or failure otherwise.
This can be ±1 timer period. If you want to specify durations of
length close to the period between timer ticks, please use ticks
exactly. However, depending on kernel WCET domains of this
duration may not be schedulable.

[RFC-20]: seL4/rfcs#33
[Domain schedules]: seL4/microkit#445

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil added a commit to seL4/capdl that referenced this pull request Jul 15, 2026
In the work on [RFC-20] it was proposed that it would make sense
to remove the (micro)seconds-to-ticks conversions from seL4
and migrate all of (MCS) userspace APIs to be in ticks, to make dealing
with these calculations and their trickiness to instead be done
at user-level - and the tradeoffs one has to make to be no longer
kernel policy.

This PR demonstrates implements this for the capDL initialiser.
For other cases supported by the capDL specification, such
(MCS) budgets and periods currently specified in microseconds,
the capDL initialiser should be able to paper over the change
in kernel APIs without the users needing to deal with this
distinction.

These changes are motivated by the [Domain schedules] PR to the
seL4 Microkit, which creates a capDL specification for the
(Rust version) of the capDL initialiser to use. At the moment,
this PR is unable to be consistent about the values exposed
to the user for the units of the domain schedule: whilst we can
statically determine the us->ticks conversion for ARM/RISC-V
platforms, on x86 this is a runtime-known value only. Since
the spec is packed at system-build-time, we cannot therefore
specify x86 domain schedules in a consistent set of units.

The concrete specification changes from this PR is the addition
of a 'unit' suffix inside the domain schedule syntax. The
initialiser can then support either ticks (the default without
any units) or microseconds, and it performs a runtime conversion
of this value to ticks for the domain scheduler API.

Canonically, the end marker is left without units as (0, 0), but
we permit inputs of (0, 0 ticks) or (0, 0 us) for consistency.

Note: the meaning of a kernel tick is different between MCS and
non-MCS configurations.

One consequence of taking input in 'us' is that there are cases
where the input is unrepresentable in 'ticks'; and so this
introduces the possiblity of failure in the initialiser that
is unchecked at build-time.

For non-MCS, as kernel ticks are an integer multiple of 1 ms,
the conversion from us to ticks will fail if the conversion is
not exact. This is easy to avoid, one has to specify a 'us' value
that is a multiple of (CONFIG_TIMER_TICK_MS * 1000), something
which does not differ per platform.

For MCS, the kernel tick reflects the underlying timer frequency
(for the domain scheduler). Thus, there exists many frequencies
for which it is impossible to be exact in the conversion from
us to ticks. (those frequencies above 1 MHz, which is most
of them on modern 64-bit platforms). We guarantee a conversion
that is accurate to the nearest tick value, or failure otherwise.
This can be ±1 timer period. If you want to specify durations of
length close to the period between timer ticks, please use ticks
exactly. However, depending on kernel WCET domains of this
duration may not be schedulable.

[RFC-20]: seL4/rfcs#33
[Domain schedules]: seL4/microkit#445

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil added a commit to seL4/capdl that referenced this pull request Jul 15, 2026
In the work on [RFC-20] it was proposed that it would make sense
to remove the (micro)seconds-to-ticks conversions from seL4
and migrate all of (MCS) userspace APIs to be in ticks, to make dealing
with these calculations and their trickiness to instead be done
at user-level - and the tradeoffs one has to make to be no longer
kernel policy.

This PR demonstrates implements this for the capDL initialiser.
For other cases supported by the capDL specification, such
(MCS) budgets and periods currently specified in microseconds,
the capDL initialiser should be able to paper over the change
in kernel APIs without the users needing to deal with this
distinction.

These changes are motivated by the [Domain schedules] PR to the
seL4 Microkit, which creates a capDL specification for the
(Rust version) of the capDL initialiser to use. At the moment,
this PR is unable to be consistent about the values exposed
to the user for the units of the domain schedule: whilst we can
statically determine the us->ticks conversion for ARM/RISC-V
platforms, on x86 this is a runtime-known value only. Since
the spec is packed at system-build-time, we cannot therefore
specify x86 domain schedules in a consistent set of units.

The concrete specification changes from this PR is the addition
of a 'unit' suffix inside the domain schedule syntax. The
initialiser can then support either ticks (the default without
any units) or microseconds, and it performs a runtime conversion
of this value to ticks for the domain scheduler API.

Canonically, the end marker is left without units as (0, 0), but
we permit inputs of (0, 0 ticks) or (0, 0 us) for consistency.

Note: the meaning of a kernel tick is different between MCS and
non-MCS configurations.

One consequence of taking input in 'us' is that there are cases
where the input is unrepresentable in 'ticks'; and so this
introduces the possiblity of failure in the initialiser that
is unchecked at build-time.

For non-MCS, as kernel ticks are an integer multiple of 1 ms,
the conversion from us to ticks will fail if the conversion is
not exact. This is easy to avoid, one has to specify a 'us' value
that is a multiple of (CONFIG_TIMER_TICK_MS * 1000), something
which does not differ per platform.

For MCS, the kernel tick reflects the underlying timer frequency
(for the domain scheduler). Thus, there exists many frequencies
for which it is impossible to be exact in the conversion from
us to ticks. (those frequencies above 1 MHz, which is most
of them on modern 64-bit platforms). We guarantee a conversion
that is accurate to the nearest tick value, or failure otherwise.
This can be ±1 timer period. If you want to specify durations of
length close to the period between timer ticks, please use ticks
exactly. However, depending on kernel WCET domains of this
duration may not be schedulable.

[RFC-20]: seL4/rfcs#33
[Domain schedules]: seL4/microkit#445

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
@midnightveil

Copy link
Copy Markdown
Collaborator

The original branch is here: https://github.com/au-ts/microkit/tree/archive/krishan-runtime-domains

I'm going to restart this branch and reorder a lot of the commits here, because upstream has changed enough it's annoying to rebase now.

@midnightveil
midnightveil marked this pull request as draft July 16, 2026 02:53
This is confusing with seL4 domains.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
Having a domain scheduler causes no issues if the domain
scheduler is unused, so we can turn it on on non-smp
configurations. (seL4 does not support SMP domain-scheduler).

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
@midnightveil
midnightveil marked this pull request as ready for review July 17, 2026 03:13
These will be useful later.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil and others added 5 commits July 17, 2026 13:24
This adds a new syntax which looks like this:

    <domains>
        <domain name="monitor" id="0" />
        <domain name="emitter" id="1" />
        <domain name="collector" />

        <domain_schedule start_index="3" index_shift="0" >
            <schedule_entry domain="monitor" duration="3000 us" />
            <schedule_entry domain="emitter" duration="8000 us" />
            <schedule_entry domain="collector" duration="2000 us" />
            <schedule_entry domain="monitor" duration="3000 us" />
        </domain_schedule>
    </domains>

Co-authored-by: Krishnan Winter <krishnan.winter@unsw.edu.au>
Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
When we have a domain schedule specified, this is also
required.

Co-authored-by: Krishnan Winter <krishnan.winter@unsw.edu.au>
Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
This is equivalent to the CAmkES domains example which
has an emitter and collector.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
Mostly just error cases :)

Plus some extra cases in the code.

Co-authored-by: Krishnan Winter <krishnan.winter@unsw.edu.au>
Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
We describe the syntax and the reference separately.

Co-authored-by: Krishnan Winter <krishnan.winter@unsw.edu.au>
Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants