\input doc:deproc


%	macros needed for this article

\define\POBox{P.\thinspace O.~Box }
\define\VAX{\leavevmode\hbox{V\kern-.12em A\kern-.1em X}}

\define\<#1>{$\langle${#1}$\rangle$}
%\define\cs{\<cs>}
%\define\css{\cs es}
\define\cs{{\tt cs}}
\define\css{\cs-es}
\define\CR{\<\smc cr>}
\define\tab{\<\smc tab>}
\def\ttvert{{\tt\char'174 }\ignorespaces}
\define\acp{ACP}
\define\irp{IRP}
\define\cscb{{\tt COPY\_SYSTEM\_CONTROL\_BLOCK}}
\define\cnpb{{\tt COPY\_NONPAGED\_BUFF}}
\define\vms{VMS}
\define\pcb{PCB}
\define\spcb{SCANPCB}
\define\pas{Pascal}
\define\vpas{\vax\ \pas}
\define\mac{MACRO}
\define\sprog{systems programming}
\def\km{kernel mode}
\def\cmkd{change-mode-to-kernel dispatcher}
\def\hll{high-level language}

\title{``But That's Impossible in \pas''\\{\it or}\\
    Systems Programming in a High-Level Language}
\author{E. W. (Wayne) Sewell\\
    Software Engineering Specialist\\
    E-Systems, Garland Division\\
    Mail Stop 53730, \POBox 660023\\
    Dallas, Texas 75266--0023}

\begin{abstract}

Popular wisdom has always held that \sprog\ can be done only in
\mac\ or Bliss. While this is true for code running in \km\ and/or
directly accessing the system memory space, it does not automatically follow that it 
is not possible to use high level languages at all.

This session illustrates techniques for \sprog\ in \pas, 
including utilization of user-written system services and accessing the system
memory space. Examples include a simple program to display various system
control blocks and portions of code from an Ancillary Control Process (\acp)
written in \pas. While the examples are in \pas, the techniques
illustrated can be used for any \hll\ which supports a record
structure, pointers, and the \VAX\ standard calling mechanism.

\end{abstract}

\begin{document}

\maketitle


\section Special Requirements of Systems Programming

Before we can begin a discussion of how to do \sprog\ in a \hll, we first
must determine what \sprog\ is. There are many different 
definitions for a systems program. Virtually everyone agrees that the 
\vms\ Scheduler is a systems program and that a payroll system is an application
program, but many other cases are not so clearly cut. For example, a compiler
is logically a systems program, but the processing it does, although complex,
makes no special demands upon the operating system and can easily be 
implemented in a \hll. For the purposes of this discussion, we will ignore
the function performed by a program, and instead define a system program to 
be any program which requires access to the internals of the \vms\ operating
system not provided by the standard libraries (system services, run-time
libraries, RMS, etc.).
Using this definition, systems programs are those which must do the 
following:

\begin{itemize}
\item[\bullet] Access the system address space, including the nonpaged pool.
The system space is simply not accessible from user mode.  An attempt to 
reference a system address from a non-privileged mode has the same result
as a reference to a non-existent virtual address---an access violation,
followed by termination of the process.
\item[\bullet] Access VMS routines in system space. Since they are not
addressable, for the reasons mentioned above, they cannot be called by
a user mode program.
\item[\bullet] Raise IPL (Interrupt Priority Level, not to be confused with
the priority of a process). Increasing IPL is a VMS mechanism for 
synchronization. A program wishing to lock the I/O data base for exclusive
access raises its IPL to a predefined level, effectively locking out all other
processes in the system attempting to access the data base.
Programs in user mode run at IPL 0, and cannot issue the privileged 
instructions necessary to change IPL.
\item[\bullet] Use MUTEXes (MUTual EXclusion---the \vms\ version of 
a semaphore). The MUTEX structures themselves and the system routines that 
manipulate them are in system space.
\end{itemize}

All of these functions require processing in \km.
Unfortunately, much as I hate to admit it, \km\ processing in a
\hll\ really {\it is} impossible. There are too many
constraints placed on code which is meant to
run in \km. For instance, all references to memory {\it must} be to valid
addresses, because an access violation in \km\ will crash
the entire system instead of just the offending process (in \mac, 
the {\tt PROBE} instruction is utilized to test addresses for validity
before actually referencing them). Many calls to
system routines require the parameters to be passed in registers rather than
pushed onto the stack as specified in the \vms\ calling standard. In fact,
these procedures require that the {\tt JSB} instruction be used rather than
the normal {\tt CALLS} or {\tt CALLG}.
There are many other special instructions in the \VAX\ architecture which
are restricted to usage in \km.
The \hll\ programmer has
very little control over any code generated by the compiler, especially with
optimization enabled. This is as it should be---one of the main advantages
of using high-level languages is a lack of involvement with the actual machine
code, but it does make it difficult to generate the special instructions needed
by \km\ such as {\tt JSB} or {\tt PROBE}.

So why not do \sprog\ in \mac\ and be done with it? 
The primary reason is that it is tedious to write entire programs in \mac.
Textual output, so easy with the \pas\ {\tt WRITELN}, is especially tiresome.
Without the inherent benefits of structured programming, \mac\ programs tend
to be harder to read without conscientious effort on 
the part of the programmer, and
the control mechanisms supplied by a \hll\ have to be performed manually.
Another problem with \mac\ is that
some classes of system programs only need \km\ at
distinct points---the rest of the processing can be done in user mode.
However, most system programs written in \mac\ change to \km\ when the program
begins and remain in that state until termination.
During development of such a program, the most trivial of 
logic errors can crash the entire system.

So what's the best way?
My personal preference for all types of programming is to code the
main program in a \hll, supplemented by \mac\ subroutines for whatever functions cannot
be performed by that language. 
In the case of a systems program, the \mac\ subroutines would exist in a \cmkd\
(described below).
The subroutines can be made short and modular, and if coded generically they 
can be reused for other programs performing a similar function.

Systems programs which are likely candidates for \hll s are
programs which need to run in \km\ only briefly, during distinct phases
such as input or output, and most processing can be done in user mode.
An example of such a program is an
Ancillary Control Process (\acp).
Systems programs which are poor candidates for \hll s are exemplified by
device drivers and other code loaded into system space,
programs which cannot be implemented as a standard VMS process, or
programs which must run in \km\ most of the time.



\section The \cmkd

A \cmkd\ is the \vms\ mechanism by which a normal
user-mode process can execute privileged code. \vms\ system services are
implemented this way. The dispatcher can be considered a special form of
subroutine library. The subroutine definition in the calling program is
exactly the same as for any other externally-defined subroutine 
(in \pas, with the ``{\tt EXTERNAL}'' designator). As with ordinary 
entry points in a shareable image,
control is passed to a transfer vector rather than the actual entry point of
the subroutine. For user mode routines, the transfer vector contains a simple
jump instruction to the actual subroutine address. However, vectors for \km\
routines instead issue a change-mode-to-kernel instruction, specifying
the unique index number of the target subroutine. When control is transferred
to the subroutine, the instructions are executed in \km\ with all of
the privileges thereof. When the subroutine finishes processing, it issues
a normal return (RET) instruction. When control is returned to the calling
program, {\it the process is once again executing in user mode}. 
The percentage of the time the process spends 
in \km\ is minimized; during development, exception conditions crash 
only the process. After the various routines in the dispatcher are debugged,
the system crashes may stop entirely, since these error-free procedures can 
be reused by the next system program developed and only the new user-mode code
must be debugged.

This paper
does not discuss the implementation of a \cmkd; this has been covered in other
presentations at various Symposia. For an example of implementation, see the
sample dispatcher in {\tt SYS\$EXAMPLES:USSDISP.MAR} or the one supplied with
this session in the Languages and Tools Session Notes.

The most important advantage to using home-grown system services is that
it minimizes the percentage of the code executing in \km. In a given
program, some operations can be performed only in \km, but many others can
be performed equally well in user mode. For instance, the sample program
described below, which does nothing more than dump the \pcb\ of the current
process, must access the non-paged pool to get its input data. This can only
be done in \km. However, there is no reason for the actual formatting of 
the \pcb\ fields, which is done with ordinary \pas\ write lines, to execute in
\km. The \km\ processing is restricted to the few instructions
necessary to get the address of the \pcb\ and copy its contents into normal
process memory space. 

One drawback to \sprog\ in \pas\ is that the 
overhead of the \cmkd\ is a factor in time-critical code. There is more 
overhead for a user-written \cmkd\ than for a simple procedure call (the amount
of overhead is exactly the same as that caused by system service calls,
since system services also utilize a dispatcher). If a process
is continuously switching between user mode and \km, this technique
is not the optimum approach and the all-\mac\ version should probably be
used.


\section The System Control Blocks

The system control blocks are defined in \pas\ as standard record types
and are referenced with standard \pas\ pointer variables.
There are two basic types of system control blocks---structured and 
unstructured. In a structured block, the definition of the 
first 11 bytes is the same for all block types: the first two longwords are 
forward and backward pointers, used to place the block into a queue; the
word following the queue pointers (at offset 8) contains the actual length of 
the block in bytes; the byte following the length (at offset 10) contains a 
binary code uniquely identifying the block type. 

As an example of a structured block, the I/O Request Packet (\irp) is 
defined in \pas\ as:

\begintt
irp_ptr=  ^irp  ;
\endtt

\begintt
irp = packed record  
irp$l_ioqfl : irp_ptr ;    
irp$l_ioqbl : irp_ptr ;    
irp$w_size : word ;    
irp$b_type : byte ;    
irp$b_rmod : byte ;    
.
.
.
end ;
\endtt

The unstructured 
system control blocks have no header because they are never queued (they
are referenced by other means) and the length of block is fixed or stored 
outside of the block. The best known example of an unstructured block is
the Process Header (PHD)---the length is fixed, and the PHD address is
contained in the \pcb.


The major disadvantage of \sprog\ in \pas\ is that code does not track 
changes to VMS data structures automatically;
creating the record layouts for the system control blocks 
is a manual operation. In \mac, the 
definition of the system blocks is provided with \vms, contained
in the system macro library {\tt SYS\$LIBRARY:LIB.MLB}. When a new version
of \vms\ is received, a new version of the library comes with it, and 
recompiling and relinking is the only action required to pick up any
changes to the definitions of the control blocks (provided there are no major
functional changes such as deletion of fields currently in use; in this case, 
\mac\ and \hll\ programmers are in the same boat). \pas, since it
is not expected to need this type of information (this is impossible in
\pas, remember?), has no corresponding definitions supplied with the 
system. The record layout must be edited by hand if a new version of \vms\
changes the system control blocks. (To minimize this activity, it is wise
to place the record layouts in an environment or include file 
rather than duplicating
them in every source file.) 

When creating the record layout, it is possible to accelerate the process
by dumping the macro definitions contained in the macro library to a temporary 
file and editing this file rather than starting from scratch. This file can
be created by entering:

\begintt

$ library/macro/extract=$xyzdef -
        /out=tempfile.dat       -
        sys$library:lib

\endtt

\noindent where {\tt XYZ} is the name of the system definition desired, 
such as \pcb.
The result of the above command is a file containing lines such as:

\begintt

$equ pcb$w_size 8
$equ pcb$b_type 10
$equ pcb$b_pri  11
$equ pcb$l_pid  96

\endtt

{\tt \$EQU} is the name of the macro which defines a symbol---it can
be edited out. The middle column is the symbol itself and the number on the
right is the offset (in bytes) of the field from the beginning of the record.
The {\tt \$L}, {\tt \$W}, and {\tt \$B} within the name is the size of the field
(longword, word, and byte, respectively). After editing, the above text 
should look like this:

\begintt

pcb$w_size : word;
pcb$b_type : byte;
pcb$b_pri : byte;
pcb$l_pid : integer;

\endtt

A great deal of care must be taken ensuring that the \pas\ records match
the system control blocks exactly. For this reason, the PACKED designator
must be used on the record statements if fields identified as words or bytes
are present. If left unpacked, these fields, which are defined as subranges 
({\tt word = 0..65535 ; byte = 0..255}),
will be allocated space according to their base type (integer, 32 bits). This
will cause all following fields to have incorrect offsets.

There are several places in the various \vms\ control blocks where privilege
masks are used, notably in the \pcb. A \vms\ privilege mask is an array of
64 bits, where each bit set to one represents the ownership of a particular 
privilege. The array is packed, so the mask is exactly a quadword (8 bytes) in 
size. In \pas, privilege masks can be implemented either as a packed array of
type boolean or as a set. Either approach can be used as long as the resultant
variable is exactly a quadword in size.

A privilege mask defined using the boolean method would be coded like this:

\begintt
type  privmask = packed array [0..63] 
               of boolean ; 
\endtt



\section \spcb---a trivial sample program 

The following program provides an example of accessing the non-paged pool.
Its sole function is to dump the contents of the 
process control block (\pcb), process header
(PHD), and Job Information Block (JIB) of the current process to the 
standard output file. The fact that most of this information can be more
easily obtained via {\tt \$GETJPI} is irrelevant, since this is only an
example of the technique of accessing system control blocks; most of the
other information in the system space {\em cannot} be acquired by 
using system services provided by \vms. One
important concept illustrated in this example is block chaining. Once we
have the address of the \pcb, we can easily acquire the address of the other
blocks from the associated pointers in the \pcb. 

\subsection Declarations

There are three routines contained in the example \cmkd:

\begin{itemize}
\item[\bullet] {\tt GET\_MY\_PCB}, which simply returns the address of the process
control block (\pcb) of the current process

\item[\bullet] \cscb, copies
one of the system control blocks from
the nonpaged-pool to process memory space or vice versa using the lengths
contained within the control blocks themselves---used for structured blocks

\item[\bullet] \cnpb, which performs the same copy operation as 
\cscb, except that the lengths are explicitly specified---used for 
unstructured blocks, which have no embedded length field
\end{itemize}

To access the fields in the control blocks, we define two pointer variables,
a local pointer and a non-paged-pool pointer, for
each of the three control blocks used by the program (\pcb, 
JIB, and PHD). The local pointer contains
the address of the control block created in the process virtual memory space,
a standard \pas\ record allocated on the heap and 
accessed by normal \pas\ references. The nonpaged pointer cannot be used as
a pointer in the standard way; the block it points to is in the non-paged pool
and therefore inaccessible to a user mode program. If it is used as a pointer,
an access violation will result and the program will abort. This variable is 
instead used to simply store the non-paged address until it can be used as
one of the parameters to the \cscb\ procedure.

\begintt
var
         
nonpaged_pcb_pointer, 
          local_pcb_pointer 
          : pcb_ptr ;

\endtt

The three procedures in the \cmkd\ are defined as \pas\ {\tt EXTERNAL} 
procedures.
It is transparent to \pas\ whether the procedures called reside in an object
library, a shareable image, or a \cmkd. The calling mechanism is the same---the
only concern is ensuring that the parameter definitions agree. Note that the
copy-from and copy-to block addresses for the two copy procedures 
(formal variables {\tt f} and {\tt t}) are
defined as integers, although they are actually pointers. These parameters 
would normally be defined as the pointer type for the block being copied. 
However, these procedures are called using pointers to three different
block types (pcb\_ptr, jib\_ptr, and phd\_ptr).
In fact, since these procedures are intended for become the nucleus of a
general purpose dispatcher to support a multitude of systems programs,
the blocks to be copied could be of {\em any} type.
The problem is that the procedure cannot
specify multiple types on a formal parameter definition (actually,
there is a method to accomplish this
using variant records, but it can become very messy). The solution is to
define the parameter as a neutral type of the same size as a pointer, such as 
an integer, and then using the type cast operator (::), which allows a temporary
override of the rigid type checking of \pas, when the procedure
is actually called.

\begintt
function copy_system_control_block 
          (%immed f , t : integer)    : 
          integer ; extern ;
\endtt
\begintt
function copy_nonpaged_buff 
          (%immed flen, tlen: integer;
          %immed f , t : integer)  
          : integer ; extern ;
\endtt
\begintt
function get_my_pcb 
          (var p : pcb_ptr) 
          : integer ; extern ;
\endtt

\subsection \spcb\ processing

The first action performed by the \spcb\ program is to get the pointer to 
the current \pcb, which is located in the nonpaged pool.
At the same time, a local \pcb\ is allocated in the normal process address
space using the \pas\ {\tt NEW} function. The contents of the local \pcb\ 
are initially undefined.

\begintt
return_code := get_my_pcb 
       (nonpaged_pcb_pointer) ; 
new(local_pcb_pointer) ; 
\endtt

After the two pointer variables are loaded the system control block copy
routine (the one used for structured blocks) is used to copy the 
inaccessible real \pcb\ to the 
local duplicate. The type cast operator (::) is used to temporarily redefine
{\tt nonpaged\_pcb\_pointer} and {\tt local\_pcb\_pointer} from type 
{\tt pcb\_ptr} to integer, which is the type specified on the formal
parameters in the procedure definition, without affecting these variables
elsewhere in the program.

\begintt
return_code := copy_system_control_block  
       (nonpaged_pcb_pointer :: integer,  
       local_pcb_pointer :: integer) ; 
\endtt

Now that the local \pcb\ is a mirror-image of the one in the nonpaged pool,
the fields within it can be accessed via normal \pas\ mechanisms. In this
sample program, we simply print the fields in decimal or hex on the standard
output device.

\begintt
with local_pcb_pointer^ do 
   writeln ('pcb$l_pid ' , 
            '(process id) = ' , 
         hex (pcb$l_pid)) ; 
\endtt

The individual privileges in a privilege mask can easily be accessed as
boolean variables in the packed array of bits.

\begintt
with local_pcb_pointer^ do
   if pcb$q_priv[prv$v_oper] then 
      writeln('this process has oper priv');
\endtt

Since the field {\tt PCB\$L\_JIB} contains the address of the JIB in the 
non-paged pool, we can initialize the non-paged pointer to it with a simple
\pas\ assignment statement.

\begintt
nonpaged_jib_pointer := 
        local_pcb_pointer^.pcb$l_jib ; 
\endtt

Now that we have the address of the JIB, we can use \cscb\ again to create a
local JIB and print its contents as we did for the \pcb. The procedure to
dump the PHD is the same except that \cnpb\ is used, since the PHD is an
unstructured block.

\subsection Procedures in the dispatcher

Within the \cmkd\ itself,
the following \mac\ code returns the address of the Process Control Block of
the current process:

\begintt
       $ent     get_my_pcb  
       movl     r4,@4(ap)  
       movl     #ss$_normal,r0 
       ret 
\endtt

One of the key points illustrated by this subroutine is its brevity,
which is enhanced by the fact that 
the address of the current \pcb\ is preloaded into register 4 by the 
dispatcher; however, the active \pcb\ can also be found via the 
absolute memory location {\tt sch\$gl\_curpcb}.

The following code copies a system control block from the process virtual 
address space to system space or vice versa. The lengths of the blocks are
contained within them.

\begintt
     $ent     copy_system_control_block 
     movl     4(ap),r2 
     movl     8(ap),r3 
     movc5    pcb$w_size(r2),(r2),#0, -
                    pcb$w_size(r3),(r3) 
     movl     #ss$_normal,r0 
     ret 

\endtt

This subroutine is almost as short as the first one. 
It would be slightly longer in practice,
since for clarity this sample routine does not include 
any validity checking of the
input parameters, which would be {\it essential} in a real routine. The
other block-copy routine, \cnpb, is not shown here because it is virtually
identical to \cscb; the only difference is that the lengths of the two 
blocks are passed as parameters instead of being retrieved from the blocks
themselves.

\section A \pas\ \acp

A \vms\ device driver operates in a limited context. The driver code does
not execute as a normal process, and therefore has no user memory space, no
event flags, no global sections, etc. It cannot issue system services, 
receive AST's, hibernate, or wait for event flags. Sometimes the functions
being performed by a driver are complex enough to 
require one of these forbidden operations. Or, one I/O operation may 
cause several lower-level operations to occur, requiring the driver to
issue I/O requests to itself or another driver via the QIO mechanism. An
example of this type of operation would be a disk read of 
a virtual block, which would be transformed to multiple logical and
physical operations (SEEK the physical block, READ the physical block, etc.).

An Ancillary Control Process (\acp) is a standard \vms\ mechanism to expand
the capabilities of a device driver. It can be considered an extension of
the driver to handle functions which cannot be performed easily by the driver.
Since the \acp\ is a full \vms\ process, it can hibernate, issue system
services, and do all of the other things a driver cannot.

The \acp\ described here is used with a simulated terminal driver, similar in
function to the Decnet REMACP. It allows remote login over a proprietary
local area network using non-VT100 terminals.

The functions performed by the \acp\ include:


\begin{itemize}
\item[\bullet] Creating and initializing the \acp\ Queue Block (AQB) 
and Volume Control Block (VCB) in the non-paged pool.

\item[\bullet] Reading the I/O Request Packets (\irp s) 

\item[\bullet] Posting the I/O complete
\end{itemize}

\subsection Declarations

In addition to the two block-copy routines listed 
above for the example program, 
the following additional routines are contained in the 
\cmkd\ for the \acp:

\begin{itemize}
\item[\bullet] {\tt GET\_IRP\_FROM\_QUEUE}, which gets the next \irp\ for
processing

\item[\bullet] {\tt POST\_IO\_COMPLETION}, which places an \irp\ which has
returned from the link into the I/O postprocessing queue

\item[\bullet] {\tt ALLOCATE\_ACP\_QUEUE\_BLOCK}, which is
used during process initialization 

\item[\bullet] {\tt CREATE\_NEW\_UCB} and {\tt KILL\_UCB}, which are used
to create and delete virtual terminals
\end{itemize}

\subsection Initializing the AQB

This section of code allocates and initializes an \acp\ Queue Block (AQB).
The {\tt ALLOCATE\_ACP\_QUEUE\_BLOCK} procedure is similar 
to the {\tt GET\_IRP\_FROM\_QUEUE} procedure, 
in that it returns the address of a block in the non-paged pool, except
that in this case, the block is actually created by the procedure.

\begintt
return_code :=
     allocate_acp_queue_block
           (nonpaged_aqb_ptr);

\endtt

All of the fields of the local block must be initialized, since this block
will be copied to the pool to become the real AQB.

\begintt
new(local_aqb_ptr); 
with local_aqb_ptr^  do begin 
     aqb$w_size := aqb$c_length; 
     aqb$b_type := dyn$c_aqb; 
     .
     .
     .
\endtt


As before, the \cscb\ procedure is used. However, this time the local block
is copied {\em to} the non-paged pool.

\begintt
return_code :=
     copy_system_control_block
           (local_aqb_ptr :: integer; 
           nonpaged_aqb_ptr :: integer); 
\endtt

\subsection The main program

The following code is the main processing loop of the \acp; 
during each iteration, it 
either gets another IRP from the \acp\ queue or another Remote Data Packet
(RDP) returning from the link.
If either {\tt process\_link\_data} or {\tt process\_request\_packet} is
executed during an iteration, the {\tt io\_performed} flag is set. 
If neither is executed, then the process will hibernate until new data is
received from either side of the interface and a \$WAKE is issued.

\begintt
repeat 
     io_performed := false; 
     if link_data_present 
          then process_link_data; 
     if irp_present
          then process_request_packet; 
     if not io_performed  then 
          hibernate_until_new_input;
until dead; 
\endtt

\subsection Reading the \irp s

I/O Request Packets (\irp s) are passed from the simulated terminal 
driver to the \acp\ via a linked list (queue) in the
non-paged pool. Since the user-mode \acp\ cannot access the pool directly, a
subroutine in the dispatcher is used to get the address of the next \irp\ in
the queue. 

\begintt

get_irp_from_queue(nonpaged_aqb_ptr,
               nonpaged_irp_ptr);

\endtt

Using a technique similar to that of the \pcb\ processing described earlier,
the \acp\ creates a local \irp\ in the normal program space and uses
the \cscb\ procedure to copy the inaccessible real \irp\ to the 
local duplicate. 

\begintt

new(local_irp_ptr);

return_code := 
     copy_system_control_block
          (nonpaged_irp_ptr :: integer,
          local_irp_ptr :: integer);
\endtt

The I/O buffer associated with the \irp, which must be transmitted across the
link and is in the form of a Remote Data Packet (RDP), must also be copied
from the non-paged pool to the process address space. Since the RDP is not
a structured block, the \cnpb\ procedure must be used and the actual length
of the buffer must be specified (the address and length are contained
in the \irp\ fields {\tt irp\$l\_svapte} and {\tt irp\$w\_boff}).

\begintt
with local_irp_ptr^ do begin 
     from_ptr := irp$l_svapte; 
     to_ptr := local_io_buff_ptr; 
     return_code := copy_nonpaged_buff 
          (irp$w_boff,irp$w_boff,
          from_ptr :: integer,
          to_ptr :: integer); 
\endtt

Since multiple I/O
operations may be in progress simultaneously and they
may not complete in the same order they were initiated, 
we must save the
\irp\ in a local table, identified by sequence number (this number is 
guaranteed by \vms\ to be unique). The RDP is then transmitted to the
remote system where the I/O will actually be performed.
\begintt
put_irp_on_local_queue(irp$l_seqnum); 
send_rdp_across_link; 
\endtt

\subsection Posting the I/O complete

When the response RDP returns from the link, now in a different format 
consisting of 
the completion code of the I/O and possibly containing 
data (in the case of a read operation), 
the original QIO must be posted complete.
Using the sequence number, we locate the \irp\ associated with this RDP. 
\cnpb\ is again used, this time to overlay the original RDP in the system space
with the response RDP just received (the block was originally allocated by
the driver to be large enough for either form of an RDP, so we
do not have to worry about overwriting system space).


Finally, we call a routine in the dispatcher to post the I/O operation 
complete. 

\begintt
return_code := 
    post_io_completion(nonpaged_irp_ptr, 
    nonpaged_ucb,
    interrupt_routine_address); 
\endtt

\subsection Procedures in the dispatcher


The following code in the \cmkd\ for the \acp\ 
returns the address of the next IRP in the \acp\ queue, if
one is present; if the queue is empty, a warning-level return code is issued.


\begintt
        $ent    get_irp_from_queue 
        movl    4(ap),r5		 
        remque  @(r5),@8(ap)		 
        bvc     10$			 
        movl    #queue_is_empty,r0		 
        ret 
10$:  movl    #ss$_normal,r0		 
        ret 

\endtt

It cannot be over-emphasized that the process is only in \km\ for the amount of
time needed to execute these tiny procedures in the dispatcher. The time window
where the \VAX\ is vulnerable to a full-system crash is very small, and 
procedures this size are comparatively easy to debug.

\section Conclusions

\noindent
\begin{itemize}
\item[\bullet] Some classes of systems programs {\em can} be implemented in a \hll
\item[\bullet] Systems programs can gain the benefits of structured programming
and can be made more readable
\item[\bullet] Development time can be shortened 
\item[\bullet] System crashes can be reduced during development
\item[\bullet] Kernel mode routines (in a \cmkd) developed for one system program can
be used again for another
\end{itemize}
\end{document}

