Category : Alternate Operating Systems - Quarterdeck DesqView, CP/M, etc
Archive   : PCCAPP.ZIP
Filename : PROGAPP.TXT

 
Output of file : PROGAPP.TXT contained in archive : PCCAPP.ZIP




A Primer on


Programming Applications


in PC-Choices



Antonio Lain, Lee Lup Yuen and Roy H. Campbell


July 16, 1992


Contents


1 Introduction 2


2 Objects in Choices 2
2.1 Introduction : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :*
* 2
2.2 Smart pointers : : : : : : : : : : : : : : : : : : : : : : : : : : : : *
* 3


3 The Standard Nameserver 5


4 Input/Output from the terminal 6


5 Using Timers 11
5.1 Free Running Timers : : : : : : : : : : : : : : : : : : : : : : : : : 11
5.2 Timeout Timers : : : : : : : : : : : : : : : : : : : : : : : : : : : 12


6 Process creation, communication and synchronization 13
6.1 Process Creation : : : : : : : : : : : : : : : : : : : : : : : : : : : *
*14
6.2 Process Synchronization : : : : : : : : : : : : : : : : : : : : : : : 15
6.3 Process Communication : : : : : : : : : : : : : : : : : : : : : : : 17


A Learning to type 18
A.1 Line.h : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : *
*: 18
A.2 Type1.cc : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :*
* 19


B Learning to type: two processes (Type2.cc) 23

1


1 Introduction


This document is an informal introduction to building applications for Choices.
It is neither an exhaustive nor a technical description of Choices Application
Programming Interface (API). The purpose of the document is to show, using
simple examples, how to build Choices applications. The programmer will find
that Choices is organized as an object-oriented system, both inside and out.
Many features in Choices resemble features in other systems and we hope that
this makes programming easier. However, some features are very different.
While our documentation needs much improvement, we hope that this guide
will be of use to the more adventurous programmer who wants to learn more
about object-oriented operating systems.
An API is an abstract interface to the underlying operating system that
allows you to program applications without knowing its internal details. For
example, the Unix API will let you use stdout, stdin together with printf() and
scanf() to print and read from the terminal. Whether the terminal is a VT-
100 or a workstation console, the functions are called in the same manner.
Similarly, file I/O is performed through streams declared FILE and operations
like fopen(), fwrite() and fread(). The application programmer does not have to
program detailed code to store files nor to provide I/O redirection.
The underlying structure of Choices is different from most operating sys-
tems you will have encountered. It is built as an object-oriented system and the
API is object-oriented. Thus, Choices provides a very different API from, say,
Unix or MS-DOS. The object-oriented nature of Choices allows a programmer
to program interactions between the application and the operating system as
interactions between objects. In addition, because of object-orientation, the
programmer can choose between several different levels of abstraction when pro-
gramming these interactions. Thus, in one module, the programmer may treat
all operating system components as `objects' that have names like `idleProcess'.
In another module, the programmer may use the specialized properties of a
particular object.



2 Objects in Choices


2.1 Introduction

Choices is built from objects. Each object is defined by a class that specifies*
* the
set of messages that the object can receive. An object encapsulates data that
represents its internal state. A message may be sent to an object to invoke the
execution of one of its methods. Choices is written in the C++ programming
language. Each class that is used in Choices is represented as an object in the
system. In particular, it is possible to send a message to a class in order to *
*find
out more about the properties of an object defined by the class. Objects can be
2


created, deleted, stored in files, passed in messages to other objects, or retu*
*rned
from methods.
Standard input and output are objects that may be accessed by the appli-
cation through the pointer variables StandardInput and StandardOutput. These
variables are assigned values for the standard input and output objects at ap-
plication startup time. For example, suppose we wish to print the message
`Hello Sailor!' on the standard output. We send the message formattedWrite()
together with a `Hello Sailor!' string argument to StandardOutput using the
following program fragment:


StandardOutput -> formattedWrite ("Hello Sailor!"n");


Access to the many objects provided by Choices is very useful in application*
*s.
In order to send them messages, we need to acquire pointers to them. Section
3 will explain how to obtain these pointers.



2.2 Smart pointers

The kernel is a dynamic collection of objects representing processes, protected
memory spaces and resources. In C++, it is difficult to manage a large collecti*
*on
of objects efficiently without much programming effort. In Choices, we provide
smart pointers that support garbage collection using reference counting. As long
as applications can still reference a particular object, the object will remain*
* in
existence. However, when the last reference to that object is removed, the obje*
*ct
is deleted automatically.
Two sets of classes, named Star and Ref classes, are used to automate garbage
collection. In general, whenever we need a local or global variable that points
to a Choices object in an application program, we use a Star pointer variable.
For example, instead of using the following C++ code to reference an object:


#include "WhatsoeverObject.h"


WhatsoeverObject *MyPointer;
Mypointer = (WhatsoeverObject *) (..something..);


we reference a Choices object using:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


WhatsoeverObjectStar MyPointer;
Mypointer = (WhatsoeverObjectStar) (..something..);


Returning a reference from a function can lead to very inefficient reference
counting code in an automatic garbage collection scheme implemented without



3


the support of a compiler. However, a function may return a smart pointer to
a Choices object very efficiently using the standard C++ compiler by following
the Choices coding conventions.
The function is coded to return a Ref pointer. The Ref pointer must not be
used directly in the application code to access a Choices object. Instead, the
pointer should be assigned first to a Star local variable and the object access*
*ed
through that variable. The following application code is correct:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


extern WhatsoeverObjectRef TrickyFunction();


WhatsoeverObjectStar MyPointer;
Mypointer = TrickyFunction();
Mypointer -> something();


But this is not:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


extern WhatsoeverObjectRef TrickyFunction();


TrickyFunction() -> something();


In addition, the Choices convention requires the formal parameters of a func-
tion to be normal pointers, not declared of type Star. Although normal pointers
to Choices objects are used for formal parameters, the actual parameters can
be of type Star. For example, this will work:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


extern void TrickyFunction (WhatsoeverObject *);


WhatsoeverObjectStar MyPointer;
TrickyFunction (MyPointer);


But this will not:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


extern void TrickyFunction (WhatsoeverObjectStar);



4




WhatsoeverObjectStar MyPointer;
TrickyFunction (MyPointer);


Because the objects in Choices are automatically garbage collected, it is not
necessary to call the destructor of an object pointed to by a Star. Moreover,
because Choices supports multiprocessing, shared memory and lightweight pro-
cesses, directly invoking the destructor of an object can result in an inconsis*
*tent
system state involving dangling pointers. That is, calling a Choices object de-
structor directly can cause the application to fail. Finally, Star pointers can*
* be
assigned to normal pointers within an application but care must be taken not
to use a dangling reference; the lifetime of an object is guaranteed only when
Star pointers are used.
The way smart pointers have been described is rather simplistic and there
are some exceptions to these rules. Nevertheless, these rules are applicable in
most of the cases. It is sometimes useful to implement a module with normal
pointers first and add the smart pointers when the system is already working.



3 The Standard Nameserver


Choices is built assuming the principle that an application should only be able
to access those objects it needs. When an application is first executed, it is
supplied with a small number of references to Choices objects that most appli-
cations need. These references StandardOutput, StandardInput, StandardFileSys-
temInterface and the StandardNameServer. References to other Choices objects
may be acquired by sending a lookup() message to the StandardNameServer.
The lookup() message includes, as parameters, an identifier for an object and a
specification of its class. The nameserver lookup() method returns a pointer to
that object if it both finds the object and the application has the appropriate
permission to use it. In certain cases, the nameserver will return a pointer to*
* an
object that is a subclass of the one required, but this should not be a problem.
The StandardNameServer contains references to many objects that may be
used by applications. For example, to access a Choices object named `John' of
class `WhatsoeverObject' we code:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"


WhatsoeverObjectStar MyObject;


MyObject = (WhatsoeverObjectStar) StandardNameServer ->
lookup ("John", "WhatsoeverObject");


5


If the nameserver cannot find an object to match the lookup request, it will
return zero. We recommend always checking that the address returned by the
nameserver is non-zero.
The StandardNameServer includes the Class objects that represent classes
used in the construction of Choices. We can write the previous example in the
following way:


#include "WhatsoeverObject.h"
#include "WhatsoeverObjectStar.h"
#include "Class.h" // ClassStar.h is automatically included


WhatsoeverObjectStar MyObject;
ClassStar WhatsoeverObjectClass;


WhatsoeverObjectClass = (ClassStar) StandardNameServer ->
lookup ("WhatsoeverObject", "Class");
MyObject = (WhatsoeverObjectStar) StandardNameServer ->
lookup ("John", WhatsoeverObjectClass);


The previous example does not show the advantages of having Class objects.
Class objects can respond to messages like parent() and child() that will return
pointers to the superclass and subclass objects. They also can find all the obj*
*ects
that are of that class using the getKindred() message. Every Choices object has
a method that allows one to enquire the object about its class. When the
classOf() message is sent to any Choices object, the Class object representing
the class of the object will be returned. This way, it is possible to implement*
* an
application that will try to obtain an object of a particular class and, if it *
*fails to
do so, to use an object of a superclass. (The resulting application may run less
efficiently than with the actual subclass, but it will have the same functional*
*ity.)
Because of the support for classes and objects in Choices, the class hierarchy *
*of
Choices does not have to be compiled into the application code (that is, the cl*
*ass
hierarchy of Choices does not have to be statically bound into the application
code).



4 Input/Output from the terminal


In Section 2, we introduced the input and output objects pointed to by the
variables StandardOutput and StandardInput. These objects are of class Out-
putStream and InputStream respectively. The messages formattedWrite() and
formattedRead() are used like the popular C printf() and scanf() functions. An
example of their use is the following:


#include "OutputStream.h"
#include "InputStream.h"



6




int main (int, char **)
-
// This program asks for your age, inputs it and prints how many months
// old you are. Calling formattedWrite() on the StandardOutput does not
// necessarily mean that the text will appear on the screen immediately.
// So we call flush() on StandardOutput to force the text to appear on
// the screen right away. StandardOutput and StandardInput are special
// variables that are always available to applications and are always
// set to the screen output and keyboard input respectively.
int age;
StandardOutput -> formattedWrite ("What's your age?"n");
StandardOutput -> flush ();
StandardInput -> formattedRead ("%d", &age);
StandardOutput -> formattedWrite ("you are %d months old"n", 12*age);
StandardOutput -> flush ();
return 0;
"

The << operator provides a more convenient way to write output to an
OutputStream. The example above is equivalent to the following one:

#include "OutputStream.h"
#include "InputStream.h"


int main (int, char **)
-
// This program asks for your age, inputs it and prints how many months
// old you are. We print text by using the "<<" operator with the
// StandardOutput. Calling "<< eor" has the same effect as calling
// flush() on the StandardOutput. We call "<< eor" on the StandardOutput
// so that whatever text we have just printed will appear on the screen
// right away. StandardOutput and StandardInput are special variables
// that are always available to applications and are always set to the
// screen output and keyboard input respectively.
int age;
*StandardOutput << "What's your age?"n" << eor;
StandardInput -> formattedRead ("%d", &age);
*StandardOutput << "you are " << 12*age << " months old"n" << eor;
return 0;
"

Note that `<< eor' has the same effect as calling flush(). OutputStream and
InputStream also support simpler messages read() and write() when formatted
input/output is not required. In this case, the parameters are a pointer to a



7


buffer and the number of bytes to read or write. The example below writes
every character typed and stops when # is typed.


// sample1.cc: Echo keyboard input. Stops when '#' is typed.
#include "OutputStream.h"
#include "InputStream.h"


int main (int, char **)
-
// This program repeatedly reads keypresses and prints them until you
// press '#'. To read a single keypress, we call the read() method on
// the StandardInput, the arguments being an array to hold the character
// typed (array c), and the number 1, to indicate that we want to read only
// one character from the keyboard. read() will return the number of
// characters read. StandardInput (of type InputStream) and StandardOutput
// (of type OutputStream) are special global variables used for reading
// characters from the keyboard and for writing characters to the screen.
*StandardOutput << "Type a sequence of keys ending with #"n" << eor;
char c [1];
int charsRead = StandardInput -> read (c, 1);
while ((c [0] != '#') && (charsRead > 0))
-
StandardOutput -> write (c, 1);
*StandardOutput << eor;
charsRead = StandardInput -> read (c, 1);
"
return 0;
"



This section discusses the MS-DOS stream-oriented file system supported
by Choices, although Choices also supports several different file systems.
The file system is accessed through an object of class FileSystemInterface
accessible through the global variable StandardFileSystemInterface. This object
enables the programmer to create new files, open existing files with a certain
mode of access, change the current directory and perform other familiar file
system operations.
A file is opened by sending an open() message to the FileSystemInterface ob-
ject. The method returns a FileStream object that may be sent read() or write()
messages. In the following examples, we use the strong type checking of C++ to
ensure that files are used with the correct access mode. The two major access
modes are represented by classes WriteFileStream and ReadFileStream, which
are subclasses of FileStream. They support write-only and read-only access to a
FileStream, respectively.



8


The basic messages supported by the FileStreams are read() or write() (de-
pending on the access mode), setOffset() and offset(). The read() and write()
messages are used in the same manner as for InputStreams and OutputStreams
(Section 4). A FileStream contains a pointer to the current read/write position
in the file. The pointer may be moved by using the setOffset() message, which
behaves like the Unix version of fseek() as shown in Table 1. The programmer
may inquire a FileStream about the location of its pointer by sending the offse*
*t()
message.


int setOffset(int delta, int mode);


______________________________________________________
|_Mode_|_Interpretation_|____New_pointer_position_____|
| 0 B|eginning of file | delta |
| 1 |Current offset c|urrent pointer position + delta |
|____2____|_End_of_file____|_____file_size_+_delta____ |

Table 1: Arguments for FileStream::setOffset()


In the next example, a file is opened in write-only mode if it already exist*
*s.
Characters are read from the terminal and appended at the end of the file. If
the file did not already exist when it is opened, it is created. This behaviour*
* is
similar to a Unix file opened in append mode.


// sample2.cc: Write keyboard input into the file "out.1". If "out.1"
// already exists, the keyboard input is appended to the file. Stops when
// '#' is typed.
#include "OutputStream.h"
#include "InputStream.h"
#include "FileSystemInterface.h"
#include "WriteFileStream.h"
#include "WriteFileStreamStar.h"


int main (int, char **)
-
// This program opens the file "out.1", repeatedly reads keypresses and
// appends them to the file until '#' is typed. The file "out.1" is
// created if it doesn't exist. To find out whether it exists, we call
// open() on the StandardFileSystemInterface, telling it to open the
// file "out.1" for writing. If the StandardFileSystemInterface returns an
// error code, we assume that the file doesn't exist, and we can go ahead
// to create it. To create the file, we call create() on the Standard
// FileSystemInterface, and abort with an error message if create() fails.
//
// If the file was opened or created successfully, open() or create()



9


// returns a WriteFileStream object (call it outf). If open() succeeded,
// the file must already exist, and we should append data to it. To
// prepare the file for appending data, we set the file pointer to the
// end of the file; this is done by calling setOffset() on outf. After
// that, we can read keypresses and write them into the file by calling
// write() on outf.
//
// We do not have to close outf explicitly because we are using smart
// pointers here - outf is a WriteFileStreamStar that contains a pointer
// to a WriteFileStream object, and when outf goes out of scope, it will
// be destroyed, along with the WriteFileStream object that it references.
// When the WriteFileStream object is destroyed, the file will be closed.
// The StandardFileSystemInterface is a special global variable of type
// FileSystemInterface used for accessing file services.


int error;
// Open the file "out.1" for writing.
WriteFileStreamStar outf = StandardFileSystemInterface ->
open ("out.1", WriteFileStreamClass, error);
if (error)
-
// If file can't be opened, assume it doesn't exist. Now create it.
outf = StandardFileSystemInterface -> create ("out.1",
WriteFileStreamClass, 0666, error); // 0666: permissions on the file.
if (error)
-
// Can't create the file.
*StandardOutput << "*** error " << error << " while openning outfile"n"
<< eor;
return 0;
"
"
else
-
// Seek to the end of the file: 2 means use end-of-file as the origin,
// 0 is the offset from the origin.
if (outf -> setOffset (0, 2) == -1)
-
// Can't seek.
*StandardOutput << "*** error while setting offset"n" << eor;
return 0;
"
"
*StandardOutput << "Type a sequence of keys ending with #"n" << eor;



10


char c [1];
int charsRead = StandardInput -> read (c, 1);
while ((c [0] != '#') && (charsRead > 0))
-
outf -> write (c, 1);
charsRead = StandardInput -> read(c,1);
"
// outf goes out of scope here and the file is automatically closed.
return 0;
"



In this example, it is possible to close the file by assigning another FileS*
*tream
object to the smart pointer outf, or by leaving the block in which the smart
pointer is declared. The smart pointer will close the file automatically when t*
*he
references to the file are deleted.
A slightly more complicated example is shown in Section A. In this case, we
have implemented a class Line that is basically a string of characters that has
methods to write and read itself from files or a terminal. The message equals()
compares two strings. This class is used to implement a simple typing tutor
program. A `teacher' line is read from a file and presented on the screen. Then
a `student' line is read from the terminal and if they are equal a new `teacher'
line is read from the file. Otherwise the same `teacher' line is presented agai*
*n.
This process is repeated until we reach the end of the file or the student deci*
*des
that he had enough and keys #.



5 Using Timers


Timers are objects in Choices. Timers are useful for measuring performance
and recovering from errors. In this section we will discuss two particular clas*
*ses
of timers: free running timers and timeout timers.



5.1 Free Running Timers

The nameserver provides access to the Choices object SystemTimer of class
FreeRunningTimer. This object responds to the message time() by returning
the time in microseconds since Choices was started. The system timer can be
used to measure the time spent on a particular part of an application program,
as in the following example:


#include "FreeRunningTimer.h"
#include "FreeRunningTimerStar.h"
...
unsigned int start, timespent;



11


FreeRunningTimerStar SystemTimer = (FreeRunningTimerStar)
StandardNameServer -> lookup ("SystemTimer", "FreeRunningTimer");
...
start = SystemTimer -> time();


... Some computation ...


timespent = SystemTimer -> time() - start;
*StandardOutput << "Time spent: " << timespent << " microseconds"n" << eor;
...


The problem with the system timer is that it rolls over every 4294 seconds
(or about an hour). The limitation is caused by using a 32-bit unsigned integer
to store the time.



5.2 Timeout Timers

Our application program can create (without using the nameserver) timers of
class TimeoutTimer. The main methods in this class are:


o unsigned int start(unsigned int microseconds): Starts the timeout timer wi*
*th
lapse microseconds. Returns the actual number of microseconds set.

o unsigned int stop(): Stops the timer. Returns the number of microseconds
left.

o unsigned int residual(): Returns the remaining number of microseconds.

o void await(): The current process waits until the timer expires.


In the following example a TimeoutTimer is used to print `Hello' on the scre*
*en
every lapse seconds.


// sample3.cc: Read a number "lapse" from StandardInput and print "Hello"
// 10 times after waiting "lapse" seconds each time.
#include "OutputStream.h"
#include "InputStream.h"
#include "TimeoutTimer.h"
#include "TimeoutTimerStar.h"


int main (int, char **)
-
// In order to wait "lapse" seconds, we create a TimeoutTimer object,
// call start() to tell it how long to wait, and then call await() to
// actually do the waiting; await() will not return until the timer has
// expired. The argument to start() is expressed in microseconds, so



12


// we have to multiply "lapse" by 100000 and pass the result as the
// argument. We use the TimeoutTimerStar smart pointer here so that
// we don't have to destroy the timer explicitly.


// Make a new timer.
TimeoutTimerStar timer = new TimeoutTimer;
int lapse;
*StandardOutput << "Time between hellos in seconds:"n" << eor;
StandardInput -> formattedRead ("%d", &lapse);
for (int i = 0; i < 10; i++)
-
// Start the timer.
timer -> start (lapse*1000000);
// Wait for the timer to expire.
timer -> await ();
// Timer has expired. Print a message.
*StandardOutput << "Hello"n" << eor;
"
return 0;
"

6 Process creation, communication and syn-

chronization


An advantage of Choices over many other operating systems is that it is pos-
sible to express concurrency using lightweight processes. Lightweight processes
can be created with a small overhead because they share the memory space
of the process that created them and can access the same global variables. A
change made to a global variable by one of the processes is visible to the othe*
*rs.
Communication between lightweight processes can be implemented in this way.
It is also possible to create Unix-style heavyweight processes in Choices us-
ing the ApplicationDispatcher methods. In this case, each heavyweight process
has its own protected memory space and independent set of global variables.
Such processes can perform interprocess communication through shared mem-
ory or messages, but the set up costs are higher and the API is a little more
complicated.
Whenever shared memory is used for communication, a synchronization
mechanism is required to provide critical sections and signalling. In order to
synchronize processes, Choices supports semaphores.
13


6.1 Process Creation

In Choices, processes are objects of class Process or one of its subclasses. Ap*
*pli-
cation processes are objects of class ApplicationProcess. The ApplicationProcess
constructor can be used to build a process and it requires as an argument a
pointer to a main `function' that will be executed by the process. A `command
line' to be passed to the process may also be specified if one is desired. Once
the process is created, it can be dispatched on the processor by placing it into
the Choices process scheduler. Sending the message ready() to the process ob-
ject will put the process into the process scheduler. A simple example is shown
below.

// sample4.cc: Create a lightweight process, print something in the current
// process and in the new process.
#include "OutputStream.h"
#include "Process.h"
#include "ProcessStar.h"


int main2 (int, char **)
-
// This function is the entry point of the lightweight process created
// in main(). When the process exits from this function, the process
// will be terminated.


// The new lightweight process will start here...
*StandardOutput << "hello sailor 2!"n" << eor;
// ...and die here.
return 0;
"


int main (int, char **)
-
// We create a lightweight process by making a new ApplicationProcess.
// The ApplicationProcess constructor accepts an argument that specifies
// which function to be executed when the new process runs. The process
// will not run automatically when we create it; we have to call ready()
// on the process to make it run (when the CPU is available). We use the
// ProcessStar smart pointer here so that we don't have to destroy the
// process explicitly.


// Create a new lightweight process that will start at main2().
ProcessStar proc = new ApplicationProcess (main2);
if (proc == 0)
-
// Can't create process.



14


*StandardOutput << "*** error while creating a process"n" << eor;
return 0;
"
// The new process won't run until we say ready().
proc -> ready();
// At this point, the new process will run when it gets the CPU.
*StandardOutput << "hello sailor 1!"n" << eor;
return 0;
"



This example above may exhibit interference between the two concurrent
processes. The StandardOutput object is shared by both processes because of
the shared memory. The processes can be synchronized to use StandardOutput
in a coordinated way using semaphores.



6.2 Process Synchronization

Semaphores may be used in the previous example to ensure that only one process
is using StandardOutput at a time. Semaphores in Choices are objects of class
Semaphore. The constructor allows the programmer to specify an initial value
for the semaphore and the maximum value (default 65535). The P() and V()
messages of a semaphore are used in the conventional way.


// sample5.cc: Create a lightweight process, print something in the current
// process and in the new process in a synchronized manner.
#include "OutputStream.h"
#include "Semaphore.h"
#include "Process.h"
#include "ProcessStar.h"


// This semaphore is for controlling access to the StandardOutput. Each
// process must P() this semaphore before using the StandardOutput, and
// V() it StandardOutput. Note that this semaphore lives in global variable
// space, and is hence accessible by lightweight processes, since they
// share the same global variable space. The semaphore is allocated in
// main().
Semaphore *sem;


int main2 (int, char **)
-
// This function is the entry point of the lightweight process created
// in main(). When the process exits from this function, the process
// will be terminated. When we want to print something, we call P()
15


// on the semaphore to enter the critical section, then use the
// StandardOutput, and call V() on the semaphore to leave the critical
// section.


// Grab the semaphore for the StandardOutput.
sem -> P ();
- // Start of critical section.
*StandardOutput << "hello sailor 2!"n" << eor;
" // End of critical section.
// Release it.
sem -> V ();
return 0;
"


int main (int, char **)
-
// We first create a semaphore to control access to the StandardOutput.
// We do that by allocating a new Semaphore object, and pass the value
// 1 to the Semaphore constructor as the initial counter value in the
// Semaphore. (P() will decrement the counter and block the current
// process if the decremented value is less than 0.)
//
// We create a lightweight process by making a new ApplicationProcess.
// The ApplicationProcess constructor accepts an argument that specifies
// which function to be executed when the new process runs. The process
// will not run automatically when we create it; we have to call ready()
// on the process to make it run (when the CPU is available). We use the
// ProcessStar smart pointer here so that we don't have to destroy the
// process explicitly.
//
// After that, whenever the old process or the new process wishes to
// print something, it calls P() on the semaphore to enter the critical
// section to use the StandardOutput, and calls V() to exit the critical
// section.


// Create the semaphore. 1 means the resource is initially available.
sem = new Semaphore (1);
ProcessStar proc = new ApplicationProcess (main2);
if (proc == 0)
-
*StandardOutput << "*** error while creating a process"n" << eor;
return 0;
";
proc -> ready ();



16


// Grab the semaphore for the StandardOutput.
sem -> P ();
- // Start of critical section.
*StandardOutput << "hello sailor 1!"n" << eor;
" // End of critical section.
// Release it.
sem -> V ();
return 0;
"



Note that in the previous example the semaphore is declared as a global
variable by the main program so that it may be shared with the lightweight
process.



6.3 Process Communication

Lightweight processes can communicate through shared variables located in
shared memory. The example that we are going to discuss is a dual-process
version of the typing tutor program introduced in Section ??. Its source code
is shown in Section B. In this case the functionality of the application has
been divided into two communicating processes representing the teacher and
the student. It is important to point out the following:


o Line.h and the class Line remain unchanged.

o Only one process is using StandardOutput at a time. Whenever a process
signals the other, the signalling process either finishes or waits for a s*
*ignal
to return from the signalled process.

o The variable NotEnd is global and is used to communicate the termination
condition to the other process.

o teacherline does not need to be global because the student process does
not need to access it. However, studentline is global because the teacher
process compares it with teacherline.

o As in the previous example, the semaphore used for signalling must be
global.



17


A Learning to type


A.1 Line.h

// line.h: Defines the class Line.
const MAXLINE = 160; // Maximum number of characters in a line.
const ENDCHAR = '#'; // Character to signify end of line.


class Line
-
// This class represents a line of text. Operations are provided to read
// and write the text, and also to compare lines.
public:
// Initialize my line - set it empty.
Line () - length = 0; "
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
int ReadfromKeyboard (InputStream *in, int &end);
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read, or until there are no more
// characters to be read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
int ReadfromFile (ReadFileStream *in, int &end);
// Write my line and a newline to "out". Return the number of characters
// written, including the newline.
int WriteToScreen (OutputStream *out);
// Write my line and a newline to "out". Return the number of characters
// written, including the newline.
int WriteToFile (WriteFileStream *out);
// Return the length of my line, not counting the newline at the end.
int Length () - return length; "
// Return true if l1 has the same contents as l2.
friend int equal (Line *l1, Line *l2);
private:
char linechar [MAXLINE]; // The characters in my line.
int length; // The number of characters in my line, not counting newline.
";


inline int equal (Line *l1, Line *l2)
18


-
// This function returns true if lines l1 and l2 are equal, that is, they
// have the same length and all their characters are equal.
if (l1 -> Length () != l2 -> Length ())
return 0;
else
-
int = l1 -> Length ();
for (int i = 0; i < ; i++)
if (l1 -> linechar [i] != l2 -> linechar [i])
return 0;
return 1;
"
"
A.2 Type1.cc

// type1.cc: Typing tutor - you type whatever you see on the screen, and the
// program will ask you to try again if the text that you typed isn't
// the same as the text that appeared on the screen. The program stops
// when you type '#'. The text to be typed comes from the file "in.1".
#include "OutputStream.h"
#include "InputStream.h"
#include "FileSystemInterface.h"
#include "ReadFileStream.h"
#include "ReadFileStreamStar.h"
#include "WriteFileStream.h"
#include "WriteFileStreamStar.h"
#include "line.h"


int Line::ReadfromKeyboard (InputStream *in, int &end)
-
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
length = 0;
int charsRead = in -> read (&linechar [length], 1);
while ((linechar [length] != ENDCHAR) && (charsRead > 0) &&
(linechar [length] != '"n'))
-
length++;



19


charsRead = in -> read (&linechar [length], 1);
"
if (linechar[length] != '"n')
end = 0;
else
end = 1;
return length;
"


int Line::ReadfromFile (ReadFileStream *in, int &end)
-
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read, or until there are no more
// characters to be read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
length = 0;
int charsRead = in -> read (&linechar [length], 1);
while ((linechar [length] != ENDCHAR) && (charsRead > 0) &&
(linechar [length] != '"n'))
-
length++;
charsRead = in -> read (&linechar [length], 1);
"
if (linechar [length] != '"n')
end = 0;
else
end = 1;
return length;
"


int Line::WriteToScreen (OutputStream *out)
-
// Write my line and a newline to "out". Return the number of characters
// written, not counting the newline.
int charWritten;
if (length != 0)
-
charWritten = out -> write (linechar, length + 1); // Outputs '"n' also.
out -> flush ();
return charWritten;
"
else return 0;



20


"


int Line::WriteToFile (WriteFileStream *out)
-
// Write my line and a newline to "out". Return the number of characters
// written, including the newline.
if (length != 0)
return out -> write (linechar, length + 1); // Outputs '"n' also.
else
return 0;
";


int main(int, char **)
-
// This program first calls open() on StandardFileSystemInterface to open
// the file "in.1" for reading. If the call was successful, open()
// returns a ReadFileStream object (call it inf). We then read a line
// from inf, print it, repeatedly read lines from the keyboard and print
// them until we have read a line from the keyboard that matches the line
// read from inf. We keep on doing this until the user presses '#' or
// inf has run out of lines.
//
// In greater detail:
// teacherline contains the line read from inf, and studentline contains
// the line read from the keyboard. We read a line from inf into
// teacherline by calling ReadfromFile() on teacherline; we print
// teacherline by calling WriteToScreen() on teacherline; we read a line
// from the keyboard into studentline by calling ReadfromKeyboard() on
// studentline; we print studentline by calling WriteToScreen() on
// studentline; we compare studentline and teacherline by calling
// equal(). NotEnd is set to false if '#' is typed or if inf has run
// out of lines.
//
// The smart pointer ReadFileStreamStar (inf) is used here so that we don't
// have to explicitly close the file - the file will be closed
// automatically when the smart pointer goes out of scope. Standard
// FileSystemInterface (of type FileSystemInterface) is a special global
// variable used for accessing file services. StandardInput and Standard
// Output are special global variables for performing I/O to and from
// the keyboard and the screen, respectively.


int error;
// Open "in.1" for reading.
ReadFileStreamStar inf = StandardFileSystemInterface -> open



21


("in.1", ReadFileStreamClass, error);
if (error)
-
// Can't open "in.1", maybe because it doesn't exist. Simply give up.
*StandardOutput << "*** error opening file in.1"n" << eor;
return 0;
"


*StandardOutput << "Type a sequence of keys ending with #"n" << eor;
int NotEnd;
Line *teacherline = new Line;
Line *studentline = new Line;
// Read the first line from the file and print it for the user to type in.
teacherline -> ReadfromFile (inf, NotEnd);
teacherline -> WriteToScreen (StandardOutput);
while (NotEnd)
-
// Read what the user types in and print it to the screen. NotEnd is
// set to false if user types '#'.
studentline -> ReadfromKeyboard (StandardInput, NotEnd);
studentline -> WriteToScreen (StandardOutput);
if (NotEnd)
-
// If user didn't type '#', check whether the displayed line and the
// typed line are equal.
if (equal (studentline, teacherline))
-
// If the user typed in exactly the same line as the line displayed,
// then read another line from the file and print it. NotEnd is set
// to false if inf has run out of characters.
teacherline -> ReadfromFile (inf, NotEnd);
teacherline -> WriteToScreen (StandardOutput);
"
else
// The lines didn't match; tell the user to type again.
*StandardOutput << "Try again"n" << eor;
"
"
delete teacherline;
delete studentline;
return 0;
"


22


B Learning to type: two processes (Type2.cc)


// type2.cc: Typing tutor, dual-process version. You type whatever you see
// on the screen, and the program will ask you to try again if the text that
// you typed isn't the same as the text that appeared on the screen. The
// program stops when you type '#'. The text to be typed comes from the
// file "in.1". One process reads lines from the file and prints them while
// another process reads lines from the keyboard and compares them with the
// lines read from the file. This program also computes the time taken
// to run the entire session.
#include "OutputStream.h"
#include "InputStream.h"
#include "NameServer.h"
#include "FileSystemInterface.h"
#include "ReadFileStream.h"
#include "ReadFileStreamStar.h"
#include "WriteFileStream.h"
#include "WriteFileStreamStar.h"
#include "FreeRunningTimer.h"
#include "FreeRunningTimerStar.h"
#include "Process.h"
#include "ProcessStar.h"
#include "Semaphore.h"
#include "line.h"


int Line::ReadfromKeyboard (InputStream *in, int &end)
-
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
length = 0;
int charsRead = in -> read (&linechar [length], 1);
while ((linechar [length] != ENDCHAR) && (charsRead > 0) &&
(linechar [length] != '"n'))
-
length++;
charsRead = in -> read (&linechar [length], 1);
"
if (linechar [length] != '"n')
end = 0;
else
end = 1;



23


return length;
"


int Line::ReadfromFile (ReadFileStream *in, int &end)
-
// Repeatedly read characters from "in" and write them into my line
// until a newline or ENDCHAR is read, or until there are no more
// characters to be read. Set "end" to true if a newline has
// been read. Return the number of characters read, not counting the
// newline. My old line is wiped out before reading any characters.
// Will not work if there are more than MAXLINE characters to be read.
length = 0;
int charsRead = in -> read (&linechar [length], 1);
while ((linechar [length] != ENDCHAR) && (charsRead > 0) &&
(linechar [length] != '"n'))
-
length++;
charsRead = in -> read (&linechar [length], 1);
"
if (linechar [length] != '"n')
end = 0;
else
end = 1;
return length;
"
int Line::WriteToScreen (OutputStream *out)
-
// Write my line and a newline to "out". Return the number of characters
// written, not counting the newline.
int charWritten;
if (length != 0)
-
charWritten = out -> write (linechar, length + 1); // Output '"n' also.
out -> flush();
return charWritten;
"
else return 0;
"


int Line::WriteToFile (WriteFileStream *out)
-
// Write my line and a newline to "out". Return the number of characters



24


// written, including the newline.
if (length != 0)
return out -> write (linechar, length + 1); // Output '"n' also.
else
return 0;
"


// This semaphore is used for synchronizing the two processes. The teacher
// process V()'s this semaphore to tell the student process to read a line
// from the keyboard, and then the teacher process P()'s this semaphore.
// The student process V()'s this semaphore to tell the teacher process
// that it has finished reading a line from the keyboard, and the student
// process P()'s this semaphore.
Semaphore *sem;


// Variables shared by both lightweight processes: NotEnd is false if
// the file ran out of lines or if the user typed '#'. studentline is
// the line typed by the user. The student process sets this variable when
// the user types a line, and the teacher process compares studentline
// with the line read from the file. The two global variables together with
// sem are accessible to both processes because they are lightweight
// processes which share the same global variable space.
int NotEnd;
Line *studentline = new Line;


int main2 (int, char **)
-
// This function runs as the teacher process, and the process dies when
// this function returns. The teacher process is created in main().
// This process opens the file "in.1" for reading by calling open() on
// the StandardFileSystemInterface, which returns a ReadFileStream object.
// We then repeated read a line from the file (by calling ReadfromFile()),
// print it (by calling WriteToScreen()), signal the student process to
// read a line from the keyboard, wait for the line to be read and compare
// the line read from the keyboard with the line from from the file.
// We continue to read lines from the keyboard and compare them with
// the line read from the file until they match.
//
// We quit when the file runs out of lines or when the user presses '#'
// (the student process signals this by storing false in NotEnd). Just
// before we quit, we wake up the student process.
//
// The StandardFileSystemInterface is a special global variable that
// is used for accessing file services.



25




// Lightweight process created in main() starts here.
int error;
// Open the file "in.1" for reading.
ReadFileStreamStar inf = StandardFileSystemInterface -> open
("in.1", ReadFileStreamClass, error);
if (error)
-
*StandardOutput << "*** error opening file "n" << eor;
return 0;
"


*StandardOutput << "Type a sequence of keys ending with #"n" << eor;
Line *teacherline = new Line;
// Read the first line from the file and print it.
teacherline -> ReadfromFile (inf, NotEnd);
teacherline -> WriteToScreen (StandardOutput);
sem -> V(); // Wake up the student process to read a line from keyboard.
sem -> P(); // Wait for the student process to finish reading the line.
while (NotEnd)
-
// NotEnd is true if user didn't type '#'. Check whether the line that
// the user has typed is equal to the line we have just read.
if (equal (studentline, teacherline))
-
// If so, read the next line from the file and print it. NotEnd is
// set to false if there are no more lines to be read from the file.
teacherline -> ReadfromFile (inf, NotEnd);
teacherline -> WriteToScreen (StandardOutput);
"
else
// The lines don't match; tell the user to type again.
*StandardOutput << "Try again"n" << eor;
if (NotEnd)
-
sem -> V(); // Wake up student process to read a line from keyboard.
sem -> P(); // Wait for student process to finish reading the line.
"
"
sem -> V(); // Wake up student process in case it's still waiting.
delete teacherline;
// This process dies here.
return 0;
"



26




int main (int, char **)
-
// This process runs as the student process, reading lines from the
// keyboard and waking up the teacher process when the lines are ready.
// In this process, we first create a semaphore for synchronizing the
// two processes. We then fetch the SystemTimer from the StandardName
// Server by calling lookup(). We create the teacher process and
// make it runnable by calling ready() on the process. By calling
// time() on the SystemTimer, we obtain the current system time in
// microseconds; this value is stored in "start".
//
// After that, we repeatedly read lines from the keyboard, print them,
// signal the teacher process to process the line and wait for the teacher
// process to finish processing the line. We stop when the user presses
// '#' or when the file runs out of lines (parent process indicates this
// by storing false in NotEnd). Next, we signal the teacher process
// to wake up. Finally we print out the duration of the session by
// subtracting the starting time ("start") from the current time
// (obtained by calling time() on the SystemTimer).
//
// Lines read from the keyboard are stored in the global variable
// studentline. studentline is read from the keyboard by calling
// ReadfromKeyboard(); studentline is printed by calling WriteToScreen().
//
// StandardNameServer is a special global variable used for fetching
// system objects.


// Create a semaphore and set its counter to 0.
sem = new Semaphore (0);
unsigned int start, duration;
// Fetch the system timer from the StandardNameServer.
FreeRunningTimerStar SystemTimer = (FreeRunningTimerStar)
StandardNameServer -> lookup ("SystemTimer", "FreeRunningTimer" );


// Create the teacher process which will start execution at main2().
ProcessStar proc = new ApplicationProcess (main2);
// Process will not run until we ready() it.
proc -> ready ();
// Remember the time now.
start = SystemTimer -> time ();
// Wait for teacher process to read a line from the file and print it.
sem -> P ();
while (NotEnd)



27


-
// NotEnd is true if the file hasn't run out of lines. Read a line
// from the keyboard and set NotEnd to false if user types '#'.
studentline -> ReadfromKeyboard (StandardInput, NotEnd);
studentline -> WriteToScreen (StandardOutput);
if (NotEnd)
-
sem -> V (); // If user didn't type '#', wake up the teacher process.
sem -> P (); // Wait for teacher process to read a line from the file.
"
"
sem -> V (); // Wake up the teacher in case it's still waiting.


delete studentline;
// Print the total duration of this session.
duration = SystemTimer -> time () - start;
*StandardOutput << "Time typing " << duration << " microseconds"n" << eor;
return 0;
"



28


  3 Responses to “Category : Alternate Operating Systems - Quarterdeck DesqView, CP/M, etc
Archive   : PCCAPP.ZIP
Filename : PROGAPP.TXT

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/