Book Review


A Real-Time Programmer’s Review of mC/OS-II

Reviewed by Graham Wall


Title:MicroC/OS-II, The Real-Time Kernel
Author:Jean J. Labrosse
Publisher:CMP Books, 1998
ISBN:0879305436
Price:$69.95, includes CD-ROM

Improving upon mC/OS, mC/OS-II is a simple, compact, dependable, and very affordable RTOS (real-time operating system). Around it has grown a whole community of embedded systems developers who have ported the RTOS to most major embedded environments. This is my perspective on mC/OS-II and the companion book by its creator Jean J. Labrosse, MicroC/OS-II: The Real-Time Kernel. In order to provide an honest appraisal of mC/OS-II, first I will explain mC/OS-II, the book’s contents, the licenses available for mC/OS-II, and to what extent one can get by without the book. Second, I will do a short critique of the RTOS, looking at its best features and how it might be improved.

What is mC/OS-II?

Much of what is known about mC/OS-II comes from the book, but the book is not the entire story. The book serves two functions: first, it is an encyclopedia of features and facts about mC/OS-II; second, it is an introduction to RTOS concepts. I will begin my description of mC/OS-II with some important concepts that a user might learn from the book.

An RTOS manages the processing time of a microcontroller or microprocessor by allowing the total work pending to be divided into logical units called tasks. A task is like an independent program that has its own stack and register context; it is usually implemented as an infinite loop or a function that contains a system call to the kernel to de-schedule the task before the function returns. A multitasking program schedules multiple tasks by assigning each task a priority related to its importance and rapidly switches control of the micro among them. The RTOS kernel arbitrates the task switching process and facilitates communication between tasks by managing mailboxes, queues, and semaphores. Semaphores allow tasks to share common system resources such as memory or peripherals, announce an event, or provide task synchronization. In a preemptive operating system, a task is preempted when a system call or an interrupt service routine calls the scheduler and the scheduler finds a higher priority task is ready and waiting to run. The preempted task’s context is saved, and the preemptor task is swapped in by loading its context and giving it control of the CPU. Since a preemptive kernel keeps track of which tasks are ready to run and have priority, and since no properly implemented task is immune to preemption, the task switch timing latencies for such an operating system are deterministic. An RTOS is “hard” if tasks must always start and finish according to a rigid schedule (if they don’t, there is probably loss of life — i.e., pacemakers and fighter jets); otherwise, the RTOS is “soft” since tasks run as fast as they possibly can, but deadlines are not guaranteed to be met. A system running a preemptive RTOS can still fail to be “hard” real time if task starvation occurs (i.e., if a task cannot finish on time because there are too many scheduled tasks sharing the micro’s time). By introducing the reader to these and other real-time systems concepts, Labrosse’s book makes a good introductory text for the student as well as a comprehensive mC/OS-II handbook for the experienced programmer.

Labrosse describes the structure of mC/OS-II’s kernel, provides a porting guide with an example port to the 80x86 real-mode large model, and provides documentation and source code for each mC/OS-II function visible to the user. He also provides useful code fragments that indicate how to write tasks and use the basic kernel services, which are handy when the project you’re working on is due yesterday. Functions for managing a user-defined memory heap are also provided.

mC/OS-II is started by placing a call to OSInit within the main procedure, creating one or more tasks using either the standard or extended task creation functions, and calling OSStart to start the scheduler. OSStart does not return, and the first task to run must enable the tick interrupt, the RTOS’s heartbeat. The lowest priority task, which is always ready to run, is the idle task OSTaskIdle, so it will execute when other tasks are not ready to run because they are waiting for an event such as a semaphore or mutex to become available, or a message to arrive at a mailbox, pipe, or queue. Here is an example of a typical main procedure and first task to run:

/* uC/OS-II system headers */
#include "includes.h"	
#define FIRST_TASK_PRIO	10
#define FIRST_TASK_STACK_SIZE	200

void main( void)
 {
 INT8U err_code;
 
 /*
 do any system initialization here not
 requiring uC/OS-II to be running
 ...
 */
 OSInit();        /* init uC/OS-II data structures */
   /* create a task, pdata is not used */
 err_code = OSTaskCreate( FirstTask, (void *)0, 
    &first_task_stack[FIRST_TASK_STACK_SIZE-1],
    FIRST_TASK_PRIO);
 if ( err_code == OS_NO_ERR) {
      OSStart();  /* start the uC/OS-II scheduler */
      }
 else {
      /* report to user that creation
         of first task failed */
      }
 }

void FirstTask( void *pdata)
 {

 /* call user defined code to start timer tick */
 InitTimeTickISR();
 for( ;;) {
      /*
      your user code here,
      ie. spawn some tasks that
      are actually useful
      ...
      */
      /* yield control to kernel scheduler */
      OSSched();
      }
 }

The “Task Ready List” in mC/OS-II supports only a fixed number of tasks, and each task must have a unique priority. This type of scheduler, where each task is mapped to a unique priority from a fixed pool of priority numbers, is called a bitmap scheduler. eCos, another RTOS that has a bitmap scheduler, gives the option of compiling in a multiple-priority queue-based scheduler instead [1]. The queue-based scheduler is often slower than the bitmap scheduler, but without the inconveniences of a fixed number of tasks and necessarily unique task priorities.

For the sake of mC/OS-II’s bitmap scheduler, task priorities are grouped into octets; each octet is mapped to a bit in the octet OSRdyGrp, which gives the user 64 possible tasks. Each unique task priority number is represented by a bit in OSRdyTbl[] having size OS_LOWEST_PRIO/8 + 1. The bottom three bits of the task priority number will be its column in OSRdyTbl[], and the three next higher bits of the priority number will be its OSRdyGrp value or row in OSRdyTbl[]. If a task is ready to run, the bit corresponding to its priority number will be set in OSRdyTbl[]. To find the highest priority task ready to run, the scheduler simply has to find the lowest priority number with its corresponding bit set in OSRdyTbl[]. If the bit corresponds to a task of higher priority than the one that is currently running, the kernel does a context switch. Although the task priorities can be changed at run time, only one task may have a given priority number at a time because the priority number doubles as the task’s handle or identifier. If you are using the book as a real-time textbook, keep in mind that not all RTOSs use a bitmap scheduler, and, in fact, often opt for a multiple-priority queue-based scheduler to allow the user to create an arbitrary number of tasks or create multiple tasks with the same priority.

Using ISRs (interrupt service routines) written in C for use with mC/OS-II is straightforward, assuming that your compiler allows inline assembly code. Otherwise, the ISR will have to be written in assembler, which is a bit more difficult. First the register context is saved, mC/OS-II is notified that you are entering an ISR, the user’s code is executed, mC/OS-II is notified that you are leaving an ISR, the register context is restored, and finally you do an interrupt return. One ISR that must be written when porting mC/OS-II is the operating system’s heartbeat clock tick. Related to kernel timing are system functions that allow the user to delay a task for a specified number of ticks, resume a task, and get and set the time of day, but I won’t examine these functions here. Here is an example of a typical ISR:

#include "includes.h"

void interrupt UartInterrupt( int vector)
 {
 #asm
 /* assembly code to push appropriate
    registers onto a stack */
 #endasm
 OSIntEnter();
 switch( vector) {
      case UART_RCV_VECT:
           /* call user code to receive 
              a character */    
           UartRcv();  
           break;
      case UART_TX_VECT:
           /* call user code to send a 
              character */     
           UartTx();   
           break;
      case UART_ERR_VECT:
           /* handle reported uart 
              error */    
           UartErr();  
           break;
      }
 OSIntExit();
 #asm
 /* assembly code to pop appropriate 
    registers off of the stack in
    reverse order */
 #endasm
 }

mC/OS-II provides straightforward functions for task management that return error codes. You can call the standard task creation function and pass pointers to the task function, arbitrary user data, top of task stack, and task priority. Or you can call the extended task creation function and pass extended user data and other options. An interesting extended option is the “id” field, which could be used in future RTOS versions to decouple priority numbers from task handles. Speaking of extended options, either the pointer to the bottom of the task stack or the stack size option is unnecessary, since either one combined with the standard pointer to the top of the task stack yields the other one. Your C compiler will happily calculate the redundant option for you, for example, ptr_to_top_of_stack = ptr_to_bottom_of_stack + size. Simple task stack checking is implemented and returns approximate usage of the task stack. Task deletion is simple and allows the user to specify that shared resources owned by the task are released before the task can be de-scheduled. The user can also suspend and resume tasks and examine the state of any active task control block.

mC/OS-II also implements mechanisms for communicating between tasks and protecting shared resources: mutexes, event flags, semaphores, queues, and mailboxes. In version 2.04 (available at <www.ucos-ii.com>), support for mutexes has been added. Mutexes are binary semaphores that are useful for solving priority inversion problems. Mutexes allow a low priority task that needs to use a shared resource to have its priority temporarily increased while it finishes using an important resource in order to minimize the amount of time that a higher priority task is upstaged while it is forced to wait for the common resource. In version 2.51, event flags have been added to allow a task to synchronize with the occurrence of multiple events. Also, by defining an event as common, a single event can signal multiple tasks. A semaphore is used to signal that an event has occurred or to block a task for a period of time (possibly indefinitely) while it waits for a resource or an event. A mailbox has room for a pointer to a single message, and a queue is an array or list of mailboxes. Each messaging mechanism provided by mC/OS-II maintains a list of tasks waiting for the event to occur.

Now that I have described mC/OS-II and summarized the book, to what extent can a user of this RTOS get by without buying the book? Anyone who is going to be spending a lot of time working with the RTOS would probably benefit from owning the book because it is a great reference manual. If you are looking for an exposition of real-time concepts, but not planning on running the RTOS, you may not get your money’s worth. Instead borrow the book from a friend or library. The book’s principal value is it is a user manual for mC/OS-II. I have found it to be the single most convenient source of information about how to make mC/OS-II system calls, and what to watch for when porting the RTOS to new environments. The application notes on the Micrimm website are also quite useful.

Suppose that you are integrating mC/OS-II into a project, what licensing options are available? No license is required for educational use. You must contact Jean Labrosse (Jean.Labrosse@uCOS-II.com) to negotiate a license if you intend to sell your product for profit or distribute mC/OS-II source code. Compared to other operating systems, the cost of licensing mC/OS-II for commercial applications is typically an order of magnitude lower than average and is royalty-free to boot. For more information, especially if the licensing terms change visit the Micrimm homepage <www.ucos-ii.com>.

Constructive Criticism of mC/OS-II

Here are the most significant pros and cons to using mC/OS-II based on my own practical experience porting and integrating mC/OS-II into embedded applications.

Benefits

The fact that mC/OS-II has been ported to a vast array of architectures, implemented in diverse designs, and worked on by many talented people speaks to its ease of use and high quality. Here is a small subset of the ported environments: Intel/AMD 80x86, ARM core, Atmel’s AVR, Infineon/ST 16x, Motorolla 6811 and PowerPC, Phillips XA, and Zilog’s Z-80. For an exhaustive list, see the Micrimm website.

I have also found that mC/OS-II is very easy to integrate into a small project and get a simple application up and running. I recently wrote a program to run, receive, transmit, and debug tasks with a few semaphores and mailboxes to test network messaging. For this small project, mC/OS-II was a great solution because it only took a couple of days to create a working program from scratch.

Since mC/OS-II is a preemptive operating system, it is capable of deterministic task switch latencies, so the next highest priority task ready to run will start in at most one operating system clock tick: this kind of predictability is essential to hard real-time systems. Its capability of determinism makes mC/OS-II suitable for a large number of diverse applications. Combined with its robustness, easy-to-use API, and pricing scheme, it is obvious why it has rocketed to pop-star fame among RTOSs.

Areas for Future Improvement

The user should probably be given the option to use a different scheduling algorithm (e.g., queue-based) so that task priorities and task handles can be completely decoupled. Given the design of the mC/OS-II ready list and its bitmap scheduler, one can see why this was done, but the user should be able to have multiple active tasks with non-unique priorities and choose task handles independently of priority. I recently ported a TCP/IP stack to mC/OS-II and found that I had to crank out new code to provide an extra level of indirection between task handles and task priorities so that task spawning and deletion worked properly. The web-server daemon wanted to assign the same priority to spawned tasks corresponding to each HTTP connection.

Implementing time-slicing is also difficult with a bitmap scheduler for the simple reason that you don’t have more than one task with the same priority to share CPU time, so the burden is on the user to proxy out the time-slicing code to a higher-level mechanism of their own design. Allowing multiple tasks to have the same priority by adding a level of indirection implies a pretty fundamental redesign of the ready list and scheduling algorithms, and probably the adoption of a queue-based scheduler. Perhaps the next version of mC/OS could provide a queue-based scheduler as a compile-time option.

Finally, there is no native debug module included with the mC/OS-II distribution. A debug module like the kind available with many other RTOSs, where the user is prompted to issue commands to view status of resources and debug parameters, would be useful. Or maybe even native support for gdb to facilitate local or remote debugging is conceivable. Basically, I would like to see a mechanism integrated with mC/OS-II to send task and resource status information to a remote or local user terminal, the ability to set level triggers on data, and task stack analysis brought out to this user interface. Particularly for embedded systems, the usability and availability of debugging tools can really have a noticeable effect on a project’s progress and overall development cost. If only one of my points could be acted upon, it would be more debug support.

In its present state, mC/OS-II is a superb solution to a plethora of embedded real-time software engineering problems; hopefully this review of the book and its RTOS will spur on mC/OS-III.

Note

[1] Gary Thomas. “eCos: An Operating System for Embedded Systems,” Dr. Dobb’s Journal, January 2000.

Graham Wall is an embedded software designer for Siemens in Peterborough, Ontario, Canada. His interests include operating systems, communication protocols, algorithms, and software testing strategies. Graham can be reached at grahamw@milltronics.com.