EC-9.6 Concurrency, Real Time and the RTOS

en

What this is and why it exists

Real time does not mean fast. It means on time.

That distinction is the whole subject. A slow machine with a guaranteed response can be correct where a fast one with an occasional pause is not.

This topic is also where the standard advice is most often wrong. A small operating system solves some problems and creates others. Knowing which is which saves a great deal of effort on systems that never needed one.

The vocabulary

  • Super-loop — a single endless loop calling each piece of work in turn.
  • Deadline — the time by which a response must be complete.
  • Hard real time — a requirement where a missed deadline is a system failure.
  • Soft real time — a requirement where a missed deadline degrades the result.
  • Task — an independently scheduled piece of work with its own stack.
  • Context switch — saving one task's state and restoring another's.
  • Mutex — a lock that gives one task exclusive access to shared data.
  • Semaphore — a counter used to signal between tasks or from a handler.
  • Priority inversion — urgent work blocked behind less urgent work holding a lock.
  • Ring buffer — a fixed area of memory written at one end and read at the other.

The mental model

The super-loop is the starting point and it is genuinely good engineering for many systems. One loop calls each piece of work in turn, forever. Nothing is hidden, the timing is straightforward to measure, and the whole program can be read top to bottom.

It breaks when one piece of work is slow and another cannot wait for it. Note that this is a scheduling problem and not a speed problem. Making the processor twice as fast halves the delay and does not change the structure that caused it.

The first response is to move the slow work out of the loop, or to split it into pieces that each run briefly. That works surprisingly often and should be tried before anything larger.

Shared data is the next problem and it arrives as soon as interrupts do. A variable written by a handler and read by the main loop can be read half-updated, if it takes more than one instruction to write. On an eight-bit part, a four-byte counter is four writes, and the main loop can read it after two of them.

Three fixes cover nearly everything. Use a single-byte flag, which is written atomically. Or turn interrupts off for the few instructions of the access, which is correct but adds latency to everything. Or use a ring buffer, where the handler writes at one end and the main loop reads from the other. The two never touch the same location.

A ring buffer with one writer and one reader needs no locking at all. That is why it is the standard structure between a handler and the rest of the program.

Now real time properly. A real-time requirement names a deadline and says what happens if it is missed. Hard means the system has failed: an airbag, a motor commutation, a safety interlock. Soft means the result is worse but acceptable: a display that stutters, an audio sample dropped.

Most real systems contain both kinds, and the useful work is identifying which requirements are which. A system where everything is treated as hard is over-engineered, and one where everything is treated as soft is unsafe.

An operating system enters when several independent pieces of work each need their own timing. It gives each one its own stack and swaps between them by saving and restoring registers. That swap is the context switch, and it costs both time and memory.

What you buy is the ability to write each piece of work as if it were the only thing running. That includes waiting for something without blocking everything else. What you pay is memory for each stack, timing that is harder to predict, and a new family of bugs.

Locks are the sharp edge. A mutex protects shared data by making other tasks wait, which is correct and introduces priority inversion. If low-priority work holds a lock that urgent work needs, the urgent work is stuck behind it. If medium-priority work is also running, the low-priority holder may never finish at all.

The standard remedy is priority inheritance: while a low-priority task holds a lock an urgent task wants, it temporarily runs at the urgent priority. Any operating system worth using offers it, and knowing to look for it is the point.

Finally, when not to use one. Consider a part with two kilobytes of working memory and one job to do. An operating system costs a large fraction of that memory, makes the timing harder to prove, and buys nothing. A loop and an interrupt are the better engineering, and choosing them deliberately is not a failure of ambition.

What you should now be able to explain or do

  • Identify the point at which a super-loop stops being adequate, and say why speed does not fix it.
  • Share data between an interrupt handler and the main flow safely, and explain why a ring buffer needs no lock.
  • State a real-time requirement as a deadline plus a consequence, and classify it as hard or soft.
  • Describe what a context switch costs and what it buys.
  • Explain priority inversion and how priority inheritance addresses it.
  • Argue for or against an operating system on a given part with given requirements.

Check yourself

Because the problem is ordering, not speed. One slow piece of work still runs to completion before the next one starts, so the waiting work is still waiting.

The writer only advances the write position and the reader only advances the read position. Neither modifies what the other owns, so there is no shared location to protect.

Urgent work waiting on a lock held by less urgent work. The urgent task's priority no longer determines when it runs, which defeats the purpose of assigning priorities at all.

When the part is small, the timing must be provable, and there is one job to do. The memory cost and the added unpredictability buy nothing that a loop and an interrupt do not already provide.

Go deeper

Back to Concurrency, Real Time and the RTOS: work through the checklist