AA EU41A TE_VAXELN_Application_Design_Guide_Ver_2.0_Mar85 TE VAXELN Application Design Guide Ver 2.0 Mar85

AA-EU41A-TE_VAXELN_Application_Design_Guide_Ver_2.0_Mar85 AA-EU41A-TE_VAXELN_Application_Design_Guide_Ver_2.0_Mar85

User Manual: AA-EU41A-TE_VAXELN_Application_Design_Guide_Ver_2.0_Mar85

Open the PDF directly: View PDF PDF.
Page Count: 134

DownloadAA-EU41A-TE_VAXELN_Application_Design_Guide_Ver_2.0_Mar85 AA-EU41A-TE VAXELN Application Design Guide Ver 2.0 Mar85
Open PDF In BrowserView PDF
First Edition - March 1985
This manual contains sample VAXELN programs
for use and reference in designing VAXELN
applications.

VAXELN

Application Design Guide

Document Order Number: AA-EU41A-TE
Software Version: 2.0
digital equipment corporation
maynard. massachusetts

First Edition, March 1985

The information in this document is subject to change without
notice and should not be construed as a commitment by Digital
Equipment Corporation. Digital Equipment Corporation assumes
no responsibility for any errors that may appear in this document.
The software described in this document is furnished under a
license and may be used or copied only in accordance with the terms
of such license.
No responsibility is assumed for the use or reliability of software on
equipment that is not supplied by Digital Equipment Corporation
or its affiliated companies.
Copyright c 1985 by Digital Equipment Corporation
All rights reserved. Printed in U.S.A.

The postage-paid READER'S COMMENTS form on the last page of
this document requests your critical evaluation to assist us in
preparing future documentation.
The Digital logo and the following are trademarks of Digital
Equipment Corporation:
DATATRIEVE
DEC
DECmate
DECnet
OECset
DECsystem-l0
DECSYSTEM-20
DECtape
DECUS
DECwriter

DIBOl
lSI-l1
MASSBUS
M ICRO/PDP-l1
MicroVAX
MicroVMS
PDP

P/OS
Professional
Rainbow

UNIX is a trademark of AT&T Bell Laboratories.

ii

RSTS
RSX
UlTRIX
UNIBUS
VAX
VAXElN
VMS
VT
Work Processor

Contents
Preface
Overview
Structuring VAXELN Applications, vii
Multiple Jobs, vii
Single Job, viii
Designing Communication Protocols, ix
Application 1: Asynchronous I/O
Example (application 1.pas), 1-3
Application 2: C Device Driver
Example (application2.c), 2-5
Application 3: C Interface to Disk and File Utilities
Example (application3.c), 3-5
Application 4: Fast Device-Handling
Example (application4.pas), 4-4
Application 5: FORTRAN Routine Inclusion
C Example (application5a.c), 5-4
Pascal Example (application5b.pas), 5-7
FORTRAN Subroutines (application5c.for), 5-9
Application 6: Interjob Communication
Example (application6a.pas, application6b.pas), 6-4
Application 7: Intra-Job Synchronization
Example (appiication7.pas), 7-3
Application 8: Making a Bootable Floppy Disk
Example (application8.pas), 8-3

iii

Application 9: Multiple Circuit Server
C Example (application9a.c), 9-5
Sample Application (application9b.c), 9-12
Pascal Example (application9c.pas), 9-15
Sample Application (application9d.pas), 9-22
Application 10: Self-Defining Data Structures
Example (application 10.pas), 10-3
Application 11: VAXELN Interface to VAXJVMS
Example (application 11.pas), 11-3
Application 12: VAXELN Time Routines
Example (application 12.pas), 12-2

IV

Preface
The V AXELN Application Design Guide provides
sample programs for your reference in designing
applications using the VAXELN toolkit.

Manual Objectives
This manual contains solutions to several
programming problems you may have. Each section's
example program can be used as written to solve your
problem, or it can be used merely as a guide in
designing your own application.

Intended Audience
This manual is designed for programmers and
students who have a working knowledge of Pascal or
the C programming language. Knowledge of the
fundamental principles of the VAXNMS operating
system, as well as knowledge of VAXELN, is required.

Structure of this Document
This manual consists of 13 sections. The first section
provides an overview of the considerations you face
when designing your VAXELN applications. The
next 12 sections each consist of a simple statement of a
problem, a description of the program that solves that
problem, and an example program.

v

Section Title

Associated Documents
The following documents are relevant to designing
VAXELN applications:

•
•
•
•

VAXELN Installation Manual (AA-EU37A-TE)
VAXELN V2.0ReleaseNotes(AA-Z454C-TE)
VAXELN User's Guide (AA-EU38A-TE)
V AXELN Pascal Language Reference Manual
(AA-EU39A-TE)

•

VAXELN C Run-Time
Manual (AA-EU40A-TEJ

Section Title

VI

Library Reference

Overview
Structuring VAXELN Applications
When designing V AXELN applications, you must
first decide how the application will be structured;
there are four ways:
•

As a single job with a single process

• As a single job with multiple processes
• As multiple jobs, each with a single process
• As multiple jobs with multiple processes
For simple applications not requiring concurrency
within the application, a single job with a single
process is best because the application can be broken
into small functional units, each a callable procedure.
For very complex applications, multiple jobs with one
or more processes per job may be needed.
In many cases, the efficiency of communicating
between concurrently executing parts of the
application is the determining factor in the overall
performance of the application.
For most
applications, this concern with efficiency leads to a
choice between two configurations: single-job/multiprocess, and multi -job/single-process.
Multiple Jobs

Multiple jobs have these advantages:
•

The application can be distributed over several
VAXELN nodes in a network. This distribution
of jobs is transparent to the user.

vii

Structuring Applications

•

Each job has its own address space. Therefore,
bugs that occur in one part of the application will
not propagate to other parts of the application.

•

Since each job is a separate functional entity,
and communication between jobs is more formal
than between processes, it may be easier to
distribute the design and implementation of the
application among several members of a
programming team.

Multiple jobs have these disadvantages:
•

Each job consumes more system resources than
would a separate process within a single job.

•

Synchronization and data passing between jobs
can affect performance.

Communication between jobs can be accomplished by
using either areas or messages. Areas are the most
efficient method of communication. However, areas
may only be used when all jobs using the area are
running on the same node.
This removes the
advantage of the application being distributable over
several nodes in a network.
Message passing may be used to communicate
between jobs even in a distributed network. However,
the overhead associated with message passing may be
prohibi ti ve, depending on the application.
For an example of the multi-job/single-process
method, see Application 6, nInterjob Communication."

Single Job
A single job with multi pIe processes has these
advantages:

Structuring Applications

viii

•

Memory sharing makes communication and
synchronization between processes fast and
easy; heap and static memory are shared by all
processes
within
the job;
interprocess
communication
using
simple
job-wide
structures, such as queues and data structures
synchronized by mutexes, provides better overall
performance.

•

Individual processes consume very few system
resources.

•

Creating a new process is significantly faster
than creating a new job.
A single job with multiple processes has these
disadvantages:
•

Since the entire application is contained in one
job, the application cannot be distributed in a
network.

•

Since heap and static memory are shared by all
processes, corruption of the heap or static
memory affects all processes. Only stacks are
protected among processes.

•

Due to the availability of data sharing between
processes, it may be more difficult to ensure
ttclean" interfaces to procedures, especially for
an application being written by a team of
programmers.

For an example of the single-job/multi-process
method, see Application 1, ttAsynchronous I/O."

Designing Communication Protocols
If, after planning the partitioning of your application,
you've decided to use message passing for interjob
lX

Communication Protocols

communication, you must choose whether to use
datagrams or circuits. You must also design both the
format of these messages and the communication
protocol.
Whether to use datagrams or circuits is usually an
easy decision: for most applications you should use
circuits; datagrams should only be used for singlemessage transactions.
Circuits are best for
continuous connections because circuits are much
more reliable than datagrams.
Having chosen whether to use datagrams or circuits,
you must now design a communication protocol; the
following paragraphs offer guidelines.
When using datagrams:
•

An application-level acknowledgment and
timeout should be used to detect lost messages.

•

A sequence number should be contained in each
message to ensure that retransmissions do not
result in duplicate requests, and that
acknowledgments can be properly paired with
requests.
When using circuits:
•

An application-level acknowledgment should
only be used when a request MUST be
confirmed; nping-pong" protocols should always
be avoided, particularly because the virtual
circui t already acknowledges each message
when necessary.

•

Small messages should be 'packed into larger
messages whenever possible. The overhead for
each message is almost always the limit to
throughput, and virtual circuit protocols have

Communication Protocols

x

access to information to perform the most
efficient segmentation and reassembly.
•

An application-level acknowledgment should
always be used when terminating a connection
to ensure that the receiver completed the
request. The virtual circuit protocol only makes
a best-effort attempt to deliver all the messages;
if it could not deliver them, the application
would never know. Alternatively, the sender of
the last message can wait on the port for the
receiver to disconnect. This also ensures that
the final message was actually received before
the circui t was disconnected.

•

After circuit connection, the applications should
exchange version number and configuration
messages; this allows applications and protocols
to be upgraded over time and to provide subset
and superset functionali ty.

xi

Communication Protocols

Communication Protocols

xu

Application 1

Asynchronous 1/0
Problem
How do you program asynchronous behavior, such as
asynchronous I/O, while computation is occurring?

Solution
VAXELN does not have the concept of ASTs
(asynchronous system traps) as VAXIVMS does, but
the concept of concurrently executing processes in
VAXELN can be used to create the features of ASTs.
In fact, the VAXELN mechanism is more flexible
since multiple processes can function as prioritized
ASTs.
The example in this section shows how to use multi pIe
VAXELN processes to perform asynchronous
operations. In the example (a simple checksum
operation) one process is reading data from a file, and
the other process is performing a calculation on the
data.
The master process starts the sequence by opening the
data file and setting up the synchronization. objects
that will be used to protect access to the data buffers.
Then the master process creates the subprocess that
will read the data from the file into the buffers. The
subprocess simply reads the file using a typical double
buffer method. As the data is available in a buffer,
the master process computes the checksum. When the
file is completely read, the checksum is displayed.

1-1

Asynchronous 110

The buffers are synchronized by using two mutexes
per buffer. One mutex indicates that the buffer is full
of data and the other indicates -that the buffer is
empty. The reader process uses a transition of the
empty mutex to indicate that the computational
process is finished with the checksum calculation.
When data is read into the buffer, the reader process
sets the full mutex to indicate that the buffer is ready
to process.
To build the sample application, use the following
commands:
$ epascal applicationl + eln$:rtlobject/lib
$ link applicationl + eln$:rtlshare/lib + rtl/lib
$ ebuild Inoedit applicationl

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inofile
program applicationl

Asynchronous I/O

1-2

Example
The following is a listing of the example
written in Pascal (applicationl.pas).
module asynchronous_io_examp1e;
{++
{

{ Abstract:
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{--}

This is a simple program to show how asynchronous
activity is performed using the VAXELN multitasking
faci1 ities.
The master process creates a subprocess to perform
the 1/0 operations. As each buffer is filled,
the master process computes a simple checksum on
the data. When all the data is read, the checksum
is displayed. The subprocess asynchronously reads
data from a file using a straightforward double
buffering scheme that is synchronized with the
master process by using EVENT objects.

include
$mutex;
{

{

Job-wide declarations.

{}
{

{
{
{}

Define a record that contains both data and the
mutex to protect that data from multiple access.

type
file_record

packed array[l .. 512] of char;

data_record
record;
full: mutex;
empty: mutex;
last_block: boolean;
data: file_record

1-3

Asynchronous 110

end;
{

{

Declare a "double buffer" of data_records.

{}
const

first = 0;
second = 1;

var
data_blk: array[first .. second] of data_record;
{

{

Declare the input file.

{}
var
data_file: file of file_record;

[inline] function other(index: integer): integer;
{++
{

{ Functional description:
{
{
{
{

This is an inline routine to "flip" the
buffer index to the other buffer index.

{ Inputs:
{
{
{

index - Buffer index.

{ Outputs:
{
{
{
{--}

Index of the other buffer.

begin
if index
then

= first
other := second

Asynchronous I/O

1-4

else
other .::: first
end;
program asynchronous_;o(output};
{++
{

{ Functional description:
{
{
{
{
{
{
{

This is the master process that creates a
subprocess to asynchronously read the data blocks.
As the data blocks are read, a checksum ;s computed.
When all the data is processed. the checksum is
displayed.

{ Inputs:
{
{
{

A data file.

{ Outputs:
{
{
{
{--}

The simple checksum is displayed.

{

{

Master-process-local variable declarations.

{}

var
reader_process: process;
checksum: integer;
i, j. k: integer;
id: integer;
status: integer;
checksum_done: boolean;
begin
{

{
{

Open the data file. If the open fails. exit using
the failure status as the job exit status.

{}

open(data_file.
file_name := '10.172: :gathered.dat'.
history := history$old.
status := status};

1-5

Asynchronous VO

if not odd(status)
then
exit(exit_status := status);
reset(data_file);

{
{
{

Initialize both data_blk structures.
Set mutexs to indicate that both buffers are empty.

{}
create_mutex(data_blk[firstJ.full);
lock_mutex(data_blk[firstJ.full);
create_mutex(data_blk[firstJ.empty);
data_blk[l].last_block := false;
create_mutex(data_blk[second].full);
lock_mutex{data_blk[second].full);
create_mutex(data_blk[second].empty);
data_blk[second].last_block .= false;

{
{

Create the subprocess to read the file.

{}
create_process(reader_process,
reader_process_code,
status := status);

{
{
{

Initialize the variables used during
the checksum computation.

{}
checksum := 0;
id := first;
checksum_done

.=

false;

{
{
{
{

Checksum computation loop:
Pass over each buffer in turn, locking it
while the data is being processed.

{}
repeat
lock_mutex(data_blk[id].full);
if not data_blk[id].last_block
then

Asynchronous 1/0

1-6

for

:= 1 to 512 do
checksum := checksum +
ord(data_blk[id].data[i])

else
checksum_done := true;
unlock_mutex(data_blk[id].empty};
id := other{id}
until checksum_done;
{

{

Close file and display the computed checksum.

{}
close(data_file);
writeln{'Data file checksum is:
end;

checksum)

{++
{

{ Functional description:
{
{
{
{
{
{
{
{
{
{

This process reads the data file using a
double buffer scheme. The buffers are "locked,"
filled with data, and unlocked. This locking
protocol will synchronize this process with the master
process, which is computing the checksum.
A boolean is set in the buffer to indicate
end-of-file.

{ Inputs:
{
{
{
{

Data_file is open.
The first buffer's lock is set.

{ Outputs:
{
{
{--}

buffer,
dlv_request->length);
/.

•
•

.

Send the response back to
the requestor .

/

ker$send(NULL, dlv_message,
request_size,
&dlv_driver_port,
NULL, FALSE);
break;

.,

/.

•

Service write request .

case WRITE_BLOCK_FUNCTION:
/.

•

•
•

Copy the packet buffer data
to the communications region
buffer.

./

COPY_BYTES(dlv_request-)buffer,
tx_region_ptr-)xbuffer,
dlv_request-)length);

,.
•
•

.

Initialize the communications
region for this request .

/

tx_region_ptr-)buf_ptr = 0;
tx_region_ptr-)write_count =
dlv_request-)length;

,.
•
•
•

C Device Driver

Disable interrupts from the
device and set the interrupt
enable bit in the CSR; this

2-12

•
•
•
•
•

causes the device to interrupt the processor (since the
ready bit should be set), and
the ISR can then perform the
output.

ElN$DISABlE_INTERRUPT(ipl);
write_register(XCSR$M_INT_ENA,
®ister_ptr->xcsr);
tx_region_ptr->writ8_in_progress
TRUE;
I•

Re-enable interrupts and wait
for the 1/0 to complete.

·1

ElN$ENABLE_INTERRUPT();
ker$wait_any(NUll, NULL, NUll,
dlv_transmit_device);

•
•

Send the response back to
the requestor, indicating
the buffer was output.

dlv request->error = 0;
kerSsend(NUll, dlv_message,
request_size,
&dlv_driver_port, NUll,
FALSE) ;
break;
/-

-I

Service done request.

case DONE_FUNCTION:

•

-

Send a message back to the
requestor indicating the
done request was received,
then set the done flag to
exit from the l~op.

2-13

C Device Driver

dlv_request-)error = 0;
ker$send{NUll. dlv_message.
request_size.
&dlv_driver_port. NUll.
FALSE) ;
TRUE;
done
}

,.

}

•
•
•
•
•
•

Since the user program is the last to receive
a message on the circuit. wait on the port until
it disconnects. then disconnect at this end;
this avoids having the circuit disconnected
before the user program receives the last
message.

ker$wait_any(&status. NUll. NUll. &dlv_driver_port);
kerSdisconnect_circuit(NUll. &dlv_driver_port);
}
}

,.
.•,

Receiver ISR •

void receive_service_routine{int_registers. int_region)
·int_registers;
·int_region;

struct register_def
struct rx_region_def
{

unsigned short

receive_input;

Read the receive buffer register.
receive_input

,.

•
•
•

read_register(&int_registers-)rbuf);

If the driver is waiting for input, put the
character in the communications region buffer.
otherwise drop it.

C Device Driver

2-14

/*

*

Check for errors on the read.

*/

if

(receive_input&RBUF$M_ERROR)
{

If an error occurred, set the
error bit in the communications
region and signal the device.

*
*

*

int_region->error = TRUE;
int_region->read_in_progress
ker$signal_dev;ce(NULL, 0);

FALSE;

}

else
{

Otherwise, put the received
character in the communcat;ons
region buffer and bump up the
buffer pointer. If this character
satisfies the request, signal
the device.

•
•
*

*

int_region->rbuffer[int_region->buf_ptr++]
receive_input&RBUF$M_CHAR;
if (int_region->buf_ptr >= int_region->read_count)
{

ker$signal_device(NULL, 0);
int_region->read_in_progress

FALSE;

}
}
}
/*

*

Transmitter ISR.

*/

void transmit_service_routine(int_registers, int_region)
struct register_def
struct tx_region_def

*int_registers;
*;nt_reg;on;

2-15

C Device Driver

{
/.

•
•

.

If the driver is waiting for output, output
characters to the OLV until done .

/

if (int_region->write_in_progress)
if (int_region->write_count > int_region->buf-ptr)
More characters to output so
output the next one and bump up
the buffer pointer.
wri te_regi ster(
int_region->xbuffer[int_region->buf_ptr++],
&int_registers->xbuf);
else

•

•
•

All characters output; clear
the write_in_progress flag,
clear interrupt_enable on the
transmitter, and signal the
device.

{

ker$signal_device(NULL, 0);
int_region->write_in_progress = FALSE;
write_register(O, &int_registers-)xcsr);
}
}

C Device Driver

2-16

Application 3

C Interface to Disk and File Utilities
Problem
How do you implement the C interface to the disk
utility and file utility procedures, described in the
VAXELN User's Guide, and the VAXELN C RunTime Library Reference Manual?

Solution
The example in this section is designed to show as
many of the disk utility and file utility procedures as
possible.
The example is also designed to show:
eHow the data types not normally found in C code
written for UNIX can be integrated with the
generic C data types and standard UNIX
extensions. For example, notice the example's
use of the RTL routine sprintf to concatenate
one C string to two VARYING_STRING data
items,yielding a VARYING_STRING result.
e How bit mask definitions are used in C to take
the place of PASCAL sets. For example, see the
volume, file, and record protection parameters
passed to ELN$INIT_VOLUME. The masks
deny a particular type of access and, therefore,
the bitwise complement (-) operator is used to
cast them into the more familiar positive-logic
format. Also note the use of the address-of
operator (&) to pass these constant values to the

3-1

C Disk and File Interface

•

•

procedure by reference, rather than by value;
this extension to the C language is unique to the
VAX C compiler.
How parameters are passed by reference to the
disk utility and file utility procedures. A
common mistake when coding C for VAXELN is
to omit an ampersand (&) on a function
parameter.
How to implement a construction necessitated
by the status code conventions of VAXELN (and
VAXNMS). The UNIX status code convention
is: 0 (= C "false") return status indicates
success; a nonzero (= C "true") return status
indicates an error. VAXELN and VAXNMS use
the low bit of a status code to denote success( =1)
or failure( =0); this is the basis for the almost
idiomatic test in the example:
if (! (status&l»

statement ...

-orif (status&l)

statement ...

To build the sample application, use the following
commands:
$ cc applicationJ + eln$:vaxelnc/lib
$ link applicationJ + eln$:crtlshare/lib + rtlshare/lib +rtl/lib
$ ebuild/noedit application3

The System Builder data file used to build this
program to be run on an RX50 drive on a Micro VAX I
system is:
characteristic lemulator=both

C Disk and File Interface

3-2

program application3
device DUA /register=%0772l50 /vector=%0154

When run, this program produces the following
output:
"Initialized disk in drive 'DUAl:' as volume name 'SAMPLE'.
Mounted disk.
Created
Created
Created
Created
Created
Created
Created
Created
Created
Created
Created

directory 'DISK$SAMPlE:[TEST_DIR], .
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;l.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;2.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;3.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;4.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;5.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;6.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;7.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;8.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;9.
DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;10.

Copied
to
Renamed
to

'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;10'
'DISK$SAMPlE:[TEST_DIR]TEST_COPY_FIlE.DAT;100'.
'DISK$SAMPlE:[TEST_DIR]TEST_COPY_FIlE.DAT;100'
'DISK$SAMPlE:[TEST_DIR]TEST_RENAME_FIlE.DAT;1234'.

Contents
Contents
Contents
Contents
Contents
Contents
Contents
Contents
Contents
Contents

of
of
of
of
of
of
of
of
of
of

data
data
data
data
data
data
data
data
data
data

file
file
f i l-e
file
fi 1e
file
file
file
file
file

0
1
2
3
4
5
6
7
8
9

Changed protection of
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;10'.
Deleted
Deleted
Deleted
Deleted
Deleted
Deleted
Deleted
Deleted
Deleted

'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;10'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;9'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;8'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;7'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;6'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;5'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;4'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;3'.
'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;2'.

3-3

C Disk and File Interface

Deleted 'DISK$SAMPlE:[TEST_DIR]TEST_FIlE.DAT;l'.
Deleted 'DISK$SAMPlE:[TEST_DIR]TEST_RENAME_FIlE.DAT;1234'.
Dismounted the disk.
End of sample program."

C Disk and File Interface

3-4

Example
The following is a listing of the example
written in C (application3.c).
Ninclude
Ninclude
Ninclude
Ninclude

$disk_utility
$file_utility
descrip
stdio

/*

* Abstract:

*
*
*
*
*
*

•
*
*

This example shows typical calls from a C program to
the disk utility and file utility procedures.
WARNING. This program initializes the disk mounted
in the drive named by the preprocessor constant
TARGET_DRIVE, defined below; no other warning will be
given. The device must be readied for writing
before the program is started.

*/
/*

*

Preprocessor definitions:

*/

Nifndef TARGET_DRIVE
Ndefine TARGET_DRIVE "DUAl:"
Nendif

/*

Default drive to use */

/*

*

File specification definitions:

*/

Ndefine DIRECTORY "DISK$SAMPLE:[TEST_DIR]"
Ndefine DATAFILE "DISK$SAMPLE:[TEST_DIR]TEST_FILE.DAT;"
Ndefine COPYFILE
"DISK$SAMPLE:[TEST_DIR]TEST_COPY_FILE.DAT;lOO"
Ndefine COPYFILE2
"DISK$SAMPLE:[TEST_DIR]TEST_RENAME_FILE.DAT;1234"

\
\

/*

*
*

Define shorthand versions of volume and file
protection masks:

3-5

C Disk and File Interface

Ndefine RWED

Ndefine R
Ndefine RE

(DSK$M_READ I
DSK$M_WRITE I
DSK$M_EXEC I
DSK$M_DElETE)
(DSK$M_READ)
(DSK$M_READ I
DSK$M_EXEC)

Ndefine NOGROUP «FIlE$DENY_READ_ACCESS
FIlE$DENY_WRITE_ACCESS
FIlE$DENY_EXECUTE_ACCESS
FIlE$DENY_DElETE_ACCESS)

\
\
\

\

«

I
I
I

\
\
\

FIlE$GROUP)

VARYING_STRING constant declarations:
VARYING_STRING_CONSTANT(drive_name,TARGET_DRIVE);
VARYING_STRING_CONSTANT(dir_fs,DIRECTORY);
VARYING_STRING_CONSTANT(data_fs,DATAFIlE);
VARYING_STRING_CONSTANT(copy_fs,COPYFIlE);
VARYING_STRING_CONSTANT(copy_fsl,COPYFIlE2);
VARYING_STRING_CONSTANT(search_fs,
"DISK$SAMPlE:[TEST_DIR]*.*;*");
VARYING_STRING_CONSTANT(username,"USER");
VARYING_STRING_CONSTANT(v01ume,"SAMPLE");

*

Define a macro used to output VARYING_STRING data
items:

Ndefine PRINT_VARYING(textl, vs, textl)
\
printf("%s%.*s%s", textl, vs.count, vs.data, textl)
/*

* Routine description:
*
This code performs the following steps:
*
*
1. Initializes the target disk with label
•
"SAMPLE".
*
2. Mounts the disk.
•
3. Creates the directory [TEST_DIR] on the
*
disk.
*
4. Writes 10 data files named
•
[TEST_DIR]TEST_FIlE.OAT;* to the disk.
•
5. Copies [TEST_DIR]TEST_FIlE.DAT;10 to
*
[TEST_DIR]TEST_COPY_FIlE.OAT;100.
*

C Disk and File Interface

3-6

*

*

*
*
*
*
*

*
*
*

*

6.

Renames [TEST_DIR]TEST_COPY_FIlE.DAT;100 to
[TEST_DIR]TEST_RENAME_FIlE.DAT;1234.
7. The renamed file in step 6 is typed on the
console.
8. Changes the protection of
[TEST_DIR]TEST_FILE.DAT;9 to exclude all
GROUP access.
9. Uses eln$directory_open and e1n$directory_1ist
to visit all files in [TEST_DIR]. Each file
visited is deleted.
10. Dismounts the disk.

*'

maine)
{

DSK$_BADBLOCK
DSKS_BADLIST
char
ELNSDIR_FILE
FILE$ATTRIBUTES_RECORD
FILE
VARYING_STRING(255)
int

'**
*
*'

bad_b10cKs[2];
bad_blocK_list
{2,&bad_b10cks};
buffer[132];
*di rectory;
*fi1e_attr;
*fp;
de1ete_fs,01d_fs,new_fs,dirtmp_fs;
status,i,j;

Initialize imaginary bad bloCK information to mark
LBNs 100-119 and 222-231, inclusive, as bad blocKs.

bad_blocKs[0].type.10gica1.start_lbn
bad_blocKs[O].type.logica1.lbn_count
bad_blocKs[O].pbn_format = FALSE;

100;
20;

bad_blocKs[1].type.10gical.start_lbn
bad_blocKs[1].type.10gical.lbn_count
bad_blocKs[l].pbn_format = FALSE;

222;
10;

'** 1. Initialize the disK using reasonable values:
*'
elnSinit_vo1ume(&drive_name,
device name
'*
/*
/*
/*
/*
/*
/*

&volume,
5,
&username,
OxOO010001,

volume name
default extension
username
owner
volume protection
[RWED,RWED,RWED,]
&-( RWED«DSKSV_SYSTEM I

3-7

*'
*/
*/
*/
*/
*/
*/

C Disk and File Interface

RWEO«OSK$V_OWNER I
RWEO«OSK$V_GROUP),
/. default file prot.
/. [RWED,RWEO,RE,]
&-( RWEO«OSK$V_SYSTEM I
RWEO«OSK$V_OWNER I
RE «OSK$V_GROUP),
/. default record prot.
/. [RWED,RWEO,R,]
&-( RWEO«DSK$V_SYSTEM I
RWEO«OSK$V_OWNER I
R «OSK$V_GROUP),
3,
accessed directories
/. maximum files
0,
/. user_directories
0,
0,
file headers
/. windows
7,
/. cluster size
0,
/. index posit ion
OSK$_MIOOLE,
/. data check
OSK$_NOCHECK,
TRUE,
share
FALSE,
group
TRUE,
system
/. verif i ed
TRUE,
&bad_block- list,'· bad list
/* status
NULL) ;

'*

,.

,.,.
,.

.,
.,

*'

*/

*'

*/
*/

*'.,*'
*/

*/

*'*'*'
.,*'*'

PRINT_VARYING("Initialized disk in drive ,n, drive_name,"''');
PRINT_VARYING{" as volume name
volume, "' .\n");
/*

*

2.

Mount the disk.

*/

eln$mount_volume(&drive_name,
&volume,
NULL) ;
printf("Mounted disk.\n\n");

'**
*'

3.

Create the directory used by this test.

eln$create_directory(&dir_fs,
NULL,
OxOOOl0001,
&new_fs) ;
PRINT_VARYING("Created directory

C Disk and File Interface

3-8

new _ f s, "'. \ n " ) ;

4.

*

*

The number of records in each file is the same as
the file's version number. Each record consists
of the records numbered from 0 to the record
version number -1 .

*
*

*

•

for(i

Create a series of simple text files with the same
filename and file type. but with version numbers
ranging from 1 through 10.

1;
{

<= 10; i++)

fp
fopen(OATAFllE, "w"l;
for(j = 0; j < i; j++)
fprintf(fp, "%d\n", j);
fc1ose(fp);
printf("Created %s%d.\n", DATAFllE, i);
}
/*

*

5.

Copy the last file to another file.

*/

e1n$copy_file(&data_fs,
©_fs,
NUll,
NUll,
NUll,
NUll,
&old_fs,
&new_fs);
PRINT_VARYING("\nCopied\t'", old_fs, "'\n");
PRINT_VARYING("to\t'". new_fs, "'.\n");
/*

•

6.

Rename the file just copied.

*/

e1n$rename_fi1e(©_fs.
©_fs2.
NUll,
&old_fs.
&new_fs);
PRINT_VARYING("Renamed\t'" , old_fs. "'\n");
PRINT_VARYING("to\t'", new_fs, "'.\n\n");

3-9

C Disk and File Interface

/.

•
•

.,

7.

Open the renamed file for reading and type it on
the console.

fp = fopen(COPYFILE2, "r");
while(fgets(buffer, sizeof(buffer), fp) != NULL)
printf("Contents of data file =\t%s", buffer);
fclose(fp);
/.

•
•

8.

Protect the data file with the highest version
number from all group access.

./

eln$protect_file(&data_fs,
NULL,
&NOGROUP,
NULL,
&new_fs) ;
PRINT_VARYING("\nChanged protection of

.

new_fs, "' .\n\nn);

/.

•

9.

Start a directory listing of this directory .

/

directory
file_attr

calloc{l. sizeof(·directory»;
calloc(l, sizeof(·file_attr»;

eln$di rect.ory_open(&di rectory,
&search_fs,
&new_fs,
&dirtmp_fs,
NULL,
NULL,
&file_attr) ;

.,

/.

•

Loop for each file in the directory and delete them .

fore;;)
{

eln$directory_list(&directory,
&dirtmp_fs,
&new_fs,
&status,
&file_attr};
if (l( status&l»

C Disk and File Interface

3-10

break;
/*

*

Concatenate volume + directory + filename.

*/

delete_fs.count

sprintf(delete_fs.data.
"DISKSSAMPLE:%.*s%.*s",
dirtmp_fs.count.
dirtmp_fs.data.
new_fs.count.
new_fs. data);

eln$delete_file(&delete_fs. NULL, &new_fs);
PRINT_VARYING("Deleted
new_fs, '" .\n");
}
/*

*

10.

Dismount the disk.

*/

elnSdismount_volume(&drive_name, NULL);
printf("Dismounted the disk.\n\n");
printf("End of sample program.");
}

3-11

C Disk and File Interface

C Disk and File Interface

3-12

Application 4

Fast Device-Handling
Problem
How do you perform device I/O while avoiding the
overhead incurred using standard Pascal IIO?

Solution
Most realtime device handling should be performed
using procedures and/or processes wi thin the job
requiring the I/O. The Pascal 110 interface is only
useful for distributed, record-and-file oriented I/O.
Very efficient device 110 can be accomplished using
VAXELN, because a program can directly initiate I/O
requests, without going through a runtime system.
The example in this section shows a set of procedures
and an interrupt service routine (ISR) that can be
used to gather data from the AXVIIC or ADVIIC
analog-to-digital converter. The example's ISR is
called by the VAXELN kernel upon receiving an
interrupt from the converter. The example's two
procedures are used to create and initialize data
structures that control the converter and to initiate
conversion and read the resulting data.
The AXVIIC and ADVIIC are typical real-time
devices. The basic strategy in the driver routines is to
write to the device's control/status register (CSR),
initiating I/O, and then wait on the DEVICE object.
When the I/O is complete, the physical device
interrupts the processor, causing the ISR to read the

4-1

Fast Device-Handling

input data from the device's data buffer register. The
ISR then signals the DEVICE object, allowing the
main driver routine to complete.
Device drivers written in this fashion have at least
one limitation: programs calling the procedures must
run in kernel mode because the drivers will almost
always need to call CREATE-DEVICE, and may also
need to change the interrupt priority level of the
processor.
The routines shown in this section are actually much
simpler in function than a real device driver typically
is; in addition to not supporting the full functionality
. of this particular device, these routines don't do
several things usually done by real-time drivers:
•

•

Polling. Many devices can be driven by polling
rather than interrupts. That is, the driver
writes to the CSR to initiate 110, and then does
not wait for an interrupt but rather goes into a
loop, reading the CSR repeatedly until the 110
request is completed. Polling usually results in
higher throughput but, since polling is done at a
raised interrupt priority level, it prevents other
processes from executing during the polling.
Multiple 110 operations per call. Higher overall
throughput can also be achieved by allowing the
driver to read or write more than one piece of
data in each call.

The AXVI1C driver supplied in your kit does, in fact,
implement both polling and multiple 110 operations
per call. After becoming familiar wi th the example in
this section, it's a good idea to study this and other
VAXELN real-time device drivers to learn more about
writing VAXELN real-time drivers.

Fast Device-Handling

4-2

The AXVI1C hardware itself is discussed in detail in
the LSI-II Analog System User's Guide.
To build the sample application, use the following
commands:
$ epascal application4 + eln$:rtlobject/lib
$ link/nosysshr application4 + eln$:rtlshare/lib +eln$:rtll1ib
$ ebuild/noedit application4

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inoconsole Inofile Inoserver lemulator=both
program application4 Idebug Imode=kernel
device AXVI Iregister=%0170400 Ivector=%0400 Inoautoload

If your device has its dip switches set to values
different from those, above, you may have to use the
System Builder to change the vector address, register
address, or both.

4-3

Fast Device-Handling

Example
The following is a listing of the example
written in Pascal (application4.pas).
module axvll;
{

{
{
{
{}

This module contains some simple procedures to read
converted analog data from an AXV11 device, and a
program to demonstrate the procedures' use.

type
{

{
{}

Input/output data and gain data.

axv_data
{
{

-%03111 •. %01111;

Identifiers, one for each physical device.

{}
axv
{
{
{
{
{
{
{
{
{
{

{
{
{
{
{
{
{
{

"anytype;
AXV control/status register (CSR) record definition:
start:
gain_setting:
ext_start_enable:

multiplexer_address:
error_int_enable:

{}

Fast Device-Handling

4-4

Initiates a conversion.
Controls gain.
Permits external start of
conversion.
Permits external start of
conversion.
Enables interrupt at end of
conversion.
Is set when conversion is
complete.
Channel being addressed.
Enables interrupt at an error
condit ion.
Set when error detected;
can't happen in this program.

axv_csr_type = [word] packed record
start: [pos{O)] boolean;
gain_setting: [pos{Z)] 0 .. 3;
ext_start_enable: [pos(4)] boolean;
clock_start_enable: [pos(5)] boolean;
done_int_enable: [pos(6)] boolean;
a_d_done: [pos(7)] boolean;
multiplexer_address: [pos(8)] O.. 15;
error_lnt_enable: [pos(14)] boolean;
a_d_error: [pos(15)] boolean
end;
{

{

Result of A/D conversion.

{}

{

{

AXV register layout in controller:

{

{
{

csr dbr -

Control/status register.
Data buffer register.

{}

axv_registers = [aligned{l)]
csr: axv_csr_type;
dbr: axv_dbr_type
end:

packed record

{

{

AXV interrupt communication region:

{

{
{

dbr_read - Temporary repository for data read
in an interrupt service routine.

{}

axv_done_interrupt_region = record
dbr_read: axv_dbr_type
end;
{

{
{
{
{
{
{
{

Data area for each device:

reg i sters:
done_region_ptr:

Device object for completed
interrupt.
Address of device's registers.
Address of completed interrupt
region.

4-5

Fast Device-Handling

{}
axv_data_area = packed record
done device: device;
reg i sters: axv_r~gi sters;
done_region_ptr: axv_done_interrupt_region
end;
A

interrupt_service done_interru~t(reg: Aaxv_registers;
com: axv_done_interrupt_region);
{++
{

{ Routine description:
{
{
{

{
{
{

Called upon receipt of an interrupt that indicates a
conversion is complete. this routine reads the data
from the just-completed conversion into the communication
region, then signals the device.

{ Inputs:
{
{
{

reg - Address of the device registers.
corn - Address of the done interrupt communication region.

{

{ Outputs:
{

{
{
{
{--}

The conversion data is stored in the communication
region and the device is signaled.

begin
{

{

n

Read the new data.

{

{

n

Signal the device to enable axv_read to continue.

end;
procedure axv_initialize(device_name: [readonly]
varying_string(30);
var identifier: axv);

Fast Device-Handling

4-6

{++
{

{ Routine description:
{
{
{
{
{
{

This procedure is called to allocate and initialize
the data area for an ADVIIC or AXVIIC, and it also
creates the necessary DEVICE objects. This procedure
must be called once for each physical device.

{ Inputs:
{
{
{
{
{

1-to-30-character string glvlng the name
of the device. It must match the name
established with the System Builder.

{ Outputs:
{
{
{
{
{
{
{
{
{
{--}

identifier -

Longword identifier to be used in
subsequent calls to axv_initialize,
axv_read, and axv_write to identify
this device.

An identifier is allocated and returned, a device object
is created, and the physical device is initialized.

begin
{

{

Get a new identifier.

{}
new(identifier::Aaxv_data_area);
with identifierA::axv_data_area do
begin
{

{

Create a device object for the completed interrupt.

{}
create_device(device_name,
done_device,
vector_number := 1,
service_routine := done_interrupt,
region := done_region_ptr,
registers := registers);

4-7

Fast Device-Handling

{

{

Initialize the device's CSR.

{}
A
write_register(registers .csr)
end;
end;
procedure axv_read(identifier: axv;
channel: integer;
var converted_data: axv_data);
{++
{

{ Routine description:
{
{
{

{

This routine is called to initiate a conversion and
gather the resulting datum from an AXVIIC or ADVIIC
device on the specified channel.

{

{ Inputs:
{
{
{
{

identifier -

Expression of type AXV giving the
value of an identifier (which was
returned by axv_initialize) of the
device to be read.

channel -

Integer expression glvlng the analog
channel to be read.

{
{
{

{
{

{ Outputs:
{

{
{
{
{--}
{
{

converted_data - Received resultant datum from the
requested conversion.

Local-variable declarations:

{}
var
begin
with identifierA::axv_data_area do
begin

Fast Device-Handling

4-8

{

{
{
{}

First, set up the CSR templates;
begin with the contents of the CSR.

csr_template

.=

read_register(registersA.csr);

{

{

Now set the following fields:

{

{
{
{
{

start -

Necessary for initiating
conversions from the program.
done_int_enable lets the device interrupt.
multiplexer_address - Sets the channel.

{}
with csr_template do
begin
start := true;
done_int_enable := true;
multiplexer_address .= channel
end;
{

{
{
{
{

Write to the device register to initiate
the conversion and wait on the device.
The wait will be satisfied when the ISR
has read the converted data and signals the device.

{}
A

write_register(registers .csr, csr_template);
wait_any(done_device);
{

{
{
{}

Finally, move the converted data into
the user-supplied variable.

converted_data
end;

.=

A
done_region_ptr .dbr_read

end;
program test(input,output);
{

{
{
{
{
{

This test program tests the above driver routines.
It prompts the user for a channel to sample and
samples the channel 5 times, printing the resulting
voltage from each sample. This program assumes
that the device is configured to give bipolar,

4-9

Fast Device-Handling

{
{
{}

offset-binary outputs; if this is not the case,
you can change the statement marked ' •• '

var
ident: axv;
channel: integer;
result: axv_data;
voltage: real;
i: integer;

{
{
{
{

the device's identifier}
channel to be read }
resultant data from a conversion}
result converted to a voltage}

begin
{

{
{}

Initialize the device.

axv_initialize('AXV1', ident);
{

{
{}

Loop to read data.

while true do
begin
{

{
{
{}

Obtain the channel number from the user.
Exit if the channel number is negative.

write('channel? ');
readln(channel);
if channel < 0
then
goto finished;
for
{' •• '}

:= 1 to 5 do
begin
axv_read(ident. channel. result);
voltage := (result - %04000) • (10.0 / %04000):
writeln(voltage:5:Z. ' volts')
end;

end;
finished:
end;
end.

Fast Device-Handling

4-10

Application 5

FORTRAN Routine Inclusion
Problem
How do you include FORTRAN routines in a
VAXELN system without rewriting them in Pascal or
C?

Solution
The example in this section demonstrates how two
FORTRAN functions are called in VAXELN Pascal
and C; it demonstrates appropriate interfaces for
passing strings, scalars, and multidimensional
arrays.
Several considerations (demonstrated in this section's
example) must be kept in mind when calling
FORTRAN subroutines and functions from VAXELN
programs:
•

Not all FORTRAN is suitable. FORTRAN that
calls any V AXNMS services or runtime routines
not included in the VAXELN libraries cannot be
used. For example, FORTRAN routines that
perform I/O cannot be used because there is no
FORTRAN I/O system included in VAXELN .

•

When linking to produce an image for a
VAXELN system in which a FORTRAN routine
is being called, specify the NOSYSSHR qualifier
to prevent the linker from searching the
VAXNMS default shareable-image library for
unresolved references.

5-1

FORTRA~

Routine Inclusion

•

By default, FORTRAN passes parameters by
reference. Therefore, in the VAXELN Pascal
declaration of a FORTRAN routine, the
[REFERENCE] attribute must normally be
specified on all parameters that VAXELN
Pascal would not otherwise pass by reference. In
C, this can be handled by being sure to pass the
address of the argument.
• FORTRAN stores array elements differently
from both VAXELN Pascal and C.
In
FORTRAN, elements are stored such that the
leftmost subscript varies the most rapidly as one
traverses the array in memory. In Pascal and C,
the rightmost subscript varies the most rapidly.
This means that, for example, in a two
dimensional array, rows become columns and
columns become rows.
To build the Pascal sample application, use the
following commands:
$ fortran application5c
$ epascal application5b + eln$:rtlobject/lib
$ link/nosysshr application5b + application5c + eln$:rtlshare/lib + eln$:rtl/lib
$ ebuild/noedit applicationb

To build the C sample application
commands:

+

use the following

$ fortran applicationc
$ cc application5a + eln$:vaxelnc/lib
$ link/nosysshr application5a+application5c + eln$:crtlshare/lib + eln$:rtlshare/lib + _$eln$:rtl/lib
$ ebuild/noedit application5a

FORTRAN Routine Inclusion

5-2

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inoconsole Inofile Inoserver lemulator=both
program application5 Idebug

5-3

FORTRAN Routine Inclusion

C Example
The following is a listing of the example
written in C (application5a.c).
Nmodule application5
#include descrip
/*

*
*

This module demonstrates the calling of
FORTRAN functions from C.

*'
float

a1[5][10]

{1.0,2.0,3.0,4.0,5.0,6.0,7.0.8.0,9.0,10.0,
1.0,2.0,3.0,4.0,5.0,6.0.7.0,8.0.9.0,10.0,
1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,
1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0.9.0,10.0,
1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0 };

float

a2[10][5]

{1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0,
1.0,2.0,3.0,4.0,5.0 };

float

vec1[5],vec2[10];

/*

*
*

*

*
*

*
*

The declarations of FORTRAN routines in C involve
the only thing that C "knows" about the function:
the function's return type. When actually coding
calls to the FORTRAN routines, you are responsible
for mapping the FORTRAN language semantics of the
arguments into C semantics, for example putting the
correct data in the argument list.

*/

/*

*

*
*

Below is the declaration of the first function.
By default, FORTRAN passes arguments by reference,
and the array dimensions are reversed from what they

FORTRAN Routine Inclusion

5-4

•
•
•
•

are in C. Therefore, although this function sums
the FORTRAN array's columns, it will sum the C array's
rows. Note that the declaration's extents are reversed
from the way they appear in the FORTRAN declaration;
that is, it's the number of rows that's passed before
the number of columns, in the FORTRAN sense.

•

•
/.
/.
/.
/.

1st
2nd
3rd
4th

float

arg
arg
arg
arg

pointer
pointer
pointer
pOinter

to
to
to
to

matrix
output
number
number

to sum ./
sum vector ./
of rows (FORTRAN) in matrix ./
of columns (FORTRAN) in matrix ./

sum();

/.

•

Declaration of second function:

./

/. 1st argument = pointer to string descriptor ./
/. FORTRAN calls this a "passed length character argument" ./
int

icmax();

main()
{

float
int
static
static

result;
i;
$DESCRIPTOR(str, "abcdefghij");
$DESCRIPTOR(str2, "zyxwvutsrq");

sum(&a1, &vec1, &10, &5);
result
result += sum(&a2, &vec2, &5, &10);
result += icmax(&str);
result += i cmax( &str2);
/.

•
*
•

Check the result of the calls. Result itself should be
436. Each of the elements of vec1 should be 55,
and each of the elements of vec2 should be 15.

*/

printf("The value of Result is %g (Should be 436)\n\n", result);
printf("\nThe values of vec1 should all be 55.
for(i = 0; i < 5: i++)
printf("%g\n", vec1[i]);

They are:\n");

printf("\nThe values of vec2 should all be 15.

They are:\n");

5-5

FORTRAN Routine Inclusion

for( i = 0; i

< 10; i++)
printf("%g\n",vec2[i]);

}

FORTRAN Routine Inclusion

5-6

Pascal Example
The following is a listing of the example
written in Pascal (application5b.pas).
module application5;
{

{
{

This module demonstrates the calling of
FORTRAN functions from VAXELN Pascal.

{}

type
real_array(m,n: integer) = array[l .. m.l .. n] of real;
real_vector(m: integer) = array[l .. m] of real;
var
a1: real_array(5.10) := (5 of (1.2.3.4.5.6.7.8.9.10»;
a2: real_array(10.5) := (10 of (1.2.3.4,5»;
vecl: real_vector(5);
vec2: real_vector(10);
{

{
{
{
{
{
{
{
{
{

Below is the declaration of a FORTRAN function.
By default. FORTRAN passes arguments by reference,
and the array dimensions are reversed from what they
are in Pascal. Therefore, although this function sums
the FORTRAN array's columns. it will sum the Pascal
array's rows. Note that the declaration's extents are
reversed from the way they appear in the FORTRAN
declaration; that is. it's the number of rows that's
passed before the number of columns. in the FORTRAN sense.

{}

function sum(ary: real_array«m>.ORT. Using a NAME object
allows your programs to identify themselves
symbolically to their "peers" and allows you the option
6-1

Interjob Communication

of making the ports visible on a local (per processor) or
universal (per local network) basis; this means your
applications can serve as system-wide or networkwide resources with very few changes in your code
required. (Typically, all that is required is changing
the Utable" argument you supplied to the
CREATE.-NAME kernel procedure in one source
module.)
The port name used in this section's example must be
established before the second job in the example
system is started, or the example system might fail
when it attempts to connect a circuit to the named
port. Correspondingly, the [nit required attribute is
specified for the first job in this system and it invokes
the INITIALIZATION-DONE kernel procedure when
it has finished establishing the port name.
The action in this example is controlled by the second
job in this example, named APPLICATION6B, which
transmi ts a data message to the first job and waits for
it to be returned by the first job. The data messages
are set up in this example so that they gradually
shrink in size until they match a particular value the
two jobs have both defined as being the end-ofdialogue indicator.
To build the sample application, use the following
commands:
S epascal application6a
Slink application6a + eln$:rtlshare/library
S epascal application6b
Slink application6b + eln$:rtlshare/library
S ebuild application6/noedit

Interjob Communica~ion

6-2

+

rtl/library

+

rtl/library

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
program application6a /initialize
program application6b

6-3

Interjob Communication

Example
The following is a listing of the example
written in Pascal (application6a.pas,
application6b.pas).
module application6a;
{++
{

{ Abstract:
{
{
{
{
{
{
{--}

This module contains the first job (named APPlICATION6A)
of a twa-job system designed to show two jobs passing
data back and forth using the SEND and RECEIVE kernel
procedures on a circuit based on a named port.

{

{

Job-wide declarations:

{}

var
io_port: port;
data_message: message;
identifier: name;
string_data: Avarying_string(32);
done: boolean := false;

program application6a;
{++
{

{ Functional description:
{
{
{
{
{
{
{

This program creates a port with an associated name
of INITIAl_JOB_PORT as part of an initialization
action.
After the initialization is performed, the program
simply waits for incoming messages (in ASCII) to be

Interjob Communication

6-4

{
{
{
{
{
{

received through a circuit established on that port.
The program transmits the data back to the sender,
stripping off the first and last characters, until it
receives a message with the value '···ENO···', which
causes this program to terminate.

{ Inputs:
{
{
{
{

Incoming ASCII string messages directed to the global
port named INITIAl_JOB_PORT.

{ Outputs:
{
{
{
{
{--}

All incoming data is transmitted back to the sender
with the first and last characters removed.

begin
{

{

Initialization section:

{

{

Create the port and name it INITIAl_JOB_PORT.

{}
create_port(io_port);
create_name(identifier, 'INITIAl_JOB_PORT', io_port);
initialization_done;
{

{

Wait here to accept the incoming circuit request.

{}
accept_circuit(io_port);
writeln('Job APPlICATION6A accepted the circuit.');
{

{

loop for each message received and process it.

{}
while not done do
begin
{

{
{
{

Wait for a message to arrive on the port.
When it arrives, receive the message and output
it's contents to the standard output file.

{}

6-5

Interjob Communication

wait_any(io_port);
receive(data_message. string_data, io_port);
writeln('Job APPlICATION6A received
string_data" ,
, t• • ' ) :

if string_data" = '···ENO···'
then
done := true
else
begin
string_data" := substr(string_data". 2.
length(string_dataA)-l);
send(data_message. io_port)
end;
end;
writeln('Job APPlICATION6A exited. ')
end;
end;

module APPlICATION6B;
{++
{

{ Abstract:
{
{
{
{
{
{
{
{--}

This module contains the second job (named
APPlICATION6B) of a two-job system designed to show two
jobs passing data back and forth using the SENO and
RECEIVE kernel procedures on a circuit based on a named
port.

{

{

Job-wide declarations:

{}
var
io_port: port;
data_message: message;
string_data: Avarying_string(32);
done: boolean := false;

Interjob Communication

6-6

program application6b;
{++
{

{ Functional description:
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{

This program starts a message dialogue with job
APPLICATION6A. The messages transmitted back and
forth start out as the string:
'··········ENO··········'
which APPLICATION6A whittles down to the final
string, '···ENO···' , by deleting the first and last
characters of the messages it receives.
This program initially connects the circuit between
the two jobs, then transmits data and receives
the modified data for retransmission.

{ Inputs:
{
{
{
{

ASCII string messages directed from APPLICATION6A
through its port named INITIAL_JOB_PORT.

{ Outputs:
{
{
{
{
{--}

All incoming data is transmitted back to the sender
without modification.

begin
{

{
{

Create the message packet and initialize the data
string.

{}
create_message(data_message,
string_data);
A
string_data := '··········ENO··········';

6-7

Interjob Communication

{

{
{}

Create a port and connect to the other program.

create_port(io_port);
connect_circuit(io_port,
destination_name := 'INITIAL_JOB_PORT');
writeln(
'Job application6b connected circuit to INITIAL_JOB_PORT.·);
{

{
{
{}

Loop transmitting and receiving data until ····ENO····
is seen.

while not done do
begin
A
done := (string_data = ····ENO····);
send(data_message, io_port);
if not done
then
begin
{

{
{
{
{

Wait for the modified string to be
sent back to our port. When it arrives,
receive it and output it's contents to
the standard output file.

{}
wait_any(io-port);
receive(data_message. string_data, io_port);
writeln('Job APPLICATION6B received
string_data
'" . ' )
end;
A

end;
writeln('Job APPLICATION6B exited.')
end;
end;

Interjob Communication

6-8

,

Application 7

Intra-Job Synchronization
Problem
How do you efficiently synchronize two processes
running in the same job?

Solution
VAXELN provides the semaphore data type, which
synchronizes processes within the same job. A
semaphore may be thought of as a gate that will let
only a given number of processes through to a certain
resource. (For more information about semaphores,
see the VAXELN User's Guide.)
The simplest semaphore is called a binary semaphore;
this semaphore allows only one process at a time to
access the resource the semaphore protects.
VAXELN
also
provides
a
more
efficient
implementation of a binary semaphore, a mutex. A
mutex is more efficient as a process synchronization
mechanism because calls to ELN$LOCILMUTEX
and ELN$UNLOCK_MUTEX do not usually incur
the overhead inherent in calling a kernel procedure;
the semaphore routines, WAIT and SIGNAL, are
kernel procedures. (For more information about
mutexes, see either the V AXELN Pascal Language
Reference Manual, or the VAXELN C Run-Time
Library Reference Manual.)
In the example in this section, mutexes are used to
synchronize two processes writing to the console. Two

7-1

Intra-Job Synchronization

processes are created, each running the same code.
Each process locks the mutex, writes to the console,
then unlocks the mutex, thus preventing both
processes from writing to the console at the same time.
To build the sample application, use the following
commands:
$epascal application7 + eln$:rtlobject/lib
$link/nosysshr application7 + eln$:rtlshare/l;b +eln$:rtll1ib
$ebu;ld/noedit application7

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUlLD, as follows:
characteristic Inoconsole Inof;le Inoserver
program application7 Idebug

If you are using EDEBUG to run this program (which
we recommend), it's a good idea to first issue the
CANCEL CONTROL command so that the debugger
will not pause at the beginning of each process's
execution.

Intra-Job Synchronization

7-2

Example
The following is a listing of the example
written in Pascal (application7.pas).
module mutex_test;
{

{
{

This module demonstrates the use of mutexes to
synchronize two processes writing to the console.

{}
include
$mutex;
const
write_limit

10;

{ Writes performed by each process. }

var
gate : mutex;
program test(input,output);
var
first_process, second_process: process;
begin
{

{
{
{

Create the mutex and the processes. Lock the mutex
as soon as it's created so that both of the created
subprocesses will be forced to wait on it.

{}
create_mutex(gate);
lock_mutex(gate);
create_process(first_process, two_flavors, 1);
create_process(second_process, two_flavors, 2);
write('Hit (CR) to start the program: ');
readln;
{

{
{

Now, start the two processes by unlocking the mutex;
wait until both are finished before exiting.

{}

7-3

Intra-Job Synchronization

unlock_mutex(gate);
wait_all(first_process,second_process)
end.
process_block two_flavors(process_number : integer);
{

{
{

{
{
{}

ThiS is the process that will be created in two
different versions. One version will have the
value 1 for process_number, the other will have
the value 2 for process_number.

var
process_name: string(3);
write_count: integer := 0:
begin
{

{

Loop once to write a message to the console.

{}

while write_count
begin

< write_limit

do

{

{
{}

Wait on the mutex.

lock_mutex(gate);
{

{
{}

Write the message.

if (process_number
2)
then
begin
process_ name . = 'two' ;
write(' , :30)
end
else
process_name .= 'one' ;

writeln('This is from process
write_count := write_count + 1;
{
{
{

process_name) ;

Unlock the gate so that the other process
can continue.

{}

Intra-Job Synchronization

7-4

unlock_mutex(gate)
end;
end;
end;

7-5

Intra-Job Synchronization

Intra-Job Synchronization

7-6

Application 8

Making a Bootable Floppy Disk
Problem
How do you make a VAXELN system bootable from a
floppy disk if you do not have Micro VMS running on
your Micro VAX?

Solution
You need a VAXELN program to initialize a floppy
disk and make it bootable. The example in this
section initializes a disk (presumably a floppy disk),
mounts it, creates all required directories, and
provides three methods for copying the bootable
system file from a host system to the floppy. The
copying can be performed by:
•

The DeL COPY command on the host system

•
•

The VAXELN COPY-FILE utility
The program itself, using the GET and PUT
functions
The example program takes 2 program arguments:
the drive specification (such as DUAl:), and the
desired volume label for the disk. The program
prompts the user for missing parameters.

8-1

Bootable Floppy Disk

To build the sample application, use the following
commands:
$ epascal applicationS + eln$:rtlobject/lib
$ link applicationS + eln$:rtlshare/lib+rtl/lib/include=(eln$msgdef_text,ker$msgdef_text,pas$msgdef_text)
$ ebuild application8

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
PROGRAM application8
DEVICE DUA Iregister=%o772150 Ivector=%0154

Bootable Floppy Disk

8-2

Example
The following is a listing of the example
written in Pascal (application8.pas).
module APPLICATION8;
{++
{

{ Abstract:
{
{
{
{
{
{
{--}

This program initializes an RX50 floppy disk, creates
required directories on it, and copies a bootable
VAXELN system image on it. A MicroVAX may then be
booted from the floppy.

include
$diSk_utility, Sfile_utility, Sget_message_text,
$elnmsg, $kernelmsg, $pascalmsg;
program make_bootable_floppy;
{

{

Local constant definitions:

{}
const
boot_file

=

'[SYSO.SYSEXE]sysboot.exe';

{

{
{}
type

Local type declarations:

blocks = packed array [1 .. 128] of integer;

{

{
{}

Local variable declarations:

var
copy_method, file_size, status: integer;
bad_block_list: dsk$_badlist(1);

8-3

Bootable Floppy Disk

answer: varying_string(lO);
drive_name: varying_string(30);
status_text, system_file_spec: varying_string(Z55);
volume_label: varying_string(12);
bootable: boolean;

procedure error_exit(status_message: varying_string(80);
status_v~lue: integer);
{++
{

{ Routine description:
{
{
{
{
{
{
{

This procedure accepts a status value and some
accompanying text. translates the status value
to VAXELN message text. and outputs both text strings
to the console. This procedure then causes the program
to terminate.

{ Inputs:
{
{

{
{
{
{
{
{
{

status_message - Supplies a varying string which is
output to the console prior to the
status message.
status_value -

Supplies the status value whose
associated text will be output to
the console.

{ Outputs:
{
{
{
{
{--}

Status code text information is written to the standard
output file (usually the console).

{

(
{}

Local variable declarations:

var
status_text: varying_string(255);

Bootable Floppy Disk

8-4

begin
{

{
{
{

Obtain the text associated with this status code and
output both the input string and the generated string
to the console.

{}

eln$get_status_text(status_value, [statusStext], status_text);
writeln;
writeln(status_message);
writeln(status_text);
{

{

Dismount the specified disk volume.

{}

dismount_vo1ume(drive_name, status:=status);
exit
end;

function get_source_spec: varyinQ_string(255);
{++
{

{ Routine description:
{
{
{
{
{

This function prompts the user for the file
specification of the source system file on
the host machine.

{ Inputs:
{
{
{

None.

{ Outputs:
{
{
{
{--}

Function returns user-entered file specification.

8-5

Bootable Floppy Disk

{

{
{}

local variable declarations:

var
source_spec

varying_string(255);

begin
{

{
{

Prompt the user for the file specification. Note that
a multi-line prompt is used. Read the specification.

{}

writeln;
writeln{'Enter full filename (including node number and access',
'control string if necessary)');
write('of system file: ');
readln (source_spec);
{

{

Return the file specification input.

{}

end;

[inline] procedure dcl_method;
{++
{

{ Routine description:
{
{
{
{
{

This procedure simply prompts the user to type the
appropriate Del commands for copying the system file to
the disk.

{ Inputs:
{
{
{

None.

{ Outputs:
{

Bootable Floppy D~sk

8-6

{

None.

{
{--}

begin
{

{
{
{}

Write an appropriate prompt string and wait for the user
to indicate that the file has been copied.

writel n;
writeln('From the host system, use');
writeln('
$ COpy systemfile.sys node::', drive_name,
'[SYSO.SYSEXE]sysboot.exe/CONTIGUOUS');
writeln('to copy the system file');
writeln;
write('Hit RETURN when complete: ');
readln (answer)
end;

[inline] procedure copyfile_method;
{++

{

{ Routine description:
{
{
{
{
{

This procedure copies the system image using the
copy_file utility procedure. Note that the file
being copied is assumed to be contiguous.

{ Inputs:
{
{
{

None.

{ Outputs:
{
{

None.

{
{--}

begin

8-7

Bootable Floppy Disk

{

{
{}

Get source-file specification.

{

{

n

Copy the file to this node.

copy_fi1e(system_fi1e_spec,
drive_name+boot_fi1e,
status := status);
if not odd(status)
then
error_exit('copy_fi1e failed', status)
end;

[in1ine] procedure get_put_method;
{++
{

{ Routine description:
{
{
{
{
{
{

This procedure copies the system file directly, using
GET and PUT. This method is used instead of the
copy_file procedure if the source system file is not
contiguous.

{ Inputs:
{
{
{

None.

{ Outputs:
{
{
{
{--}

None.

{

{

n

Local variable declarations:

Bootable Floppy Disk

8-8

var
file_attributes:

A

fi1e$attributes_record;

begin
{

{
{}

Get the source file specification.

{

{
{}

Open the source file.

open(source_fi1e_var,
file_name := system_fi1 e_spec ,
history := hfstory$old,
file_attributes := file_attributes,
status := status);
if not odd(status)
then
error_exit('Open source file failed', status);

{

{
{}

Compute the size of the file.

file size := file attributes
A .end of file block;
A
if f11e_attributes .first_free_byte ~ 0 then
{

{
{}

If the file is not of zero length, continue.

if file_size> 0
then
begin
{

{
{}

Open the destination file.

Make it contiguous.

open(destination_file_var,
file_name .= drive_name+boot_file,

8-9

Bootable Floppy Disk

history := history$new,
contiguous := true,
filesize := file_size);
if not odd(status)
then
error_exit('Copy_file failed', status);
rewrite{destinatio"_file_var):
{

{

Copy the source to the destination.

{}
while not eof(source_file_var) do
begin
A
A
destination_file_var := source_file_var
put{destination_file_var);
get{source_file_var)
end;
end:
end:

{

{

Main program starts here.

{}
begin
{

{
{
{

Get the drive name and desired volume label as program
arguments 1 and 2, respectively. If no drive or volume
label is specified, ask the user.

{}

drive_name := program_argument(l);
if drive_name
then
begin
write{'Enter drive name
readln(drive_name);
write{'Enter volume label
readln(volume_label)
end
else

Bootable Floppy Disk

8-10

');
'):

begin
volume_label := program_argument(2);
i f vol ume_l abe 1
then
begin
write('Enter volume label
');
readln(vo1ume_1abe1)
end;
end;
{

{

Set name and label to detaults, if not specified.

{}
if drive_name = "
then
drive_name .= 'DUAl:';
if volume_label = "
then
volume_label := 'SCRATCH';
if substr(drive_name, length(drive_name), 1) (> '. ,
then
. = drive_name + '.'
. .,
{

{

Ask the user if all is correct.

{}
writeln;
writeln('····· Initializing', drive_name, ' ••••• ');
writeln('This will destroy all information on this disk');
write('Do you wish to continue (V or N [V])? ');
readl n (answe r) ;
if (answer = ") or
(substr(answer, 1, 1)
(substr(answer, 1, 1)
then
begin

'V')

or

'y' )

{

{

Initialize the volume.

{}
init_volume(drive_name,
volume_label,
verified := false,
bad_list := bad_b10ck_1ist::dskS_bad1ist(O).
status := status);

8-11

Bootable Floppy Disk

if not odd(status)
then
error_exit('init_volume failed', status);
{

{

Mount the floppy.

{}
mount_volume(drive_name, volume_label, status);
if not odd(status)
then
error_exit('mount_vo1ume failed', status);
{

{
{}

Create the necessary directories.

create_directory(drive_name + '[SYSO]', status};
if not odd(status)
then
error_exit('create_directory failed', status};
create_directory(drive_name + '[SYSO.SYSEXE]', status};
if not odd(status}
then
error_exit('create_directory failed', status};
{

{
{
{
{}

The floppy is now initialized and is bootab1e.
The system image may be copied in one of three
ways, shown below.

write1n;
write1n('Ready to copy system image. '};
write1n('
1 Copy from host using DCl copy'};
writeln('
2 Copy via VAXElN COPY_FILE utility');
writeln('
3 Copy with GETs and PUTs');
writeln;
writeln('Enter desired copy method (note: method 2');
write(
'requires a contiguous file on the host system) 1-3: ');
readln(copy_method};
bootable := true;
case copy_method of
1:

2:

Bootable Floppy Disk

copyfi1e_method;

8-12

otherwi se
bootable ,= false;
end;
dismount_volume(drive_name, status ,= status);
if not odd(status)
then
error_exit('dismount_volume failed', status);
writeln;
if bootable
then
writeln('Operation complete - disk bootable'}
else
writeln('Operation complete - disk initialized')
end
else
begin
writeln;
writeln('Initialization aborted')
end;
end;
end;

8-13

Bootable Floppy Disk

Bootable Floppy Disk

8-14

Application 9

Multiple Circuit Server
Problem
How do you build and use a ttserver" in VAXELN? A
server is a process that performs a particular
processing function for other processes; the other
processes are referred to here as ttapplications."
Description

A server can be used to describe many data processing
and control problems, especially those problems that
require one or more of the following characteristics:
• Resource Control. If a central resource, a disk
data file for example, must be protected in one of
your systems, access to that resource can be
metered by a server.
• Complex Synchronization. The example in this
section is of a multi-thread server, but a singlethread server can also be useful for forcing all
operations of a particular kind through a single
gateway. For example, in the case of a central
application database where a data file must be
protected against concurrent access, the server
could be used to perform an intelligent
GETIPUT
operation,
with
additional
application-specific record processing performed
as a side-effect. This capability is an extension
of the extant VAXELN synchronization
features.

9-1

Multiple Circuit Server

•

•

Modularity. The server model is the epitome of
modularity. Writing code under VAXELN to
communicate with a server process is not much
harder than writing code to call a subroutine.
An additional benefit of the server model is a
natural consequence of VAXELN: the server and
application programs can be moved around the
local network without any changes.
This
freedom allows you to easily balance the
resource requirements of your applications
program across the nodes in your network.
However, servers need not be network-wide
resources; implementing node-local servers
under VAXELN is a trivial modification to the
more common network-wide server. In the
server example shown in this section, the only
thing making the server node-local rather than
network-wide is the scope of the NAME variable,
SERVER$PORT.
Reliability.
The server and application
communicate with each other through a circuit,
a reliable communication mechanism.

Solution
The example programs in this section show the design
of a simple network-wide multi-thread server. In
order to keep the emphasis on the basic framework of
the server, the example's function is simple: records of
ASCII text sent to the server are converted to upper
case and are sent back to the application.
The server job has the [nit required attribute set in it's
System Builder data file; this allows the master
process to create the global NAME object (used by

Multiple Circuit Server

9-2

applications to locate the server's input port) before
any application has a chance to execute.
After initialization, the server's master process
simply waits for incoming connection requests on the
port
associated
wi th
the
global
name
SERVER$PORT. For each such request it receives,
the master process creates a process to handle the
complete server dialogue and connects the circuit from
the application to the new subprocess.
The code for the subprocess is also quite simple. In the
example server, the logical end-of-dialogue is defined
as the receipt of a null record by the server. The code
is a basic structure of looping until a null record is
received, translating records and retransmitting them
back to the application.
Also in the example server is an additional bit of logic
to implement a rudimentary timeout capability. IT
the server does not receive a record from the
application before the timeout expires, the server
assumes that the application has implictly terminated
the dialogue. In an actual application however, a
timeout's lapse should probably cause the output of an
error message or some other abnormal event; the
correct behavior in this situation is highly dependent
upon the application.
What follows on the next several pages are a Pascal
example, a Pascal sample application, a C example,
and a C sample application.
Note that either of the server examples can be used
with either of the sample applications; the examples
implement identical capabilities.

9-3

Multiple Circuit Server

The following is a listing of the System Builder data
file used to build a system containing the sample
server and sample application pair:
program application9a linitialize
program application9b

Multiple Circuit Server

9-4

C Example
The following is a listing of the example
written in C (application9a.c).
#module multiple_circuit
#include $vaxelnc
#include ctype
#include descrip

,.

•
• Abstract:

•
•
•
•
•
•
•
•

.,
,.
.,•

This module shows an example of how a typical server is
implemented using an individual process to send each
incoming circuit request to the server's global port,
named SERVER$PORT. This module demonstrates how
the master process dispatches incoming circuit requests
to the subordinate server processes.

Job-wide declarations:

LARGE_INTEGER timeout_interval;
multiple_circuit()
{

,.
• Functional description:

•
•
•

•
•
•

This is the master process for the server example.
It simply lis~ens for circuit requests from remote
processes and creates a subprocess to handle each
request.

• Inputs:

•
•
•
•

Incoming circuit connection requests to
the global port, named SERVER$PORT.

9-5

Multiple Circuit Server

• Outputs:

•
•
•
•

All incoming requests are handled by creating
a subprocess to satisfy each request.

,.
.,•

Master-process-local variable declarations:

PORT
NAME
PORT
static
void
;nt
static
PROCESS

,.

·circuit_port;
global_port_name;
master_process_job_port;
$DESCRIPTOR(server_name,"SERVER$PORT");
server$process();
status;
$DESCRIPTOR(timeout_string,"
o 00:10:00.00");
subprocess;

•

MAS T E R PRO C E S SIN I T I A LIZ A T ION:

•

Begin by creating a name for this job's port.
If the name already exists, a server process
already exists; simply exit.

•

•
•

ker$create_name(&status,
&global_port_name,
&server_name,
&master_process_job_port,
NAME$UNIVERSAL);
if (! (status&l»

ker$ex;t(NUlL, 1);
Compute 10-minute timeout constant used by subprocesses.
timeout_interval

eln$time_value(&timeout_string);

,.

.,
•

The initialization is done; inform VAXELN .

Multiple Circuit Server

9-6

ker$initialization_done(NULL);
/*

MAS T E R

*
*
*

PRO C E SSM A I N LIN E

COD E:

Loop indefinitely waiting for a remote circuit request.
When one is received, create a port to handle the
circuit and try to establish the circuit with the
sender.

*
*

*
*
*

If the circuit can be established, create a process
to service this circuit and pass this newly created port
to the process as a parameter.

*
*

If the circuit cannot be established, simply delete
the new port and continue looping, waiting for requests.

*

*
*

*/

for(;;)
{
/*

*

Wait for any requests on the job port.

*/

/*

Allocate a new port and create the PORT object.

*

*/

circuit_port = calloc(l,sizeof (PORT»;
ker$create_port(NULL, circuit_port. 4);
/*

*

Setup the circuit using the new port.

*/

ker$accept_circuit(&status,
&master_process_job_port,
circuit_port.
FALSE,
NULL,
NULL) ;
if (status&l)
{

9-7

Multiple Circuit Server

/.

•

Start the server process and
pass it to the circuit port.

•

./

ker$create_process(NUll,
&subprocess,
server$process,
NUll,
ci rcuit_port);
/.

•
•

Note that it is now the responsibility of
the subprocess to delete the PORT object
and deallocate the port variable memory at
the completion of the server's dialogue
with the remote application. Of course,
this house-cleaning must also be done if
the circuit is broken due to error.

•

•
•

•

•
•
•
•

Now lower the process priority of the
created subprocess to be just BELOW the
priority of the master process; this
ensures that none of the created
subprocesses ever prevent the master
process from servicing connection requests.

•

..•
•

./

ker$set_process_priority(NULL, subprocess, 9);
}

else
{
/.

•
•

.

The connect failed; delete and
deallocate the PORT object .

/

ker$delete(NULl, circuit_port);
cfree(circuit_port);
}
}

}

void server$process(circuit_port) PORT ·circuit_port;
{
/.

•
•

SUB PRO C E SSM A I N LIN E

Multiple Circuit Server

9-8

COD E

• Routine description:

•
•
•
•
•
•
•
•
•
•
•
•
•

•
•
•
•
*
*

•
•
*
*

•

*

•
•
•
•
*
*

This is the entry routine for a separate process that
is created to handle an incoming connection request.
In this example, the service performed by the server,
and the protocol observed by the two circuit partners,
is vastly simplified to keep the example small and
understandable.
The proto~ol is simple: Messages containing text strings
are sent from the "application" (the other half of the
circuit) to this process (the "server"). The server
processes each message by converting all the lower-case
letters in the string to upper-case, and transmiting
the converted text back to the application. The
application terminates the exchange by sending a record
consisting of the null string.
A receive timeout is built into this server to add a
little realism to a simplified example. If the timeout
expires, the server abandons the circuit as if the
exchange had been terminated normally. In an actual
application, some further application-specific error
processing, such as printing a diagnostic message, would
most likely occur.
Inputs:
circuit_port - Circuit upon which a request
has been accepted.

• Outputs:

*
*

The incoming request is handled.

*

*/

1*

*

Process-local variable declarations:

./

BOOLEAN
int
MESSAGE
VARYING_STRING(80)

done = FALSE;
i,status.wait_result,message_size;
message_id;
*message_ptr;

1*

*

•

Loop until:

9-9

Multiple Circuit Server

•
•
•
•
•
•

Receive timeout occurs
or
Receive error occurs
or
Null string is received from application

while(!done}
{
/.

•

Wait for the port or a timeout.

*/

ker$wait_any(NUll,
&wait_result,
&timeout_interval,
ci rcuit_port};
/*

*
*

If the result of the wait service was O.
the wait terminated because of a timeout.

*/

if (wait_result
O)
done = TRUE;
else
{

Otherwise, a message has been sent to
the port. Receive the message.
ker$receive(&status,
&message_id,
&message_ptr,
&message_size,
circuit_port,
NULL,
NULL) ;
if (.! (status&l»
done = TRUE;
else
{

if (message_ptr-)count
done = TRUE;
else
{

Multiple Circuit Server

9-10

0)

/*

A nonzero-length string has
successfully been received.
Convert the string to upper
case.

*
*
*
*
*/

for(i=O; icount; i++)
message_ptr->data[i] =
_toupper(message_ptr-)data[i]);
ker$send(NULL,
message~id,

message_size,
ci rcu it_port,
NULL,
FALSE) ;

}
}

}
}
/*

*
*

The exchange has terminated; delete the port,
deallocate the local port storage, and exit.

*/

ker$delete(NULL, circuit_port);
cfree(circuit_port);
}

9-11

:\IIultiple Circuit Server

Sample Application
The following is a listing of a sample application written
in C (application9b.c).
#module multiple_circuit_sender
#include $vaxelnc
#include descrip
#include stdio
/*

*
* Abstract:
*
*
This module shows an example of a simple terminal-driven
*
application that makes use of the server example program
*
described above.
*
*
The application reads a line from the terminal and
*
passes the line to the server for processing. The
*
processed line is read back from the server and
*
displayed at the terminal.
*
*
The process continues until the user enters a blank
*
line, which is the protocol established in the server as
*
the "end-of-dialogue" marker.

*

*/

multiple_circuit_sender()
{
/*

*

Variable declarations:

*/

PORT
static
int
BOOLEAN
MESSAGE
VARYING_STRING(BO)

~ultiple

circuit_port;
$DESCRIPTOR(destination_name,"SERVERSPORT");
discard;
done = FALSE;
message_id;
*message_ptr;

Circuit Server

9-12

Start by connecting our job port to the sample server,
using the job port's universal name, SERVER$PORT.
On error, exit the job with appropriate status.

•

ker$job_port(NULL, &circuit_port);
ker$connect_circuit(NULL,
&circuit_port,
NULL,
&destination_name,
FALSE,
NULL,
NULL) ;
Print the prompt for user input.
printf("\nEnter your input data.\n"):
printf("Terminate your input by entering a blank 1ine.\n");
/.

•
•
•

Loop for each nonb1ank line entered. Send it
to the server for processing, read it back from
the server, and print it .

.
/

whi1e(!done)
{

ker$create_message(NULL,
&message_id,
&message_ptr,
sizeof (·message_ptr»;
gets(message_ptr->data);
message_ptr-)count = str1en(message_ptr-)data);
if (message_ptr->count == 0)
done = TRUE;
ker$send(NULL,
message_id,
sizeof (·message_ptr),
&ci rcu it_port,
NULL,
FALSE) ;

9-13

MUltiple Circuit Server

if (! done)

{

ker$wait_any(NUll.
&discard.
NUll,
&ci rcu it_port);
ker$receive(NUll,
&message_id.
&message_ptr,
&discard.
&circuit_port,
NUll,
NUll) ;
printf("%.*s\n",
message_ptr-)count,
message_ptr-)data);
}

}
}

Multiple Circuit Server

9-14

Pascal Example
Below is a listing of the example written
in PASCAL (application9c.pas).
module multiple_circuit;
{++
{
{

{ Abstract:
{
{
{
{
{
{
{
{
{
{--}

This module shows an example of how a typical server is
implemented, in Pascal, using an individual process to
service each incoming circuit request sent to the server's
global port, named SERVER$PORT. This module
demonstrates how the master process dispatches incoming
circuit requests to the subordinate server processes.

{

{

Job-wide declarations:

{}
var
timeout_interval: large_integer;

program multiple_circuit;
{++
{

{ Functional description:
{
{
{
{
{
{

This is the master process for the server example.
It simply listens for circuit requests from remote
processes and creates a subprocess to handle each
request.

9-15

Multiple Circuit Server

{ Inputs:
{
{
{
{

Incoming circuit connection requests to the global port,
named SERVERSPORT.

{ Outputs:
{
{
{
{
{--}
{
{

All incoming requests are handled by creating a
subprocess to accomplish each request.

Master-process-local variable declarations:

{}
var
master_procesS~job_port:

port;

circuit_port: port;
global_port_name: name;
status: integer;
subprocess: process;
begin
{

{

MAS T E R PRO C E S SIN I T I A LIZ A T ION:

{

{
{
{
{}

Begin by creating a name for this job's port. If the
name already exists, there is already a server process
in existence; simply exit.

create_name{global_port_name,
'SERVER$PORT' ,
master_process_job_port,
table := name$universal,
status := status);
if not odd{status)
then
exit(exit_status

.=

1);

{

{
{}

Compute 10-minute timeout constant used by subprocesses.

timeout_interval := time_value{'

Multiple Circuit Server

9-16

o 00:10:00.00');

{

{

The initialization is done; inform VAXELN.

{}
initialization_done;
{
{

{
{
{
{
{
{
{
{
{
{
{
{
{
{}

MAS T E R

PRO C E SSM A I N LIN E

COD E:

Loop indefinitely waiting for a remote circuit request.
When one is received, create a port to handle the
circuit and try to establish the circuit with the
sender.
If the circuit can be established, create a process
to service this circuit and pass this newly created port
to the process as a parameter.
If the circuit cannot be established, simply delete
the new port and continue looping, waiting for requests.

while true do
begin
{

{

Wait for any requests on the job port.

{}

{

{

Allocate a new port and create the port object.

{}
new(circuit port);
A
create_port(circuit_port ,limit .= 4);
{

{

Setup the circuit using the new port.

{}
accept_circuit(master_process_job_port,
connect := Circuit_portA,
status := status);
if odd(status)
then
begin

9-17

Multiple Circuit Server

{

{
{

Start the server process and
pass it to the circuit port.

{}
create_process(subprocess,
server$process,
ci rcuit_port);
{

{
{
{
{
{
{
{
{

Note that it is now the responsibility
of the subprocess to delete the PORT
object and deallocate the port variable
memory at the completion of the server's
dialogue with the remote application.
Of course, this house-cleaning must also
be done if the circuit is broken due to
error.

{

{
{
{
{
{
{
{

Now, lower the process priority of the
created subprocess to just BELOW the
priority of the master process; this
ensures that none of the created
subprocesses ever prevent the master
process from servicing connection
requests.

{}
set_process_priority(subprocess, 9)
end
else
begin
{

{
{

The connect failed; delete and
deallocate the PORT object.

{}
A

delete(circuit_port ) ;
dispose(circuit_port)
end
end;
end.

Multiple Circuit Server

9-18

process_block server$process(circuit_port: Aport);
{
{
{

SUB PRO C E S S

M A I N LIN E

COD E

{ Routine description:
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{

This is the entry routine for a separate process that
is created to handle an incoming connection request.
In this example, the service performed by the server,
and the protocol observed by the two circuit partners,
is vastly simplified to keep the example small and
understandable.
The protocol is simple: Messages containing text strings
are sent from the "application" (the other half of the
circuit) to this process (the "server"). The server
processes each message by converting all the lower-case
letters in the string to upper-case and then transmits
the converted text back to the application. The
application terminates the exchange by sending a record
consisting of the null string.
A receive timeout is built into this server to add a

little realism to a simplified example. If the timeout
expires, the server abandons the circuit as if the
exchange had been terminated normally. In an actual
application, some further application-specific error
processing, such as printing a diagnostic message, would
most likely occur.

{ Inputs:
{
{
{
{

circuit_port - Circuit on which a request
has been accepted.

{ Outputs:
{
{
{
{--}

The incoming request is handled.

{

{

Process-local variable declarations:

{}
var
done: boolean := false;
status, wait_result: integer;

9-19

Multiple Circuit Server

message_id: message;
message_ptr: Avarying_string(80);
begin
{

{

loop until:

{

{
{
{
{
{

Receive timeout occurs
or
Receive error occurs
or
Null string is received from application

{
{}

while not done do
begin
{

{

Wait for the port or a timeout.

{}
A
wait_any{circuit_port ,
result := wait_result,
time := timeout_interval);
{

{
{

If the result of the wait service was 0,
the wait terminated because of a timeout.

{}
if wait_result = 0
then
done := true
else
begin
{

{
{

Otherwise, a message has been sent to
the port. Receive the message.

{}
receive(message_id,
message_ptr,
ci rcuit_port A,
status := status);
if not odd(status)
then
done := true

Multiple Circuit Server

9-20

else
begin
A
if length(message_ptr
then
done .= true
else
begin

}

0

{
{

{
{
{

nonzero-length string
has successfully been
received. Convert the
string to upper case.

A

{}
message_ptr .=
A
translate_string(message_ptr ,
'ABCOEFGHIJKLMNOPQRSTUVWXYZ',
oldchars .=
'abcdefghijklmnopqrstuvwxyz'};
send(message_id,
ci rcuit_port"}
end;
A

end;
end;
end;
{

{
{

The exchange has terminated; delete the port, deallocate
the local port storage, and exit.

{}
delete(circuit_port"};
dispose(circuit_port}
end;
end;

9-21

Multiple Circuit Server

Sample Application
The following is a listing of a sample application written
in PASCAL (application9d.pas).
module

mu1tip1e_circuit_sende~;

{++
{
{

{ Abstract:
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{
{--}

This module shows an example of a simple terminal-driven
application that makes use of the server example program
described above.
The application reads a line from the terminal and passes
the line to the server for processing. The processed line
is read back from the server and displayed at the
terminal.
The process continues until the user enters a blank line,
which is the protocol established in the server as the
"end-of-dia10gue" marker.

program mu1tip1e_circuit_sender;
{

{

Variable declarations:

{}
var
circuit_port: port;
done: boolean := false;
message_id: m~ssage;
message_ptr: varying_stri ng (80);
begin
{

{
{
{

Start by connecting our job port to the sample server,
using the job port's universal name, SERVER$PORT.
On error, exit the job with appropriate status.

{}

Multiple Circuit Server

9-22

job_port(circuit_port);
connect_c;rcuit(circuit_port,
destination name

.=

'SERVER$PORT');

{

{

Print the prompt for user input.

{}
writel n;
writeln('Enter your input data. ');
writeln('Terminate your input by entering a blank line. ');
writeln;
{

{
{
{

Loop for each nonblank line entered. Send it to the
server for processing, read it back from the server,
and print it.

{}
while not done do
begin
create_message(message_id,
message_ptr);
A
readln(message_ptr ) ; A
if length(message_ptr ) = 0
then
done := true;
send(message_id, circuit_port);
if not done
then
begin
wait_any(circuit_port);
receive(message id, message ptr, circuit_port);
writeln(message=ptr
end;
end;
A

)

-

end;
end;

9-23

Multiple Circuit Server

Multiple Circuit Server

9-24

Application 10

Self-Defining Data Structures
Problem
How do you neatly access self-defining data structures
using VAXELN Pascal?
A self-defining data
structure is one in which the content of one field
determines the size of one or more following fields.

Solution
The VAXELN Pascal concept of flexible types,
together with the WITH-AS statement, provides a
powerful tool to easily access self-defining data
structures. The general strategy is to define a flexible
"template" type that consists of a variable number of
fill bytes followed by a series of data items known to
occur together.
The example in this section shows the use of this
technique to access the contents of a structure
consisting of variably sized strings, along with some
data pertaining to each string. The example shows
the construction of a routine to walk through such a
structure and access all the data.
To build the sample application, use the following
commands:
$ epascal applicationl0 + eln$:rtlobject/lib
$ link/nosysshr applicationl0 + eln$:rtlshare/lib +eln$:rtl/lib
$ ebuild/noedit applicationl0

10-1

Self-Defining Data Structures

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inoconsole Inofile Inoserver
program applicationlO Idebug

Self-Defining Data Structures

10-2

Example
The following is a listing of the example
written in Pascal (applicationlO.pas).
PROGRAM test(output);
{

{
{
{
{
{

This program demonstrates accessing a self-defining
data structure using a flexible type and a WITH-AS
statement. This self-defining data structure
consists of a block of bytes containing repeated
instances of name and age data.

{

{
{
{
{
{
{
{

The first byte is an unsigned integer giving the
count of bytes in the immediately following string,
which is a person's name. The name string is followed
by an unsigned byte giving the person's age.
The last byte in the data block is a zero length
for a name string. (The name string is, of course,
nonexistent.)

{}
{

{
{

Below is an example data block. It would be more
common to have this data block read from a disk.

{}
VAR
data_block: arraY[l .. 57] of char := (
chr(4). 'F','r','e','d', chr(19),
chr(3), 'B', '0', 'b', chr(26),
chr(6}, 'M','a','r','t','h','a', chr(32),
chr(4}, 'J', 'a', 'c', 'k', chr(14),
chr(6), 'V','i','c','t','o','r', chr(52),
chr(4}, 'O','a','w','n', chr(17),
chr(6}, 'M','a','r','c','i','a', chr(29},
chr(7}, 'B', 'a', 'r', 'b', 'a', 'r', 'a', chr(5),
chr(O}) ;
TYPE
unsigned_byte

= [BYTE]O .. 255;

{

{
{

Below is the template type that will be used
to access data in data blocks.

10-3

Self-Defining Data Structures

{}
temp1ate(m,n : integer) = packed record
fill: byte_data(m);
name: string(n);
age : unsigned_byte;
next_length : unsigned_byte;
end;
PROCEDURE print_block(blk_ptr : Aanytype);
{

{
{
{}

This procedure prints out the contents of a
data block whose address is given by blk_ptr.

var
skip_count: integer;
string_length: intege~;
begin
{

{
{
{}

Set the length of the first name string and
initialize the number of bytes of data to skip.

string_length := blk_ptrA::unsigned_byte;
skip_count := 1;
{

{
{}

Output a header.

writeln('Contents of data block:');
write1n;
while string_length > 0 do
begin
with x as blk_ptrA::template(skip_count,string_length) do
begin
{

{

First, write the name and age.

{}
write(x.name);
writeln(', age'

Self-Defining Data Structures

x.age:1);

10-4

{

{
{
{
{

Increment the skip_count to skip over
the name string, as well as the count
byte and age byte. Then, get the
string length of the next name string.

{}

skip_count .= skip_count + string_length + 2;
string_length .= x.next_length
end;
end;
end;
{

{
{}

Main program starts here.

begin
print_block(address(data_block»
end;

10-5

Self-Defining Data Structures

Self-Defining Data Structures

10-6

Application 11

VAXELN Interface to VAXNMS
Problem
How does your VAXELN system communicate with a
VAXNMS system?

Solution
First, the following command procedure (filename
time.com) must be present in the default DECnet
directory on the V AXNMS machine:
$
$
$
$

open/write fred sys$net
time = f$time()
write fred time
close fred

Then, when the VAXELN system connects to the
target machine, this command file is executed; this
execution causes the VAXELN system to receive a
message containing the current time. Another way to
accomplish this is having the command file run a
program that opens the file and writes to it.
To build the sample application, use the following
commands:
$ epascal application11 + eln$:rtlobject/lib
$ link/nosysshr application11 + eln$:rtlshare/lib +eln$:rtl/lib
$ ebuild/noedit application11

11-1

Interface to VAXJVMS

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inoconsole Inofile Inoserver
program APPLICATION11 Idebug

Interface to VAX!VMS

11-2

Example
The following is a listing of the example
written in Pascal (applicationll.pas).

{

{
{
{

This is a VAXElN application that initiates a connection
with a COM file on a remote VMS system to request the
time of day. Note the following:

{

{
{

Change 10.172 to the node address
of the VMS system.

{

{
{

The command procedure TIME.COM must be present in the
default DECnet directory of the machine running VMS.

{}
var
this_port: port;
this_message: message;
these_data: Astring(32);
actual_time: string(32);
current_time: large_integer;
program time_request(input, output);
begin
{

{
{

First, create the port that will be used to communicate
with the VMS system.

{}

{

{
{
{
{

Executing the connect_circuit causes TIME.COM in the
default DECnet directory on the VMS system to be run.
10.172 is the number of the node on which VMS was running.
TIME.COM itself looks like this:

{
{
{

$ open /write fred sys$net
$ time = f$time()

{

$ write fred time

11-3

Interface to VAXlVMS

{

$

close fred

{}
connect_circuit(this_port, destination_name .= '10.172::time');
writeln('Connected to the VMS system');
wait_any(this_port);
{

{
{}

Read the message and display it.

receive(this_message, these_data, this_port);
writeln('The message was "', these_data A , ' " ' ) ;
disconnect_circuit(this_port);
{

{
{
{}

Set the time; then get the time to
double check that everything worked.
A

actual_time := substr(these_data
1, 23);
current_time := time_value(actual_time);
set_time(current_time);
get_time(current_time);
actual_time := time_string(current_time);
writeln('The current time is " actual_time);
writeln('Done')
,

end.
end;

Interface to VAXlVMS

11-4

Application 12

VAXELN Time Routines
Problem
How do you manipulate time data in VAXELN?

Solution
VAXELN provides the SET_TIME and GET_TIME
routines to set and retrieve the system time; they use
large integers to manipulate times.
The
TIME_VALUE, TIME_STRING, and TIME-FIELDS
routines
are
also
provided;
they
convert
LARGEJNTEGERs, representing time, to and from
strings. The example in this section demonstrates the
use of all of these routines.
To build the sa..rnple application, use the following
commands:
$ epascal application12 + eln$:rtlobject/lib
$ link/nosysshr application12 + eln$:rtlshare/1ib +e 1 n$ : rt 111 i b
$ ebui1d/noedit app1ication12

The sample application can then be loaded into a
target machine and executed. The data file must
contain information for EBUILD, as follows:
characteristic Inoconso1e Inofi1e Inoserver
program application12 Idebug

12-1

VAXELN Time Routines

Example
The following is a listing of the example
wri tten in Pascal (application 12.pas).
module timer_test;
{

{
{

This module demonstrates the use of
the VAXELN time routines.

{}

var
elapsed_time, actual_time, current_time: large_integer;
resul t: integer;
time_rec: time_record;
a_time_string: varying_string(80);
elapsed_time_string: varying_string(12);
program timer(input,output);
var
i: integer;
begin
writeln('Program starting');
{

{

Set the date and time.

{}

write('Enter today' 's date and time: ');
readln(a_time_string); .
current_time := time_value(a_time_string);
set_time{current_time);
writeln('The date and time have been set');
{

{
{
{}

Use the time_fields function to con\ert
current_time back to a string.

time_rec := time_fields(current_time);
with time_rec do
writeln(day:l, 'I', month:1, 'I', year:1,
hour:2, ':', minute:2, ':', second:2,

VAXELN Time Routines

12-2

hund redth : 2) ;

{

{

Loop 5 times to display the time every 5 seconds.

{}
for

:= 1 to 5 do
begin

{

{

Set up for a delay of 5 seconds.

{}
elapsed_time := time_value('O ::5');
get_time(actual_time);
{

{

Wait for 5 seconds to go by.

{}
wait_any(time := elapsed_time, status
get_time(current_time);

.=

result):

{

{
{

Compute the elapsed time.
Oisplay the actual and elapsed time.

{}
elapsed_time := current_time - actual_time:
a_time_string := time_string(elapsed_time):
elapsed_time_string := substr(a_time_string. 12):
writeln( 'The actual time was' time_string(actual_time»;
writeln;
writeln('The elapsed time is' elapsed_time_string);
writeln;
writeln
end;
end:
end.

12-3

VAXEL~

Time Routines

VAXELN Time Routines

12-4



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.3
Linearized                      : No
XMP Toolkit                     : Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:56:37
Create Date                     : 2014:09:04 12:19:53-08:00
Modify Date                     : 2014:09:04 11:36:17-07:00
Metadata Date                   : 2014:09:04 11:36:17-07:00
Producer                        : Adobe Acrobat 9.55 Paper Capture Plug-in
Format                          : application/pdf
Document ID                     : uuid:4da1fb27-fca0-a64e-a910-1d438f9fb24e
Instance ID                     : uuid:5faa151b-62cd-8543-8bc1-7128218628b7
Page Layout                     : SinglePage
Page Mode                       : UseOutlines
Page Count                      : 134
EXIF Metadata provided by EXIF.tools

Navigation menu