|
| 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 |
0 commit comments