What Happened to RT-11 SJ
or
Where Has TT.SYS Gone?

Bob Schor
Department of Otolaryngology
University of Pittsburgh
200 Lothrop Street
Pittsburgh, PA   15213
(412) 647-2116
bschor@vms.cis.pitt.edu


Perhaps the most noticable change in RT-11 with the 
introduction of Version 5.6 and Version 5.7 is the
whole-scale renaming of the various monitors.  Three 
new monitors have been added, and one long-time old 
friend, RT-11 SJ, the Single Job Monitor, has gone 
away.  The purpose of this talk is to explore why it 
has gone, review the consequences for the user, and 
try to demonstrate the old saying "When Life Hands 
You a Lemon, Make Lemonade".

First, where has it gone?  RT-11 SJ was the "Single 
Job" monitor in the new 5.6 monitor scheme, it has 
been replaced by RT-11 SB (there will be more on names
in another talk).  What were the functionalities of SJ,
its pluses and minuses, which have contributed to 
making it such a favorite of many users in the Real 
Time and Data Acquisition environment?

SJ = Single Job, you were guaranteed to be the only 
job running, which meant that you never had to worry 
about some other program (other than the operating 
system) corrupting yours.  You ran in Kernel mode, 
meaning you had all of the power of the PDP-11 at your 
disposal  you could even HALT the machine!  The 
monitor was small, and fast, giving you access to a 
reasonable chunk of the 56kB of memory (plus full 
access to the 8kB I/O page) in the machine.  Even the 
non-essential parts of the terminal device (the part 
used to do .READs and .WRITEs) were split out into 
TT.SYS so that user programs which didn't do "regular" 
I/O to the terminal would have more space.

But there were problems.  The major problem involved 
Completion Routines.  There were cases, particularly 
likely to occur while using VM:, in which the system 
could get thoroughly hung.  The only solution involved
bringing in 75-80% of the FB functionality, so the 
developers decided to put in 100% and solve this and 
other possible problems in a clean manner.  SB is the 
resulting product.

Some of the problems in SJ arose from the schizoid 
nature of its development.  As features and 
capabilities were designed into the evolving monitor 
set (first FB, then XM), attempts were made to "build 
in" as many of these into SJ as sensible, given the 
different underlying monitor structures (and the fact 
that SJ really was optimized for small size and quick 
response, structure-be-damned).  The development of 
the later monitors benefitted from knowledge of SJ 
development, and did many things better (and perhaps 
"right").  One particular difference is that 
information about job context in later monitors is 
kept in a separate impure area, allowing for 
controlled access; in SJ, this information tends to 
be scattered throughout the monitor.
One further SJ 
problem is that interrupts just sort of "happened"  
the monitor did not deal with them in as robust a 
method as with FB/XM.

These fundamental monitor differences between SJ, on 
the one hand, and FB/XM, on the other hand, were 
reflected in the existence of two monitor sources, 
RMONSJ and RMONFB; the latter built both FB and XM 
using a conditional code switch which basically turned
on memory management (MMG$T).

The solution to the "broken SJ" problem, and the 
evolutionary pressure which created SB, was to further 
conditionalize RMON to specify the number of supported
jobs; if the answer was 1, you had SB (and, incidently, 
XB and ZB basically came for free).  Because only one 
job needs to be supported, the structure of the 
monitor's impure area is particularly simple, and the 
monitor itself can be simplified (as it doesn't need 
code to switch context, but can leave its pointer 
looking at the guaranteed job context).  
Context-switching mainly goes away.

Some size comparisons.  The examples below are for a 
monitor running out of VM, as well as the more 
conventional configuration running out of DU.

RT-11 v5.5     RT-11 v5.6

SJ   FB   XM   SB   FB   XM   VM 
128  128  86   128  128  86

RMON

2516 5076 8056 3913 5148 8248

USR

(2065) (2065) 2577 (2065) (2065) 2577

BkGd

25772 23212 17697 24375 23140 17505

DU

915   915   144   1000  1000  154

RMON 

2516 5076 8056 3913 5148 8248

USR (2065) (2065) 2577 (2065) (20650) 2577

BkGd 

24985 22425 17639 23503 22268 17437

The above chart contains the bad news; the smallest 
monitor, RT-11SB, has grown in size by about 1400 
words (the other monitors have also grown slightly, 
but nowhere nearly as much).  It is therefore possible 
that code that used to run under RT-11SJ will not run 
(immediately) under SB because of lack of memory.  
This brings up the entire issue of code migration.

There are two sorts of migration issues to consider.  
First, aside from size and speed, are there any other 
ways that SB differs from SJ, and could thus cause 
migration headaches?  Second, what if size or speed 
really is an issue?

The good news is that almost all programs which ran 
under SJ should run under SB (assuming the code still 
fits in memory).  Also, all programs which ran under 
FB should definitely run under SB!  One subtle change 
in SB lies in $CNFG1; the low bit, BMON$, used to 
distinguish between SJ and FB.  Under 5.6, this 
defaults to 1, i.e. "FB".  Should this matter to your 
program, the developers have handily provided a SET 
MODE SJ switch to change this bit back to 0.  The 
"correct" way to distinguish SB from FB is to look at 
$JOBS and see if it is 1 or a larger number.

However, if your program actually went in and played 
around with internal monitor tables, particularly if 
it didn't use the RMON offsets to do so, it is likely 
that it may fail under SB, with its reorganized (and 
rationalized) structure.

The general goal of the entire 5.6 development effort 
was portability of old software without modification. 
"Vanilla" programs, those which handle interrupts and 
mapping through system calls, rather than directly, 
and which do i/o through the monitor, should 
immediately run.  It is probably true (but we have no 
direct data) that programs which attempt to push the 
CPU to its limit in terms of speed while processing 
interrupts will probably run somewhat slower under SB;
there is more overhead associated with the (correct, 
safe) processing of interrupts.
How about speed?  
This is harder.  If this is really an issue, there are
some things to try.  One is to use VM: as a system 
device, or for i/o (depends on where the bottleneck 
lies).  Another is to buy a faster processor!  

How about space?  Assuming it doesn't fit under SB, 
try it under XB.  While this is an even larger (and 
somewhat slower) monitor, it allows you to use VBGEXE 
to create a virtual environment where you have direct 
access to 56kB (and, in some cases, 64kB) of memory.  
The reason for this gain in memory is because all of 
the monitor stuff now runs in Kernel mode, and your 
program moves to a separate User Space.
VBGEXE/XM 
support three memory models.  [Those familiar with 
TSX+ will recognize some of this].  One is 56kB of 
User space + direct access to the I/O page.  Another 
is 56kB + a copy of RMON tables in the top 8kB, 
allowing very rapid access through the $SYPTR at 
location 54.  The third is to simply have 64kB of 
User space directly.  Note that in any of these 
environments, you've got more space available than 
under any unmapped monitor, even SJ.
What are the 
drawbacks?  One may be speed -- if you are right at 
the edge, going from SB to XB might push you over.  
See "speed" discussion above.  How about problems in 
your program?

The big issue is interrupts.  If your program deals 
directly with interrupts, that is, if it contains an 
Interrupt Service Routine, you have to do a bit of 
work to run in a virtual environment.  The first 
problem is fielding the interrupt.  When an interrupt 
occurs, you vector to a location in low memory in 
Kernel Space, regardless of the mode you were in when 
the interrupt occurs.  So problem #1 is getting 
vectors into Kernel Memory.  .PROTECT will correctly 
give you the status of the Kernel vector and will 
protect the vector; you could then use .PEEK/.POKE 
to load it and point it to your ISR.  The next 
question is in which mode do you wish to execute the 
interrupt code, the choices being Kernel Mode (i.e. 
not in your program's address space) or User Mode (
i.e. code within your program).  It might sound like 
User Mode is the logical choice for a User ISR, but 
there is a problem which boils down to a bit of 
hardware lore.  Recall that when executing an RTI to 
return from interrupt, the PS, used to send you back 
to the previous mode, is updated from the value on 
the stack.  If you check the description of RTI, you 
will notice that if you are in User Mode, you can 
only return to User mode; Supervisor gets you back to 
Supervisor or User; Kernel gets you back to anywhere. 
The problem now is, in which mode does your interrupt 
code execute?  You control User Mode mapping, yet if 
you put the ISR in User Mode, you have to guarantee 
that no User Interrupt occurs while running Kernel 
Mode code (such stuff as USR, handler functions, 
monitor calls, etc.) or else you will return in the 
wrong mode (you will return in User Mode, yet were 
interrupted in Kernel Mode; since User mapping <> 
Kernel mapping, you won't wind up where you should).

This problem can be (and has been) solved; an example 
is the code required for Completion Routines.  The 
solution is to place special code in (User) location 
0 which executes an internal EMT called .ASTX, A
Synchronous Trap eXit.  This gets us back into Kernel 
mode, within the monitor, which realizes we're 
returning from a Completion Routine, and can look up 
the PS/PC saved on entering the Routine.  Now the RTI,
exited from Kernel Mode, can return us properly to 
any mode necessary.
The obvious solution is to put 
the interrupt code in Kernel Space, and the 
recommended way of doing this is to write a handler 
which does the interrupt processing for you.  At the 
cost of learning about handlers and learning some 
possibly-unfamiliar data structures and methods, this 
solution has a number of secondary benefits.  First, 
it gets even more of your user program out of User 
Space (remember, we're trying to save memory) and 
puts it into Kernel Space (which is where .FETCH will 
store it).

Second, routines have been built into the monitor 
that handle a lot of the dirty work for you.  For 
example, you generally need to get the 22-bit address 
of the user buffer to hand to the hardware device -- 
RT-11 automatically translates the user buffer 
address for you (using PAR1 to point to it, and 
giving you the relocation constant and displacement 
bias).

In addition, address translation routines (formerly 
handled by the ATX pseudohandler) are built into the 
monitor for non-standard requests, such as .SPFUNs 
(allowing you to pass arbitrary "buffer-like" 
parameters and translate those of interest to you).

There are all kinds of clever tricks you could do to 
speed things up using a handler.  For example, you 
could bypass the address translation mechanism 
inherent in .READ/.WRITE processing by using a series 
of .SPFUNs to give the handler specialized 
information.  You could, for example, use one .SPFUN 
to tell the handler "Here is where my buffer lives, 
and it will stay here for the duration of the program.
Go set up the DMA registers accordingly."  You use 
another one to say "Now, using already-initialized a
ddresses, go start the transfer."  Yet another could 
be used to mess with flags in the User Space to give 
you the Moral Equivalent of .WAIT.
If you want to be 
still more devious, you can directly interact with 
the handler in the following way.  First, .DSTAT will 
give you the (Kernel) address of the handler after it 
has been loaded into (Kernel) memory by .FETCH.  You 
can now read and write into the handler using 
.PEEK/.POKE.

You can even call handler routines using the .CALLK 
mechanism.

An interesting dichotomy arose in discussing the 
"SB Memory" problem with the RT-11 Developers.  In 
suggesting the mapped monitors and VBGEXE, their view 
of what was happening differed from mine.  I 
naturally saw the process as "shrinking the monitor", 
or more precisely, getting the monitor (and USR and 
handlers) out of my address space.  They saw it as 
"cleaning up Kernel Space" by getting rid of the User 
Program.  I was viewing memory, which to me meant User
Space, as the scarce resource; they were looking at 
scarce Kernel memory.  By banishing the User, more 
memory exists for handlers, the USR, specialized 
buffers, even special purpose routines which the user 
can .CallK (if you really get squeezed ...).

OK, so suppose we do all this, and now want to use 
VBGEXE.  How do we do it?  There are two bits, NOVBG$ 
and VBGEX$, in the Job Definition Word ($JSX, location
4 of the .SAV image) which interact with VBG.  One 
forbids the job running under VBG (the BOOT command 
and RESORC, for example, set this bit).  Under the 
guidelines given above, user programs which have 
interrupt service routines should set this bit!  (It 
is cleared by default).  The other bit gives implicit 
permission for VBG to run the job.  If you have SET 
RUN VBGEXE, then any program with this permission bit 
set will automatically use VBGEXE to load the program.
Finally, if neither bit is set, VBG will be used if 
you explicitly request it using the V or VRUN commands.

Note that running under VBGEXE, with the additional 
memory it provides, can produce some dramatic 
improvements in your programs.  One of the first 
programs to have the VBGEX$ bit set was MACRO -- 
under VBG, large assemblies run faster by an order of 
magnitude!

All that now remains is to answer the last question 
asked in the title, namely Where Has TT.SYS Gone?  By 
now, you all realize the answer to the question, 
namely that its functionality has all been absorbed 
into the monitor, just as it was in FB (and XM).  
However, RT-11 Version 5.6 (but not Version 5.7) 
actually had a copy of TT.SYS!  What gives?  It 
turns out that there was still a Version 5.5 monitor 
included on the 5.6 kit  RT11MT, the monitor which 
is loaded as part of a bootable magtape.  Space is 
very tight for bootable tape, and the larger 5.6 
monitor could not be so simply shoe-horned into 
memory.  Since the entire purpose of RT11MT was the 
creation of a bootable disk system, the 5.6 solution
was to use the smaller 5.5 MT monitor for this purpose.
In version 5.7, however, the tape bootstrap code was 
changed to allow the entire 5.7 monitor to be read in.
So there are no more 5.5 SJ monitors, hence no longer 
a need for TT.SYS.




