Semaphores, Events, Messages, and Timers

At this point, you have a basic understanding of the core parts of the RTOS. I have mentioned system calls several times but never really elaborated on what a system call is. Like threads and tasks , the definition of a system call depends on the company you keep. In general, a system call refers to an operating system facility that, when invoked, causes a context switch into the kernel because the resource with which the system call interfaces is only accessible in kernel mode. With embedded systems, the term system call usually just refers to one of the RTOSs API functions, and that definition is the one Ive used in this book. If the RTOS has all of the nice stuff Ive talked about so far, it still cant do much. Compare it to a farm tractor that has a heavy-duty motor and a lot of power but doesnt have any attachments. The attachments included with the RTOS are the system calls that allow us to communicate between tasks, to communicate between interrupt handlers and tasks, to guarantee that only one task executes a certain function at a time, to set up timers, and so forth. Every RTOS comes with some set of system calls, but, in general, all RTOSs share a few basic functions.

Semaphores can be used to synchronize access to a shared resource. A common form of synchronization is mutual exclusion, meaning that tasks coordinate their access to a resource so that only one task at a time is manipulating the resource. For example, if a semaphore-protected function has begun executing and a context switch transfers control to another section of code that also tries to call the protected function, the second call is blocked. Mutually exclusive access is needed whenever more than one task (i.e., code in separate threads or processes) is allowed to access the same resource.

For example, assume a system has some memory-mapped register that contains eight bits, each of which control an LED (1 = on), and assume that I use the function in Listing B.1 to modify those bits.

Listing B.1: LedOn() .
image from book
 unsigned char LedOn(unsigned char onbits)  {     unsigned char current_bits;     current_bits = *(unsigned char *)LED_PORT;     current_bits = onbits;     *(unsigned char *)LED_PORT = current_bits;     return(current_bits); } 
image from book
 

What would happen if (referring to Listing B.1), just after the variable current_bits is loaded from the LED_PORT address, an asynchronous context switch occurred and some other task called this function, passing it a different value? Quick answer: the setting established by the second task would be lost. Lets assume that on the first call to LedOn() , the value of onbits is 0x01 , and the current value in LED_PORT is 0x80 . If no context switch occurred, the result would be that LED_PORT would be 0x81 , meaning that two LEDs would be lit.

If, however, a context switch happens in just the right place, the result is different. On the first call to LedOn() after the line

 current_bits = *(unsigned char *)LED_PORT; 

the value stored in current_bits is 0x80 . Now assume that a context switch occurs after this line and that another task calls this function with 0x02 as the onbits value. The second invocation runs to completion leaving, for the momemt, the LED_PORT value at 0x82 . At some point in the future, context is restored to the original task. This instance of the function resumes at the line

 current_bits = onbits 

with current_bits (in the original context) set to 0x80. When this value of current_bits is logically ORed with onbits ( 0x01 ), the result 0x81 is written to the LED port. The result is that the value established by the preempting task ( 0x82 ) is lost.

The solution to this problem is mutual exclusion. If these LED operations were properly wrapped with semaphore operations ( Get at the top and Release at the bottom), then when the context switch occurred, the preempting task would not have been allowed to manipulate the LEDs until the task that owned the semaphore was done.

Events are OS-provided flags that, when set by one task or interrupt handler, cause some other task to wake up (i.e., to enter the ready-to-run state). Typically, signaling with events involves two system calls: one to post an event and one to block waiting for an event. Events are typically not queued. If the same event is posted several times prior to the acknowledgment of the event by the task that is blocked while waiting for it, those additional postings are lost. Because events are a simple flag, after the flag is set, setting it again has no significance. Usually events have lower overhead than other types of interprocess communication and thus are commonly used for communication between an interrupt handler and a task.

Messages provide a mechanism that allows a task (or interrupt handler) to send some data to another task. Unlike events, messages are queued. When multiple messages are sent to some task that is blocked while waiting for the message, each message is queued by the OS for later consumption by the receiver of the message.

Most RTOSs support the ability to send a message to be posted to the end of the queue and also to be posted to the head of the queue. In some situations, it can be very handy to be able to expedite a message by posting it to the head of the queue, but this feature must not be abused. When a message is placed in a queue, it is usually on a first-in first-out (FIFO) basis. If the message is put at the top of the queue, then it becomes a last-in first-out (LIFO) basis, which is ok, but youd bet ter be aware of the difference.

Messages can be passed from task-to-task or from interrupt-to-task, but a message usually imposes more overhead than an event.

Timers are probably the most heavily used facility within an RTOS. Not only is there typically some set of system calls specifically for timers, but many of the other system calls usually provide some time-out mechanism as well. The most common timer function is the ability to put a task to sleep for some period of time or to wake a task at some time of day in the future. The simplest example would be a task that blinks an LED (see Listing B.2).

Listing B.2: task_BLINKER().
image from book
 void task_BLINKER(void) {     while(1)     {         Turn_On_Led();         GoToSleep(1000);         Turn_Off_Led();         GoToSleep(1000);     } } 
image from book
 

The GoToSleep() system call is the timer function that allows the task to wake up periodically. Aside from GoToSleep() , other system calls intrinsically use timers to support the ability to time-out. For example, a task might want to wait for an incoming event, but, if the event does not occur for 30 seconds, some other action must occur. In this case, the system call is an event mechanism, but, under the hood, the event mechanism is using timers.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net