Skip to content

Commit 97650f1

Browse files
Alexander pinheiro paschoalettoalexpaschoaletto
authored andcommitted
docs: CBS: adding docs for the Constant Bandwidth Server (CBS)
This commit simply adds the documentation for the CBS. Signed-off-by: Alexander pinheiro paschoaletto <1222703@isep.ipp.pt>
1 parent 4cc958a commit 97650f1

File tree

7 files changed

+386
-1
lines changed

7 files changed

+386
-1
lines changed

doc/introduction/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Zephyr offers a large and ever growing number of features including:
7878

7979
* Cooperative and Preemptive Scheduling
8080
* Earliest Deadline First (EDF)
81+
* Constant Bandwidth Server (CBS)
8182
* Meta IRQ scheduling implementing "interrupt bottom half" or "tasklet"
8283
behavior
8384
* Timeslicing: Enables time slicing between preemptible threads of equal

doc/kernel/services/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ synchronization.
3232

3333
threads/index.rst
3434
scheduling/index.rst
35+
sched_server/cbs.rst
3536
threads/system_threads.rst
3637
threads/workqueue.rst
3738
threads/nothread.rst
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
.. _constant_bandwidth_server_v1:
2+
3+
Constant Bandwidth Server (CBS)
4+
###############################
5+
6+
A :dfn:`CBS` is an extension of the Earliest Deadline First (EDF) scheduler
7+
that allows tasks to be executed virtually isolated from each other, in a way
8+
that if a task executes for longer than expected it doesn't interfere on the
9+
execution of the others. In other words, the CBS prevents that a task
10+
misbehavior causes other tasks to miss their own deadlines.
11+
12+
.. contents::
13+
:local:
14+
:depth: 1
15+
16+
Introduction
17+
************
18+
19+
The regular EDF scheduler selects which task should be executing based on
20+
whichever has the earliest absolute deadline of all. The absolute deadline
21+
of a task, then, is the time instant in which the task *should* have already
22+
be completely executed. In *hard* real-time systems, not meeting the deadline
23+
means a catastrophic failure, whereas in *soft* real-time systems, it means
24+
annoyance to the end user. Many complex systems, however, feature both *hard*
25+
and *soft* tasks to execute, in which case they are called *hybrid* real-time
26+
systems.
27+
28+
Either way, having a missed deadline is never desirable. On single-core systems,
29+
the EDF guarantees schedulability (i.e. no task will miss a deadline) when the
30+
sum of each task's **bandwidth** - that is, the task execution time divided by
31+
its deadline - is less than or equal to **1** (which means 100% of CPU usage).
32+
However, that implies that the execution times are known beforehand - for hard
33+
tasks, the Worst-Case Execution Time (WCET) is used, and for soft tasks, the
34+
average execution time is preferred. These values are still prone to estimation
35+
errors if the system is not thoroughly tested, and even if it is, a true
36+
worst-case scenario might never truly show up during development phase.
37+
38+
However, these calculations are not relevant to the regular EDF scheduler at
39+
runtime - it doesn't actively account for tasks that execute for more than the
40+
estimated time, it just cares for the configured deadline of the threads. As
41+
a result, a system which features tasks with variable execution times might
42+
often cause other tasks to miss their deadlines.
43+
44+
The Constant Bandwidth Server (CBS)
45+
***********************************
46+
47+
The CBS solves this problem in EDF by trading the aforementioned execution
48+
time for a time *budget* that represents for how long the task is *allowed*
49+
to execute, somewhat similar to the time slice in the round-robin scheduler.
50+
Unlike round-robin, though, the kernel automatically downgrades the thread
51+
priority by postponing its deadline when the budget runs out. So, if even
52+
with the lowered priority the task in question remains the one with the
53+
earliest deadline, it keeps executing as if nothing happened.
54+
55+
In a nutshell, the CBS is a work-conserving wrapper for the tasks that
56+
automatically recalculates their deadlines when they exceed their
57+
configured timing constraints. If a task takes longer than expected,
58+
it can be preempted by awaiting tasks instead of jeopardizing their
59+
deadlines. The key features of a CBS are:
60+
61+
* A **budget**, which represents the maximum amount of time a CBS can
62+
serve the tasks given to it before triggering a deadline recalculation;
63+
* A **period**, which is used to recalculate the absolute deadline of
64+
the server when the kernel intervenes; and
65+
* A **job queue**, dedicated to receive the jobs that the CBS should
66+
execute. All jobs received are executed in a First-In-First-Out manner.
67+
68+
.. note::
69+
For the remainder of this documentation, a **job** is an instance of a
70+
task (similar to what objects are to classes in OOP). So while the task
71+
is the function *declaration*, the job is the function *call*.
72+
73+
Tasks assigned to a CBS don't have a deadline of their own. Instead, each
74+
time a job is pushed to a CBS, it receives the deadline of that CBS.
75+
76+
In the following section we'll go through the basics of the CBS mechanism.
77+
78+
How it works
79+
============
80+
81+
Assume we have two jobs of tasks *A* and *B*, with an execution time of 2 and 3
82+
seconds each, that are pushed to a CBS with (budget, period) = (3,7) seconds.
83+
Both jobs are pushed sequentially to the CBS, so job A will be served first
84+
and job B will go next. If the CBS is running alone in the system, this is
85+
what would happen:
86+
87+
.. image:: example-1.svg
88+
:align: center
89+
:width: 80%
90+
91+
* At instant t=1, both jobs are pushed. The CBS was idle, so the
92+
deadline is calculated as (t + period) = (1 + 7) = 8. As there
93+
is no other contending task in the system, the CBS starts
94+
executing job A immediately.
95+
* At instant t=3, job A finishes execution. The remaining budget
96+
is of 1 second, since job A spent 2 seconds with its own execution.
97+
Job B, the next one in line, starts here.
98+
* At instant t=4, the budget runs out. This triggers the kernel,
99+
which replenishes the budget at the cost of postponing the CBS
100+
deadline by 7 units (the CBS period). So the new deadline is
101+
(8 + 7) = 15. Since there is no other contending task with an
102+
earliest deadline, the CBS resumes execution of job B.
103+
* At instant t=6, job B is completed. The remaining budget is of
104+
1 second and will be like this until a new job is pushed.
105+
106+
Note that, originally, the CBS has an utilization factor (bandwidth) of
107+
(budget / period) = (3 / 7) = 0.4286. When the budget runs out at t=4,
108+
the CBS is granted 3 more seconds of execution at the expense of an
109+
increased deadline, then becoming (6 / 14) = 0.4286. That means the CBS
110+
is always expected to use at most 42.86% of the CPU - hence the name
111+
*Constant Bandwidth Server*.
112+
113+
Now let's add to this example by imagining two more jobs C and D, with
114+
execution times of 1.3 and 1, are pushed to the CBS at instants t=8 and
115+
t=16, respectively. This is the execution outcome:
116+
117+
.. image:: example-2.svg
118+
:align: center
119+
:width: 80%
120+
121+
122+
* At instant t=8, job C is pushed. The CBS was idle, so the kernel
123+
checks if the available bandwidth of the server - that is, the
124+
budget left (1) divided by the current time until the deadline (7,
125+
since at this point the deadline is set at t=15) - is greater or
126+
equal than the configured server bandwidth (0.4286). Since
127+
(1 / 7) = 0.14, the current bandwidth is lower than configured.
128+
Thus, job C is served with the current budget and deadline.
129+
* At instant t=9, the budget runs out. The kernel then replenishes
130+
the budget and postpones the deadline in 7 seconds, leading to a
131+
new value of (15 + 7) = 22. As there are no other contending tasks,
132+
job C resumes execution.
133+
* At instant t=9.3, job C is completed. The remaining budget is
134+
2.7 seconds.
135+
* At instant t=16, job D is pushed. The CBS was idle, so the kernel
136+
performs the same bandwidth check as above. This time the current
137+
budget is 2.7 seconds and the deadine is 22, so the available
138+
bandwidth is (2.7 / (22 - 16)) = (2.7 / 6) = 0.45. As it is higher
139+
than the configured 0.4286, serving job D with the current values
140+
could jeopardize the system schedulability (thus, it's better to
141+
intervene). The budget is replenished to 3, and the new deadine is
142+
set at (t + period) = (16 + 7) = 23.
143+
* At instant t=17, job D is completed. The remaining budget is of
144+
2 seconds and will be like this until a new job is pushed.
145+
146+
The working principle is simple: the CBS executes jobs that might come
147+
at random intervals in FIFO order, and every time a new job comes to an
148+
idle server, the kernel checks if the available bandwidth is higher
149+
than configured. If that's the case, an intervention happen and the
150+
server deadline is recalculated as to not risk the system schedulability.
151+
Moreover, the deadline is postponed whenever the running job exhausts
152+
the budget.
153+
154+
Use Cases
155+
*********
156+
157+
The CBS is optimal for situations where tasks have an unpredictable period
158+
between activations and variable (or unknown) execution times. For these
159+
scenarios, it introduces safeguards that add predictability to the
160+
execution. There are two main ways of using a CBS: within a hard taskset or
161+
to aid in the execution of a soft taskset.
162+
163+
Case 1: CBS + Hard tasks
164+
========================
165+
166+
This is the use case where the hard real-time tasks are scheduled directly
167+
under EDF, and the soft real-time tasks are wrapped by one (or more) CBS's.
168+
Such might be the case for hard tasks because it might not be of interest
169+
that those have their deadlines automatically recalculated when running
170+
for longer than expected. For example, a hard task could be the sampling of
171+
a sensor for a control loop while a soft task could be the rendering of
172+
the user interface.
173+
174+
When selecting this approach, the CBS can behave like an EDF version of
175+
the :ref:`Workqueue Threads<workqueues_v2>`, offering improved response
176+
times for offloaded work without risking the deadlines of the hard taskset.
177+
The following diagram illustrates the execution outcome for each approach
178+
considering the exact same set of two periodic hard tasks (with periods =
179+
deadlines) and one CBS for the handling of aperiodic jobs A, B and C.
180+
181+
.. image:: example-3.svg
182+
:align: center
183+
:width: 80%
184+
185+
186+
the main difference between these alternatives is that, due to the fact
187+
that the workqueue thread only executes when no hard task is active, it
188+
is subject to a considerable amount of delay for the soft tasks. In fact,
189+
job A is still attempting to finish when job B becomes active. The CBS,
190+
however, offers a deadline of its own to the EDF scheduler and thus manages
191+
to finish job A sooner (also causing less context switches). A downside
192+
is that the hard tasks T1 an T2 become prone to more latency, but they
193+
still never miss a deadline.
194+
195+
Case 2: Soft tasks
196+
==================
197+
198+
This use case is somewhat similar to what the Linux kernel does in its
199+
SCHED_DEADLINE algorithm. Here, each application task, periodic or not,
200+
has one exclusive CBS to handle their jobs. This solution is optimal in
201+
the sense that, while the system behaves just like the regular EDF when
202+
all execution times are equal to or lower than the respective budget,
203+
any overrun is guaranteed not to influence the rest of the system. Thus,
204+
in this use case the CBS acts as a *fail-safe* for EDF overruns.
205+
206+
API Usage
207+
*********
208+
209+
The CBS API consists of basically two elements, a :c:macro:`K_CBS_DEFINE`
210+
to statically define a CBS and a :c:func:`k_cbs_push_job` function to insert
211+
jobs on its execution queue. To define the CBS, we need to specify its
212+
name, budget, period and the static priority it will have. The latter is
213+
needed because the EDF scheduler is a tie-breaker for competing threads of
214+
the same static priority (hence, for "pure" EDF results, all application
215+
threads should have the same static value). In the following code snippet,
216+
the CBS is defined with a budget of 50 milliseconds and a period of 200
217+
milliseconds, which yields a bandwidth of 0.25 (up to 25% of the CPU usage):
218+
219+
.. code-block:: c
220+
221+
#define BUDGET K_MSEC(50)
222+
#define PERIOD K_MSEC(200)
223+
#define STATIC_PRIORITY 5
224+
225+
K_CBS_DEFINE(my_cbs, BUDGET, PERIOD, STATIC_PRIORITY);
226+
227+
now, all that's left to do is insert a job into the CBS queue, which will
228+
then be served in a FIFO manner. A job is a regular C function that expects
229+
a void pointer as an argument. Similar to the :ref:`Message Queues<message_queues_v2>`,
230+
the :c:func:`k_cbs_push_job` function also accepts a timeout value to wait
231+
for an open slot if the CBS queue happens to be full.
232+
233+
.. note::
234+
one can call :c:func:`k_cbs_push_job` from an ISR or a thread context,
235+
but ISRs should **not** attempt to wait trying to push a job on the CBS
236+
queue.
237+
238+
.. code-block:: c
239+
240+
void some_job(void *arg){
241+
printf("I am job '%c'.\n", (char) arg);
242+
}
243+
244+
int main(){
245+
...
246+
k_cbs_push_job(my_cbs, some_job, 'A', K_NO_WAIT);
247+
...
248+
}
249+
250+
In this example, the CBS will execute ``some_job`` and suspend afterwards,
251+
remaining dormant and away from the CPU for as long as no other jobs are
252+
pushed to it. In the event of a job having an infinite loop inside it, say:
253+
254+
.. code-block:: c
255+
256+
void some_job(void *arg){
257+
for(;;){
258+
printf("Hello, world!\n");
259+
k_sleep(K_SECONDS(1));
260+
}
261+
}
262+
263+
The CBS will be active and measuring the budget consumption *whenever the job
264+
is actively running* - that is, whevener it's not suspended as well. When a
265+
job calls :c:func:`k_sleep`, it suspends the CBS with it. If one desires
266+
that a *busy wait* happens instead, :c:func:`k_busy_wait` should be issued.
267+
268+
.. note::
269+
At implementation level, the CBS really is just a kernel-level :ref:`thread<threads_v2>`
270+
packed with a :ref:`timer<timers_v2>` and a :ref:`message queue<message_queues_v2>`
271+
to deliver the functionality described in the previous sections.
272+
Thus, it makes no sense to "push a thread" to a CBS, just regular
273+
functions as shown above.
274+
275+
One can see a CBS as an EDF thread that automatically recalculates
276+
its deadline, thus not needing to worry about doing it manually. Users
277+
that desire to adopt this approach can push the thread entry point as
278+
a job, like so:
279+
280+
.. code-block:: c
281+
282+
void my_thread_entry_point(void *arg){
283+
...
284+
/* your thread code goes here */
285+
...
286+
}
287+
288+
/*
289+
you can consider the budget equal
290+
to the expected execution time,
291+
and the period equal to the
292+
relative deadline of the thread.
293+
*/
294+
295+
K_CBS_DEFINE(my_cbs, budget, period, static_priority);
296+
297+
int main(){
298+
...
299+
k_cbs_push_job(my_cbs, my_thread_entry_point, (void *) &args, K_NO_WAIT);
300+
...
301+
}
302+
303+
With the only adaptation needed being to concentrate the thread
304+
arguments into one single argument (which could be easily done
305+
with a struct).
306+
307+
308+
Logging CBS events
309+
==================
310+
311+
The CBS API provides a logging feature of the main events regarding the
312+
server execution. Those can be viewed on the chosen console by enabling
313+
:kconfig:option:`CONFIG_CBS_LOG` on the ``prj.conf`` file or through
314+
``menuconfig``. The following events are supported and, although registered
315+
synchronously as they happen, will be effectivelly logged on-screen by a
316+
low-priority background task by default, in a strategy to keep the overhead
317+
as minimal as possible.
318+
319+
.. list-table:: Constant Bandwidth Server (CBS) events
320+
:widths: 10 90
321+
:header-rows: 1
322+
323+
* - Event
324+
- Description
325+
* - J_PUSH
326+
- a job has been pushed to the CBS job queue.
327+
* - J_COMP
328+
- a job has been completed.
329+
* - B_COND
330+
- the kernel verified that the available (budget, deadline) pair yielded
331+
a higher bandwidth than what's configured for that CBS. This condition
332+
is only checked right after J_PUSH happens. When true, the deadline is
333+
recalculated as (t + period) and the budget is replenished.
334+
* - B_ROUT
335+
- the budget ran out mid-execution and was replenished. The deadline is
336+
postponed in (period) units (i.e. deadline += period).
337+
* - SWT_TO
338+
- the CBS thread entered the CPU to start or resume the execution of a job.
339+
* - SWT_AY
340+
- the CBS thread left the CPU due to preemption or completing a job execution.
341+
342+
The example below shows an example output when :kconfig:option:`CONFIG_CBS_LOG` is
343+
enabled. The value alongside the event log is the budget level, in hardware cycles.
344+
The CBS thread does an underlying conversion from timeout units passed on
345+
:c:macro:`K_CBS_DEFINE` (e.g. :c:macro:`K_USEC`) to ensure units compatibility
346+
with :c:func:`k_thread_deadline_set()`, which currently accepts only hardware cycles.
347+
348+
.. code-block:: console
349+
350+
[00:00:12.028,000] <inf> CBS: cbs_1 J_PUSH 43543 // first job is pushed
351+
[00:00:12.028,000] <inf> CBS: cbs_1 B_COND 100000 // condition met, budget replenished
352+
[00:00:12.028,000] <inf> CBS: cbs_1 J_PUSH 100000 // one more job pushed
353+
[00:00:12.028,000] <inf> CBS: cbs_1 SWT_TO 100000 // CBS thread enters CPU to execute
354+
[00:00:12.031,000] <inf> CBS: cbs_1 J_COMP 68954 // first job completed
355+
[00:00:12.034,000] <inf> CBS: cbs_1 J_COMP 38914 // second job completed
356+
[00:00:12.034,000] <inf> CBS: cbs_1 SWT_AY 38914 // CBS thread leaves the CPU
357+
358+
359+
Configuration Options
360+
**********************
361+
362+
Related configuration options:
363+
364+
* :kconfig:option:`CONFIG_CBS`
365+
* :kconfig:option:`CONFIG_CBS_LOG`
366+
* :kconfig:option:`CONFIG_CBS_THREAD_STACK_SIZE`
367+
* :kconfig:option:`CONFIG_CBS_QUEUE_LENGTH`
368+
* :kconfig:option:`CONFIG_CBS_INITIAL_DELAY`
369+
* :kconfig:option:`CONFIG_CBS_THREAD_MAX_NAME_LEN`
370+
371+
API Reference
372+
**************
373+
374+
.. doxygengroup:: cbs_apis

doc/kernel/services/sched_server/example-1.svg

Lines changed: 3 additions & 0 deletions
Loading

doc/kernel/services/sched_server/example-2.svg

Lines changed: 3 additions & 0 deletions
Loading

doc/kernel/services/sched_server/example-3.svg

Lines changed: 3 additions & 0 deletions
Loading

doc/zephyr.doxyfile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2493,7 +2493,7 @@ PREDEFINED = __DOXYGEN__ \
24932493
CONFIG_NET_UDP \
24942494
CONFIG_SCHED_CPU_MASK \
24952495
CONFIG_SCHED_DEADLINE \
2496-
CONFIG_SCHED_DEADLINE \
2496+
CONFIG_CBS \
24972497
CONFIG_SETTINGS_RUNTIME \
24982498
CONFIG_SMP \
24992499
CONFIG_SYS_CLOCK_EXISTS \

0 commit comments

Comments
 (0)