microbit: Ammendments to ensure fiber is never pages out due to eventing and minor cleanups
- renamed fiber flag to be more general. - renamed idle to idleFiber to be consistent with the rest of the code. - factored content of idle() into a funciton to avoid dupication. - added test case to MessageBus::process() to avoid forking fibers if not requested. - removed fiber_allow_run_idle_within mutator in favour of exposing currentFiber, as a more general solution.
This commit is contained in:
parent
9ccdd25e9f
commit
3cfc7ec6e7
3 changed files with 46 additions and 49 deletions
|
@ -24,7 +24,7 @@
|
|||
#define MICROBIT_FIBER_FLAG_FOB 0x01
|
||||
#define MICROBIT_FIBER_FLAG_PARENT 0x02
|
||||
#define MICROBIT_FIBER_FLAG_CHILD 0x04
|
||||
#define MICROBIT_FIBER_FLAG_RUN_IDLE_WITHIN 0x08
|
||||
#define MICROBIT_FIBER_FLAG_DO_NOT_PAGE 0x08
|
||||
|
||||
/**
|
||||
* Thread Context for an ARM Cortex M0 core.
|
||||
|
@ -68,6 +68,7 @@ struct Fiber
|
|||
Fiber *next, *prev; // Position of this Fiber on the run queues.
|
||||
};
|
||||
|
||||
extern Fiber *currentFiber;
|
||||
|
||||
/**
|
||||
* Initialises the Fiber scheduler.
|
||||
|
@ -119,13 +120,6 @@ Fiber *create_fiber(void (*entry_fn)(void), void (*completion_fn)(void) = releas
|
|||
*/
|
||||
Fiber *create_fiber(void (*entry_fn)(void *), void *param, void (*completion_fn)(void *) = release_fiber);
|
||||
|
||||
/**
|
||||
* Allow the idle thread to run within the current thread's stack frame.
|
||||
* This is useful to prevent paging of the thread's stack to the heap.
|
||||
*/
|
||||
void fiber_allow_run_idle_within();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Calls the Fiber scheduler.
|
||||
|
@ -234,10 +228,15 @@ void queue_fiber(Fiber *f, Fiber **queue);
|
|||
*/
|
||||
void dequeue_fiber(Fiber *f);
|
||||
|
||||
/**
|
||||
* Set of tasks to perform when idle.
|
||||
* Service any background tasks that are required, and attmept power efficient sleep.
|
||||
*/
|
||||
void idle();
|
||||
|
||||
/**
|
||||
* IDLE task.
|
||||
* Only scheduled for execution when the runqueue is empty.
|
||||
* Performs a procressor sleep operation, then returns to the scheduler - most likely after a timer interrupt.
|
||||
* Only scheduled for execution when the runqueue is empty. Typically calls idle().
|
||||
*/
|
||||
void idle_task();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
Fiber *currentFiber = NULL; // The context in which the current fiber is executing.
|
||||
Fiber *forkedFiber = NULL; // The context in which a newly created child fiber is executing.
|
||||
Fiber *idle = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
|
||||
Fiber *idleFiber = NULL; // IDLE task - performs a power efficient sleep, and system maintenance tasks.
|
||||
|
||||
/*
|
||||
* Scheduler state.
|
||||
|
@ -159,9 +159,9 @@ void scheduler_init()
|
|||
|
||||
// Create the IDLE fiber.
|
||||
// Configure the fiber to directly enter the idle task.
|
||||
idle = getFiberContext();
|
||||
idle->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
|
||||
idle->tcb.LR = (uint32_t) &idle_task;
|
||||
idleFiber = getFiberContext();
|
||||
idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
|
||||
idleFiber->tcb.LR = (uint32_t) &idle_task;
|
||||
|
||||
// Flag that we now have a scheduler running
|
||||
uBit.flags |= MICROBIT_FLAG_SCHEDULER_RUNNING;
|
||||
|
@ -226,15 +226,6 @@ void scheduler_event(MicroBitEvent evt)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the idle thread to run within the current thread's stack frame.
|
||||
* This is useful to prevent paging of the thread's stack to the heap.
|
||||
*/
|
||||
void fiber_allow_run_idle_within()
|
||||
{
|
||||
currentFiber->flags |= MICROBIT_FIBER_FLAG_RUN_IDLE_WITHIN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocks the calling thread for the given period of time.
|
||||
|
@ -621,7 +612,7 @@ void schedule()
|
|||
// We're in a normal scheduling context, so perform a round robin algorithm across runnable fibers.
|
||||
// OK - if we've nothing to do, then run the IDLE task (power saving sleep)
|
||||
if (runQueue == NULL || fiber_flags & MICROBIT_FLAG_DATA_READY)
|
||||
currentFiber = idle;
|
||||
currentFiber = idleFiber;
|
||||
|
||||
else if (currentFiber->queue == &runQueue)
|
||||
// If the current fiber is on the run queue, round robin.
|
||||
|
@ -631,21 +622,18 @@ void schedule()
|
|||
// Otherwise, just pick the head of the run queue.
|
||||
currentFiber = runQueue;
|
||||
|
||||
if (currentFiber == idle && oldFiber->flags & MICROBIT_FIBER_FLAG_RUN_IDLE_WITHIN)
|
||||
if (currentFiber == idleFiber && oldFiber->flags & MICROBIT_FIBER_FLAG_DO_NOT_PAGE)
|
||||
{
|
||||
// Run the idle task right here using the old fiber's stack.
|
||||
// Keep idling while the runqueue is empty, or there is data to process.
|
||||
|
||||
// Run in the context of the original fiber, to preserve state of flags...
|
||||
// as we are running on top of this fiber's stack.
|
||||
currentFiber = oldFiber;
|
||||
|
||||
do
|
||||
{
|
||||
uBit.systemTasks();
|
||||
|
||||
if(scheduler_runqueue_empty())
|
||||
{
|
||||
if (uBit.ble)
|
||||
uBit.ble->waitForEvent();
|
||||
else
|
||||
__WFI();
|
||||
}
|
||||
idle();
|
||||
}
|
||||
while (runQueue == NULL || fiber_flags & MICROBIT_FLAG_DATA_READY);
|
||||
|
||||
|
@ -659,13 +647,13 @@ void schedule()
|
|||
if (currentFiber != oldFiber)
|
||||
{
|
||||
// Special case for the idle task, as we don't maintain a stack context (just to save memory).
|
||||
if (currentFiber == idle)
|
||||
if (currentFiber == idleFiber)
|
||||
{
|
||||
idle->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
|
||||
idle->tcb.LR = (uint32_t) &idle_task;
|
||||
idleFiber->tcb.SP = CORTEX_M0_STACK_BASE - 0x04;
|
||||
idleFiber->tcb.LR = (uint32_t) &idle_task;
|
||||
}
|
||||
|
||||
if (oldFiber == idle)
|
||||
if (oldFiber == idleFiber)
|
||||
{
|
||||
// Just swap in the new fiber, and discard changes to stack and register context.
|
||||
swap_context(NULL, ¤tFiber->tcb, NULL, currentFiber->stack_top);
|
||||
|
@ -681,6 +669,25 @@ void schedule()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set of tasks to perform when idle.
|
||||
* Service any background tasks that are required, and attmept power efficient sleep.
|
||||
*/
|
||||
void idle()
|
||||
{
|
||||
// Service background tasks
|
||||
uBit.systemTasks();
|
||||
|
||||
// If the above did create any useful work, enter power efficient sleep.
|
||||
if(scheduler_runqueue_empty())
|
||||
{
|
||||
if (uBit.ble)
|
||||
uBit.ble->waitForEvent();
|
||||
else
|
||||
__WFI();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* IDLE task.
|
||||
* Only scheduled for execution when the runqueue is empty.
|
||||
|
@ -690,16 +697,7 @@ void idle_task()
|
|||
{
|
||||
while(1)
|
||||
{
|
||||
uBit.systemTasks();
|
||||
|
||||
if(scheduler_runqueue_empty())
|
||||
{
|
||||
if (uBit.ble)
|
||||
uBit.ble->waitForEvent();
|
||||
else
|
||||
__WFI();
|
||||
}
|
||||
|
||||
idle();
|
||||
schedule();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,7 +229,7 @@ void MicroBitMessageBus::process(MicroBitEvent evt)
|
|||
// Otherwise, we invoke it in a 'fork on block' context, that will automatically create a fiber
|
||||
// should the event handler attempt a blocking operation, but doesn't have the overhead
|
||||
// of creating a fiber needlessly. (cool huh?)
|
||||
if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING)
|
||||
if (l->flags & MESSAGE_BUS_LISTENER_NONBLOCKING || currentFiber->flags & MICROBIT_FIBER_FLAG_DO_NOT_PAGE)
|
||||
async_callback(l);
|
||||
else
|
||||
invoke(async_callback, l);
|
||||
|
|
Loading…
Reference in a new issue