mcs: add periodic scheduling
This commit adds periodic scheduling with sporadic servers.
This commit is contained in:
parent
debdaa7d9a
commit
34c1f920b1
27 changed files with 878 additions and 119 deletions
|
|
@ -119,13 +119,18 @@ static inline void debug_printTCB(tcb_t *tcb)
|
|||
}
|
||||
|
||||
word_t core = SMP_TERNARY(tcb->tcbAffinity, 0);
|
||||
printf("%15s\t%p\t%20lu\t%lu\n", state, (void *) getRestartPC(tcb), tcb->tcbPriority, core);
|
||||
printf("%15s\t%p\t%20lu\t%lu", state, (void *) getRestartPC(tcb), tcb->tcbPriority, core);
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
printf("\t%lu", (word_t) thread_state_get_tcbInReleaseQueue(tcb->tcbState));
|
||||
#endif
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static inline void debug_dumpScheduler(void)
|
||||
{
|
||||
printf("Dumping all tcbs!\n");
|
||||
printf("Name \tState \tIP \t Prio \t Core\n");
|
||||
printf("Name \tState \tIP \t Prio \t Core%s\n",
|
||||
config_set(CONFIG_KERNEL_MCS) ? "\t InReleaseQueue" : "");
|
||||
printf("--------------------------------------------------------------------------------------\n");
|
||||
for (tcb_t *curr = NODE_STATE(ksDebugTCBs); curr != NULL; curr = curr->tcbDebugNext) {
|
||||
debug_printTCB(curr);
|
||||
|
|
|
|||
122
include/kernel/sporadic.h
Normal file
122
include/kernel/sporadic.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2019, Data61
|
||||
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
* ABN 41 687 119 230.
|
||||
*
|
||||
* This software may be distributed and modified according to the terms of
|
||||
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
* See "LICENSE_GPLv2.txt" for details.
|
||||
*
|
||||
* @TAG(DATA61_GPL)
|
||||
*/
|
||||
#ifndef __KERNEL_SPORADIC_H
|
||||
#define __KERNEL_SPORADIC_H
|
||||
/* This header presents the interface for sporadic servers,
|
||||
* implemented according to Stankcovich et. al in
|
||||
* "Defects of the POSIX Spoardic Server and How to correct them",
|
||||
* although without the priority management.
|
||||
*
|
||||
* Briefly, a sporadic server is a period and a queue of refills. Each
|
||||
* refill consists of an amount, and a period. No thread is allowed to consume
|
||||
* more than amount ticks per period.
|
||||
*
|
||||
* The sum of all refill amounts in the refill queue is always the budget of the scheduling context -
|
||||
* that is it should never change, unless it is being updated / configured.
|
||||
*
|
||||
* Every time budget is consumed, that amount of budget is scheduled
|
||||
* for reuse in period time. If the refill queue is full (the queue's
|
||||
* minimum size is 2, and can be configured by the user per scheduling context
|
||||
* above this) the next refill is merged.
|
||||
*/
|
||||
#include <types.h>
|
||||
#include <util.h>
|
||||
#include <object/structures.h>
|
||||
#include <machine/timer.h>
|
||||
#include <model/statedata.h>
|
||||
|
||||
/* To do an operation in the kernel, the thread must have
|
||||
* at least this much budget - see comment on refill_sufficient */
|
||||
#define MIN_BUDGET_US (2u * getKernelWcetUs())
|
||||
#define MIN_BUDGET (2u * getKernelWcetTicks())
|
||||
|
||||
/* Short hand for accessing refill queue items */
|
||||
#define REFILL_INDEX(sc, index) ((sc)->scRefills[(index)])
|
||||
#define REFILL_HEAD(sc) REFILL_INDEX((sc), (sc)->scRefillHead)
|
||||
#define REFILL_TAIL(sc) REFILL_INDEX((sc), (sc)->scRefillTail)
|
||||
|
||||
/* Return the amount of items currently in the refill queue */
|
||||
static inline word_t refill_size(sched_context_t *sc)
|
||||
{
|
||||
if (sc->scRefillHead <= sc->scRefillTail) {
|
||||
return (sc->scRefillTail - sc->scRefillHead + 1u);
|
||||
}
|
||||
return sc->scRefillTail + 1u + (sc->scRefillMax - sc->scRefillHead);
|
||||
}
|
||||
|
||||
static inline bool_t refill_single(sched_context_t *sc)
|
||||
{
|
||||
return sc->scRefillHead == sc->scRefillTail;
|
||||
}
|
||||
|
||||
/* Return the amount of budget this scheduling context
|
||||
* has available if usage is charged to it. */
|
||||
static inline ticks_t refill_capacity(sched_context_t *sc, ticks_t usage)
|
||||
{
|
||||
if (unlikely(usage > REFILL_HEAD(sc).rAmount)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return REFILL_HEAD(sc).rAmount - usage;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the head refill has sufficient capacity
|
||||
* to enter and exit the kernel after usage is charged to it.
|
||||
*/
|
||||
static inline bool_t refill_sufficient(sched_context_t *sc, ticks_t usage)
|
||||
{
|
||||
return refill_capacity(sc, usage) >= MIN_BUDGET;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the refill is eligible to be used.
|
||||
* This indicates if the thread bound to the sc can be placed
|
||||
* into the scheduler, otherwise it needs to go into the release queue
|
||||
* to wait.
|
||||
*/
|
||||
static inline bool_t refill_ready(sched_context_t *sc)
|
||||
{
|
||||
return REFILL_HEAD(sc).rTime <= (NODE_STATE(ksCurTime) + getKernelWcetTicks());
|
||||
}
|
||||
|
||||
/* Create a new refill in a non-active sc */
|
||||
void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t period);
|
||||
|
||||
/* Update refills in an active sc without violating bandwidth constraints */
|
||||
void refill_update(sched_context_t *sc, ticks_t new_period, ticks_t new_budget, word_t new_max_refills);
|
||||
|
||||
|
||||
/* Charge the head refill its entire amount.
|
||||
*
|
||||
* `used` amount from its current replenishment without
|
||||
* depleting the budget, i.e refill_expired returns false.
|
||||
*
|
||||
* return any uncharged usage.
|
||||
*/
|
||||
ticks_t refill_budget_check(sched_context_t *sc, ticks_t used);
|
||||
|
||||
/*
|
||||
* Charge a scheduling context `used` amount from its
|
||||
* current refill. This will split the refill, leaving whatever is
|
||||
* left over at the head of the refill.
|
||||
*/
|
||||
void refill_split_check(sched_context_t *sc, ticks_t used);
|
||||
|
||||
/*
|
||||
* This is called when a thread is eligible to start running: it
|
||||
* iterates through the refills queue and merges any
|
||||
* refills that overlap.
|
||||
*/
|
||||
void refill_unblock_check(sched_context_t *sc);
|
||||
|
||||
#endif
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
#include <object/structures.h>
|
||||
#include <arch/machine.h>
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
#include <kernel/sporadic.h>
|
||||
#include <machine/timer.h>
|
||||
#include <mode/machine.h>
|
||||
#endif
|
||||
|
|
@ -85,28 +86,22 @@ static inline bool_t isHighestPrio(word_t dom, prio_t prio)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
static inline bool_t isCurThreadExpired(void)
|
||||
{
|
||||
return NODE_STATE(ksCurThread)->tcbSchedContext->scRemaining <
|
||||
(NODE_STATE(ksConsumed) + getKernelWcetTicks());
|
||||
}
|
||||
|
||||
static inline bool_t isCurDomainExpired(void)
|
||||
{
|
||||
return CONFIG_NUM_DOMAINS > 1 &&
|
||||
NODE_STATE(ksDomainTime) < (NODE_STATE(ksConsumed) + getKernelWcetTicks());
|
||||
ksDomainTime < (NODE_STATE(ksConsumed) + getKernelWcetTicks());
|
||||
}
|
||||
|
||||
static inline void commitTime(sched_context_t *sc)
|
||||
static inline void commitTime(void)
|
||||
{
|
||||
assert(sc->scCore == SMP_TERNARY(getCurrentCPUIndex(), 0));
|
||||
if (unlikely(sc->scRemaining < NODE_STATE(ksConsumed))) {
|
||||
/* avoid underflow */
|
||||
sc->scRemaining = 0;
|
||||
} else {
|
||||
sc->scRemaining -= NODE_STATE(ksConsumed);
|
||||
}
|
||||
|
||||
if (likely(NODE_STATE(ksConsumed) > 0 && (NODE_STATE(ksCurThread) != NODE_STATE(ksIdleThread)))) {
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed)));
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
refill_split_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), 0));
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
}
|
||||
if (CONFIG_NUM_DOMAINS > 1) {
|
||||
if (unlikely(ksDomainTime < NODE_STATE(ksConsumed))) {
|
||||
ksDomainTime = 0;
|
||||
|
|
@ -168,6 +163,12 @@ static inline void updateRestartPC(tcb_t *tcb)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* End the timeslice for the current thread.
|
||||
* This will recharge the threads timeslice and place it at the
|
||||
* end of the scheduling queue for its priority.
|
||||
*/
|
||||
void endTimeslice(void);
|
||||
|
||||
/* Update the kernels timestamp and stores in ksCurTime.
|
||||
* The difference between the previous kernel timestamp and the one just read
|
||||
* is stored in ksConsumed.
|
||||
|
|
@ -191,34 +192,65 @@ static inline void updateTimestamp(void)
|
|||
* @return true if the thread/domain has enough budget to
|
||||
* get through the current kernel operation.
|
||||
*/
|
||||
bool_t checkBudget(void);
|
||||
static inline bool_t checkBudget(void)
|
||||
{
|
||||
/* currently running thread must have available capacity */
|
||||
assert(refill_ready(NODE_STATE(ksCurSC)));
|
||||
|
||||
if (unlikely(NODE_STATE(ksCurThread) == NODE_STATE(ksIdleThread))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ticks_t capacity = refill_capacity(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
if (unlikely(capacity < MIN_BUDGET)) {
|
||||
if (capacity == 0) {
|
||||
NODE_STATE(ksConsumed) = refill_budget_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
}
|
||||
if (NODE_STATE(ksConsumed) > 0) {
|
||||
refill_split_check(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed));
|
||||
}
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
NODE_STATE(ksCurTime) += 1llu;
|
||||
if (likely(isRunnable(NODE_STATE(ksCurThread)))) {
|
||||
endTimeslice();
|
||||
rescheduleRequired();
|
||||
}
|
||||
return false;
|
||||
} else if (unlikely(isCurDomainExpired())) {
|
||||
commitTime();
|
||||
rescheduleRequired();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Everything checkBudget does, but also set the thread
|
||||
* state to ThreadState_Restart. To be called from kernel entries
|
||||
* where the operation should be restarted once the current thread
|
||||
* has budget again.
|
||||
*/
|
||||
bool_t checkBudgetRestart(void);
|
||||
|
||||
static inline bool_t checkBudgetRestart(void)
|
||||
{
|
||||
assert(isRunnable(NODE_STATE(ksCurThread)));
|
||||
bool_t result = checkBudget();
|
||||
if (!result) {
|
||||
setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Set the next kernel tick, which is either the end of the current
|
||||
* domains timeslice OR the end of the current threads timeslice.
|
||||
*/
|
||||
void setNextInterrupt(void);
|
||||
|
||||
/* End the timeslice for the current thread.
|
||||
* This will recharge the threads timeslice and place it at the
|
||||
* end of the scheduling queue for its priority.
|
||||
*/
|
||||
void endTimeslice(void);
|
||||
|
||||
static inline void checkReschedule(void)
|
||||
{
|
||||
if (isCurThreadExpired()) {
|
||||
endTimeslice();
|
||||
} else if (isCurDomainExpired()) {
|
||||
rescheduleRequired();
|
||||
}
|
||||
}
|
||||
/* Wake any periodic threads that are ready for budget recharge */
|
||||
void awaken(void);
|
||||
/* Place the thread bound to this scheduling context in the release queue
|
||||
* of periodic threads waiting for budget recharge */
|
||||
void postpone(sched_context_t *sc);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ NODE_STATE_DECLARE(tcb_t, *ksIdleThread);
|
|||
NODE_STATE_DECLARE(tcb_t, *ksSchedulerAction);
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
NODE_STATE_DECLARE(tcb_t, *ksReleaseHead);
|
||||
NODE_STATE_DECLARE(time_t, ksConsumed);
|
||||
NODE_STATE_DECLARE(time_t, ksCurTime);
|
||||
NODE_STATE_DECLARE(bool_t, ksReprogram);
|
||||
|
|
|
|||
|
|
@ -291,18 +291,35 @@ struct tcb {
|
|||
typedef struct tcb tcb_t;
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
typedef struct refill {
|
||||
/* Absolute timestamp from when this refill can be used */
|
||||
ticks_t rTime;
|
||||
/* Amount of ticks that can be used from this refill */
|
||||
ticks_t rAmount;
|
||||
} refill_t;
|
||||
|
||||
#define MIN_REFILLS 2u
|
||||
#define MAX_REFILLS (MIN_REFILLS + seL4_MaxRefills)
|
||||
|
||||
struct sched_context {
|
||||
/* budget for this sc -- remaining is refilled from this value */
|
||||
ticks_t scBudget;
|
||||
/* period for this sc -- controls rate at which budget is replenished */
|
||||
ticks_t scPeriod;
|
||||
|
||||
/* core this scheduling context provides time for - 0 if uniprocessor */
|
||||
word_t scCore;
|
||||
|
||||
/* current budget for this tcb (timeslice) -- refilled from budget */
|
||||
ticks_t scRemaining;
|
||||
|
||||
/* thread that this scheduling context is bound to */
|
||||
tcb_t *scTcb;
|
||||
|
||||
/* Amount of refills this sc tracks */
|
||||
word_t scRefillMax;
|
||||
/* Index of the head of the refill circular buffer */
|
||||
word_t scRefillHead;
|
||||
/* Index of the tail of the refill circular buffer */
|
||||
word_t scRefillTail;
|
||||
|
||||
/* circular buffer of budget refills, ordered by rAmount */
|
||||
refill_t scRefills[MAX_REFILLS];
|
||||
};
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -296,6 +296,9 @@ block DebugException {
|
|||
block thread_state(blockingIPCBadge, blockingIPCCanGrant,
|
||||
blockingIPCCanGrantReply, blockingIPCIsCall,
|
||||
tcbQueued, blockingObject,
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
tcbInReleaseQueue,
|
||||
#endif
|
||||
tsType) {
|
||||
field blockingIPCBadge 28
|
||||
field blockingIPCCanGrant 1
|
||||
|
|
@ -304,9 +307,16 @@ block thread_state(blockingIPCBadge, blockingIPCCanGrant,
|
|||
padding 1
|
||||
|
||||
-- this is fastpath-specific. it is useful to be able to write
|
||||
-- tsType and without changing tcbQueued
|
||||
-- tsType and without changing tcbQueued or tcbInReleaseQueue
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
padding 30
|
||||
#else
|
||||
padding 31
|
||||
#endif
|
||||
field tcbQueued 1
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
field tcbInReleaseQueue 1
|
||||
#endif
|
||||
|
||||
field_high blockingObject 28
|
||||
field tsType 4
|
||||
|
|
|
|||
|
|
@ -382,15 +382,25 @@ block DebugException {
|
|||
-- Thread state: size = 24 bytes
|
||||
block thread_state(blockingIPCBadge, blockingIPCCanGrant,
|
||||
blockingIPCCanGrantReply, blockingIPCIsCall,
|
||||
tcbQueued, blockingObject,
|
||||
tsType) {
|
||||
tcbQueued,
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
tcbInReleaseQueue,
|
||||
#endif
|
||||
blockingObject, tsType) {
|
||||
field blockingIPCBadge 64
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
padding 59
|
||||
#else
|
||||
padding 60
|
||||
#endif
|
||||
field blockingIPCCanGrant 1
|
||||
field blockingIPCCanGrantReply 1
|
||||
field blockingIPCIsCall 1
|
||||
field tcbQueued 1
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
field tcbInReleaseQueue 1
|
||||
#endif
|
||||
|
||||
#if BF_CANONICAL_RANGE == 48
|
||||
padding 16
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@ void tcbSchedDequeue(tcb_t *tcb);
|
|||
void tcbDebugAppend(tcb_t *tcb);
|
||||
void tcbDebugRemove(tcb_t *tcb);
|
||||
#endif
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
void tcbReleaseRemove(tcb_t *tcb);
|
||||
void tcbReleaseEnqueue(tcb_t *tcb);
|
||||
tcb_t *tcbReleaseDequeue(void);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SMP_SUPPORT
|
||||
void remoteQueueUpdate(tcb_t *tcb);
|
||||
|
|
|
|||
|
|
@ -611,6 +611,8 @@
|
|||
description="Timeslice in microseconds, when the budget expires the thread will be pre-empted."/>
|
||||
<param dir="in" name="period" type="seL4_Time"
|
||||
description="Period in microseconds, the budget is replenished every time the period expires."/>
|
||||
<param dir="in" name="max_refills" type="seL4_Word"
|
||||
description="extra max refills for sporadic real time tasks"/>
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
|
|
|
|||
|
|
@ -79,4 +79,9 @@ typedef enum {
|
|||
SEL4_FORCE_LONG_ENUM(seL4_LookupFailureType),
|
||||
} seL4_LookupFailureType;
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
#define seL4_MinRefills 0
|
||||
#define seL4_MaxRefills 10
|
||||
#endif
|
||||
|
||||
#endif /* __API_CONSTANTS_H */
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ enum {
|
|||
|
||||
#define seL4_EndpointBits 4
|
||||
#define seL4_NotificationBits 4
|
||||
#define seL4_SchedContextBits 5
|
||||
#define seL4_SchedContextBits 8
|
||||
|
||||
#ifdef CONFIG_ARM_HYPERVISOR_SUPPORT
|
||||
#define seL4_PageTableBits 12
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ enum {
|
|||
#define seL4_TCBBits 11
|
||||
#define seL4_EndpointBits 4
|
||||
#define seL4_NotificationBits 5
|
||||
#define seL4_SchedContextBits 5
|
||||
#define seL4_SchedContextBits 8
|
||||
|
||||
#define seL4_PageTableBits 12
|
||||
#define seL4_PageTableEntryBits 3
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
#define seL4_ASIDPoolBits 12
|
||||
#define seL4_ASIDPoolIndexBits 10
|
||||
#define seL4_WordSizeBits 2
|
||||
#define seL4_SchedContextBits 5
|
||||
#define seL4_SchedContextBits 8
|
||||
|
||||
#define seL4_HugePageBits 30 /* 1GB */
|
||||
#define seL4_PDPTBits 0
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
#define seL4_NumASIDPoolsBits 3
|
||||
#define seL4_ASIDPoolBits 12
|
||||
#define seL4_ASIDPoolIndexBits 9
|
||||
#define seL4_SchedContextBits 5
|
||||
#define seL4_SchedContextBits 8
|
||||
|
||||
/* Untyped size limits */
|
||||
#define seL4_MinUntypedBits 4
|
||||
|
|
|
|||
|
|
@ -454,8 +454,9 @@ static void handleRecv(bool_t isBlocking)
|
|||
#ifdef CONFIG_KERNEL_MCS
|
||||
static inline void mcsIRQ(irq_t irq)
|
||||
{
|
||||
commitTime(ksCurSC);
|
||||
checkReschedule();
|
||||
if (checkBudget()) {
|
||||
commitTime();
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define mcsIRQ(irq)
|
||||
|
|
@ -464,9 +465,19 @@ static inline void mcsIRQ(irq_t irq)
|
|||
|
||||
static void handleYield(void)
|
||||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* checkBudgetRestart should have failed if we got here */
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), NODE_STATE(ksConsumed)));
|
||||
/* Yield the current remaining budget */
|
||||
refill_budget_check(NODE_STATE(ksCurSC), REFILL_HEAD(NODE_STATE(ksCurSC)).rAmount);
|
||||
/* we just charged all of the time to the yielding thread */
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
endTimeslice();
|
||||
#else
|
||||
tcbSchedDequeue(NODE_STATE(ksCurThread));
|
||||
SCHED_APPEND_CURRENT_TCB;
|
||||
rescheduleRequired();
|
||||
#endif
|
||||
}
|
||||
|
||||
exception_t handleSyscall(syscall_t syscall)
|
||||
|
|
|
|||
|
|
@ -600,6 +600,10 @@ BOOT_CODE VISIBLE void init_kernel(
|
|||
fail("Kernel init failed for some reason :(");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
NODE_STATE(ksCurTime) = getCurrentTime();
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
#endif
|
||||
schedule();
|
||||
activateThread();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -728,6 +728,11 @@ BOOT_CODE VISIBLE void boot_sys(
|
|||
ARCH_NODE_STATE(x86KScurInterrupt) = int_invalid;
|
||||
ARCH_NODE_STATE(x86KSPendingInterrupt) = int_invalid;
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
NODE_STATE(ksCurTime) = getCurrentTime();
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
#endif
|
||||
|
||||
schedule();
|
||||
activateThread();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,4 +44,7 @@ add_sources(
|
|||
src/smp/lock.c
|
||||
src/smp/ipi.c
|
||||
)
|
||||
add_sources(DEP KernelIsMCS CFILES src/object/schedcontext.c src/object/schedcontrol.c)
|
||||
add_sources(
|
||||
DEP KernelIsMCS
|
||||
CFILES src/object/schedcontext.c src/object/schedcontrol.c src/kernel/sporadic.c
|
||||
)
|
||||
|
|
|
|||
|
|
@ -324,10 +324,9 @@ BOOT_CODE cap_t create_it_asid_pool(cap_t root_cnode_cap)
|
|||
BOOT_CODE static bool_t configure_sched_context(tcb_t *tcb, sched_context_t *sc_pptr, ticks_t timeslice)
|
||||
{
|
||||
tcb->tcbSchedContext = sc_pptr;
|
||||
tcb->tcbSchedContext->scBudget = timeslice;
|
||||
tcb->tcbSchedContext->scRemaining = timeslice;
|
||||
tcb->tcbSchedContext->scTcb = tcb;
|
||||
refill_new(tcb->tcbSchedContext, MIN_REFILLS, timeslice, 0);
|
||||
|
||||
tcb->tcbSchedContext->scTcb = tcb;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -430,6 +429,7 @@ BOOT_CODE tcb_t *create_initial_thread(cap_t root_cnode_cap, cap_t it_pd_cap, vp
|
|||
|
||||
NODE_STATE(ksConsumed) = 0;
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
NODE_STATE(ksReleaseHead) = NULL;
|
||||
#endif
|
||||
|
||||
tcb->tcbPriority = seL4_MaxPrio;
|
||||
|
|
|
|||
336
src/kernel/sporadic.c
Normal file
336
src/kernel/sporadic.c
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Copyright 2019, Data61
|
||||
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
|
||||
* ABN 41 687 119 230.
|
||||
*
|
||||
* This software may be distributed and modified according to the terms of
|
||||
* the GNU General Public License version 2. Note that NO WARRANTY is provided.
|
||||
* See "LICENSE_GPLv2.txt" for details.
|
||||
*
|
||||
* @TAG(DATA61_GPL)
|
||||
*/
|
||||
#include <types.h>
|
||||
#include <api/failures.h>
|
||||
#include <object/structures.h>
|
||||
|
||||
/* functions to manage the circular buffer of
|
||||
* sporadic budget replenishments (refills for short).
|
||||
*
|
||||
* The circular buffer always has at least one item in it.
|
||||
*
|
||||
* Items are appended at the tail (the back) and
|
||||
* removed from the head (the front). Below is
|
||||
* an example of a queue with 4 items (h = head, t = tail, x = item, [] = slot)
|
||||
* and max size 8.
|
||||
*
|
||||
* [][h][x][x][t][][][]
|
||||
*
|
||||
* and another example of a queue with 5 items
|
||||
*
|
||||
* [x][t][][][][h][x][x]
|
||||
*
|
||||
* The queue has a minimum size of 1, so it is possible that h == t.
|
||||
*
|
||||
* The queue is implemented as head + tail rather than head + size as
|
||||
* we cannot use the mod operator on all architectures without accessing
|
||||
* the fpu or implementing divide.
|
||||
*/
|
||||
|
||||
/* return the index of the next item in the refill queue */
|
||||
static inline word_t refill_next(sched_context_t *sc, word_t index)
|
||||
{
|
||||
return (index == sc->scRefillMax - 1u) ? (0) : index + 1u;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PRINTING
|
||||
/* for debugging */
|
||||
UNUSED static inline void print_index(sched_context_t *sc, word_t index)
|
||||
{
|
||||
|
||||
printf("index %lu, Amount: %llx, time %llx\n", index, REFILL_INDEX(sc, index).rAmount,
|
||||
REFILL_INDEX(sc, index).rTime);
|
||||
}
|
||||
|
||||
UNUSED static inline void refill_print(sched_context_t *sc)
|
||||
{
|
||||
printf("Head %lu tail %lu\n", sc->scRefillHead, sc->scRefillTail);
|
||||
word_t current = sc->scRefillHead;
|
||||
/* always print the head */
|
||||
print_index(sc, current);
|
||||
|
||||
while (current != sc->scRefillTail) {
|
||||
current = refill_next(sc, current);
|
||||
print_index(sc, current);
|
||||
}
|
||||
|
||||
}
|
||||
#endif /* CONFIG_PRINTING */
|
||||
#ifdef CONFIG_DEBUG_BUILD
|
||||
/* check a refill queue is ordered correctly */
|
||||
static UNUSED bool_t refill_ordered(sched_context_t *sc)
|
||||
{
|
||||
word_t current = sc->scRefillHead;
|
||||
word_t next = refill_next(sc, sc->scRefillHead);
|
||||
|
||||
while (current != sc->scRefillTail) {
|
||||
assert(REFILL_INDEX(sc, current).rTime <= REFILL_INDEX(sc, next).rTime);
|
||||
current = next;
|
||||
next = refill_next(sc, current);
|
||||
}
|
||||
}
|
||||
|
||||
#define REFILL_SANITY_START(sc) ticks_t _sum = refill_sum(sc); refill_ordered(sc);
|
||||
#define REFILL_SANITY_CHECK(sc, budget) \
|
||||
do { \
|
||||
assert(refill_sum(sc) == budget); refill_ordered(sc); \
|
||||
} while (0)
|
||||
|
||||
#define REFILL_SANITY_END(sc) \
|
||||
do {\
|
||||
REFILL_SANITY_CHECK(sc, _sum);\
|
||||
} while (0)
|
||||
#else
|
||||
#define REFILL_SANITY_START(sc)
|
||||
#define REFILL_SANITY_CHECK(sc, budget)
|
||||
#define REFILL_SANITY_END(sc)
|
||||
#endif /* CONFIG_DEBUG_BUILD */
|
||||
|
||||
/* compute the sum of a refill queue */
|
||||
static ticks_t refill_sum(sched_context_t *sc)
|
||||
{
|
||||
ticks_t sum = REFILL_HEAD(sc).rAmount;
|
||||
word_t current = sc->scRefillHead;
|
||||
|
||||
while (current != sc->scRefillTail) {
|
||||
current = refill_next(sc, current);
|
||||
sum += REFILL_INDEX(sc, current).rAmount;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* pop head of refill queue */
|
||||
static inline refill_t refill_pop_head(sched_context_t *sc)
|
||||
{
|
||||
/* queues cannot be smaller than 1 */
|
||||
assert(!refill_single(sc));
|
||||
|
||||
UNUSED word_t prev_size = refill_size(sc);
|
||||
refill_t refill = REFILL_HEAD(sc);
|
||||
sc->scRefillHead = refill_next(sc, sc->scRefillHead);
|
||||
|
||||
/* sanity */
|
||||
assert(prev_size == (refill_size(sc) + 1));
|
||||
assert(sc->scRefillHead < sc->scRefillMax);
|
||||
return refill;
|
||||
}
|
||||
|
||||
/* add item to tail of refill queue */
|
||||
static inline void refill_add_tail(sched_context_t *sc, refill_t refill)
|
||||
{
|
||||
/* cannot add an empty refill */
|
||||
assert(refill.rAmount != 0);
|
||||
/* cannot add beyond queue size */
|
||||
assert(refill_size(sc) < sc->scRefillMax);
|
||||
|
||||
word_t new_tail = refill_next(sc, sc->scRefillTail);
|
||||
sc->scRefillTail = new_tail;
|
||||
REFILL_TAIL(sc) = refill;
|
||||
|
||||
/* sanity */
|
||||
assert(new_tail < sc->scRefillMax);
|
||||
}
|
||||
|
||||
void refill_new(sched_context_t *sc, word_t max_refills, ticks_t budget, ticks_t period)
|
||||
{
|
||||
sc->scPeriod = period;
|
||||
sc->scRefillHead = 0;
|
||||
sc->scRefillTail = 0;
|
||||
sc->scRefillMax = max_refills;
|
||||
assert(budget > MIN_BUDGET);
|
||||
/* full budget available */
|
||||
REFILL_HEAD(sc).rAmount = budget;
|
||||
/* budget can be used from now */
|
||||
REFILL_HEAD(sc).rTime = NODE_STATE(ksCurTime);
|
||||
REFILL_SANITY_CHECK(sc, budget);
|
||||
}
|
||||
|
||||
void refill_update(sched_context_t *sc, ticks_t new_period, ticks_t new_budget, word_t new_max_refills)
|
||||
{
|
||||
|
||||
/* refill must be initialised in order to be updated - otherwise refill_new should be used */
|
||||
assert(sc->scRefillMax > 0);
|
||||
|
||||
/* figure out how much budget is available */
|
||||
ticks_t total_budget = refill_sum(sc);
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
|
||||
/* first deal with a difference in max refills - merge
|
||||
* any refills that exceed the new max */
|
||||
while (new_max_refills < refill_size(sc)) {
|
||||
/* merge refills */
|
||||
|
||||
assert(!refill_single(sc));
|
||||
refill_t refill = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += refill.rAmount;
|
||||
}
|
||||
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
|
||||
/* move anything in the list that is beyond the old max */
|
||||
if (sc->scRefillMax > new_max_refills) {
|
||||
word_t curr = sc->scRefillHead;
|
||||
for (curr = sc->scRefillHead; curr < sc->scRefillMax; curr++) {
|
||||
word_t diff = sc->scRefillMax - new_max_refills;
|
||||
REFILL_INDEX(sc, curr - diff) = REFILL_INDEX(sc, curr);
|
||||
}
|
||||
}
|
||||
sc->scRefillMax = new_max_refills;
|
||||
|
||||
/* now deal with the period change - update each refill by the difference in period */
|
||||
word_t current = refill_next(sc, sc->scRefillHead);
|
||||
while (current != sc->scRefillTail) {
|
||||
/* adjust the period of each refill by new one (except the head) */
|
||||
REFILL_INDEX(sc, current).rTime += (new_period - sc->scPeriod);
|
||||
current = refill_next(sc, current);
|
||||
}
|
||||
sc->scPeriod = new_period;
|
||||
REFILL_SANITY_CHECK(sc, total_budget);
|
||||
|
||||
/* now deal with the new budget */
|
||||
if (new_budget > total_budget) {
|
||||
/* if the budget has increased, just add it to the last refill */
|
||||
REFILL_TAIL(sc).rAmount += (new_budget - total_budget);
|
||||
} else {
|
||||
/* if the budget has decreased, iterate through from head to
|
||||
* tail until the amount decreased has been removed from the refill
|
||||
* buffer */
|
||||
ticks_t remove = total_budget - new_budget;
|
||||
while (remove >= REFILL_HEAD(sc).rAmount) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
remove -= old_head.rAmount;
|
||||
}
|
||||
REFILL_HEAD(sc).rAmount -= remove;
|
||||
if (REFILL_HEAD(sc).rAmount < MIN_BUDGET) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += old_head.rAmount;
|
||||
}
|
||||
}
|
||||
|
||||
/* merge any overlapping refills */
|
||||
refill_unblock_check(sc);
|
||||
REFILL_SANITY_CHECK(sc, new_budget);
|
||||
}
|
||||
|
||||
ticks_t refill_budget_check(sched_context_t *sc, ticks_t usage)
|
||||
{
|
||||
/* this function should only be called when the sc is out of budget */
|
||||
assert(refill_capacity(sc, usage) == 0);
|
||||
REFILL_SANITY_START(sc);
|
||||
|
||||
while (REFILL_HEAD(sc).rAmount <= usage) {
|
||||
/* exhaust and schedule replenishment */
|
||||
usage -= REFILL_HEAD(sc).rAmount;
|
||||
if (refill_single(sc)) {
|
||||
/* update in place */
|
||||
REFILL_HEAD(sc).rTime += sc->scPeriod;
|
||||
} else {
|
||||
refill_t old_head = refill_pop_head(sc);
|
||||
old_head.rTime = old_head.rTime + sc->scPeriod;
|
||||
refill_add_tail(sc, old_head);
|
||||
}
|
||||
}
|
||||
|
||||
/* budget overrun */
|
||||
if (usage > 0 && sc->scPeriod > 0) {
|
||||
/* budget reduced when calculating capacity */
|
||||
/* due to overrun delay next replenishment */
|
||||
REFILL_HEAD(sc).rTime += usage;
|
||||
/* merge front two replenishments if times overlap */
|
||||
if (!refill_single(sc) &&
|
||||
REFILL_HEAD(sc).rTime + REFILL_HEAD(sc).rAmount >=
|
||||
REFILL_INDEX(sc, refill_next(sc, sc->scRefillHead)).rTime) {
|
||||
|
||||
refill_t refill = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += refill.rAmount;
|
||||
}
|
||||
}
|
||||
|
||||
REFILL_SANITY_END(sc);
|
||||
|
||||
/* return any usage we haven't dealt with */
|
||||
return usage;
|
||||
}
|
||||
|
||||
void refill_split_check(sched_context_t *sc, ticks_t usage)
|
||||
{
|
||||
/* invalid to call this on a NULL sc */
|
||||
assert(sc != NULL);
|
||||
/* something is seriously wrong if this is called and no
|
||||
* time has been used */
|
||||
assert(usage > 0);
|
||||
assert(usage <= REFILL_HEAD(sc).rAmount);
|
||||
|
||||
REFILL_SANITY_START(sc);
|
||||
|
||||
/* first deal with the remaining budget of the current replenishment */
|
||||
ticks_t remnant = REFILL_HEAD(sc).rAmount - usage;
|
||||
if (remnant < MIN_BUDGET && refill_single(sc)) {
|
||||
/* delay entire replenishment - can't merge, nothing to merge with */
|
||||
REFILL_HEAD(sc).rTime += sc->scPeriod;
|
||||
REFILL_SANITY_END(sc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (refill_size(sc) == sc->scRefillMax || remnant < MIN_BUDGET) {
|
||||
assert(!refill_single(sc));
|
||||
/* merge remnant with next replenishment - either it's too small
|
||||
* or we're out of space */
|
||||
refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += remnant;
|
||||
} else {
|
||||
assert(remnant >= MIN_BUDGET);
|
||||
/* split the head refill */
|
||||
REFILL_HEAD(sc).rAmount = remnant;
|
||||
}
|
||||
|
||||
/* schedule the used amount */
|
||||
refill_t new = (refill_t) {
|
||||
.rAmount = usage, .rTime = REFILL_HEAD(sc).rTime + sc->scPeriod
|
||||
};
|
||||
refill_add_tail(sc, new);
|
||||
REFILL_SANITY_END(sc);
|
||||
}
|
||||
|
||||
void refill_unblock_check(sched_context_t *sc)
|
||||
{
|
||||
/* advance earliest activation time to now */
|
||||
REFILL_SANITY_START(sc);
|
||||
if (refill_ready(sc)) {
|
||||
REFILL_HEAD(sc).rTime = NODE_STATE(ksCurTime);
|
||||
|
||||
/* merge available replenishments */
|
||||
while (!refill_single(sc)) {
|
||||
ticks_t amount = REFILL_HEAD(sc).rAmount;
|
||||
if (REFILL_INDEX(sc, refill_next(sc, sc->scRefillHead)).rTime <= NODE_STATE(ksCurTime) + amount) {
|
||||
refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += amount;
|
||||
REFILL_HEAD(sc).rTime = NODE_STATE(ksCurTime);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* it's possible that a refill is not bigger than min budget (if a task
|
||||
* uses less than min budget, it will still be scheduled for refill), if
|
||||
* so merge with the next refill, as it's not enough to schedule the task. */
|
||||
if (!refill_sufficient(sc, 0)) {
|
||||
assert(!refill_single(sc));
|
||||
refill_t insufficient = refill_pop_head(sc);
|
||||
REFILL_HEAD(sc).rAmount += insufficient.rAmount;
|
||||
}
|
||||
}
|
||||
REFILL_SANITY_END(sc);
|
||||
}
|
||||
|
|
@ -49,7 +49,8 @@ static inline bool_t PURE isBlocked(const tcb_t *thread)
|
|||
static inline bool_t PURE isSchedulable(const tcb_t *thread)
|
||||
{
|
||||
return isRunnable(thread) &&
|
||||
thread->tcbSchedContext != NULL;
|
||||
thread->tcbSchedContext != NULL &&
|
||||
!thread_state_get_tcbInReleaseQueue(thread->tcbState);
|
||||
}
|
||||
#else
|
||||
#define isSchedulable isRunnable
|
||||
|
|
@ -102,6 +103,9 @@ void suspend(tcb_t *target)
|
|||
}
|
||||
setThreadState(target, ThreadState_Inactive);
|
||||
tcbSchedDequeue(target);
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
tcbReleaseRemove(target);
|
||||
#endif
|
||||
}
|
||||
|
||||
void restart(tcb_t *target)
|
||||
|
|
@ -294,10 +298,20 @@ static void switchSchedContext(void)
|
|||
{
|
||||
if (unlikely(NODE_STATE(ksCurSC) != NODE_STATE(ksCurThread)->tcbSchedContext)) {
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
commitTime(ksCurSC);
|
||||
commitTime();
|
||||
refill_unblock_check(NODE_STATE(ksCurThread->tcbSchedContext));
|
||||
|
||||
assert(refill_ready(NODE_STATE(ksCurThread->tcbSchedContext)));
|
||||
assert(refill_sufficient(NODE_STATE(ksCurThread->tcbSchedContext), 0));
|
||||
} else {
|
||||
rollbackTime();
|
||||
}
|
||||
|
||||
/* if a thread doesn't have enough budget, it should not be in the scheduler */
|
||||
if (!refill_ready(NODE_STATE(ksCurSC)) || !refill_sufficient(NODE_STATE(ksCurSC), 0)) {
|
||||
assert(!thread_state_get_tcbQueued(NODE_STATE(ksCurSC)->scTcb->tcbState));
|
||||
}
|
||||
|
||||
NODE_STATE(ksCurSC) = NODE_STATE(ksCurThread)->tcbSchedContext;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -312,6 +326,10 @@ static void scheduleChooseNewThread(void)
|
|||
|
||||
void schedule(void)
|
||||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
awaken();
|
||||
#endif
|
||||
|
||||
if (NODE_STATE(ksSchedulerAction) != SchedulerAction_ResumeCurrentThread) {
|
||||
bool_t was_runnable;
|
||||
if (isSchedulable(NODE_STATE(ksCurThread))) {
|
||||
|
|
@ -386,7 +404,8 @@ void chooseThread(void)
|
|||
assert(thread);
|
||||
assert(isSchedulable(thread));
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(thread->tcbSchedContext->scRemaining > getKernelWcetTicks());
|
||||
assert(refill_sufficient(thread->tcbSchedContext, 0));
|
||||
assert(refill_ready(thread->tcbSchedContext));
|
||||
#endif
|
||||
switchToThread(thread);
|
||||
} else {
|
||||
|
|
@ -398,7 +417,9 @@ void switchToThread(tcb_t *thread)
|
|||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(thread->tcbSchedContext != NULL);
|
||||
assert(thread->tcbSchedContext->scRemaining >= getKernelWcetTicks());
|
||||
assert(!thread_state_get_tcbInReleaseQueue(thread->tcbState));
|
||||
assert(refill_sufficient(thread->tcbSchedContext, 0));
|
||||
assert(refill_ready(thread->tcbSchedContext));
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BENCHMARK_TRACK_UTILISATION
|
||||
|
|
@ -463,7 +484,7 @@ void setPriority(tcb_t *tptr, prio_t prio)
|
|||
void possibleSwitchTo(tcb_t *target)
|
||||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
if (target->tcbSchedContext != NULL) {
|
||||
if (target->tcbSchedContext != NULL && !thread_state_get_tcbInReleaseQueue(target->tcbState)) {
|
||||
#endif
|
||||
if (ksCurDomain != target->tcbDomain
|
||||
SMP_COND_STATEMENT( || target->tcbAffinity != getCurrentCPUIndex())) {
|
||||
|
|
@ -497,55 +518,40 @@ void scheduleTCB(tcb_t *tptr)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
static void recharge(sched_context_t *sc)
|
||||
void postpone(sched_context_t *sc)
|
||||
{
|
||||
sc->scRemaining = sc->scBudget;
|
||||
assert(sc->scBudget > 0);
|
||||
tcbSchedDequeue(sc->scTcb);
|
||||
tcbReleaseEnqueue(sc->scTcb);
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
}
|
||||
|
||||
void setNextInterrupt(void)
|
||||
{
|
||||
time_t next_thread = NODE_STATE(ksCurTime) + NODE_STATE(ksCurThread)->tcbSchedContext->scRemaining;
|
||||
time_t next_interrupt = NODE_STATE(ksCurTime) +
|
||||
REFILL_HEAD(NODE_STATE(ksCurThread)->tcbSchedContext).rAmount;
|
||||
|
||||
if (CONFIG_NUM_DOMAINS > 1) {
|
||||
time_t next_domain = ksCurTime + ksDomainTime;
|
||||
setDeadline(MIN(next_thread, next_domain) - getTimerPrecision());
|
||||
} else {
|
||||
setDeadline(next_thread - getTimerPrecision());
|
||||
next_interrupt = MIN(next_interrupt, NODE_STATE(ksCurTime) + ksDomainTime);
|
||||
}
|
||||
}
|
||||
|
||||
bool_t checkBudget(void)
|
||||
{
|
||||
if (unlikely(isCurThreadExpired())) {
|
||||
commitTime(ksCurSC);
|
||||
endTimeslice();
|
||||
return false;
|
||||
} else if (unlikely(isCurDomainExpired())) {
|
||||
commitTime(ksCurSC);
|
||||
rescheduleRequired();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
if (NODE_STATE(ksReleaseHead) != NULL) {
|
||||
next_interrupt = MIN(REFILL_HEAD(NODE_STATE(ksReleaseHead)->tcbSchedContext).rTime, next_interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
bool_t checkBudgetRestart(void)
|
||||
{
|
||||
assert(isRunnable(NODE_STATE(ksCurThread)));
|
||||
bool_t result = checkBudget();
|
||||
if (!result) {
|
||||
setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
|
||||
}
|
||||
return result;
|
||||
setDeadline(next_interrupt - getTimerPrecision());
|
||||
}
|
||||
|
||||
void endTimeslice(void)
|
||||
{
|
||||
recharge(NODE_STATE(ksCurThread)->tcbSchedContext);
|
||||
if (likely(thread_state_get_tsType(NODE_STATE(ksCurThread)->tcbState) ==
|
||||
ThreadState_Running)) {
|
||||
assert(isRunnable(NODE_STATE(ksCurSC->scTcb)));
|
||||
if (refill_ready(NODE_STATE(ksCurSC)) && refill_sufficient(NODE_STATE(ksCurSC), 0)) {
|
||||
/* apply round robin */
|
||||
assert(refill_sufficient(NODE_STATE(ksCurSC), 0));
|
||||
assert(!thread_state_get_tcbQueued(NODE_STATE(ksCurThread)->tcbState));
|
||||
SCHED_APPEND_CURRENT_TCB;
|
||||
} else {
|
||||
/* postpone until ready */
|
||||
postpone(NODE_STATE(ksCurSC));
|
||||
}
|
||||
rescheduleRequired();
|
||||
}
|
||||
|
|
@ -586,6 +592,10 @@ void rescheduleRequired(void)
|
|||
&& isSchedulable(NODE_STATE(ksSchedulerAction))
|
||||
#endif
|
||||
) {
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(refill_sufficient(NODE_STATE(ksSchedulerAction)->tcbSchedContext, 0));
|
||||
assert(refill_ready(NODE_STATE(ksSchedulerAction)->tcbSchedContext));
|
||||
#endif
|
||||
SCHED_ENQUEUE(NODE_STATE(ksSchedulerAction));
|
||||
}
|
||||
NODE_STATE(ksSchedulerAction) = SchedulerAction_ChooseNewThread;
|
||||
|
|
@ -594,3 +604,20 @@ void rescheduleRequired(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
void awaken(void)
|
||||
{
|
||||
while (unlikely(NODE_STATE(ksReleaseHead) != NULL && refill_ready(NODE_STATE(ksReleaseHead)->tcbSchedContext))) {
|
||||
tcb_t *awakened = tcbReleaseDequeue();
|
||||
SMP_COND_STATEMENT(assert(awakened->tcbAffinity == getCurrentCPUIndex()));
|
||||
refill_unblock_check(awakened->tcbSchedContext);
|
||||
if (unlikely(!refill_ready(awakened->tcbSchedContext))) {
|
||||
tcbReleaseEnqueue(awakened);
|
||||
} else {
|
||||
assert(refill_sufficient(awakened->tcbSchedContext, 0));
|
||||
tcbSchedAppend(awakened);
|
||||
possibleSwitchTo(awakened);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ UP_STATE_DEFINE(tcb_queue_t, ksReadyQueues[NUM_READY_QUEUES]);
|
|||
UP_STATE_DEFINE(word_t, ksReadyQueuesL1Bitmap[CONFIG_NUM_DOMAINS]);
|
||||
UP_STATE_DEFINE(word_t, ksReadyQueuesL2Bitmap[CONFIG_NUM_DOMAINS][L2_BITMAP_SIZE]);
|
||||
compile_assert(ksReadyQueuesL1BitmapBigEnough, (L2_BITMAP_SIZE - 1) <= wordBits)
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* Head of the queue of threads waiting for their budget to be replenished */
|
||||
UP_STATE_DEFINE(tcb_t *, ksReleaseHead);
|
||||
#endif
|
||||
|
||||
/* Current thread TCB pointer */
|
||||
UP_STATE_DEFINE(tcb_t *, ksCurThread);
|
||||
|
|
|
|||
|
|
@ -103,6 +103,11 @@ void sendIPC(bool_t blocking, bool_t do_call, word_t badge,
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* blocked threads should have enough budget to get out of the kernel */
|
||||
assert(dest->tcbSchedContext == NULL || refill_sufficient(dest->tcbSchedContext, 0));
|
||||
assert(dest->tcbSchedContext == NULL || refill_ready(dest->tcbSchedContext));
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +200,9 @@ void receiveIPC(tcb_t *thread, cap_t cap, bool_t isBlocking)
|
|||
} else {
|
||||
setThreadState(sender, ThreadState_Running);
|
||||
possibleSwitchTo(sender);
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(sender->tcbSchedContext == NULL || refill_sufficient(sender->tcbSchedContext, 0));
|
||||
#endif
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -219,10 +219,10 @@ void handleInterrupt(irq_t irq)
|
|||
maskInterrupt(true, irq);
|
||||
#endif
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
/* Bill the current thread. We know it has enough budget, as otherwise we would be
|
||||
* dealing with a timer interrupt not a signal interrupt */
|
||||
commitTime(ksCurSC);
|
||||
checkReschedule();
|
||||
/* Bill the current thread. */
|
||||
if (unlikely(checkBudget())) {
|
||||
commitTime();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
|
@ -231,8 +231,10 @@ void handleInterrupt(irq_t irq)
|
|||
#ifdef CONFIG_KERNEL_MCS
|
||||
updateTimestamp();
|
||||
ackDeadlineIRQ();
|
||||
commitTime(ksCurSC);
|
||||
checkBudget();
|
||||
if (likely(checkBudget())) {
|
||||
commitTime();
|
||||
}
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
#else
|
||||
timerTick();
|
||||
resetTimer();
|
||||
|
|
@ -245,6 +247,11 @@ void handleInterrupt(irq_t irq)
|
|||
updateTimestamp();
|
||||
#endif
|
||||
handleIPI(irq, true);
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
if (unlikely(checkBudget())) {
|
||||
commitTime();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
#endif /* ENABLE_SMP_SUPPORT */
|
||||
|
||||
|
|
|
|||
|
|
@ -112,14 +112,22 @@ exception_t decodeSchedContextInvocation(word_t label, cap_t cap, extra_caps_t e
|
|||
|
||||
void schedContext_resume(sched_context_t *sc)
|
||||
{
|
||||
assert(sc->scTcb != NULL);
|
||||
assert(!sc || sc->scTcb != NULL);
|
||||
if (likely(sc) && isSchedulable(sc->scTcb)) {
|
||||
assert(sc->scTcb != NULL);
|
||||
/* this should NOT be called when migration is possible */
|
||||
#if CONFIG_MAX_NUM_NODES > 1
|
||||
/* this should NOT be called when migration is possible */
|
||||
assert(sc->scCore == getCurrentCPUIndex());
|
||||
SMP_COND_STATEMENT(assert(sc->scCore == sc->scTcb->tcbAffinity));
|
||||
SMP_COND_STATEMENT(assert(sc->scCore == getCurrentCPUIndex()));
|
||||
#endif
|
||||
if (isRunnable(sc->scTcb) && sc->scBudget > 0) {
|
||||
recharge(sc);
|
||||
possibleSwitchTo(sc->scTcb);
|
||||
refill_unblock_check(sc);
|
||||
|
||||
if (isRunnable(sc->scTcb) && sc->scRefillMax > 0) {
|
||||
if (!(refill_ready(sc) && refill_sufficient(sc, 0))) {
|
||||
assert(!thread_state_get_tcbQueued(sc->scTcb->tcbState));
|
||||
postpone(sc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,15 +155,19 @@ void schedContext_unbindTCB(sched_context_t *sc, tcb_t *tcb)
|
|||
assert(sc->scTcb == tcb);
|
||||
|
||||
tcbSchedDequeue(sc->scTcb);
|
||||
tcbReleaseRemove(sc->scTcb);
|
||||
|
||||
sc->scTcb->tcbSchedContext = NULL;
|
||||
if (sc->scTcb == NODE_STATE(ksCurThread)) {
|
||||
sc->scTcb = NULL;
|
||||
|
||||
SMP_COND_STATEMENT(remoteTCBStall(tcb);)
|
||||
|
||||
if (tcb == NODE_STATE(ksCurThread)) {
|
||||
rescheduleRequired();
|
||||
} else {
|
||||
SMP_COND_STATEMENT(remoteTCBStall(tcb));
|
||||
}
|
||||
|
||||
sc->scTcb = NULL;
|
||||
}
|
||||
|
||||
void schedContext_unbindAllTCBs(sched_context_t *sc)
|
||||
|
|
|
|||
|
|
@ -14,22 +14,45 @@
|
|||
#include <mode/api/ipc_buffer.h>
|
||||
#include <object/schedcontext.h>
|
||||
#include <object/schedcontrol.h>
|
||||
#include <kernel/sporadic.h>
|
||||
|
||||
static exception_t invokeSchedControl_Configure(sched_context_t *target, ticks_t budget, word_t core)
|
||||
static exception_t invokeSchedControl_Configure(sched_context_t *target, word_t core, ticks_t budget, ticks_t period,
|
||||
word_t max_refills)
|
||||
{
|
||||
target->scBudget = budget;
|
||||
target->scCore = core;
|
||||
recharge(target);
|
||||
/* don't modify parameters of tcb while it is in a sorted queue */
|
||||
if (target->scTcb) {
|
||||
tcbReleaseRemove(target->scTcb);
|
||||
}
|
||||
|
||||
if (target->scTcb != NULL) {
|
||||
/* target may no longer have budget for this core */
|
||||
if (!isSchedulable(target->scTcb)) {
|
||||
tcbSchedDequeue(target->scTcb);
|
||||
} else {
|
||||
possibleSwitchTo(target->scTcb);
|
||||
if (budget == period) {
|
||||
/* this is a cool hack: for round robin, we set the
|
||||
* period to 0, which means that the budget will always be ready to be refilled
|
||||
* and the code doesn't need special casing
|
||||
*/
|
||||
period = 0;
|
||||
}
|
||||
|
||||
if (core == target->scCore && target->scRefillMax > 0 && target->scTcb && isRunnable(target->scTcb)) {
|
||||
/* the scheduling context is active - it can be used, so
|
||||
* we need to preserve the bandwidth */
|
||||
refill_update(target, period, budget, max_refills);
|
||||
} else {
|
||||
/* the scheduling context isn't active - it's budget is not being used, so
|
||||
* we can just populate the parameters from now */
|
||||
refill_new(target, max_refills, budget, period);
|
||||
|
||||
if (core != target->scCore && target->scTcb) {
|
||||
/* if the core changed and the SC has a tcb, the SC is getting
|
||||
* budget - so migrate it */
|
||||
target->scCore = core;
|
||||
SMP_COND_STATEMENT(migrateTCB(target->scTcb));
|
||||
}
|
||||
}
|
||||
|
||||
if (target->scTcb && isRunnable(target->scTcb) && target->scRefillMax > 0) {
|
||||
schedContext_resume(target);
|
||||
}
|
||||
|
||||
return EXCEPTION_NONE;
|
||||
}
|
||||
|
||||
|
|
@ -41,13 +64,15 @@ static exception_t decodeSchedControl_Configure(word_t length, cap_t cap, extra_
|
|||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
if (length < TIME_ARG_SIZE) {
|
||||
if (length < (TIME_ARG_SIZE * 2) + 1) {
|
||||
userError("SchedControl_configure: truncated message.");
|
||||
current_syscall_error.type = seL4_TruncatedMessage;
|
||||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
time_t budget_us = mode_parseTimeArg(0, buffer);
|
||||
time_t period_us = mode_parseTimeArg(TIME_ARG_SIZE, buffer);
|
||||
word_t max_refills = MIN_REFILLS + getSyscallArg(TIME_ARG_SIZE * 2, buffer);
|
||||
|
||||
cap_t targetCap = extraCaps.excaprefs[0]->cap;
|
||||
if (unlikely(cap_get_capType(targetCap) != cap_sched_context_cap)) {
|
||||
|
|
@ -57,17 +82,44 @@ static exception_t decodeSchedControl_Configure(word_t length, cap_t cap, extra_
|
|||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
if (budget_us > getMaxUsToTicks() || budget_us < getKernelWcetUs()) {
|
||||
if (budget_us > getMaxUsToTicks() || budget_us < MIN_BUDGET_US) {
|
||||
userError("SchedControl_Configure: budget out of range.");
|
||||
current_syscall_error.type = seL4_RangeError;
|
||||
current_syscall_error.rangeErrorMin = getKernelWcetUs();
|
||||
current_syscall_error.rangeErrorMin = MIN_BUDGET_US;
|
||||
current_syscall_error.rangeErrorMax = getMaxUsToTicks();
|
||||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
if (period_us > getMaxUsToTicks() || period_us < MIN_BUDGET_US) {
|
||||
userError("SchedControl_Configure: period out of range.");
|
||||
current_syscall_error.type = seL4_RangeError;
|
||||
current_syscall_error.rangeErrorMin = MIN_BUDGET_US;
|
||||
current_syscall_error.rangeErrorMax = getMaxUsToTicks();
|
||||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
if (budget_us > period_us) {
|
||||
userError("SchedControl_Configure: budget must be <= period");
|
||||
current_syscall_error.type = seL4_RangeError;
|
||||
current_syscall_error.rangeErrorMin = MIN_BUDGET_US;
|
||||
current_syscall_error.rangeErrorMax = period_us;
|
||||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
if (max_refills > MAX_REFILLS) {
|
||||
userError("Max refills invalid");
|
||||
current_syscall_error.type = seL4_RangeError;
|
||||
current_syscall_error.rangeErrorMin = 0;
|
||||
current_syscall_error.rangeErrorMax = MAX_REFILLS - MIN_REFILLS - 1;
|
||||
return EXCEPTION_SYSCALL_ERROR;
|
||||
}
|
||||
|
||||
setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
|
||||
return invokeSchedControl_Configure(SC_PTR(cap_sched_context_cap_get_capSCPtr(targetCap)),
|
||||
usToTicks(budget_us), cap_sched_control_cap_get_core(cap));
|
||||
cap_sched_control_cap_get_core(cap),
|
||||
usToTicks(budget_us),
|
||||
usToTicks(period_us),
|
||||
max_refills + MIN_REFILLS);
|
||||
}
|
||||
|
||||
exception_t decodeSchedControlInvocation(word_t label, cap_t cap, word_t length, extra_caps_t extraCaps,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ void tcbSchedEnqueue(tcb_t *tcb)
|
|||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(isSchedulable(tcb));
|
||||
assert(tcb->tcbSchedContext->scRemaining > getKernelWcetTicks());
|
||||
assert(refill_sufficient(tcb->tcbSchedContext, 0));
|
||||
assert(refill_ready(tcb->tcbSchedContext));
|
||||
#endif
|
||||
|
||||
if (!thread_state_get_tcbQueued(tcb->tcbState)) {
|
||||
|
|
@ -122,7 +123,8 @@ void tcbSchedAppend(tcb_t *tcb)
|
|||
{
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
assert(isSchedulable(tcb));
|
||||
assert(tcb->tcbSchedContext->scRemaining > getKernelWcetTicks());
|
||||
assert(refill_sufficient(tcb->tcbSchedContext, 0));
|
||||
assert(refill_ready(tcb->tcbSchedContext));
|
||||
#endif
|
||||
if (!thread_state_get_tcbQueued(tcb->tcbState)) {
|
||||
tcb_queue_t queue;
|
||||
|
|
@ -253,6 +255,85 @@ tcb_queue_t tcbEPDequeue(tcb_t *tcb, tcb_queue_t queue)
|
|||
return queue;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KERNEL_MCS
|
||||
void tcbReleaseRemove(tcb_t *tcb)
|
||||
{
|
||||
if (likely(thread_state_get_tcbInReleaseQueue(tcb->tcbState))) {
|
||||
if (tcb->tcbSchedPrev) {
|
||||
tcb->tcbSchedPrev->tcbSchedNext = tcb->tcbSchedNext;
|
||||
} else {
|
||||
NODE_STATE(ksReleaseHead) = tcb->tcbSchedNext;
|
||||
/* the head has changed, we might need to set a new timeout */
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
}
|
||||
|
||||
if (tcb->tcbSchedNext) {
|
||||
tcb->tcbSchedNext->tcbSchedPrev = tcb->tcbSchedPrev;
|
||||
}
|
||||
|
||||
tcb->tcbSchedNext = NULL;
|
||||
tcb->tcbSchedPrev = NULL;
|
||||
thread_state_ptr_set_tcbInReleaseQueue(&tcb->tcbState, false);
|
||||
}
|
||||
}
|
||||
|
||||
void tcbReleaseEnqueue(tcb_t *tcb)
|
||||
{
|
||||
assert(thread_state_get_tcbInReleaseQueue(tcb->tcbState) == false);
|
||||
assert(thread_state_get_tcbQueued(tcb->tcbState) == false);
|
||||
|
||||
tcb_t *before = NULL;
|
||||
tcb_t *after = NODE_STATE(ksReleaseHead);
|
||||
|
||||
/* find our place in the ordered queue */
|
||||
while (after != NULL &&
|
||||
REFILL_HEAD(tcb->tcbSchedContext).rTime >= REFILL_HEAD(after->tcbSchedContext).rTime) {
|
||||
before = after;
|
||||
after = after->tcbSchedNext;
|
||||
}
|
||||
|
||||
if (before == NULL) {
|
||||
/* insert at head */
|
||||
NODE_STATE(ksReleaseHead) = tcb;
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
} else {
|
||||
before->tcbSchedNext = tcb;
|
||||
}
|
||||
|
||||
if (after != NULL) {
|
||||
after->tcbSchedPrev = tcb;
|
||||
}
|
||||
|
||||
tcb->tcbSchedNext = after;
|
||||
tcb->tcbSchedPrev = before;
|
||||
|
||||
thread_state_ptr_set_tcbInReleaseQueue(&tcb->tcbState, true);
|
||||
}
|
||||
|
||||
tcb_t *tcbReleaseDequeue(void)
|
||||
{
|
||||
assert(NODE_STATE(ksReleaseHead) != NULL);
|
||||
assert(NODE_STATE(ksReleaseHead)->tcbSchedPrev == NULL);
|
||||
|
||||
tcb_t *detached_head = NODE_STATE(ksReleaseHead);
|
||||
NODE_STATE(ksReleaseHead) = NODE_STATE(ksReleaseHead)->tcbSchedNext;
|
||||
|
||||
if (NODE_STATE(ksReleaseHead)) {
|
||||
NODE_STATE(ksReleaseHead)->tcbSchedPrev = NULL;
|
||||
}
|
||||
|
||||
if (detached_head->tcbSchedNext) {
|
||||
detached_head->tcbSchedNext->tcbSchedPrev = NULL;
|
||||
detached_head->tcbSchedNext = NULL;
|
||||
}
|
||||
|
||||
thread_state_ptr_set_tcbInReleaseQueue(&detached_head->tcbState, false);
|
||||
NODE_STATE(ksReprogram) = true;
|
||||
|
||||
return detached_head;
|
||||
}
|
||||
#endif
|
||||
|
||||
cptr_t PURE getExtraCPtr(word_t *bufferPtr, word_t i)
|
||||
{
|
||||
return (cptr_t)bufferPtr[seL4_MsgMaxLength + 2 + i];
|
||||
|
|
|
|||
Loading…
Reference in a new issue