There are 2 other files named paspsi.doc in the archive. Click here to see a list.
This document describes a set of routines for using the PSI system in PASCAL.
The user should read the section on PSI in the Monitor Calls Manual before
trying to use this facility, although this file, especially the examples,
may give those who haven't read it some idea of the facilities, at least.
To use these routines, external declarations must be included in the
user's program. Also, the routines themselves must be loaded with
the program. Currently this requires that you load the file
AIL:PASPSI.REL with your program.
Warning: I have tried simple programs, but this is not well debugged.
If anyone finds a use for it, please tell me what you are doing and I
will help you, since some final debugging may be needed.
Later note: It turns out that you are far more likely to find a monitor
bug having to do with PSI, HIBER, etc., than you are to find a bug in
my routines. The PSI system is rotten. (I mean that not as a meaningless
insult but seriously: it is full of places that will fall in on you if
you step on them.)
The PSI system has a number of conditions on which an interrupt can
occur. These include ^C, illegal memory reference, and various
I/O conditions (e.g. transfer done, disk full). To establish an
interrupt condition, three things must be done:
(1) You must initialize the PSI system. This is done with PSIINI.
It uses the PIINI. UUO to initialize PSI and give the monitor
a "vector" which will contain an entry for each condition
you establish. However, initializing the PSI system does
not turn it on. Thus,
(2) You must turn on the PSI system. This is done with PSION.
It uses the PISYS. UUO to turn on PSI. Note that this must
be done after the PSIINI, and that it is in a sense less
permanent. That is, you may establish conditions and then
keep turning them on and off with PSION and PSIOFF. PSION
will restore the handling of interrupts which was suspended
by PSIOFF. PSIINI does a complete reinitialization (i.e.
it cancels all conditions).
(3) Finally, you must establish the conditions on which an
interrupt will be taken. This is done with PSIADD. PSIADD
uses the PISYS. UUO to add the specified condition. It also
takes the location of a PASCAL procedure as an argument and sets
things so that that procedure will be called to process the
interrupt. Note that in the default case this procedure
is executed at interrupt level, and any other interrupts will
be held until it is finished. This can be overriden, but
you will have to be very careful indeed if you do so.
PSIADD returns an "index". This is needed as a unique
way to refer to the condition for purposes of cancelling it
(or other nefarious functions that may be added later).
Note that there are two different ways to cancel an interrupt
added by PSIADD: PSIOFF just suspends interrupt processing
of all interrupts. The interrupts are still set, and processing
will be resumed at the next PSION. PSIREM(index) removes
the one particular interrupt condition referred to. It is
as if it had never been there. (Thus if you establish an
interrupt on ^C, PSIOFF causes ^C interrupts to be delayed
until the next PSION, but does not let ^C stop your program.
PSIREM would restore normal ^C behavior.)
I/O interrupts are a special case. For them, you
specify not only the interrupt type (this would be the device,
I/O channel, or UDX), but also a "reason". This might be
disk full, etc. It is only possible to have one condition
(i.e. one index value) for a given device, but that condition
may specify as many reasons as you like. When an interrupt
happens you will be able to find out which reason caused it.
To establish multiple reasons, you simply set several bits
in the reason argument of the PSIADD. Do not try to do more
than one PSIADD for the same device, as the monitor will
ignore all but the first. (To add reasons you will have to
do PSIREM and then PSIADD with all the reasons including the
Note that the monitor does not return the type
which caused an interrupt. That is why we are enforcing
a restriction of using a separate index for each type.
However it when the type is an I/O device, you are told
which reason caused the interrupt, and we pass this information
on to your procedure. (We also pass the index of the
condition, in case the same procedure is handling more than
When the interrupt-handler passes control to the routine you specified,
certain information is present in accumulators. These will show up
as parameters to your interrupt procedure. Thus I recommend making
the interrupt procedure a procedure with four parameters:
arg 1 - index - this is the index of the condition, as
returned by the PSIADD when you added it
arg 2 - PC - this is where the main program was when the
arg 3 - reasons - This is the reason field (right half of
.PSVFL) from the vector. It is useful for
I/O interrupts, to tell you which of the
enabled reasons caused the interrupt.
arg 4 - status - definition depends upon the interrupt. See the
monitor calls manual. This is the status word (.PSVIS)
from the vector when the interrupt occurred.
When designing a program that will use PSI, you must constantly keep
in mind the fact that an interrupt can occur anywhere. Thus there can
be subtle timing problems. For example, if you an interrupt occurs
while you are waiting for TTY input, and the interrupt procedure
also asks for TTY input, it will be very hard to unscramble the input.
I do save and restore the status of the TTY file control block around
interrupts, but that still may give strange results. Note that an
interrupt processor should never use the same file as the main program,
except for TTY. Terrible, subtle bugs will result.
The other known limitation on what you can do in interrupts has to do
with NEW and DISPOSE. If you don't use DISPOSE in your program, there
is no problem. Programs that don't use DISPOSE get a simple version of
NEW that is designed to be reentrant. When you use DISPOSE (even once),
we give you a full-scale dynamic memory manager. This memory manager is
not reentrant. So if you interrupt NEW or DISPOSE and do another NEW or
DISPOSE at interrupt level, the heap will probably get corrupted. There
is no problem if any of the following 3 things is true:
- you don't use DISPOSE, because then you get a reentrant memory
- you don't use NEW or DISPOSE at interrupt level
- you know that an interrupt can never occur during NEW or DISPOSE
(e.g. you are handling floating-point overflow via interrupts,
since the memory manager is guaranteed not to produce floating
Otherwise, you will have to contrive to treat calls to NEW or DISPOSE as
critical sections. (Unfortunately I have not implemented critical
sections for you on Tops-10. I have on Tops-20, but would need a test
site before I could do it on Tops-10. I have no way to test Tops-10
interrupt handling code.)
Note that there is no way to predict the lexical context in which the
interrupt processor routine will be called. Thus it is not possible to
set up the lexical display correctly. This means that such routines
must refer only to their own local variables and variables declared at
the outermost level of the program. They may not refer to anything at
an intermediate level. This restriction cannot be enforced, but
violating it will lead to strange results. Note that you may often set
a global flag to indicate that an interrupt occurred, and let the main
program handle it when it notices.
index _ psiadd(type,reason,procedure)
This is the simplest form of the PSIADD. It adds a condition
whose type is specified by type. This is either an I/O
device, channel, or UDX, or it is a negative number specifying
some non-I/O type (e.g. -3 is ^C). For an I/O type, you
must also specify the reason, as explained above. This is
a half word of bits. For non-I/O types, reason must be 0.
Procedure should probably be a global procedure, i.e. defined
at the outer level of the program.
index _ psiadd(type,reason,procedure,control)
This is the full form of PSIADD.
Control is for the brave soul who wants special effects
from the PSI system. With it you can specify that when an
interrupt occurs the PSI system should be left running even
during processing of the interrupt. Or you can say that it
should be turned off until explicitly reenabled with PSION.
See the Monitor Calls manual for the bits to use for these
things. (Note that they should be put in the right half
word of control. PSIADD will move them to the left half
of .PSVLF.) Control defaults to PS.VTO, which holds all
interrupts until the immediate processing routine finishes.
(This is the procedure passed to psiadd, either your immediate
interrupt routine, or PSIDFR.) This is probably the only
sane choice. Note that the PSI interrupt handler is not
reentrant. If you override our default and allow other
interrupts to happen during processing of an interrupt, the
interrupt handler will get confused.
As explained above, this turns on processing of interrupts.
It may be done before or after PSIADD. Of course it has
no effect until at least one condition has been added.
PSION is illegal until a PSIINI has been given.
As explained above, this suspends processing of interrupts,
but does not change which conditions have been added.
Removes a condition set up by PSIADD. Frees its index
entry for use by someone else (since there are a limited
number of entries in the tables).
Clears any pending interrupts for the condition referred
to by index. The condition is still enabled (assuming
PSIOFF isn't in effect), you just clear it out. I'm not
sure that this is useful for very much. DEC says you might
use it in an interrupt level routine if it caused more interrupts
which you wanted to ignore.
Must be done before any other PSI function. If it is done
again in the middle of a program it will cancel all interrupts
and completely clear out the system. PSION and at least one
PSIADD will be needed again before anything else can happen.
A typical use of PSI is shown below. Assume PASCAL did not tell you
when an arithmetic exception (e.g. divide by zero) happened. (In
fact the runtimes go to some considerable pain to diagnose these
arithmetic exceptions, but this is still a useful example.) We will
set up a trap for them and print an appropriate message. Since you
may get tired of seeing the same message, the interrupt is removed
after four times.
function psiadd(a,b:integer;procedure c;d:integer):integer;extern;
procedure psiini; extern;
procedure psion; extern;
writeln(tty,'% Arithmetic exception at user PC ',PC-1:6:O);
if count>3 then psirem(index);
index:=psiadd(-10b,0,trapper,0); %-10b is the code for arithmetic
while true do i:=trunc(1.0/0.0); %This will trigger it, since it
divides by zero\