399 lines
14 KiB
ReStructuredText
399 lines
14 KiB
ReStructuredText
.. _scheduling_v2:
|
|
|
|
Scheduling
|
|
##########
|
|
|
|
The kernel's priority-based scheduler allows an application's threads
|
|
to share the CPU.
|
|
|
|
Concepts
|
|
********
|
|
|
|
The scheduler determines which thread is allowed to execute
|
|
at any point in time; this thread is known as the **current thread**.
|
|
|
|
There are various points in time when the scheduler is given an
|
|
opportunity to change the identity of the current thread. These points
|
|
are called **reschedule points**. Some potential reschedule points are:
|
|
|
|
- transition of a thread from running state to a suspended or waiting
|
|
state, for example by :c:func:`k_sem_take` or :c:func:`k_sleep`.
|
|
- transition of a thread to the :ref:`ready state <thread_states>`, for
|
|
example by :c:func:`k_sem_give` or :c:func:`k_thread_start`
|
|
- return to thread context after processing an interrupt
|
|
- when a running thread invokes :c:func:`k_yield`
|
|
|
|
A thread **sleeps** when it voluntarily initiates an operation that
|
|
transitions itself to a suspended or waiting state.
|
|
|
|
Whenever the scheduler changes the identity of the current thread,
|
|
or when execution of the current thread is replaced by an ISR,
|
|
the kernel first saves the current thread's CPU register values.
|
|
These register values get restored when the thread later resumes execution.
|
|
|
|
|
|
Scheduling Algorithm
|
|
====================
|
|
|
|
The kernel's scheduler selects the highest priority ready thread
|
|
to be the current thread. When multiple ready threads of the same priority
|
|
exist, the scheduler chooses the one that has been waiting longest.
|
|
|
|
A thread's relative priority is primarily determined by its static priority.
|
|
However, when both earliest-deadline-first scheduling is enabled
|
|
(:kconfig:option:`CONFIG_SCHED_DEADLINE`) and a choice of threads have equal
|
|
static priority, then the thread with the earlier deadline is considered
|
|
to have the higher priority. Thus, when earliest-deadline-first scheduling is
|
|
enabled, two threads are only considered to have the same priority when both
|
|
their static priorities and deadlines are equal. The routine
|
|
:c:func:`k_thread_deadline_set` is used to set a thread's deadline.
|
|
|
|
.. note::
|
|
Execution of ISRs takes precedence over thread execution,
|
|
so the execution of the current thread may be replaced by an ISR
|
|
at any time unless interrupts have been masked. This applies to both
|
|
cooperative threads and preemptive threads.
|
|
|
|
|
|
The kernel can be built with one of several choices for the ready queue
|
|
implementation, offering different choices between code size, constant factor
|
|
runtime overhead and performance scaling when many threads are added.
|
|
|
|
* Simple linked-list ready queue (:kconfig:option:`CONFIG_SCHED_DUMB`)
|
|
|
|
The scheduler ready queue will be implemented as a simple unordered list, with
|
|
very fast constant time performance for single threads and very low code size.
|
|
This implementation should be selected on systems with constrained code size
|
|
that will never see more than a small number (3, maybe) of runnable threads in
|
|
the queue at any given time. On most platforms (that are not otherwise using
|
|
the red/black tree) this results in a savings of ~2k of code size.
|
|
|
|
* Red/black tree ready queue (:kconfig:option:`CONFIG_SCHED_SCALABLE`)
|
|
|
|
The scheduler ready queue will be implemented as a red/black tree. This has
|
|
rather slower constant-time insertion and removal overhead, and on most
|
|
platforms (that are not otherwise using the red/black tree somewhere) requires
|
|
an extra ~2kb of code. The resulting behavior will scale cleanly and
|
|
quickly into the many thousands of threads.
|
|
|
|
Use this for applications needing many concurrent runnable threads (> 20 or
|
|
so). Most applications won't need this ready queue implementation.
|
|
|
|
* Traditional multi-queue ready queue (:kconfig:option:`CONFIG_SCHED_MULTIQ`)
|
|
|
|
When selected, the scheduler ready queue will be implemented as the
|
|
classic/textbook array of lists, one per priority (max 32 priorities).
|
|
|
|
This corresponds to the scheduler algorithm used in Zephyr versions prior to
|
|
1.12.
|
|
|
|
It incurs only a tiny code size overhead vs. the "dumb" scheduler and runs in
|
|
O(1) time in almost all circumstances with very low constant factor. But it
|
|
requires a fairly large RAM budget to store those list heads, and the limited
|
|
features make it incompatible with features like deadline scheduling that
|
|
need to sort threads more finely, and SMP affinity which need to traverse the
|
|
list of threads.
|
|
|
|
Typical applications with small numbers of runnable threads probably want the
|
|
DUMB scheduler.
|
|
|
|
|
|
The wait_q abstraction used in IPC primitives to pend threads for later wakeup
|
|
shares the same backend data structure choices as the scheduler, and can use
|
|
the same options.
|
|
|
|
* Scalable wait_q implementation (:kconfig:option:`CONFIG_WAITQ_SCALABLE`)
|
|
|
|
When selected, the wait_q will be implemented with a balanced tree. Choose
|
|
this if you expect to have many threads waiting on individual primitives.
|
|
There is a ~2kb code size increase over :kconfig:option:`CONFIG_WAITQ_DUMB` (which may
|
|
be shared with :kconfig:option:`CONFIG_SCHED_SCALABLE`) if the red/black tree is not
|
|
used elsewhere in the application, and pend/unpend operations on "small"
|
|
queues will be somewhat slower (though this is not generally a performance
|
|
path).
|
|
|
|
* Simple linked-list wait_q (:kconfig:option:`CONFIG_WAITQ_DUMB`)
|
|
|
|
When selected, the wait_q will be implemented with a doubly-linked list.
|
|
Choose this if you expect to have only a few threads blocked on any single
|
|
IPC primitive.
|
|
|
|
Cooperative Time Slicing
|
|
========================
|
|
|
|
Once a cooperative thread becomes the current thread, it remains
|
|
the current thread until it performs an action that makes it unready.
|
|
Consequently, if a cooperative thread performs lengthy computations,
|
|
it may cause an unacceptable delay in the scheduling of other threads,
|
|
including those of higher priority and equal priority.
|
|
|
|
|
|
.. image:: cooperative.svg
|
|
:align: center
|
|
|
|
To overcome such problems, a cooperative thread can voluntarily relinquish
|
|
the CPU from time to time to permit other threads to execute.
|
|
A thread can relinquish the CPU in two ways:
|
|
|
|
* Calling :c:func:`k_yield` puts the thread at the back of the scheduler's
|
|
prioritized list of ready threads, and then invokes the scheduler.
|
|
All ready threads whose priority is higher or equal to that of the
|
|
yielding thread are then allowed to execute before the yielding thread is
|
|
rescheduled. If no such ready threads exist, the scheduler immediately
|
|
reschedules the yielding thread without context switching.
|
|
|
|
* Calling :c:func:`k_sleep` makes the thread unready for a specified
|
|
time period. Ready threads of *all* priorities are then allowed to execute;
|
|
however, there is no guarantee that threads whose priority is lower
|
|
than that of the sleeping thread will actually be scheduled before
|
|
the sleeping thread becomes ready once again.
|
|
|
|
Preemptive Time Slicing
|
|
=======================
|
|
|
|
Once a preemptive thread becomes the current thread, it remains
|
|
the current thread until a higher priority thread becomes ready,
|
|
or until the thread performs an action that makes it unready.
|
|
Consequently, if a preemptive thread performs lengthy computations,
|
|
it may cause an unacceptable delay in the scheduling of other threads,
|
|
including those of equal priority.
|
|
|
|
|
|
.. image:: preemptive.svg
|
|
:align: center
|
|
|
|
To overcome such problems, a preemptive thread can perform cooperative
|
|
time slicing (as described above), or the scheduler's time slicing capability
|
|
can be used to allow other threads of the same priority to execute.
|
|
|
|
.. image:: timeslicing.svg
|
|
:align: center
|
|
|
|
The scheduler divides time into a series of **time slices**, where slices
|
|
are measured in system clock ticks. The time slice size is configurable,
|
|
but this size can be changed while the application is running.
|
|
|
|
At the end of every time slice, the scheduler checks to see if the current
|
|
thread is preemptible and, if so, implicitly invokes :c:func:`k_yield`
|
|
on behalf of the thread. This gives other ready threads of the same priority
|
|
the opportunity to execute before the current thread is scheduled again.
|
|
If no threads of equal priority are ready, the current thread remains
|
|
the current thread.
|
|
|
|
Threads with a priority higher than specified limit are exempt from preemptive
|
|
time slicing, and are never preempted by a thread of equal priority.
|
|
This allows an application to use preemptive time slicing
|
|
only when dealing with lower priority threads that are less time-sensitive.
|
|
|
|
.. note::
|
|
The kernel's time slicing algorithm does *not* ensure that a set
|
|
of equal-priority threads receive an equitable amount of CPU time,
|
|
since it does not measure the amount of time a thread actually gets to
|
|
execute. However, the algorithm *does* ensure that a thread never executes
|
|
for longer than a single time slice without being required to yield.
|
|
|
|
Scheduler Locking
|
|
=================
|
|
|
|
A preemptible thread that does not wish to be preempted while performing
|
|
a critical operation can instruct the scheduler to temporarily treat it
|
|
as a cooperative thread by calling :c:func:`k_sched_lock`. This prevents
|
|
other threads from interfering while the critical operation is being performed.
|
|
|
|
Once the critical operation is complete the preemptible thread must call
|
|
:c:func:`k_sched_unlock` to restore its normal, preemptible status.
|
|
|
|
If a thread calls :c:func:`k_sched_lock` and subsequently performs an
|
|
action that makes it unready, the scheduler will switch the locking thread out
|
|
and allow other threads to execute. When the locking thread again
|
|
becomes the current thread, its non-preemptible status is maintained.
|
|
|
|
.. note::
|
|
Locking out the scheduler is a more efficient way for a preemptible thread
|
|
to prevent preemption than changing its priority level to a negative value.
|
|
|
|
|
|
.. _thread_sleeping:
|
|
|
|
Thread Sleeping
|
|
===============
|
|
|
|
A thread can call :c:func:`k_sleep` to delay its processing
|
|
for a specified time period. During the time the thread is sleeping
|
|
the CPU is relinquished to allow other ready threads to execute.
|
|
Once the specified delay has elapsed the thread becomes ready
|
|
and is eligible to be scheduled once again.
|
|
|
|
A sleeping thread can be woken up prematurely by another thread using
|
|
:c:func:`k_wakeup`. This technique can sometimes be used
|
|
to permit the secondary thread to signal the sleeping thread
|
|
that something has occurred *without* requiring the threads
|
|
to define a kernel synchronization object, such as a semaphore.
|
|
Waking up a thread that is not sleeping is allowed, but has no effect.
|
|
|
|
.. _busy_waiting:
|
|
|
|
Busy Waiting
|
|
============
|
|
|
|
A thread can call :c:func:`k_busy_wait` to perform a ``busy wait``
|
|
that delays its processing for a specified time period
|
|
*without* relinquishing the CPU to another ready thread.
|
|
|
|
A busy wait is typically used instead of thread sleeping
|
|
when the required delay is too short to warrant having the scheduler
|
|
context switch from the current thread to another thread and then back again.
|
|
|
|
Suggested Uses
|
|
**************
|
|
|
|
Use cooperative threads for device drivers and other performance-critical work.
|
|
|
|
Use cooperative threads to implement mutually exclusion without the need
|
|
for a kernel object, such as a mutex.
|
|
|
|
Use preemptive threads to give priority to time-sensitive processing
|
|
over less time-sensitive processing.
|
|
|
|
.. _cpu_idle:
|
|
|
|
CPU Idling
|
|
##########
|
|
|
|
Although normally reserved for the idle thread, in certain special
|
|
applications, a thread might want to make the CPU idle.
|
|
|
|
.. contents::
|
|
:local:
|
|
:depth: 2
|
|
|
|
Concepts
|
|
********
|
|
|
|
Making the CPU idle causes the kernel to pause all operations until an event,
|
|
normally an interrupt, wakes up the CPU. In a regular system, the idle thread
|
|
is responsible for this. However, in some constrained systems, it is possible
|
|
that another thread takes this duty.
|
|
|
|
Implementation
|
|
**************
|
|
|
|
Making the CPU idle
|
|
===================
|
|
|
|
Making the CPU idle is simple: call the k_cpu_idle() API. The CPU will stop
|
|
executing instructions until an event occurs. Most likely, the function will
|
|
be called within a loop. Note that in certain architectures, upon return,
|
|
k_cpu_idle() unconditionally unmasks interrupts.
|
|
|
|
.. code-block:: c
|
|
|
|
static k_sem my_sem;
|
|
|
|
void my_isr(void *unused)
|
|
{
|
|
k_sem_give(&my_sem);
|
|
}
|
|
|
|
void main(void)
|
|
{
|
|
k_sem_init(&my_sem, 0, 1);
|
|
|
|
/* wait for semaphore from ISR, then do related work */
|
|
|
|
for (;;) {
|
|
|
|
/* wait for ISR to trigger work to perform */
|
|
if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {
|
|
|
|
/* ... do processing */
|
|
|
|
}
|
|
|
|
/* put CPU to sleep to save power */
|
|
k_cpu_idle();
|
|
}
|
|
}
|
|
|
|
Making the CPU idle in an atomic fashion
|
|
========================================
|
|
|
|
It is possible that there is a need to do some work atomically before making
|
|
the CPU idle. In such a case, k_cpu_atomic_idle() should be used instead.
|
|
|
|
In fact, there is a race condition in the previous example: the interrupt could
|
|
occur between the time the semaphore is taken, finding out it is not available
|
|
and making the CPU idle again. In some systems, this can cause the CPU to idle
|
|
until *another* interrupt occurs, which might be *never*, thus hanging the
|
|
system completely. To prevent this, k_cpu_atomic_idle() should have been used,
|
|
like in this example.
|
|
|
|
.. code-block:: c
|
|
|
|
static k_sem my_sem;
|
|
|
|
void my_isr(void *unused)
|
|
{
|
|
k_sem_give(&my_sem);
|
|
}
|
|
|
|
void main(void)
|
|
{
|
|
k_sem_init(&my_sem, 0, 1);
|
|
|
|
for (;;) {
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
/*
|
|
* Wait for semaphore from ISR; if acquired, do related work, then
|
|
* go to next loop iteration (the semaphore might have been given
|
|
* again); else, make the CPU idle.
|
|
*/
|
|
|
|
if (k_sem_take(&my_sem, K_NO_WAIT) == 0) {
|
|
|
|
irq_unlock(key);
|
|
|
|
/* ... do processing */
|
|
|
|
|
|
} else {
|
|
/* put CPU to sleep to save power */
|
|
k_cpu_atomic_idle(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Suggested Uses
|
|
**************
|
|
|
|
Use k_cpu_atomic_idle() when a thread has to do some real work in addition to
|
|
idling the CPU to wait for an event. See example above.
|
|
|
|
Use k_cpu_idle() only when a thread is only responsible for idling the CPU,
|
|
i.e. not doing any real work, like in this example below.
|
|
|
|
.. code-block:: c
|
|
|
|
void main(void)
|
|
{
|
|
/* ... do some system/application initialization */
|
|
|
|
|
|
/* thread is only used for CPU idling from this point on */
|
|
for (;;) {
|
|
k_cpu_idle();
|
|
}
|
|
}
|
|
|
|
.. note::
|
|
**Do not use these APIs unless absolutely necessary.** In a normal system,
|
|
the idle thread takes care of power management, including CPU idling.
|
|
|
|
API Reference
|
|
*************
|
|
|
|
.. doxygengroup:: cpu_idle_apis
|