Reentrancy
As mentioned earlier, there is a possible issue with the reentrancy of
code. The wop class, as it stands, is vulnerable if objects of this
class are likely to be accessed by multiple program threads " i.e.
tasks in a multi-tasking system or interrupt service routines as well
as mainline code. If the interrupt occurred between updating of the
shadow data and writing to the port, problems would result.
As is common in many real-time programming situations, imparting
reentrancy is a matter of locking and unlocking a resource. To do this
we need to extend the class slightly by adding two further (private)
member functions:
class wop
{
int shadow;
int* address;
int initval;
void lock()
{ }; // dummy
functions
void unlock()
{ };
public:
wop(long, int);
// constructor
~wop(); // destructor
void operator|=(int);
// overloaded
void operator&=(int);
// operators
};
For the moment the two functions " lock() and unlock() " are dummies
and do nothing. It is possible to imagine how they might really work.
Perhaps they would simply disable and re-enable interrupts. Some
possibilities will be discussed later.
These functions are used to make the two operator functions
reentrant, thus:
void wop::operator|=(int val)
{
lock();
shadow |= val;
// set bit(s) in copy
*address =
shadow; //
update port
unlock();
}
void wop::operator&=(int val)
{
lock();
shadow &= val;
// clear bit(s) in copy
*address = shadow;
// update port
unlock();
}
To implement these functions in the most flexible way, it is
possible to take advantage of the object oriented capabilities of C++.
If the functions are made replaceable (virtual), new variants may be
deployed without further changes to the class:
class wop
{
int shadow;
int* address;
int initval;
virtual void lock();
// replaceable functions
virtual void unlock();
public:
wop(long);
// constructor
~wop();
//
destructor
void operator|=(int);
// overloaded
void operator&=(int);
// operators
};
A new class - rwop - may be derived from the wop base class, which
inherits all of its characteristics, but adds function locking
functions:
class rwop : public wop
{
int flag;
void lock()
// replacement functions
{
while (flag)
;
flag = 1;
};
void unlock()
{
flag = 0; };
public:
rwop(long, int);
// constructor
};
The lock() and unlock() functions now use a private member variable
(flag) to control access to the object. (This is not strictly
reentrant, but serves to illustrate the point for the moment.) A new
constructor is required:
rwop::rwop(long port,
int init=0) :
wop(port, init)
{
unlock();
}
This simply executes the wop constructor code and calls unlock().Now
the application programmer can use the rwop class in exactly the same
way as wop was used and does not really need to know that anything has
been changed:
main()
{
rwop out(0x10000, 0x0f);
out |= 0x30;
// set bits 4
and 5
out &= ~7;
// clear bits 0, 1 and 2
}
Using an RTOS
If reentrancy is needed because an RTOS is in use, it is logical to use
the services provided by the kernel to create a reentrant derivative of
the wop class:
class vwop : public wop
{
int* mbox;
int err;
void lock() //
replacement functions
{
sc_pend(&mbox, 0, &err);
};
void unlock()
{
sc_post(&mbox, (char*)1,
&err);
};
public:
vwop(long, int); // constructor
};
In this case a mailbox is used to store a token. A binary semaphore
is another - perhaps more likely - way to implement the flagging. Again
the application programmer can use the vwop class in an identical way
to the earliest wop class.
The vwop class successfully encapsulates two separate, mutually
exclusive fragments of expertise: knowledge of the write-only port
hardware and RTOS programming experience. The application programmer
needs no awareness of either.