This caused various problems as there was no ability to select direction of travel. My room wasn’t on the top floor, so when the doors opened I had no idea whether I was going to take a trip to a higher floor even though I wanted to go down to breakfast. To make matters worse the indication panel above the door didn’t reflect the subsequent direction of travel only the current direction. So for example I was on the 4th floor and the lift, coming from the ground level, would arrive and be announced with an arrow pointing upwards. You would get in not knowing whether or not the lift was actually terminating at your floor or stopping just to continue upwards. Close but no cigar…
March 8th, 2010After not too much of a learning curve we’ve migrated the website to support wordpress and the associated tools (SQL, PHP, etc.). After checking everything was fine and giving the go ahead for the “go live” for some reason it’s lost the links to all the diagrams from the posts! So I’ll be retrofitting the diagrams over the next week.
First impressions, WordPress is a wonderful leap forward from blogger.
Next post: reflections from embedded world 2010.
Blogger.com to discontinue support for FTP
March 4th, 2010Unfortunately Google (via blogger.com) have announced that as of the 26th March 2010 they will discontinue support for FTP uploads from blogger accounts. Current the Feabhas blog is created and managed from blogger but uploaded to our Feabhas server. So having really just got my head around blogging it’s got to be all change. We have a number of posts to publish (including my trip report to embedded world 2010), but I plan to hold off until we have resolved the best route forward (ideally wordpress) . I’m hoping it has no impact on the atom feed, but only time will tell. Watch this space…
Artisan – Aonix merger
February 1st, 2010Declarations and Definitions in C
January 18th, 2010
Program Objects (Variables)
- if the statement has an “=” it’s a definition?
- otherwise, if it has “extern” and no “=” it’s a declaration?
- otherwise it’s a tentative-definition that may become a declaration or a actual-definition
Object Definitions
int ev = 20; /* definition – reserves enough memory to hold an int */
Object Declaration
Usage
int main(void)
{
ev = 10; /* fails to compile as ev has not been declared */
return 0;
}
int ev = 20; /* definition – allocates 32-bits */
extern int ev; /* declaration – no memory reserved but defines sizeof(ev) */
int main(void)
{
ev = 10; /* okay to use ev as declared, knows to read (say) 32-bits; k = 20 */
return 0;
}
int ev = 20; /* definition – memory reserved here and initialised */
int ev = 20; /* definition and implicit-declaration: reserves memory */
int main(void)
{
ev = 10; /* okay to use ev as declared (implicitly) */
return 0;
}
extern int ev; /* 1st declaration */
extern int ev; /* 2nd declaration */
int main(void)
{
ev = 10; /* okay to use ev as declared */
return 0;
}
int ev = 20; /* definition */
int ev = 20; /* actual definition */
int td; /* tentative definition */
int main(void)
{
…
return 0;
}
If an actual definition is found later in the source file, then the tentative definition just acts as a declaration. If the end of the source file is reached and no actual definition is found, then the tentative definition acts as an actual definition (and implicit declaration) with an initialisation of 0 (zero).
int ev; /* tentative definition becomes declaration */
int td; /* tentative definition become actual definition initialised to 0 */
int main(void)
{
…
return 0;
}
int ev = 20; /* actual definition */
extern int ev = 20; /* actual-definition */
I’m sure someone can (and will) tell me why this is useful, but in my 25 years of doing C I’ve never had need to use it. I my view anyone found doing this should be made to sit in the corner wearing a hat with a big ‘D’ on it!
extern int(ev);
int(ev);
int(ev) = 20;
Functions
void f(int p) /* definition and implicit-declaration */
{
...
}
int main(void)
{
f(10); /* okay to call f as declared */
return 0;
}
void f(int p); /* declaration */
int main(void)
{
f(10); /* okay to call f as declared */
return 0;
}
void f(int p) /* definition */
{
// …
}
- the validity of the identifier
- the storage required to pass any parameters (by stack or register)
- the storage required for any return information
void f(int); /* declaration */
int main()
{
f(20); /* call f with no declaration */
return 0;
}
void f(int i) /* definition and implicit-declaration */
{
// …
}
f(20);
int f();
error: ‘f’ : redefinition; different basic types
int main(void)
{
f(20); /* call f implicit-designator of int f() */
return 0;
}
int f(int i) /* definition’s designator matches implicit-designator */
{
// …
}
If the return type is omitted, int is assumed.
int main()
{
f(20); /* call f implicit-designator of int f() */
return 0;
}
f(int i) /* definition’s designator has implicit return type of int */
{
// …
}
warning: 'f' undefined; assuming extern returning int
void f() /* definition and implicit-decln of void f(void) */
{
// ...
}
int main()
{
f(20); /* error as call doesn’t match decln */
return 0;
}
void f(); /* declaration */
void f(void); /* prototype-declaration – not the same as above */
eters is supplied. This has a horrible implication; take for example the following code:
void f(); /* declaration */
int main(void)
{
f(20); /* okay to call f as declared */
return 0;
}
void f(int i) /* definition */
{
// …
}
void f(); /* declaration */
int main(void)
{
f(20); /* okay to call f as declared!!! */
return 0;
}
void f(void) /* definition */
{
// …
}
Guideline: For all function always supply a function-prototype.
void f() /* definition and implicit-decln of void f(void) */
{
// ...
}
int main(void)
{
f(20); /* error a call doesn’t match decln */
return 0;
}
Unscrambling C Declarations
December 9th, 2009Even though the C programming language has been around since the late 1960’s, many programmers still have trouble understanding how C declarations are formed. This is not unsurprising due to the complexity that can arise when mixing pointer, array and function-pointer declarations.
Very simple ones (specifically those not involving “[]” or “()“) can be read from right-to-left, e.g.
int x
int a[10]
- A function cannot return a function – () foo()
- A function cannot return an array – [] foo ()
- An array cannot hold functions – foo[]()
int x x is an integer
Rule 1: Read from left to right looking for an identifier.
int a[10] x is an array of (ten) integers
void x(int y) x is a function that takes an integer parameter (y) and returns nothing (void)
Rule 2: look right from the identifier for postfix operators () or []. If [] then it is an array, else if () then it is a function.
int * x x is a pointer to an integer
Rule 3: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
const int x x is an integer constant
Rule 4: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
int const x x is a constant integer (This is identical to the previous declaration. This is part of the confusing syntax of the C programming language, but Rule 4 still applies).
int const * x x is a pointer to a constant integer (as above – still confused?)
int * x[10] x is an array of pointers to integers ( Rule 2, Rule 3)
int * x(void) x is a function that returns a pointer to an integer (Rule 2, Rule 3)
int **x x is a pointer to a pointer to an integer (Rule 3, Rule 3)
int * const x
Rule 4b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
int const * const x x is a constant pointer to a constant integer
int (*x)(void) x is a pointer to a function that returns an integer
Rule 2: If the identifier is within parentheses, then evaluate inside the parentheses first
void (*x)(int y) x is a pointer to a function that takes an integer (y) as a parameter and returns void
- Rule 1: Read from left to right looking for an identifier
- Rule 2: If the identifier is within parentheses, then evaluate inside the parentheses first
- Rule 3: look right for postfix operators ( ) or [ ]. If [] then it is an array, else if () then it is a function.
- Rule 4: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
- Rule 5a: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
- Rule 5b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
void (*fpa[10])(int)
Rule 1: From left to right find identifier, this gives us fpa
Rule 2: (*fpa[]) parentheses win, so evaluate inside the parentheses
Rule 3: fpa[10] postfix [] wins; fpa is a ten element array ($ now represents fpa[10])
Rule 4: *$ prefix * wins; fpa is an array of pointers. Now we’ve evaluated inside the parentheses we step outside.
Rule 3: $() postfix, () wins fpa is an array of pointers to functions
Rule 2: void $(int) parentheses; fpa is an array of pointers to functions each taking an integer parameter and returning void
First, as always is rule 1; signal is the identifier. signal is in parentheses, so based on Rule 2 we must evaluate that first. If we match parenthesis then we get:
(*signal(int sig, void(*func)(int)))
(*signal())
void (* signal() )(int)
void (*$)(int)
signal(int sig, void(*func)(int))
style='font-family: "Courier New",Courier,monospace;'>int sig – sig is an integer
void(*func)(int) - func is a pointer to a function that has an integer parameter and returns void.
- signal is a function
- that returns a pointer to a function that has an integer parameter and returns void
- and takes two parameters of
- an integer, and
- a pointer to a function that has an integer parameter and returns void
Avoid by design, as far as possible. If this fails, divide and conquer remembering that typedef is your friend. A typedef declaration does not introduce a new type, only a synonym for the type specified. For example:
MILES m; /* m is of type int */
int_ptr ip; /* ip is of type integer pointer int* */
typedef void (*FuncPtr)(int);
void (*signal(int sig, void(*func)(int)))(int)
FuncPtr signal(int sig, FuncPtr)
void (*fpa[10])(int)
FuncPtr fpa[10]
Rule 1: Read from left to right looking for an identifier
Rule 2: If the identifier is with parentheses, then evaluate inside the parentheses first
Rule 3: look right for postfix operators ( ) or [ ]. If [] then it is an array, else if () then it is a function.
Rule 4: look left for prefix pointer asterisk ‘*’. If found the identifier is a pointer.
Rule 5a: If a const and/or volatile is next to a type specifier (int, long, etc.) it applies to that specifier
Rule 5b: if a const and/or volatile is not next to a type then it applies to the pointer asterisk on its immediate left
Also check out http://www.cdecl.org/ (thanks @FrankSansC)
Task Synchronisation – Part 2: Multiple Tasks and RTOS APIs
November 16th, 2009So far we have only dealt with the simple case of two task synchronisation. We now address the case where multiple tasks are waiting at the synchronisation point:
In the design of real-time systems it is common for a task to synchronise on a number of different conditions. This many involve a conjunction (AND) or disjunction (OR) of events. For example, a motor fault condition may be specified as:
- low oil pressure AND > 30sec after start-up
- Isobutane present in outlet line OR
- Isobutane present in front flush OR
- Isobutane present in rear flush
Given the array of synchronisation requirements and options how do modern RTOS support synchronisation? The majority of RTOSs support the following:
- Event Flags / Event Flag Groups
- Semaphores as Signals
EVENT FLAGS
Typically an Event Flag is implemented as a Manual-reset, Persistent, Unilateral synchronisation object. It is by far the simplest idea and mechanism, and is best suited to one-to-many (1:M) or many-to-one (1:M) synchronisation. An API may consist of the following calls:
- Set – sets the flag, readying any waiting tasks; can be called from an ISR
- Clear – clears the flag, if the flag is cleared then any arriving task is blocked
- Wait – non-consuming pend on a flag
- Set(group, bitmask)
- Clear(group, bitmask)
- Wait(group, bitmask, AND_OR, timeout)
RTOSs differ in the implementation of Event Flag Groups, with some only supporting M:1 synchronisation and not supporting 1:M or M:M synchronisation. In this case each event flag group is bound to a specific task (i.e. EFGs cannot stand as independent resources), altering the API to:
- Set(task_id, bitmask)
- Clear(task_id, bitmask)
- Wait( bitmask, AND_OR, timeout, &events;)
A surprising number of RTOSs do not support the concept of Event Flags, thus no support for any form of disjunctive or conjunctive synchronisation. The usual argument for not supporting them is that it can be difficult (if not impossible) to make timing deterministic (especially disjunction). Timing typically takes an O(N) form where N is the number of waiting tasks.
Some examples of event flag support from commercial RTOSs are:
-
>VxWorks
- 32 events in an event field
- Each task has its own events field that can be filled by having tasks and/or ISRs sending events to the task.
- ThreadX
- 32 event flags in a group
- Each event flag group is a public resource
- Nucleus PLUS
- 32 event flags in a group
- Each event flag group is a public resource
- uC/OS-III
- 8, 16 or 32 event flags in a group (compile time configured)
- Each event flag group is a public resource
The generic concept of a signal is for synchronisation between two tasks, with a simple API of:
- signal
- wait – timeout option
Most RTOSs, then, do not support the concept of a signal directly, instead directing the programmer to use the semaphore as a synchronisation object (the Semaphore as a Signal pattern). When using a semaphore as a signal, the SO takes the form of an Auto-reset Persistent Unilateral synchronisation object.
The Semaphore as a Signal pattern is regularly used to synchronise tasks with ISRs triggered by external events. This mechanism is favoured since the ISR will never block when posting to the semaphore (thus avoiding the potential to ‘stall’ the system).
If we have sporadic interrupts, then the ISR may signal the semaphore multiple times before the task waits on it. Does the application need to know only that the event has occurred, or does it need to know the actual number of times an event has occurred? Either is valid, depending on the system requirements. Note that most RTOSs use the counting semaphore as a signal and thus will count the number of events.
As an example, VxWorks does not support signals, but does support both the binary semaphore and the counting semaphore. Either can be used for synchronisation if created EMPTY (0). The following calls can be used by tasks to use the semaphore as a synchronisation object.
- semGive()
- Binary – giving a “full” semaphore has no effect
- Counting – increments count by one
- semTake()
- will block if count is zero
- semFlush()
- all waiting tasks are unblocked; semaphore left at zero
As already stated, one limitation of bilateral synchronisation (aka the Rendezvous) is that it cannot be used for ISR to task synchronisation . Because of this, bilateral synchronisation is rarely supported by commercial RTOSs. Notable, though, is the ITRON Project, which creates standards for real-time operating systems used in embedded systems (µITRON4.0 Specification). Like the Ada programming language it supports the concept of the Rendezvous for bilateral synchronisation (it actually uses the term Rendezvous Ports).
So far we have considered general synchronisation between two or more tasks. There is one further synchronisation use case we need to examine. Take, for example, the C code for a simple stack shown below. These routines use the variable count to keep track of the stack size. If the stack is empty then pop returns STACK_EMPTY and the caller must check for and take appropriate error handling actions.
Suppose that we do not want to return STACK_EMPTY, but want to wait (synchronise) for the stack to contain data. Since the waiting task owns the mutex no other task will be able to enter the critical region and push an element onto the stack. Thus the waiting task could never be signalled the stack is no longer empty – a deadlock.
SUMMARY
When designing software for embedded systems that is relying on a particular Real-Time Operating System, it is essential to understand that the behaviour of synchronisation primitives differ from RTOS to RTOS. Many questions need answering such as:
- Does the RTOS support only unilateral synchronisation, or does it include primitives for bilateral synchronisation?
- If multiple tasks are waiting then how many are readied?
- If only one, then how is it selected (priority / FIFO)?
- Is the signal persistent or non-persistent?
- Is the synchronisation object manual-reset or auto-reset?
- Does the RTOS support multiple event Conjunction and/or Disjunction?
Task Synchronisation
October 15th, 2009- Asynchronous device driver where we are dealing with slow devices. We don’t necessarily want tasks blocked waiting on the device.
- At system start-up, many RTOSs start tasks as active (ready to run). We may have an ordering dependency for execution (e.g. initialisation of global resources) where all tasks must wait for a given condition (the concept of a barrier which can be very important in multi-processor systems).
- Having a managed task abort notification, rather than deleting tasks (which can lead to resource issues). Similar in concept to the UNIX/Linux kill signal. Also used to manage task pools.
- the relation that exists when things occur at the same time; “the drug produces an increased synchrony of the brain waves” [syn: synchronism] [ant: asynchronism]
- an adjustment that causes something to occur or recur in unison [syn: synchronization]
- coordinating by causing to indicate the same time; “the synchronization of their watches was an important preliminary” [syn: synchronization]
In regard to task synchronisation there four classes of interaction we need to address:
- one-to-one – only two task synchronising
- one-to-many
- many-to-one
- many-to-many
Initially we address the condition where only two tasks are synchronising.
- Task2 runs until it reaches the synchronisation point as defined by an RTOS synchronisation object (SO), at which point it waits for Task1
- Task1 now runs and reaches the synchronisation point, signals Task2 via the SO. Both tasks are now ready to run.
- The higher priority task now continues execution and the lower priority task is made ready (If the tasks are of equal priority typically Task1 will continue as this avoids a context switch).
- Symmetric or Bilateral Synchronisation
- Asymmetric or Unilateral Synchronisation
- if Task2 arrives at the SO first it will wait for Task1, and then synchronisation with Task1 when it arrives
- if Task1 arrives at the SO first it will not wait for Task2, thus unilateral synchronisation.
Mutex vs. Semaphores – Part 3 (final part): Mutual Exclusion Problems
October 5th, 2009As hopefully you can see from the previous posting, the mutex is a significantly safer mechanism to use for implementing mutual exclusion around shared resources. Nevertheless, there are still a couple of problems that use of the mutex (in preference to the semaphore) will not solve. These are:
- Circular deadlock
- Non-cooperation
Circular Deadlock
Circular deadlock, often referred to as the “deadly embrace” problem is a condition where two or more tasks develop a circular dependency of mutual exclusion. Simply put, one task is blocked waiting on a mutex owned by another task. That other task is also block waiting on a mutex held by the first task.
So how can this happen? Take as an example a small control system. The system is made up of three tasks, a low priority Control task, a medium priority System Identification (SI) task and a high priority Alarm task. There is an analogue input shared by the Control and the SI tasks, which is protected by a mutex. There is also an analogue output protected by a different mutex.
The Control task waits for mutexes ADC and DAC:
mutex_lock (DAC);
/* critical section */
mutex_unlock (ADC);
mutex_unlock (DAC);
The SI Task waits for mutexes DAC and ADC:
mutex_lock (DAC);
mutex_lock (ADC);
/* critical section */
mutex_unlock (DAC);
mutex_unlock (ADC);
Unfortunately, under certain timing conditions, this can lead to deadlock. In this example the Control task has locked the ADC, but before locking the DAC has been pre-empted by the higher priory SI task. The SI task then locks the DAC and tries to lock the ADC. The SI task is now blocked as the ADC is already owned by the Control task. The Control task now runs and tries to lock the DAC. It is blocked as the DAC is held by the SI task. Neither task can continue until the mutex is unlocked and neither mutex can be unlocked until either task runs – classic deadlock.
For circular deadlock to occur the following conditions must all be true:
- A thread has exclusive use of resources (Mutual exclusion)
- A thread can hold on to a resource(s) whilst waiting for another resource (Hold and wait)
- A circular dependency of thread and resources is set up (Circular waiting)
- A thread never releases a resource until it is completely finished with it (No resource preemption)
These conditions can be addressed in a number of ways. For example, a design policy may stipulate that if a task needs to lock more than one mutex it must either lock all or none.
With the Priority Ceiling Protocol (PCP) method each mutex has a defined priority ceiling, set to that of the highest priority task which uses the mutex. Any task using a mutex executes at its own priority – until a second task attempts to acquire the mutex. At this point it has its priority raised to the ceiling value, preventing suspension and thus eliminating the “hold and wait” condition.
In the deadlock example shown before, the significant point is when the SI task tries to lock the DAC. Before that succeeded and lead to cyclic deadlock. However with a PCP mutex, both the ADC and DAC mutex will have a ceiling priority equal to the SI’s task priority. When the SI task tries to lock the DAC, then the run-time system will detect that the SI’s task priority is not higher than the priority of the locked mutex ADC. The run-time system suspends the SI task without locking the DAC mutex. The control task now inherits the priority of the SI task and resumes execution.
The last, but most important aspect of mutual exclusion covered in these ramblings relies on one founding principle: we have to rely on all tasks to access critical regions using mutual exclusion primitives. Unfortunately this is dependent on the design of the software and cannot be detected by the run-time system. This final problem was addressed by Tony Hoare, called the Monitor.
The monitor is a mechanism not typically supplied by the RTOS, but something the programmer tends to build (a notable exception is Ada95’s protected object mechanism). A monitor simply encapsulates the shared resource and the locking mechanism into a single construct (e.g. a C++ Object that encapsulates the mutex mechanism). Access to the shared resource, then, is through a controlled interface which cannot be bypassed (i.e. the application never explicitly calls the mutex, but calls upon access functions).
Finishing Off…
This goal of these initial postings is to demonstrate that common terms used in the real-time programming community are open to ambiguity and interpretation. Hopefully you should now be clear about the core differences between the Binary Semaphore, General (counting) Semaphore and the Mutex.
The underlying difference between the Semaphores and the Mutex is the Principle of Ownership. Given the principle of ownership a particular implementation of a mutex may support Recursion, Priority inheritance and Death Detection.
ENDNOTE
An aspect of the mutex I haven’t covered here is that many operating systems support the concept of a condition variable. A condition variable allows a task to wait on a synchronization primitive within a critical region. The whole aspect Synchronization Patterns (e.g. semaphore as a signal) within the context of RTOSs will be the subject of my next posting.
Mutex vs. Semaphores – Part 2: The Mutex
September 11th, 2009In Part 1 of this series we looked at the history of the binary and counting semaphore, and then went on to discuss some of the associated problem areas. In this posting I aim to show how a different RTOS construct, the mutex, may overcome some, if not all, of these weaknesses.
To address the problems associated with semaphore, a new concept was developed during the late 1980’s. I have struggled to find it’s first clear definition, but the major use of the term mutex (another neologism based around MUTual EXclusion) appears to have been driven through the development of the common programming specification for UNIX based systems. In 1990 this was formalised by the IEEE as standard IEEE Std 1003.1 commonly known as POSIX.
The mutex is similar to the principles of the binary semaphore with one significant difference: the principle of ownership. Ownership is the simple concept that when a task locks (acquires) a mutex only it can unlock (release) it. If a task tries to unlock a mutex it hasn’t locked (thus doesn’t own) then an error condition is encountered and, most importantly, the mutex is not unlocked. If the mutual exclusion object doesn’t have ownership then, irrelevant of what it is called, it is not a mutex.
The concept of ownership enables mutex implementations to address the problems discussed in part 1:
- Accidental release
- Recursive deadlock
- Task-Death deadlock
- Priority inversion
- Semaphore as a signal
Accidental Release
As already stated, ownership stops accidental release of a mutex as a check is made on the release and an error is raised if current task is not owner.
Recursive Deadlock
Due to ownership, a mutex can support relocking of the same mutex by the owning task as long as it is released the same number of times.
Priority Inversion
With ownership this problem can be addressed using one of the following priority inheritance protocols:
- [Basic] Priority Inheritance Protocol
- Priority Ceiling Protocol
The Basic Priority Inheritance Protocol enables a low-priority task to inherit a higher-priorities task’s priority if this higher-priority task becomes blocked waiting on a mutex currently owned by the low-priority task. The low priority task can now run and unlock the mutex – at this point it is returned back to its original priority.
The details of the Priority Inheritance Protocol and Priority Ceiling Protocol (a slight variant) will be covered in part 3 of this series.
Death Detection
If a task terminates for any reason, the RTOS can detect if that task current owns a mutex and signal waiting tasks of this condition. In terms of what happens to the waiting tasks, there are various models, but two doiminate:
- All tasks readied with error condition;
- Only one task readied; this task is responsible for ensuring integrity of critical region.
When all tasks are readied, these tasks must then assume critical region is in an undefined state. In this model no task currently has ownership of the mutex. The mutex is in an undefined state (and cannot be locked) and must be reinitialized.
When only one task is readied, ownership of the mutex is passed from the terminated task to the readied task. This task is now responsible for ensuring integrity of critical region, and can unlock the mutex as normal.
Mutual Exclusion / Synchronisation
Due to ownership a mutex cannot be used for synchronization due to lock/unlock pairing. This makes the code cleaner by not confusing the issues of mutual exclusion with synchronization.
Caveat
A specific Operating Systems mutex implementation may or may not support the following:
- Recursion
- Priority Inheritance
- Death Detection
Review of some APIs
It should be noted that many Real-Time Operating Systems (or more correctly Real-Time Kernels) do not support the concept of the mutex, only supporting the Counting Semaphore (e.g. MicroC/OS-II). [ CORRECTION: The later versions of uC/OS-II do support the mutex, only the original version did not].
In this section we shall briefly examine three different implementations. I have chosen these as they represent the broad spectum of APIs offered (Footnote 1):
- VxWorks Version 5.4
- POSIX Threads (pThreads) – IEEE Std 1003.1, 2004 Edition
- Microsoft Windows Win32 – Not .NET
VxWorks from Wind River Systems is among the leading commercial Real-Time Operating System used in embedded systems today. POSIX Threads is a widely supported standard, but has become more widely used due to the growth of the use of Embedded Linux. Finally Microsoft Window’s common programming API, Win32 is examined. Windows CE, targeted at embedded development, supports this API.
However, before addressing the APIs in detail we need to introduce the concept of a Release Order Policy. In Dijkstra’s original work the concept of task priorities were not part of the problem domain. Therefore it was assumed that if more than one task was waiting on a held semaphore, when released the next task to acquire the semaphore would be chosen on a First-Come-First-Server (First-In-First-Out; FIFO) policy. However once tasks have priorities, the policy may be:
- FIFO – waiting tasks ordered by arrival time
- Priority – waiting tasks ordered by priority
- Undefined - implementation doesn’t specify
VxWorks v5.4
VxWorks supports the Binary Semaphore, the Counting Semaphore and the Mutex (called the Mutual-Exclusion Semaphore in VxWorks terminology). They all support a common API for acquiring (semTake) and releasing (semGive) the particular semaphore. For all semaphore types, waiting tasks can be queued by priority or FIFO and can have a timeout specified.
The Binary Semaphore has, as expected, no support for recursion or inheritance and the taker and giver do not have to be same task. Some additional points of interest are that there is no effect of releasing the semaphore again; It can be used as a signal (thus can be created empty); and supports the idea of a broadcast release (wake up all waiting tasks rather than just the first). The Counting Semaphore, as expected, is the same as the Binary Semaphore with ability to define an initial count.
The Mutual-Exclusion Semaphore is the VxWorks mutex. Only the owning task may successfully call semGive. The VxWorks mutex also has the ability to support both priority inheritance (basic priority inheritance protocol) and deletion safety.
POSIX
POSIX is an acronym for Portable Operating System Interface (the X has no meaning). The current POSIX standard is formally defined by IEEE Std 1003.1, 2004 Edition. The mutex is part of the core POSIX Threads (pThreads) specification (historically referred to as IEEE Std 1003.1c-1995).
POSIX also supports both semaphores and priority-inheritance mutexes as part of what are called Feature Groups. Support for these Feature Groups is optional, but when an implementation claims that a feature is provided, all of its constituent parts must be provided
and must comply with this specification. There are two main Feature Groups of interest, the Realtime Group and Realtime Threads Groups.
The semaphore is not part of the core standard but is supported as part of the Realtime Feature Group. The Realtime Semaphore is an implementation of the Counting semaphore.
The default POSIX mutex is non-recursive , has no priority inheritance support or death detection.
However, the Pthreads standard allows for non-portable extensions (as long as they are tagged with “-np”). A high proportion of programmers using POSIX threads are programming for Linux. Linux supports four different mutex types through non-portable extensions:
- Fast mutex – non-recursive and will deadlock [default]
- Error checking mutex – non-recursive but will report error
- Recursive mutex – as the name implies
- Adaptive mutex - extra fast for mutli-processor systems
These are extreamly well covered by Chris Simmonds in his posting Mutex mutandis: understanding mutex types and attributes.
Finally the Realtime Threads Feature Group adds mutex support for both priority inheritance and priority ceiling protocols.
Win32 API
Microsoft Window’s common API is referred to as Win32. This API supports three different primitives:
- Semaphore – The counting semaphore
- Critical Section - Mutex between threads in the same process; Recursive, no timeout, queuing order undefined
- Mutex – As per critical sections, but can be used by threads in different processes; Recursive, timeout, queuing order undefined
The XP/Win32 mutex API does not support priority inheritance in application code, however the WinCE/Win32 API does!
Win32 mutexes do have built-in death detection; if a thread terminates when holding a mutex, then that mutex is said to be abandoned. The mutex is released (with WAIT_ABANDONED error code) and a waiting thread will take ownership. Note that Critical sections do not have any form of death detection.
Critical Sections have no timeout ability, whereas mutexes do. However Critical Sections support a separate function call TryEnterCriticalSection. A major weakness of the Win32 API is that the queuing model is undefined (i.e. neither Priority nor FIFO). According to Microsoft this is done to improve performance.
So, what can we gather from this? First and foremost the term mutex is less well defined than the semaphore. Secondly,the actual implementations from RTOS to RTOS vary massively. I urge you to go back and look at your faviourite RTOS and work out what support, if any, you have for the mutex. I’d love to hear from people regarding mutual exclusion support (both semaphores and mutexes) for their RTOS of choice. If you’d like to contact me do so at nsc(at)acm.org.
Finally, Part 3 will look at a couple of problems the mutex doesn’t solve, and how these can be overcome. As part of that it will review the Basic Priority Inheritance Protcol and the Prority Ceiling Protocol.
At a later date I will also address the use of, and problems associted with, the semaphore being used for task synchronisation.
ENDNOTES
- Please I do not want to get into the “that’s not a real-time OS” debate here – let’s save that for another day!
- A number of people pointed out that Michael Barr (former editor of Embedded Systems Programming, now president of Netrino) has a good article about the differences between mutexes & semaphores at the following location: http://www.netrino.com/node/202. I urge you to read his posting as well.
- Apologies about not having the atom feed sorted – this should all be working now

