-
Notifications
You must be signed in to change notification settings - Fork 4
Event Driven Execution
This document provides a detailed overview of the proposed TockOS application publish/subscribe model and its implementation. It assumes that the hardware has support for memory protection, e.g., an MPU.
In an embedded environment, much of an application's time is spent responding to events generated by hardware or software. As such, an event-driven, publish subscribe application execution model seems fitting.
In this model, applications, drivers, and the kernel can publish events to later be consumed by applications that have subscribed to events of that type. The application sits in an event-loop, waiting for a notification that an event it has subscribed to is available to be consumed, and executing an event-handler, some code, when it is notified. Applications can request additional execution time when needed and yield unused time.
Not all applications should be able to receive notifications for all events. Some publications might be private, intended only for applications that have authenticated in some way. For example, GPS location data might be restricted to applications developed by a particular company. Here, the application might authenticate to the publisher via a signed certificate; the publisher need simply to verify the certificate using a public key.
Applications should not be able to access event data they are not subscribed to. Further, applications should never be able to modify event data. The former is necessary to enforce privacy guarantees, and the latter provides full isolation between event-handlers.
Asynchronous execution of event-handlers is implicit in the event-driven model. In the common case, the kernel will time-slice application execution, and applications will not immediately be notified of an event's publication; instead, they poll to discover waiting events. If the application wishes, it can request to be notified immediately that some event has occured.
Because applications are sharing time, it is important that applications spend only as much time as required handling some event. Purely round-robin, homogeneous time-sliced scheduling can result in wasted time. As such, applications should be able to yield their scheduled time. Further, some applications or drivers may require a minimum amount of time to execute some operation. Therefore, applications and/or drivers should be able to request time beyond the routinely allocated slice.
In order for an application or driver to publish events, it must first register the event's type with the kernel. Upon registering, the kernel allocates fixed sections of memory pertaining to that event type and returns a pair of pointers, one to a header and the other to a log, to the publisher. Only the registered event publisher is allowed write access to these sections of memory.
The header contains metadata about the events in the log. It contains the size of the log, the number of outstanding events, and the event numbers of the oldest and newest events that have not been handled.
The log is a circular buffer of event structures. To simplify implementation, all event structures for a given event type must be sized equally.
To publish an event, the publisher writes a new event to the event log, increments the outstanding event count in the header, and updates the number of the newest unhandled event. If the number of outstanding events is less than the size of the log, the log is full; some scheme should be determined for how to handle this case.
An application calls the kernel to subcribe to a particular event type. Upon subscribing, the kernel returns a pointer to the location of the event log for that event type; the kernel ensures that the event log is read-only to the application. The kernel also creates a copy of the event type's header and returns a pointer to the copy. The header is writeable by the application.
During typical execution, the kernel schedules applications via time multiplexing. During an application's time slot, the application handles events by reading each of the headers for the event types it has subscribed to, handling new events as it sees fit. When it handles an event, the application decrements the outstanding event count and increments the oldest outstanding event number. The kernel uses the application's copy of the header to determine which applications have handled which events.
An application calls the kernel with a list of event types it would like to be notified about and a pointer to a function that should execute when one of those events is published. The callback function takes as input the path of the event that was published. The application now proceeds as before, reading the event log and handling events as it sees fit.
When an application has asked the kernel to be notified about publication, the kernel traps on writes to the publisher's version of the event type header. The kernel can then dispatch events when the header is updated by the publisher.
Applications should be able to discover events based on some human-readable path, such as 'gps::location', which a publishing driver/application registers with the kernel.
Here are some benefits of this design:
- The kernel is not present in the typical publish/event-read cycle.
- Event handling and handler dispatch is done completely user-side.
- This means batching and prioritization can occur user-side.
- Event handling is zero-copy; only one event structure is allocated per event.
- Libraries can layer different event-handling APIs atop this design.