Home > Multitasking, Projects, RTOS, SKC++ > SKC++: Pause for Thought

SKC++: Pause for Thought

Tuesday, October 27, 2009 Leave a comment Go to comments

I’ve been looking over my SKC++ postings so far and have come to the conclusion that it’s time for a review. Also, before launching into the details of the kernel’s message-passing techniques, I need to show why message-passing is needed in the first place. I’ll start by presenting some thoughts on the tasking model.

Encapsulation, Context and Mutual Exclusion

Here’s a new diagram. It’s become more complex, but I’ll deal with it piece by piece and I hope you’ll eventually agree that my new dual approach to task access has merit. If not – or even if so – please let me know in your comments.

TaskAccess

Before I wade into deeper water, let me explain the new colour code. Only things in beige are part of SKC++; the other classes shown are simply examples of how to use what SKC++ provides. This article is concerned mainly with the red and the green classes, so it is more of an Application Note than an explanation of SKC++ internals.

Task1

I said in an earlier article that I liked this encapsulation model, and I still do. It’s compact and cohesive and the user’s interface is restricted to calling public functions specified by the task designer and coded into a single task class. These functions are represented in Task1 by a single example, someUserFunction. The user has no direct access to the post function and the implementation details (how someUserFunction works and which event(s) it posts) are completely under the control of the task designer, who also controls what the activity function does with the event(s) it receives.

It’s worth emphasising that the only way (so far revealed) of communicating a user’s request from someUserFunction to activity is for the former to post one or more events to the latter. This is because the two functions run in separate contexts (or use separate stacks, to put it differently). Task1::activity runs, naturally enough, in the context of its Task1 instance, while Task1::someUserFunction runs in the context of whichever task happens to call it. In other words, they run concurrently, not sequentially, and there is no direct connection between them. Only if Task1::activity called someUserFunction itself (which is possible but unusual) would they run in the same context.

It turns out, as we shall see shortly, that allowing functions which run in different contexts to inhabit the same class is potentially dangerous. This is why I coloured Task1 red and came up with an alternative scheme, coloured green. But before describing that, let’s look at the danger zone more carefully…

Shared Data and the Red Model

A choice of 16 events may be enough to specify an activity function’s response to a user’s request in simple cases, but it is unacceptably limited in general. With what we have so far, the only way we can pass a greater amount of data is for someUserFunction to set it up in an agreed area of memory, then signal its readiness to activity in the usual way by posting an event. The activity function then picks up the event, processes the data and all is well. Except that it isn’t! From anyone who knows anything at all about multitasking, the words “mutual exclusion” emanate as a conditioned reflex to any mention of shared data. And there’s more. Here’s a list of problems with the Task1 approach when shared data might be involved:

  1. Mutual exclusion is needed to stop data getting messed up because of pre-emption of one task by another.
  2. There is no simple way for Task1::activity to indicate when it has finished with the shared data.
  3. Solutions to 1 and 2 are untidy and requiring tailored coding by the application programmer on every separate occasion increases the probability of error.
  4. The OO paradigm (code and data neatly packaged together in a class) can lull some programmers into complacency about mutual exclusion, with disastrous results.

SKC++ has a Mutex class (to be described in a later article) and this could be used to solve problem 1. But there is a better way, and that way is message-passing. Again, I will defer a full explanation of how SKC++ message-passing works; for now, it is enough to state that it deals neatly with 1, 2 and 3. Most inter-task communication can and should be dealt with by message-passing, but it remains sub-optimal for some purposes, notably some kinds of interrupt processing. Interrupt processing is, once more, a topic for a later article. For now, though, I put forward the view that message-passing solves three of our four problems most of the time but we must not close the door altogether on shared data, with suitable mutual exclusion.

What about problem 4? First of all, I’ll explain further what I mean. Traditionally, there are System Programmers and there are Application Programmers. This distinction has stood the test of time and I am happy to stick with it.

A System Programmer’s job comprises getting the RTOS (or whatever it may be called) to run on the hardware, writing device drivers and providing whatever services the Application Programmers have been deemed to need in order to simplify their job. To do all this requires care, time, experience and knowledge of both hardware and software matters. Paramount is an understanding of mutual exclusion issues, particularly those relating to interrupt processing. Such a person is more likely than most to code tasks needing shared data but is also more likely than most to handle the sharing properly. On the other hand, many good System Programmers have a hardware background and are less likely than career Application Programmers to be C++ experts. For System Programmers, therefore, the simple, flexible but slightly dangerous red model, as exemplified by Task1, is probably best.

An Application Programmer’s job is to turn a disparate collection of hardware and low-level software components into a product; everything else is a distraction. Application Programmers need to be skilled at making best use of the chosen language (C++ in this case) but are often much less knowledgeable about the dangers of shared data in a pre-emptive system. Of course, if a safe message-passing facility is provided, most programmers will use it, so where’s the problem? In my experience, there is one widespread problem, in particular, which comes down to a bad habit encouraged by the OO paradigm. It is that people overuse object attributes, sharing data items between object methods instead of holding them as local variables and passing them as parameters. Following this habit, it is easy to share data between contexts by accident, even for seasoned real-time programmers who think they are programming safely!

I need to justify that last statement. Consider the three distinct ways to use the attribute someData in Task1:

  1. Used only by activity.
  2. Shared between activity and someUserFunction.
  3. Used only by someUserFunction.

In all cases, we will assume that our programmer is knowledgeable enough never to export someData out of context by using a pointer or a reference. This is easier to assume than to enforce, and there is little we can do about such errors of principle other than look for them in code inspections.

Case 1 is obviously safe and compliance can be confirmed by inspection.

Case 2 is obviously unsafe unless the appropriate mutual exclusion mechanisms are implemented. Again, this can be checked by inspection.

Case 3 is subtle enough to cause an undetected bug. If we were sharing someData, without mutual exclusion, among two or more public functions, an inspection would probably reveal the problem because each function may run in the context of any task. But here, someData “belongs” to just one function. The problem is the same (someUserFunction can be called concurrently by the code of any number of tasks) but it is much easier to miss.

Case 3 is insidious but all cases require careful code inspection to check that attributes have been used safely. The checks can be made easier by adopting one of two stringent rules:

  • No attributes in active objects, or
  • No public methods in active objects.

I believe the former to be too drastic. My solution, therefore, is to adopt the latter (the green model) for application programming while retaining the simpler but trickier red model for system programming.

The Green Model

An example of the green model is shown on the diagram. Task2 contains no public functions so someData can be shared only between activity and any private functions (none shown) which it may call. Everything encapsulated in a Task2 object now belongs to only one context.

The user-accessible public functions (represented here by the single function someFunction) now must live in a different class, which I have called Task2Access. To allow Task2Access objects to post events (and, in due course, send messages) to the Task2 object (which I have here specified to be a singleton, as most tasks are), SKC++ has sprouted a new interface, ITask. The only things an “outsider” is allowed to do to a task, once it has been created, is to start it and to post events (later, messages, too) to it, so only these operations (plus the ubiquitous virtual destructor) are exposed in the interface.

Each Task2Access object must hold a pointer (of type ITask*) to the relevant task object. How that pointer is obtained is a matter of choice. In this example, the first instantiation of Task2Access actually creates the single instance of Task2, then starts it, but there is no requirement to do things this way.

Simply putting the public functions in a separate class does not remove the “case 3” problem. If two or more client tasks share a single instance of Task2Access, they also implicitly share its someData attribute – note, though, that this is an entirely different variable from the one of the same name in Task2, so “case 2” does not apply. The solution, of course, is not to share instances of Task2Access. Whereas Task2 has a single instance, Task2Access instances, if they contain attributes, can and must be created as local variables whenever required. Each client task’s code will then be using its own local instance of Task2Access, necessarily in its own context, whenever it calls any Task2Access function.

This example of the green model is just that: a particular example. In another situation, a task access class might be written as a a container for several task pointers, offering users some common set of functions each of which could post to any or all (or none) of the tasks in order to get its job done. There is much flexibility with this model, but there are also rules for success, which can be summarised as follows:

  • No public functions in task classes; put them in task-access classes, instead.
  • Task-access objects to be created locally (on the stack) for local use and not passed around.

Summary

  • The green model prevents unintentional data sharing between contexts and encourages kernel-supported message-passing. It is therefore to be preferred for all kinds of programming where these characteristics do not cause more problems than they solve.
  • It is recognised that, for performance and other reasons, the green model may be inappropriate for some kinds of system programming and for such situations the red model is retained.

Coming Soon

Message-passing.

Advertisements
Categories: Multitasking, Projects, RTOS, SKC++ Tags:
  1. Monday, November 2, 2009 at 19:06

    Peter, I confess I don’t fully understand all the above because my background experience hasn’t given me the language required. I do get the general drift.

    One insight I find very interesting and valuable, though, is your observation about different kinds of programmers, System and Application. I myself pontificate in the shower these days about preemptive vs cooperative O/Ss and the target audience. I believe for an iPhone preemptive is a necessary evil, because anyone can squirt a new app into it at any time. For small, static (i.e. the program is frozen at Flash time) highly deterministic embedded controls, I vote cooperative (this is my domain).

    So, I think maybe you should add one more bullet point to your September 9 post “Simplifying RTOS”

    * Failing to properly define the target audience.

    IMHO anything that tries to be all things to all people winds up a mess.

    David Stonier-Gibson http://splatco.com

  2. Peter Bushell
    Tuesday, November 3, 2009 at 08:07

    @David Stonier-Gibson
    While C++ has advantages in medium to large embedded projects, things can get complicated and explanations even more so! I confess that this article is quite hard to follow. However, my aim is to encapsulate as much of the necessary complexity as I can inside SKC++ in order to make things simpler for the user. Once I’ve broken the back of SKC++ itself, I’ll come up with some practical examples to demonstrate this simplicity of use.

    As you are doubtless aware, I have agreed with you elsewhere (on a LinkedIn discussion) that co-operative multitasking is often to be preferred in systems where pre-emption is definitely not needed. A co-operative system, although its partitioning suggests concurrency, is actually sequential in operation. Therefore it is much less finicky about programming practices, making it much less error-prone. With my teaching hat on, though, I always encourage the type of programming which is safe in a pre-emptive environment. That way, people’s skills are more transferable.

    Perhaps I can deal with your final point by saying that it will be possible to turn pre-emption off in SKC++!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: