To do good and to communicate forget not.
-- Hebrews, ch. 13, v. 16.
Interprocess communication (IPC) is one of the major abilities that is being implemented in the new generation of operating systems. UNIX, Windows NT and OS/2 all implement IPC in a somewhat similar manner. RCOS follows this example by supplying primitives by which application programmers can write programs that use shared memory and semaphores.
The aim of this chapter is to demonstrate how to write RCOS programs that use shared memory and semaphores. It assumes you understand the concepts of semaphores and shared memory.
To implement semaphores and shared memory an operating system must contain specific data structures and algorithms to implement and manage their use. In RCOS these algorithms and data structures are defined in the files MSHARE.CPP (shared memory) and IPC.CPP (semaphores).
For users or their programs to make use shared memory or semaphores the operating system provides a set of system calls. These system calls define the valid operations that a program can perform on semaphores and shared memory. Under RCOS a system call is generated by using the CSP p-code instruction. The different system calls supported by RCOS are defined in the PCI.HPP and PCI.CPP files.
In turn the programming language that application programs are written in must contain a mechanism by which user programs can execute system calls. The PLL/2 source language that is used to write application programs for RCOS implements a special function that allows access some of the system calls provided by RCOS. The function name is SYSTEM and it takes a variable number of parameters depending on the system call being called.

Diagram 4.1.
How IPC Primitives are Accessed.
Tables 4.1 and 4.2. lists the PLL/2 system functions used by PLL/2 programs to access the IPC facilities provided by RCOS. Refer to the following section on identifying IPC primitives for an explanation of handle and id.
Function Prototype Purpose
SYSTEM( SEM_CLOSE, handle ) Disconnect from a semaphore. If
the process is the last using the
semaphore it is deleted.
SYSTEM( SEM_CREATE, 'id'[,init_value]) Create a new semaphore identified
by id and initialised to an
optional init_value. If left out
initial value is 1 and semaphore is
a binary semaphore.
SYSTEM( SEM_OPEN, 'id') Connect to an existing semaphore.
SYSTEM( SEM_SIGNAL, handle) signal the specified semaphore
SYSTEM( SEM_WAIT, handle) wait on the specified semaphore
Table 4.1.
Semaphore SYSTEM Functions.
For example:
VAR
sem, dummy: INTEGER;
BEGIN {main}
sem := SYSTEM(SEM_CREATE, 'my_sem', 0);
Writeln('I am waiting for other process to signal.... ' );
dummy := SYSTEM(SEM_WAIT, sem)
Writeln('The other program did a signal on my semaphore. ' );
sem := SYSTEM( SEM_CLOSE, sem );
END.
Function Prototype Purpose
SYSTEM(SHR_CLOSE, handle) Disconnect from a shared memory
segment. If no more processes are
connected then delete the shared
memory segment.
SYSTEM( SHR_CREATE, 'id', size) Create a new shared memory segment
of a specified size
SYSTEM( SHR_OPEN, 'id' ) Connect to an existing shared
memory segment
SYSTEM( SHR_READ, handle, return the contents of a shared
offset ) memory segment at offset
SYSTEM( SHR_WRITE,handle, offset, Write to the shared memory segment
variable ) at offset the contents of variable.
SYSTEM( SHR_CLOSE,handle) return the size of a shared memory
segment
Table 4.2.
Shared Memory SYSTEM Functions.
For example:
VAR
myshm, value, result, temp : INTEGER;
BEGIN
myshm := SYSTEM(SHR_OPEN, 'myshm' );
IF myshm > 0 THEN
BEGIN
{ Get Value from shared memory }
value := SYSTEM(SHR_READ, myshm, 0 );
Write( 'I read ', value# );
WriteLn( ' from shared memory, squaring it..' );
result := value * value;
temp := SYSTEM(SHR_WRITE, myshm, 0, result );
temp := SYSTEM(SHR_CLOSE, myshm)
END
END.
There are three basic steps involved in writing a program to use either IPC primitives, semaphores or shared memory. They are
All programs that use either shared memory or semaphores must complete these three steps. (actually programs don't need to manipulate the primitive but then why do the other 2 steps if it doesn't?)
Before you can do any of the three steps mentioned above you need to be able to identify the semaphore or shared memory segment you are manipulating.
There are two types of identifiers used for IPC primitives
The unique string identifier is only used for CREATE and OPEN functions. These functions actually return the second type of identifier (discussed below) if they are successful. This string must be unique throughout the entire operating system. Be sure that programs attempting to connect to the same IPC primitive use the SAME unique string identifier. If they don't the two programs won't be using the same IPC primitive..
Opening or creating an IPC primitive will return a unique integer identifier and it is this identifier that a program should use to access a semaphore or shared memory segment. (If the open/create function doesn't work a negative number will be returned.)
There are two different methods for connecting to an IPC primitive
This is only done by the first process using of the primitive. During creation the operating system allocates space for the primitive and carries out other initialisation procedures.
Once the primitive has been created by one processes other processes wishing to use it must connect to it, rather than creating it all over again.
In the example programs below, the program SEM1 is the first program that must be executed and so it uses SEM_CREATE. The program SEM2 then uses SEM_OPEN to connect to the semaphore that already exists because SEM1 has created it.
Both shared memory and semaphores have a small set of valid operations that a program can use. The following examines just the manipulation operations and not the creation, connecting or closing operations.
RCOS supports the standard semaphore operations
If the semaphore value is 0 then place the process onto the blocked queue else decrement the value of the semaphore
if there is a process blocked on the semaphore wake it up else increment the value of the semaphore.
The initialisation of a semaphore is carried out as part of the SEM_CREATE operation. If the semid used in either a SEM_WAIT or SEM_SIGNAL operation is invalid an error will be generated.
The three valid operations on a shared memory segment are
Shared memory segments can be almost any size (within memory limits). The function call SYSTEM( SEM_CREATE, 'mysem', 1 ) creates a shared memory segment that is one PLL/2 integer variable in size. The function call SYSTEM( SEM_CREATE, 'mysem', 10 ) creates one that is ten PLL/2 integer variables in size.
If you examine Table 4.2. you will see that the read and write functions require an offset. The function call SYSTEM( SEM_READ, handle, 0 ) will return the contents of the first location in the shared memory segment. The function call SYSTEM( SEM_WRITE, handle, 4, myvar ) will write the contents of the variable myvar into the fifth location in the shared memory segment, remember start counting at 0 (we're all C programmers after all).
Both shared memory and semaphores are a restricted resource under RCOS (like most operating systems). RCOS can only support a certain number of both semaphores and shared memory. Therefore it is important that every program that makes use of an IPC primitive disconnects from the primitive. Both primitives provide a ???_CLOSE function, where ??? is replaced with SEM or SHM.
This function will disconnect the current process from the IPC primitive. If it is the last process connected to the primitive it will also delete the primitive from the system. Thereby allowing some other process to create a new primitive.
This section provides four example programs that make use of RCOS's IPC primitives. You should find these programs in the directory into which you installed RCOS.
Filenames Purpose
sem1.pll Provide a simple example of using semaphores
sem2.pll
shm1.pll Provide an example of using shared memory, and of
shm2.pll using semaphores to control access to shared memory
shmarr1.pll A shm example using a large shm segment and that
shmarr2.pll fixes some of the problems suffered by shm1 and
shm2
Table 4.3.
Example IPC Programs.
These two programs are designed to demonstrate how to use semaphores as a simple form of process synchronisation. The program sem1 must always be executed first, it creates the semaphore (used by both programs), displays a message and then waits for the program sem2 to signal on the semaphore. sem1 will do nothing more until sem2 is executed.
sem2 connects to the semaphore using a SEM_OPEN operation, does a SEM_SIGNAL on the semaphore and then closes.
PROGRAM Sem1 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Simple example use of semaphores }
{ AUTHOR: David Jones }
{ DATE: 15-JAN-95 }
{-----------------------------------------------------------}
VAR
sem, dummy: INTEGER;
BEGIN {main}
sem := SYSTEM(SEM_CREATE, 'my_sem', 0);
Writeln('I am waiting for other process to signal.... ' );
dummy := SYSTEM(SEM_WAIT, sem)
Writeln('The other program did a signal on my semaphore. ' );
sem := SYSTEM( SEM_CLOSE, sem );
END.
PROGRAM Sem2 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Simple example use of semaphores, number 2 }
{ AUTHOR: David Jones }
{ DATE: 15-JAN-95 }
{-----------------------------------------------------------}
VAR
mysem, dummy: INTEGER;
BEGIN {main}
mysem := SYSTEM(SEM_OPEN, 'my_sem');
Writeln('I am the second process.' );
dummy := SYSTEM(SEM_SIGNAL, mysem);
Writeln('I just did a signal on the semaphore.' );
mysem := SYSTEM( SEM_CLOSE, mysem );
END.
Exercises
4.1. Compile and execute the above two programs. Observe what happens when they block, both on the Devices display and on the CPU scheduler display.
shm1 and shm2 demonstrate the use of shared memory. The first program shm1 reads a number from the user and then writes it into the shared memory segment. Once the value has been written the second program shm2 reads the number from the shared memory segment, squares it and writes the result back to the shared memory segment. After the result has been written shm1 (the first program) then reads the result and displays it.
These two programs provide an example of using shared memory to communicate between two different processes.
All the waiting done by shm1 and shm2 is not achieved by magic. Instead semaphores are used to synchronise access to the shared memory segments. In some instances semaphores may be used to implement mutual exclusion on the shared memory (to make sure only one process can be modifying it at any one time).
PROGRAM Shm1 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Simple Shared memory example }
{ Binary semaphore used to synchronise access }
{ }
{ AUTHOR: David Jones }
{ DATE: 15-JAN-95 }
{-----------------------------------------------------------}
VAR
sem, shm, value, result, temp : INTEGER;
BEGIN
{ create semaphore and make sure it is set to 0 }
sem := SYSTEM(SEM_CREATE, 'mysem', 0);
{ create a shm segment that will hold 1 variable }
shm := SYSTEM(SHR_CREATE, 'myshm', 1);
IF (sem > 0) AND (shm > 0) THEN { create succeeds }
BEGIN
Write('Enter a number ==> ' );
Read( value# );
Writeln( 'Value was read' );
temp := SYSTEM(SHR_WRITE, shm, 0, value);
{ Signal other process that it now has a value to work with }
temp := SYSTEM( SEM_SIGNAL, sem );
{ Wait for other process to read value }
{ from shared memory and square it }
Write('Waiting.. ');
temp := SYSTEM(SEM_WAIT, sem);
WriteLn; WriteLn;
result := SYSTEM(SHR_READ, shm, 0);
WriteLn( value#, ' squared is ', result# );
temp := SYSTEM(SEM_CLOSE, sem);
temp := SYSTEM(SHR_CLOSE, shm)
END;
END.
PROGRAM shm2 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Simple Shared memory example }
{ Binary semaphore used to synchronise access }
{ }
{ AUTHOR: David Jones }
{ DATE: 15-JAN-95 }
{-----------------------------------------------------------}
VAR
mysem, myshm, value, result, temp : INTEGER;
BEGIN
mysem := SYSTEM(SEM_OPEN, 'mysem');
IF mysem > 0 THEN
BEGIN
{ wait for other process to signal it's }
{ placed a value in the shared memory }
Writeln( 'I am now waiting for a signal from other process ' );
temp := SYSTEM( SEM_WAIT, mysem );
myshm := SYSTEM(SHR_OPEN, 'myshm' );
IF myshm > 0 THEN
BEGIN
{ Get Value from shared memory }
value := SYSTEM(SHR_READ, myshm, 0 );
Write( 'I read ', value# );
WriteLn( ' from shared memory, squaring it..' );
result := value * value;
temp := SYSTEM(SHR_WRITE, myshm, 0, result );
{ tell other process I've squared the value }
temp := SYSTEM(SEM_SIGNAL, mysem );
temp := SYSTEM(SEM_CLOSE, mysem);
temp := SYSTEM(SHR_CLOSE, myshm)
END
END
END.
Exercise
4.2. Compile and execute the two shared memory programs above. Observe their behaviour.
4.3. The synchronisation of the two shm programs is not implemented correctly. Start shm1, enter a number and press return (don't start shm2). What happens? Why?
The last two programs serve two purposes
The problem above is caused by the fact that the programs use one semaphore to block on two different events
shm1 has to block waiting for shm2 to calculate the result, and
shm2 has to block waiting for shm1 to place the number into the shared memory segment.
One solution to this problem is to use a different semaphore for each event. That is shm1 will block on one semaphore while shm2 will block on another one.
These programs perform the same tasks as shm1 and shm2 but instead of squaring a single number they square ten numbers.
PROGRAM Shm1 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Shared memory example using shm segment array }
{ Binary semaphore used to synchronise access }
{ AUTHOR: David Jones }
{ DATE: 17-JAN-95 }
{-----------------------------------------------------------}
VAR
count, semA, semB, shm, value, result, temp : INTEGER;
BEGIN
{ create two semaphores and set both to 0 }
{ semA - is used to block Shm1 waiting for Shm2 }
{ semB - is used to block Shm2 waiting for Shm1 }
semA := SYSTEM(SEM_CREATE, 'sem1', 0);
semB := SYSTEM(SEM_CREATE, 'sem2', 0);
{ create shared memory segment for 10 values }
shm := SYSTEM(SHR_CREATE, 'myshm', 10);
IF (semA > 0) AND (shm > 0) AND (semB > 0 ) THEN
BEGIN
FOR count := 1 TO 10 DO
BEGIN
Write( count#, ', ' );
temp := SYSTEM(SHR_WRITE, shm, count-1, result )
END;
Writeln; Writeln;
{ Signal other process that it now has a value to work with }
temp := SYSTEM( SEM_SIGNAL, semB );
{ Wait for other process to read value }
{ from shared memory and square it }
Write('Waiting.. ');
temp := SYSTEM(SEM_WAIT, semA);
WriteLn; WriteLn;
FOR count := 1 TO 10 DO
BEGIN
result := SYSTEM(SHR_READ, shm, count-1);
WriteLn( count#, ' squared is ', result# )
END;
{ close all the IPC primitives }
temp := SYSTEM(SEM_CLOSE, semA);
temp := SYSTEM(SEM_CLOSE, semB);
temp := SYSTEM(SHR_CLOSE, shm)
END;
END.
PROGRAM shm2 (INPUT, OUTPUT);
{-----------------------------------------------------------}
{ PURPOSE: Simple Shared memory example }
{ Binary semaphore used to synchronise access }
{ AUTHOR: David Jones }
{ DATE: 18-JAN-94 }
{-----------------------------------------------------------}
VAR
count, mysemA, mysemB, myshm, value, result, temp : INTEGER;
BEGIN
mysemA := SYSTEM(SEM_OPEN, 'sem1');
IF mysemA > 0 THEN { if TRUE then sem has been created }
BEGIN
mysemB := SYSTEM(SEM_OPEN, 'sem2');
Writeln( 'I am now waiting for a signal from other process ' );
{ wait for other process to signal it's }
{ placed a value in the shared memory }
temp := SYSTEM( SEM_WAIT, mysemB );
myshm := SYSTEM(SHR_OPEN, 'myshm' );
IF myshm > 0 THEN
BEGIN
FOR count := 1 TO 10 DO
BEGIN
{ Get Value from shared memory }
value := SYSTEM(SHR_READ, myshm, count-1 );
Write( 'I read ', value# );
WriteLn( ' from shared memory, squaring it..' );
result := value * value;
{ write result back to shared memory }
temp := SYSTEM(SHR_WRITE, myshm, count-1, result )
END;
{ tell other process I've squared the value }
temp := SYSTEM(SEM_SIGNAL, mysemA );
temp := SYSTEM(SEM_CLOSE, mysemA);
temp := SYSTEM(SEM_CLOSE, mysemB);
temp := SYSTEM(SHR_CLOSE, myshm)
END
ELSE
BEGIN
Writeln('ERROR: You must execute shmarr1 before this program.')
END
END
END.
It is possible to incorporate both actions into one program by using the return value of the create function. The program SEMTX9.PLL included with RCOS provides one demonstration of this. The basic method is summarised below.
result := SYSTEM( SEM_CREATE, "mySEM" );
IF result > 0 THEN { CREATE successful }
THEN
BEGIN
{ do create program actions }
END
ELSE
BEGIN
{ create not successful so try to open it }
result := SYSTEM( SEM_OPEN, "mySEM" );
{ do open program actions }
END;
Exercise
4.4. Rewrite the sem1 and sem2 example programs using the above
mechanism.
Programs make use of IPC primitives by using language level facilities that allow the program to access the operating system's system calls. The facility of RCOS's PLL/2 language that provide access to RCOS's system calls is the function SYSTEM.
RCOS provides both binary and counting semaphores. The semaphore operations that RCOS supports are SEM_OPEN, SEM_CREATE, SEM_WAIT, SEM_SIGNAL and SEM_CLOSE.
Shared memory under RCOS allows processes to share a single variable (PLL/2's integer type) using the operations SHR_OPEN, SHR_CREATE, SHR_READ, SHR_WRITE and SHR_CLOSE.