There is increasing interest in active objects (AOs) to deal with the problems of handling events in complex real-time systems [Ref 1 & 2]. Events are generally signaled by interrupts. In order to minimize interrupt service routine (ISR) complexity most RTOSs allow tasks to do deferred interrupt processing. As rightly pointed out by Willett, there are many problems with this, most concerning of which are:
- Slow task switching times.
- Preemption by higher-priority tasks.
- Priority inversion.
If the task is generating a control signal, the net result of these deficiencies can be significant jitter of the signal, which is not good for smooth operation nor for equipment lifetime. The third problem is of particular concern. Even if mutexes have priority promotion, other objects that tasks may wait on, such as message queues, event groups, and semaphores, do not. A semaphore, in particular, cannot support priority promotion because its owner is unknown.
Consequently, a developer may put a large amount event-handling code into an ISR in order to get less jitter or faster response. This can result in large, complicated ISRs such as the STMicro HAL_SD_IRQHandler(), which makes many function calls and is virtually impossible to analyze. ISRs should NOT be written that way.
An active object has two main characteristics:
- It waits for an event.
- It runs to completion.
SecureSMX has two objects, which I think could serve as active objects or at least as hosts for them:
- Link Service Routines (LSRs)
- One-shot tasks.
These are discussed next.
Link Service Routines

The above figure shows how LSRs work. They are invoked by ISRs, which put them into the LSR Queue. LSRs run after all ISRs have run and before any tasks can run. They have no priorities, so they run in the order invoked. This may seem at variance with AO property 1, listed above, because LSRs do not wait directly on events. However, ISRs normally occur singly and LSRs normally run right after the ISRs that invoke them. LSR dispatch overhead is very small because they run in handler mode (hmode) and share the main stack with ISRs. Thus, no context switch is required.
Normally, an LSR is invoked as the last action of an ISR before the ISR re-enables the interrupt that caused it and stops running. Then if the interrupt should occur immediately again, the ISR is ready to handle it. LSRs normally run with interrupts enabled to facilitate this. In addition, LSRs can be invoked multiple times before running, so events (i.e. interrupts) will not be lost. In addition, LSRs can make all of the same system calls as tasks, except they cannot wait. They must run to completion and they do not preempt each other so no blocking is necessary.
Even when interrupts occur simultaneously, LSRs will still tend to run in the priority order of the ISRs. This is because invoking them is usually the last action of the ISRs. Hence higher-priority ISRs tend to preempt lower-priority ISRs and invoke their LSRs first. Of course, this is not a perfect priority dispatch engine. However, a benefit of LSRs is that they maintain temporal integrity, which can be important for system stability. For example, if the processing of an event depends upon whether another event has occurred or not, then temporal integrity is important.
However, some events must take priority over others. For example, if a crane is about to tip over, the drop-the-load-event should take priority over all other events because it will cause less damage than both a crane collapse and a dropped load. This kind of event should trigger the non-maskable interrupt as well as being wholly handled by its ISR. Very important events, like this, can be handled wholly or partially by ISRs, then post-emergency cleanup can be done in LSRs or tasks. (As shown in the above diagram, an LSR might simply start a task by signaling the event semaphore where the task waits.)
Since an LSR can be invoked multiple times before running, events will not be lost even under extreme loads. Each event corresponds to an LSR, possibly with a unique parameter, in the LSR queue. This helps to handle extreme conditions such as fly-by-wire, while firing both guns, ejecting flack, executing evasive maneuvers, and calling for help. The joystick may become a bit sticky, but the pilot will retain control and the plane won’t crash!
One-Shot Tasks

The above diagram compares the operations of a normal task and a one-shot task. A normal task is created, gets a permanent stack, and is started. It initializes itself, then goes into an infinite loop waiting for an event, processing it, and waiting again. When in its main loop a normal task may block on a mutex or other object.
A one-shot task is created, initialized, then waits for an event. When the event occurs, the one-shot task is started, whereupon it gets a stack from the stack pool, runs to completion, stops and releases the stack back to the stack pool, and waits for the next event.
A one-shot task can wait on the same events as a normal task, but it does so without a stack. For processing events, a major disadvantage of normal tasks is that each one requires a permanent stack. Task stacks can be 500 to 1000 bytes, or more. So if there is a large number of events to process, considerable SRAM is needed for the task stacks.
One-shot tasks, require only one stack per priority level. This is because they cannot preempt each other, they run to completion, and they give up the stack when done. For most systems only a few priority levels are needed, so memory usage is much less than for normal tasks. Some event systems use a one-stack solution for any number of priority levels. This was pioneered by OSEK. In this case, the stack must accommodate the sum of the maximum stack depths for each priority level. Consequently it offers only a small SRAM improvement over the one-shot solution and it requires that tasks must be unstacked in the reverse order of being stacked (LIFO). One-shot tasks can be restarted in any order.
Normal and One-shot tasks are not mutually exclusive. A system can have both. Hence the portion of a system that handles real-time events can be implemented with one-shot tasks and the portion of the system that performs background functions can be implemented with lower-priority normal tasks. The advantage of this is that the normal tasks can block to wait for resources and to coordinate with each other. This is more consistent with the way open source components, network stacks, file systems, and other high-level software is written.
Security

In the high-security systems that SecureSMX is designed to support, one-shot tasks would likely be privileged mode (pmode) tasks and normal tasks would likely be unprivileged mode (umode) tasks. As shown in the diagram, the event-driven ISRs, LSRs and one-shot tasks operate below the impenetrable pmode barrier, which gives them extra protection. The vulnerable umode tasks run in isolated partitions above the pmode barrier and thus are doubly blocked from damaging mission-critical, security, and system code and data.
The LSR described previously is called a trusted LSR (tLSR) because it runs in hmode, where great damage can be done if it is breached. SecureSMX adds a safe LSR (sLSR), which runs in umode in its associated partition. Hence a breach of it is contained by the partition. An sLSR has its own stack and its own memory protection array (MPA), which usually contains the same regions as tasks in the same partition. This allows sharing data and subroutines with the tasks. Overhead for an sLSR is more than for a tLSR, but still less than a task, and it does not have the task problems discussed previously.
Conclusion
SecureSMX offers many options to support event processing, background processing, and high security for both. It is available under a dual GPL/commercial license. Full source code may be downloaded from www.github.com/Micro-Digital/SecureSMX.
References
- “Why Traditional RTOS Architectures Struggle with Modern Embedded Systems”, LinkedIn, Mike Willett, July 2026.
- “A Clash of Worldviews: RTOS Model vs. Actor Model”, LinkedIn, Miro Samek, June 2026.