Link here

Shared Resources

How to use resource variables and uninterruptable memory operators to manage and synchronize access to shared resources and devices in a multitasking application

 

Access to shared resources must be managed

The application and the multitasking executive must manage each shared resource so that the state of the resource is not corrupted by unsynchronized access from multiple tasks. For example, assume that two tasks need to share access to a Liquid Crystal Display (LCD). One task might display special alerts or error messages, and the other task might display standard messages. Of course we don’t want part of standard message interspersed with an error message. We want a single task, but not both, to write to the display at any given time. This can be accomplished by:

  1. Defining a resource variable that controls access to the resource (in this case the LCD) by holding a value that identifies which task is allowed to control the resource at any specified time;
  2. Before using the resource, a task must request and GET permission to use it (thereby becoming the controlling task); and,
  3. No other tasks may use the resource until the controlling task has RELEASEd it.

The device driver routines built into the operating system automatically call resource management routines named GET and RELEASE associated with pre-defined resource variables as described below. These minimize contention for shared I/O devices in a multitasking system. For example, the serial input/output functions AskKey() Key() and Emit() all GET the serial resource associated with the serial port (serial1 or serial2) upon entry, and RELEASE the resource before exiting. This ensures that only one task at a time will attempt to input or output a serial character from the serial port.

Consider another example in which a resource variable is required. Suppose that two distinct tasks named Task A and Task B use PORTM on the Freescale HCS12 (9S12) microcontroller. Task A might need to read and modify the lower 4 bits of the port, and Task B might need to control the upper 4 bits. We do not want either task to unexpectedly modify the bits that are controlled by the other task. If we aren’t careful, the following scenario could occur:

Assume that all 8 bits of PORTM are initially 0. Task A wants to set the lowest order bit to 1. It executes the following code:

char temp_var = PORTM;
temp_var |= 1;
PORTM = temp_var;

This code implements a read/modify/write operation: the port register is first read, then the least significant bit is modified, then the modified value is written back to the port register. C programmers may complain that this code is needlessly complex and could be accomplished with the simple statement

PORTM |= 1;

In fact, even if we coded this using the simpler statement, the compiler might still implement it using read/modify/write assembly code instructions, so that the task switch could occur after the port is read; this would allow for corruption. Using the three-statement high level code allows us to explain the potential multitasking failure mechanism clearly.

The code seems to change only the lowest bit in the port. But assume that the timeslicer causes a switch to Task B just after the temp_var = PORTM; read operation, and that Task B sets the highest order bit in PORTM to a 1 by writing to the port as

PORTM |= 0x80;

Task A does not know that Task B has modified the contents of PORTM. Task A thinks that it should restore the top 4 bits as all zeros. Now when control returns to Task A, it stores the value 1 into PORTM which clears the top bit in the port. This is incorrect behavior; Task A has modified the state of the highest order bit in PORTM which is supposedly controlled only by Task B!

Defining a resource variable to control access to PORTM solves this problem. As explained in detail below, Task A should GET the resource, and when Task B subsequently tries to GET the same resource it would be forced to Pause() until Task A RELEASEs control. The operating system provides a defining word called RESOURCE that creates a resource variable, and the three utility words GET, TRY_TO_GET, and RELEASE to manage the appropriation and release of the resources.

Another method of solving the port contention problem is presented at the end of this document; it involves using uninterruptable operators to access the port so that no task switch can occur in the middle of a read/modify/write operation.

 

Resource variables

To assure proper operation in a multitasking program, each shared resource should be associated with a resource variable. A resource variable is a 32-bit variable that contains zero if the resource is available, and contains the task identifier if it is owned by a task. The task identifier is returned by the task's name, assigned by the TASK statement; it is the base address of the task area. The following resource variables are defined in the operating system (see the Serial.h include file in the Mosaic IDE Plus):

SERIAL            // Synonym for SERIAL1_RESOURCE
SERIAL1_RESOURCE  // For primary serial port (HCS12 UART0)
SERIAL2_RESOURCE  // For secondary serial port (HCS12 UART1)
SPI_RESOURCE      // For Serial Peripheral Interface SPI0 channel
                  //  (goes to Wildcard Bus, real-time clock)

The pre-defined resource variables listed above are automatically cleared to 0\0 by every warm or cold restart.

The I/O (input/output) drivers for the serial and Serial Peripheral Input ports in the operating system invoke the resource management routines GET and RELEASE using the appropriate resource variables to assure proper operation in multitasked environments. See the Serial.h section of the Categorized Word List in the C Function summary for a complete list of the built-in I/O driver routines.

As an example, let's define a new resource variable that controls access to PORTM on the HCS12 processor and initialize it to contain a 32-bit zero value as follows:

RESOURCE portm_resource;
portm_resource = 0;  // MAKE SURE to also do this initialization in a function called by main

RESOURCE creates a 32-bit variable in common RAM. It is important to initialize the resource variable to contain zero as part of the application’s runtime initialization after each startup. Failure to perform the initialization after each powerup or restart could leave "garbage" in the resource variable that precludes any task from using the associated resource.

After the first initialization of the resource variable to zero, only the resource operators GET TRY_TO_GET and RELEASE should be used to modify its contents; standard assignment and fetch commands are not robust operators and should not be used. GET TRY_TO_GET and RELEASE are carefully written to ensure that only one task at a time controls a resource. As specified in their glossary entries, they disable interrupts for short periods while the resource variable contents are being verified so that interrupting tasks do not corrupt the verification process.

A resource variable that contains a zero value signals that the associated resource is available. A task can only use a resource if the resource variable contains either zero or the task identifier (i.e., the task base address) of the requesting task. If the resource variable contains the task address of another task, then the resource cannot be accessed. When a task is finished using a resource, it executes RELEASE to clear the resource variable so that other tasks may access the resource.

 

Getting and releasing a resource

If a program running in a task environment executes the command

GET( &portm_resource );

it claims control of the port PORTM resource if it is available. Notice that the & (address-of) operator must precede the name of the resource, as the GET routine operates on the address of the resource variable. GET stores the task identifier (base address of the task area) into portm_resource if the resource is available. If the resource is not available (i.e., portm_resource contains a nonzero value that is not equal to the requesting task’s identifier), GET enters a loop executing Pause() until the resource is available. This allows other tasks to operate while the requesting task is waiting for the resource to be released. After a task is done using a resource (in this example, after port PORTM has been modified), it executes

RELEASE( &portm_resource );

which zeros the contents of the resource variable so that other tasks can claim it. A task can only RELEASE a resource that it controls; RELEASE does nothing unless the current task’s identifier is stored in the resource variable.

Returning to the example in which Task A and Task B are accessing the PORTM IO port, we can write code that prevents corruption of the port’s contents during multitasking. Assuming that portm_resource has been defined and initialized as shown above, Task A should execute the following code to set the least significant bit of PORTM:

GET( &portm_resource );
PORTM |= 1;
RELEASE( &portm_resource );

and Task B should execute the following code to set the most significant bit in PORTM:

GET( &portm_resource );
PORTM |= 0x80;
RELEASE( &portm_resource );

If Task A GETs the resource first, Task B is prevented from accessing PORTM (that is, it enters a loop calling Pause()) until Task A RELEASEs the resource. This ensures that the accesses to the port do not improperly interact with one another.

The word TRY_TO_GET is similar to GET, except that it does not call Pause() if the resource is not available. This enables a task program to continue processing rather than waiting for an unavailable resource. For example:

if( TRY_TO_GET( &portm_resource ) )
{
    PORTM |= 0x80;
    RELEASE( &portm_resource );
}

If the resource is available, TRY_TO_GET claims the resource by storing the calling task’s identifier into the resource variable, and returns a true flag to signal that the resource was claimed. In the code fragment above, this enters the if statement, sets the top bit, and invokes RELEASE. In this example, the code does nothing if TRY_TO_GET returns a false flag, indicating that another task has control of PORTM.

 

Access to the serial ports

The PDQ Board is a Single Board Computer (SBC) with two built-in asynchronous serial communications ports named Serial1 and Serial2. They are implemented by hardware UARTs (Universal Asynchronous Receiver/Transmitters) in the Freescale 9S12 processor. Each serial port is initialized by the operating system to run at a default baud rate of 115,200 baud, and Serial1 is the default serial port used by the default task after a factory cleanup. The QED-Forth interpreter runs on the default task in the absence of a user-specified autostart routine. This interactive monitor can communicate with the Mosaic Terminal or other serial devices using either serial port. The functions UseSerial1() and UseSerial2 specify the serial port that is used by a task. These functions revector the three fundamental serial routines Key() AskKey() and Emit so that they access the specified serial port (1 or 2). The three serial primitives are in turn called by higher level C functions such as printf() and scanf().

The resource variables SERIAL1_RESOURCE and SERIAL2_RESOURCE are declared in the user.h include file to manage access to the serial I/O ports. A user variable called SERIAL_ACCESS controls how the serial routines GET and RELEASE the serial resource variables. The SERIAL.ACCESS variable gives you flexibility in specifying how multiple tasks share (or don't share) the serial I/O line.

If SERIAL_ACCESS contains the constant RELEASE_ALWAYS, then Key() AskKey() and Emit always GET the serial resource in use before accessing the port, and RELEASE the serial resource after accessing it. This allows tasks to share a serial port on a character-by-character basis.

If SERIAL_ACCESS contains the constant RELEASE_NEVER, then Key() AskKey() and Emit always GET the serial resource in use before accessing the port, but they do not RELEASE the serial resource. This allows a task to seize and retain control of a serial port.

After a COLD restart the user variable SERIAL_ACCESS contains the default constant RELEASE_AFTER_LINE. In this case `Key() AskKey() and Emit do not GET or RELEASE the serial resource. Rather, the QED-Forth interpreter running in the default task GETs the serial resource before each line is interpreted, and RELEASEs the serial resource after each line is interpreted. This eliminates the considerable overhead involved in executing GET and RELEASE as each character is received and echoed by the QED-Forth interpreter during program development. To reliably attain the maximum baud rate during downloads to the PDQ Board, SERIAL_ACCESS should contain RELEASE_AFTER_LINE. The RELEASE_AFTER_LINE option is not very useful within a C program that does not use the interactive Forth monitor, so a typical C application program should set SERIAL_ACCESS to either RELEASE_ALWAYS or RELEASE_NEVER.

In the multitasking demo program listing above, SERIAL_ACCESS is set to RELEASE_ALWAYS in the Setup_Task() function. In the demo program only the main task accesses the serial port. To avoid perplexing problems that may arise during debugging, keep in mind the serial port access issues. For example, if a printf() debug statement is inserted into the Count_Forever() action routine of the Counter_Task then both tasks would be trying to simultaneously access the serial port. Because the SERIAL_ACCESS user variable is set to RELEASE_ALWAYS in this program, the two tasks would alternate characters to the terminal window, resulting in a hard-to-read debugging output. If SERIAL_ACCESS had been set to RELEASE_NEVER, then the main task would never relinquish control of the serial port, and the debug information would never print out. In addition, the Counter_Task action program would be stuck in a Pause() loop waiting for access to the serial port, so that task would stop operating properly, all as a result of the debugging print statement.

In such a case where both tasks need to print to a serial port the best solution is to devote separate serial ports for each task. For example, inserting the statement UseSerial2() at the top of the Count_Forever() task action routine would enable the main function running in the default task to use serial port 1, and the Counter_Task to use serial port 2. The recoded function would look like this:

void Count_Forever( void )
// Infinite loop task action program.
{
    Use_Serial2();
    for(;;)
    {
        Inc_Counter();
        Pause();
    }
}

Under this scenario, dual Mosaic Terminal windows would be open, one pointed to the serial 1 port, and the other to the serial 2 port, providing simultaneous monitoring of the serial communications from the two tasks.

 

Uninterruptable Memory Operations

Earlier in this document we presented an example that showed how two tasks interfered with one another as they accessed a shared I/O port (PORTM). The problem was that the timeslicer caused a task switch in the middle of a read/modify/write process. Task A read the port, but before it could write to the port, it was interrupted by the timeslicer. While Task B was active it modified PORTM, and when control returned to Task A, the modification made by Task B was undone. This problem was solved by defining a resource variable called portm_resource and using the GET and RELEASE operators to ensure that only one task at a time could access the port.

A different way to solve this problem is to use uninterruptable operations to change the contents of a memory location. If your application program needs to modify bits in a single byte, the operating system provides convenient operators that disable interrupts during the read/modify/write operation to ensure that no interruption or corruption of the operation can occur.

The uninterruptable operators named CHANGE_BITS(), CLEAR_BITS(), SET_BITS() and TOGGLE_BITS() modify the contents of specified bits in a specified byte without allowing a timeslicer (or any other interrupting event) to occur before the operation is complete. These functions disable interrupts before reading the memory contents and restore the prior state of the interrupt flag (enabled or disabled) after writing to the specified byte. Interrupts remain disabled for under 13 cycles, corresponding to less than 2/3 of a microsecond. Consult the glossary for detailed descriptions of these operators.

Returning to our example, Task A can set the least significant bit in port PORTM by executing

SET_BITS( 0x01, &PORTM );

Notice that the & (address-of) operator must precede the name of the port, as the SET_BITS() routine operates on the address of the memory location to be changed.

Because SET_BITS() is uninterruptable, we can be sure that there will not be a task switch during the read/modify/write operation. In similar fashion, Task B can set the most significant bit in PORTM using the statement

SET_BITS( 0x80, &PORTM );

These work properly in a multitasking application program.

Similar problems can arise when one task writes to a floating point or long 32-bit variable, and a second task needs to read the saved value. The data that is read may be invalid if the read or the write is interrupted between the time of the writing/reading of the first 16 bits and the writing/reading of the second 16 bits. The SEND and RECEIVE mailbox operators described earlier are one solution to this problem, but they do not allow a 32-bit zero as a valid data value. Another solution is to use one of the operating system's uninterruptable 32-bit operators. The uninterruptable storage operators are STORE_LONG_PROTECTED() and STORE_FLOAT_PROTECTED() and the uninterruptable fetch operators are FETCH_LONG_PROTECTED() and FETCH_FLOAT_PROTECTED().

The assembly instructions for 8 bit and 16 bit read and write operations are themselves uninterruptable. Thus, for simple fetching or storing (as opposed to read/modify/write operations), the standard C fetch and store assignment syntax for 1-byte and 2-byte variables is robust with respect to multitasking.



See also:

 
This page is about: Using Resource Variables and Non-interruptable Memory Operators to Manage Shared Devices and Program Resources in Multitasking C Applications – Defining, initializing and invoking resource variables to synchronize and control inter-task accesses to resources and devices including serial ports, analog to digital converters, keypads, liquid crystal displays, and IO (input/output) ports.
 
 
Navigation