GENWiki

Premier IT Outsourcing and Support Services within the UK

User Tools

Site Tools


rfc:ien:ien136
                    Memory Management Extensions
                 to the SRI Micro Operating System
                       for PDP-11/23/34/35/40
                              IEN  136
                            1st MAY 1980
                            S.R. Wiseman
                            B.H. Davies
                ROYAL SIGNALS & RADAR ESTABLISHMENT
         North Site, Leigh Sinton Rd., Malvern, Worcs., UK.
                                 1
CONTENTS
________

0. Introduction

	PART 1 - SYSTEM DESIGN

1. Requirement

1.2 Overview of solution

1.3 Use of Memory Management Facility

	PART 2 - TARGET MACHINE ASPECTS

2.1 Alterations to MOS

2.1.1	The Process Control Table
2.1.2	The Scheduler
2.1.3	The $CREAP Macro
2.1.4	Input and Output Routines
2.1.5	Memory Sizing
2.1.6	Protection Routines
2.1.7	System Configuration

2.2 The EMMOS Debugger - MUD

2.2.1	A Summary of MUD Commands
2.2.2	MUD Command Syntax
2.2.3	The Relocation Mechanism
2.2.4	Addressing Modes
2.2.5	Command Specifications
2.2.6	Using MUD

2.3 Linker and Loader Functions

2.3.1	The Linker
2.3.2	The Loader
                                 2

PART 3 - HOST MACHINE ASPECTS

3.1 Linking Using the RSX-11M Task Builder

3.1.1	Use of the Task Builder's Task Image File
3.1.2	The Format of the Task Image File
3.1.3	The Overlay Description Language
3.1.4	The EMMOS ODL File
3.1.5	Installation of the Debugger
3.1.6	Creating the Task Image File

3.2 The EMMOS RSX Task Image Loader

3.2.1	Inputs and Outputs
3.2.2	The Console Load Map
3.2.3	The Configuration Table
3.2.4	The Overlay Description Table
3.2.5	The Process Description Table
3.2.6	The Load Map
3.2.7	The Physical Memory Allocation Algorithm
3.2.8	The Logical Operation of the Loader
3.2.9	Error Messages
3.2.10	Tracing
3.2.11	Changing the System

3.3 Process Creation

Appendix A: Pitfalls and How to Avoid Them

                                 3

0. Introduction

   This  document describes the requirement, design and implementation

of memory management extensions to the Stanford Research Insitute's Micro Operating System (MOS) for PDP-11 minicomputers. The document is divided into three parts. Part 1 covers the rationale behind the way in which the memory management facility of the PDP-11 is used. The results of the work fall naturally into Parts 2 and 3. Part 2 describes the modifications to MOS itself and the debugger which are independent of the operating system of the host machine on which the target system is generated. Part 3 describes the Linking and Loading modules which are dependent on the operating system of the host machine. The host machines used at RSRE are PDP -11/34 and 40s running RSX-11M v3.2. Also we have provision in our standard MOS for writing processes in a high level language, CORAL 66, which requires the use of an auxiliary stack pointed to by R0. This facility may be excluded from the extended MOS by the use of a configuration switch.

                                 4
			PART ONE
			________
		     SYSTEM DESIGN
                                 5

1.1 Requirement _ For a number of planned real-time software projects the 28k words of code and data space accessible without memory management in the PDP-11 is inadequate. Therefore the question arises is it possible to use the memory management registers to provide more code and/or data space up to 124k words, with minimal overheads in the form of context switching and with no modifications to presently written processes? 1.2 Overview of Solution The reason for the provision of a two state memory management system in the PDP-11s is not only to allow access to memory above 28k words but also, by the use of kernel and user modes, to provide protection between processes and the core resident part of the operating system. This usually means that the I/O page is non-resident when a user process is running and that I/O is handled in kernel mode. Thus in a real-time system there may be unacceptable overheads due to the considerable amount of context switching involved in this protection mechanism. However if one is willing to dispense with interprocess and operating system protection a simple and elegant solution which produces no detectable overheads is possible. The basic concept of the solution is that while a process is running there is no need to have in scope any other process so that a snapshot of the 32k virtual memory at any one time will look like an ordinary MOS system configured for one process. The only extra executable code involved is in the scheduler for paging out the process that has just finished running and paging in the process that is about to be run. The I/O page, operating system, handlers and common buffer area are always resident otherwise access to these entities would have to be mediated by the operating system so that they could be paged in with consequent increase in overheads. A typical MOS process runs for something like 1 millisecond and the additional paging overhead is 4 MOV instructions taking about 20 micro seconds, a 2% overhead. A major change in the MOS layout occurs in the positioning of the stacks. Because we want the permanently resident part of MOS to be as small as possible and never greater than 8k words the stacks have been removed from the Process Control Tables and are paged in and out with their processes. 1.3 Use of the Memory Mangement Facility At any one time only the running process will reside in virtual memory, the others will be suspended and so have no need to be accessible. Permanently resident in virtual memory will be the vector area, global buffer area, operating system including all handlers, and the I/O page. Because the maximum size of a page is 4K words and it's position is fixed in virtual memory, the allocation of these pages is somewhat restricted. However a trade-off between global buffer space, 6 operating system size and process space is possible to some extent, and the following configurable options are catered for:- Options where code and stack space of any process does not exceed 4k words:- Option 1 20k words of buffer space <4k of operating system and handlers |———————–| 32k 7 | I/O | |———————–| 28k 6 |Running process & stack| |———————–| 24k 5 | Operating System EMMOS| |———————–| 20k 4 | Global Buffers | |- -| 16k 3 | | |- -| 12k 2 | | |- -| 8k 1 | | |- -| 4k 0 | | page |———————–| 0k Option 2 16k words of buffers <8k operating system and handlers |———————–| 32k 7 | I/O | |———————–| 28k 6 |Running process & stack| |———————–| 24k 5 | Operating System EMMOS| |- -| 20k 4 | | |———————–| 16k 3 | Global buffers | |- -| 12k 2 | | |- -| 8k 1 | | |- -| 4k 0 | | page |———————–| 0k 7 Options where code and stack space of any process does not exceed 8K words:- Option 3 16k words of buffer space < 4k words of operating system and handlers |———————–| 32k 7 | I/O | |———————–| 28k 6 |Running process & stack| |- -| 24k 5 | | |———————–| 20k 4 | Operating System EMMOS| |———————–| 16k 3 | Global Buffers | |- -| 12k 2 | | |- -| 8k 1 | | |- -| 4k 0 | | page |———————–| 0k Option 4 12k words of buffers <8k words for operating system and handlers |———————–| 32k 7 | I/O | |———————–| 28k 6 |Running process & stack| |- -| 24k 5 | | |———————–| 20k 4 | Operating System EMMOS| |- -| 16k 3 | | |———————–| 12k 2 | Global Buffers | |- -| 8k 1 | | |- -| 4k 0 | | page |———————–| 0k 8 Only kernel mode is used by EMMOS, even user processes run in kernel mode because no attempt has been made to produce a secure system. However with options 3 and 4 the process space and stack space are allocated separate pages wherever possible. Thus the debugger or a procedure call may be used to write-protect the code space if desired. Holes appear in virtual memory from the end of the code or stack to the end of the 4k boundary ensuring that a system stack cannot underflow and the running process cannot access another process' code or stack . However, a process can damage another process indirectly by, for example, supplying a pointer to data on its stack to another process. Because the debugger is bigger than 4k, options 1 and 2 do not allow use of the debugger. This is not a serious drawback as it is envisaged that debugging could take place with options 3 or 4 and that options 1 or 2 could then be used to give another 4k words of buffer space. Using EMMOS requires a stricter discipline when writing processes than was required with MOS. Strictly speaking the code of a process should not contain any private data space. If a process wants private data space it should use its stack. Any data space shich is shared by two or more processes should be obtained , via MOSMEM calls, from the global buffer area. Exceptionally a single incarnation of an assembler process could reserve space after its code for local tables but any attempt by another process to access this area will result in disaster! All high level language processes should be written as closed procedures with private data area on their stacks. Shared Library procedures must either be replicated so that they are paged in with each process that requires to use them or they must be linked with the EMMOS operating system so that they are permanently resident 9 A typical example of real and virtual memory allocations for an EMMOS system are shown below:- 128k |———————–| | I/O addresses | 124k |———————–| | | | | | | |———————–| | stack (proc3) | |———————–| | stack (proc2) | |———————–| 32k |_|

 7	|	I/O		|	| Codebody2 (proc 2&3)  |
|-----------------------| 28k	|_______________________|
 6	|Running process & stack|	| stack (proc1)         |
|-                     -| 24k	|_______________________|
 5	|			|	| Codebody1 (proc1)     |    
|-----------------------| 20k	|-----------------------|
 4	| Operating System EMMOS|	| Operating System EMMOS| 
|-                     -| 16k	|-		       -|
 3	|			|	|			|
|-----------------------| 12k	|-----------------------|
 2	| Global Buffers        |	| GLOBAL buffers        |
|-                     -|  8k	|-                     -|
 1	|			|	|			|
|-                     -|  4k	|-                     -|
 0	|			|	|			|
|-----------------------|  0k	|-----------------------|

Page Virtual Memory Physical Memory

   The  linker/loader  system  used  with  EMMOS  should be reasonably

intelligent! It is it's job to reserve physical memory in accordance with the stack size requested for each process. In particular it must evaluate the total memory requirements of a process that has more than one incarnation and determine whether or not some or all of the code has to be replicated.This may arise when the stacks of all the incarnations of a process plus the code will not fit into the paging window. The linker/loader is also responsible for outputting error messages when the process and stack requirements cannot be satisfied by that particular configuration of EMMOS.

                                 10
			PART TWO
			________
	         TARGET MACHINE ASPECTS
                                 11
2.1	 Alterations to MOS
___________________________
   Perhaps  the most vital alteration is that made to the scheduler. A

context swap now involves saving the suspended process' window and restoring the new process' window. This however, only involves about four move instructions extra, for a typical configuration ( 8K processes ).

   The  major  alteration is, however, to the Process Control Tables (

PCTs ) and consequently to the PCT initializer. In particular the stacks for a process no longer reside here, hence a process' PCT is relatively small. Extra fields in the PCTs give the memory management window necessary to 'page-in' the process and the ends of the system and CORAL stacks. The PCT initializer now has to cope with setting up these fields, which it does by consulting the load map, supplied by the linker/loader.

   Additions have also been made to the $CREAP macro. The sizes of the

system and CORAL stacks are given to the $CREAP which in turn passes the information to the linker/loader ( depending on the method used ).

   It  has  also  been  necessary  to  offer  modified synchronous I/O

routines 'SOUT' and 'SIN'. The original versions can be used if the I/O is performed on buffers from the permanently resident buffer pool, however they will not work if the I/O buffer is taken from the local code or data space. This is because the local area may be paged-out when the interrupt routine tries to get the next character, causing it to pick up rubbish or trap out. The new versions will copy the local I/O buffer into a global buffer before initiating the I/O, thus ensuring that it is always paged-in. This is likely to be the main overhead of the system.

   Finally,  the  memory  sizing  performed  in  MOSMEM  is  no longer

required. This is because the position of the global buffer pool is fixed by the page allocation scheme.

                                 12
2.1.1 The Process Control Table

The following fields have been added to the PCT:

pct_mmr : ARRAY [ first_page..last_page ] OF window_type;
pct_r0e, pct_r6e : virtual_byte_addresses;

where

TYPE window_type = RECORD par, pdr : INTEGER  END;
TYPE virtual_byte_addresses = 0..#177777;

The following field has been removed from the PCT:

pct_stk : ARRAY [ 1..stk_len ] OF BYTES
   pct_mmr is an array containing the window required to 'page-in' the

process. The bounds are not 0..7 because it is known that most pages remain constant, only pages first_page to last_page are changed since this is the amount of virtual memory allocated to a process. Each element of the array is four bytes long and contains the page address register ( par ) and page descriptor register ( pdr ) values for that page.

   pct_r0e  and pct_r6e contain the virtual byte addresses of the ends

( smallest numbered address ) of the R0 ( CORAL ) and R6 ( system ) stacks. These values are used to detect stack overflow.

   Since  the stacks no longer reside in the PCT the field pct_stk has

been removed.

   The  new  fields  in  the PCT are set up directly from the Load Map

produced by the linker/loader.

                                 13
2.1.2 The Scheduler
   A  context  swap  now  involves  saving  and  restoring  the memory

mapping, as well as the general register values. We can actually avoid saving the mapping when a process is suspended because normally it remains constant. The protection on a page is the only thing likely to change and if this is done using the supplied routines then we can still avoid doing the saving.

   When  the  process  is  made  runnable  it  is  first 'paged-in' by

restoring it's memory mapping. To ensure that the stack pointer and it's stack are restored together, interrupts are disabled earlier than in the old version. If an interrupt is allowed to occur between 'paging-in' the stack and restoring the stack pointer then disaster will obviously follow.

   Stack  overflow, on both the R0 ( CORAL ) and R6 ( system ) stacks,

is checked for when the process is suspended. The ends of the stacks are marked with overflow detect words, the address of which is held in the PCT. It is a simple matter to check if these words are still intact. If the system has no CORAL processes or library routines then the checks made on the R0 stack can be omitted by setting a switch in the EMMOS configuration file.

2.1.3 The $CREAP Macro
   This  macro  initializes  parts of the PCT, the rest is done at run

time. Two extra parameters are now required, which specify the size, in words, of the R0 ( CORAL ) and R6 ( system ) stacks. To avoid confusion the parameters should be called by name, the default size is zero words:

eg: $CREAP G802,<UX25 >,,DV.IMP+0,DV.IMP+1,R0SIZE=200,R6SIZE=20

$CREAP	G802,<UX25  >,,DV.IMP+2,DV.IMP+3,R6SIZE=30,R0SIZE=350
   The  macro  converts the size into memory blocks ( 40 octal words )

and places the information, along with the process id and name, in the .PSECT LDRCON. This information is used by the linker/loader in a way that depends on the methods being used. Note that different incarnations of the same process can have different sizes of stacks. In the above example ( all numbers are octal ) the first process would have

ceiling( 200/40 ) =  4 memory blocks = 200 words for R0
ceiling(  20/40 ) =  1 memory block  =  40 words for R6

and the second process would have

ceiling( 350/40 ) = 10 memory blocks = 400 words for R0
ceiling(  30/40 ) =  1 memory block  =  40 words for R6
   Note:  "ceiling(  r )" is a function that rounds up real numbers to

the next largest integer.

                                 14
2.1.4 Input and Output Routines
   The synchronous I/O routines, SIN and SOUT, have to be modified for

use with EMMOS. The user interface is unchanged, but the method of operation is slightly different. SOUT now grabs a global buffer, copies the string into it from the user's buffer and then performs the SIO on the global buffer, to output the characters. SIN grabs a global buffer, calls SIO to fill it and then copies the string into the user's buffer.

   This  is  necessary  because  if  the string to be output is in the

process' local area, it will be 'paged-out' with the process. SIO uses a pointer to the string ( a virtual address ) to get the characters, so if it does this when the initiating process is 'paged-out', it will either pick up rubbish or cause a memory management error. Similarly for SIN.

   The  copying would not be required for a string already in a global

buffer, because the buffer will always be 'paged-in', regardless of which process is running. In this case the MOS versions could be renamed and used to avoid a loss of efficiency.

   The  same  problems  apply  to  the  asynchronous I/O routine, SIO.

However, any copying from local data space to a global buffer that is needed, is left up to the user. Checks have been included in the code that ensure both the IORB and the data area are in the global buffer pool or the resident EMMOS code. These checks can be omitted for a program known to work by setting a switch in the EMMOS configuration file.

2.1.5 Memory Sizing
   This  is  now not carried out. The limits of the global buffer area

are assumed to be the ends of the pages allocated to buffers ( less space for the vectors if this includes page 0 ). It is assumed that the hardware has been configured in a sensible manner.

                                 15
2.1.6 Protection Routines
   Two  routines are supplied, one to inspect the access control field

( ACF ) of a page, the other to change the ACF. Under the memory management system each page can be either

1.	non-resident	-all attempts at access will cause
			 a trap
2.	read-only	-attempts to write into a location
			 in the page will cause a trap
3.	(unused)	-same as non-resident
4.	read/write	-neither reads nor writes are trapped
   The  two routines will only work for one of the process pages. They

both return an integer result. This equals 'true' if the page specified is a process page, else it equals 'false' and the ACF is not changed or inspected. The pre-defined constants, 'non resident', 'read only', 'un used' and 'read write', should be used to specify and compare the ACFs.

INTEGER PROCEDURE change page protection ( VALUE INTEGER page, access );

   If  the  page specified is a process page then the ACF of that page

is changed to the given access. The copy of the memory management registers held in the PCT table for this process, is also updated.

INTEGER PROCEDURE current protection ( VALUE INTEGER page;

			       LOCATION INTEGER pct, pdr );
   If the page specified is a process page then pct and pdr are set to

the values of the ACF field of the memory management registers for that page, taken from the PCT window area and active page registers respectively. The two different versions are supplied in case one of them has been corrupted. Both should always be the same.

CORAL Constant Values
true		1
false		0
non resident	0
read only	2
un used		4
read write	6
                                 16

cont.

   The  assembler  versions of these routines are completly re-entrant

and run to completion, so it is possible to have one copy of the code in the resident part of the system which is shared between all processes. The routine to change the page protection is called $CHAPP and the routine to inspect the current protection is called $CURPP, they have the same effect as the CORAL versions but are called as follows:

Change Page Protection :

called by JSR	PC,$CHAPP
with	R2 = number of the page to be changed
	R3 = the new access of that page
returns with
	R1 = TRUE or FALSE
	R0 and R2...R6 unchanged

Inspect Current Page Protection :

called by JSR	PC,$CURPP
with	R2 = number of page to be examined
returns with
	R1 = TRUE or FALSE
	R3 = value from PCT
	R4 = value from active PDR
	R0, R5 and R6 unchanged

Assembler Constant Values

TRUE	1
FALSE	0
NONRES	0
READON 	2
UNUSED	4
RWRITE	6
                                 17
2.1.7 System Configuration
   The  page  allocation  scheme, operating system options and various

buffer addresses, are specified in the configuration file, EMMSYS.SML .

2.1.7i The Page Allocation Scheme
   The virtual memory is divided up into 8 pages of 4K words, numbered

0..7. Page 7 is always allocated for the I/O page, the remaining seven are split between the global buffers, the operating system and the running process ( including it's stacks ). The order is critical, buffers should be at the low numbered end, then the system, then the process and finally the I/O page. The exact split is given by specifying the first and last page used by each part.

eg buffers 0 - 3 emmos 4 - 5 process 6 - 6 or buffers 0 - 2 emmos 3 - 4 process 5 - 6

   When  decreasing the space allocated to the global buffers, be sure

that the workspace buffers ( see 2.1.7iii ) are still located before EMMOS, otherwise EMMOS may be loaded on top of one of them before it's finished with. Note: different linking/loading schemes may lend themselves to a different ordering of buffers, system and process, in which case minor alterations to the PCT initializer may be necessary. The I/O page must always be page 7.

                                 18
2.1.7ii Operating System Options

EMMOS: EMMOS/MOS selection switch

= 0	for EMMOS
= 1	for MOS ( SAVMAP  and SET250 have no meaning )

SAVMAP: save mapping when process suspended switch

= 0	when a process is suspended the window is saved as part
	of the process' volatile environment. This allows a 
	process to change the window dynamically ( eg the
	protection ). Note, only that part of the window for the
	pages allocated to processes is saved and restored, so
	changing other pages will affect all processes.
= 1	the window is not saved each time the process is 
	suspended. The original value is reloaded when the
	process is run again, erasing any changes made.
It is recommended that SAVMAP = 1 and the supplied protection
routines are used to change the page allocation, with the other
fields left well alone.

SET250: memory management error trap initialization on/off

= 0	when the system is initialized the trap is set to a local
	handler.
= 1	the trap is left untouched, this option should only be
	used when the debugger is used ( MUD will set up it's
	own handler ).

COPYIO: sin/sout version selection switch

= 0 ( OR EMMOS = 0 )
	use the versions of sin and sout that copy to/from a
	global buffer before initiating the I/O.
= 1 ( AND EMMOS = 1 )
	the MOS versions will be used

NOCORL: CORAL process indicator

= 0	no processes are written in CORAL, or use CORAL libraries,
	so the R0 stack is not checked for overflow each time a
	process is suspended. The stack is still set up as
	usual. The R6 stack is still checked.
= 1	There is a process using CORAL, so the R0 stack will
	be used. Both stacks are checked for overflow each
	time the process is suspended.

CHKSIO: SIO buffer address checking on/off

=0	Each time SIO is called the IORB and the data addresses
	are checked to ensure that they are in the global buffer
	pool or the resident EMMOS code. An attempt to use a 
	local iorb or data area will cause a BUGHLT.
=1	No checks are performed. The use of a local IORB or data
	area is likely to cause the system to crash.
                                 19
2.1.7iii Workspace Buffer Addresses
   The  linker/loader and system initializer must agree on the postion

of the load map. It's address is given by MAPADD in the system configuration file. The linker/loader is given this information in some implementation dependant way.

   Note  that  in  the RSX linker/loader system, the area specified by

MAPADD is also used by the loader for other workspace buffers during load time, because of this MAPADD must specify an area of store, lying between the end of the loader and the beginning of where EMMOS will be loaded, of about

25 * max_number_of_processes ( decimal ) words long
   The   loader   ends   around  032000,  without  removing  redundant

libraries, so even for a max number of 30 processes ( 25 * 30 decimal is about 1400 octal ), giving MAPADD = 32000 would mean the tables would end way below 040000. This allows us to load a system with only 8K of buffers.

                                 20
2.2	The EMMOS Debuggger  - MUD
__________________________________
   MUD (Marvellous Universal Debugger) is split into two parts, RESMUD

and MUD proper. RESMUD is a small program written in MACRO-11 that is permanently resident in virtual memory. It acts as the interface between the operating system and the debugger, MUD, which is only brought into virtual memory when it is needed.

   MUD  is  loaded by the loader in the same way as any other overlay.

Normally , however, overlays that are not $CREAPed do not have entries in the load map, but the loader creates a special entry for the debugger. RESMUD uses this entry to find out where MUD has been loaded, and transfers control to it. For this reason RESMUD must be the first module loaded, even before MOSPM, if the debugger is to be used.

   If  RESMUD  is not included in the system, then MUD will be ignored

if it is itself included, and the debugger cannot be used. EMMOS will ignore MUD, unless a process is foolishly creaped with the name MUDSYS!

   The  debugger is able to reference the whole of the 18 bit physical

store without the user knowingly changing the memory management registers.

                                 21
2.2.1 A Summary of MUD Commands
   The name of a command is one or two letters long, in upper or lower

case, as follows:

Print Commands:

pb	print breakpoint locations
			( see breakpoint commands )
pr	print the value of a relocation register
			( see relocation commands )

Spy Commands:

s	spy on store
sr	spy on the registers
sw	spy on the memory management window

Breakpoint Commands:

b	set a breakpoint
k	kill a breakpoint
pb	print breakpoint locations
			( see print commands )

Enter User Program Commands:

g	go to a specific address
gs	go to a specific address single stepping
c	continue from a breakpoint
cs	continue from a breakpoint single stepping

Relocation Commands:

r	set a relocation register
pr	print the value of a relocation register
			( see print commands )

Exit Command

x	leaves MUD and halts

Special Commands

i	change I/O channel
e	evaluate an arithmetic expression
f	find a memory location containing a given value
                                 22
2.2.2 MUD Command Syntax
   The  syntax  is  fairly  simple  and  is  quite free with spaces. A

command consists of the command name, one or two letters( upper or lower case ), optionally followed by one or two operands.

<name>
<name> <op1>
<name> <op1> <op2>
   The two operands and the name may be seperated by commas or spaces.

The operands specify 18 bit addresses or numbers in octal. Their syntax is:

<operand> ::= <repeat symbol>
	  or= <number> <relocation> <mode>
<repeat symbol>	::=  *
<number>  ::= <18 bit octal number>
	  or= null
<relocation> ::=  .  <relocation register>
	     or= null
<relocation register> ::= <octal digit>
                      or= null
<mode> ::= V
       or= P
       or= null
   If  the  octal  digit is ommitted, it is assumed to be zero. If the

relocation dot is not present, then the operand is assumed to be an absolute physical address. If the octal digit is missed out, then relocation register one is assumed. Hence the following examples all mean the same:

s100.,150.
s100.1  150.
s 100.   ,   150.1
s 100.1   150.1
                                 23
2.2.3 The Relocation Mechanism
   There  are  seven relocation registers supplied ( r1..7 ) which can

be used to modify an address in a command. Values are put into these registers by the 'r' command (qv). To specify that an address is to be relocated it is followed by a dot and perhaps a digit. The digit is the name of the relocation register to be used and is one by default. When relocation is specified, the contents of the relocation register are added to the address giving the actual address used. For example:

r2,100	- sets relocation register 2 to 100
s20.2	- spys on location 20+100=120
r350	- sets relocation register 1 to 350
s30.	- spys on location 30+350=400
   Many  commands  allow  an  asterisk  to be used as an operand, this

usually means 'all' in some way, for example:

k100	- kill the breakpoint at location 100
k *	- kill all breakpoints
                                 24
2.2.4 Addressing Modes
   An  operand  can  refer  to  a  virtual  location  by following the

relocation indicator by a 'V' ( the default mode is 'P', so normally addresses are physical ). The corresponding physcial location will be computed using the user's window. Page lengths are ignored.

   For  example,  if  the  base  address of page 5 ( virtual addresses

120000-137777 ) is 120100 and relocation register 2 contains 120000, then the following are equivalent:

s120110   - physical address by default
s120110p  - physical address specified
s120010v  - pa = 010 + base(5) = 120110
s110.2    - pa = 110 + 120000  = 120110
s110.2p   -   . . . . physical specified
s10.2v    - va = 10 + 120000 = 120010
          - pa = 010 + base(5) = 120110
                                 25
2.2.5 Command Specification

pb print breakpoint locations

pb	prints out a list of all the breakpoints
	that are set, showing their location as an
	absolute address and as an offset +
	relocation.

pr print relocation registers

pr	print out the value of relocation register 1
pr 'd'	print out the value of relocation register 'd'
pr *	print out the value of all the relocation
	registers.

r set a relocation register

r 'n'	set relocation register 1 to 'n'
r 'd' 'n'	set relocation register 'd' to 'n'
r * 'n'	set all relocation registers to 'n'
r *	note that 'n' is zero by default so that
	r* will set all relocation registers to 
	zero. Initially all are zero anyway.

x exit

x	leaves MUD and halts. This instruction would only
	be used when running under a monitor operating system.
                                 26

cont.

s spy on store

s 'l'	spy on the store starting at location 'l'.
	The address is displayed followed by the
	contents. The contents can be changed by
	typing a number followed by <return>, in
	which case the new value will be displayed.
	To advance to the next location just type
	space, to retreat to the previous location
	type tab. To finish the spy type <return>.
	Note that typing <return> with no number
	does not change the value in store.
	eg
	mud> r100
	mud> s20.
	000120> 123456  23
	000120> 000023   000122> 077177
	mud>
	this leaves location 122 unchanged, but puts
	23 into location 120.
s 'l1' 'l2'
	spys on locations 'l1' to 'l2', but does
	not allow you to change any values.
	Effectively this is just a display or dump
	command. 'l1' should be less than or equal
	to 'l2' or nothing will happen. To stop
	the printing hit <escape>. This is handy if
	you specify an enormous amount of printing
	by mistake.

sr spy on registers

sr	spys starting at register 0
sr 'd'	spys starting at register 'd'
	This is similar to spy on store, but you can
	only progress forward one register at a
	time, by typing <space>. After reaching
	register 7, the program counter, you get to
	look at the psw. The only way to spy on the
	psw is to first spy on register 7. If you
	space forward from the psw you get back to
	register 0, to get out of the spy type
	<return>.
                                 27

cont.

sw spy on the window

sw	spy on the memory management registers. At present
	you are restricted to looking at the registers for
	the pages allocated to processes, which under EMMOS
	should be the only ones to change.
	The registers are automatically split into their
	composite fields for displaying
	and changing. To go forward to the next field, type
	<space>, to go to the next page, type <tab> and to
	exit type <return>. Numeric fields are changed by
	typing a number followed by <return>, other fields
	are changed by typing one character.
	the expansion direction, ed> is either up=u or down=d
	the access control is either  non resident  = n
	                              read only     = r
	                          or  read/write    = w
	an access control of 'unused' indicated an error
	in the memory management registers. The written into
	bit cannot be examined or changed.

b set a breakpoint

b 'l'	puts a breakpoint at location 'l'. Note 
	that you are not allowed to place a breakpoint
	on top of another breakpoint. It
	is possible to set breakpoints on top of emt
	instructions, but, once they have been
	reached, it is not possible to 
	continue or single step from them.

k kill a breakpoint

k 'l'	kills the breakpoint at location 'l'. If
	the program is halted at the breakpoint, it
	is not possible to continue from there, use 
	'go' instead.
k *	kills all breakpoints. If the program had
	halted at a breakpoint it will not be
	possible to continue, use 'go' instead.
                                 28

cont.

g go to command

g 'l'	jumps to location 'l' and starts
	executing. 'l' must be a virtual address,
	mapped according to the window. The
	registers are loaded as set.
gs 'l'	as above, but single stepping. Only one
	instruction is executed before control is
	returned to MUD. When single stepping all
	breakpoints are temporarily removed, so no
	breakpoints as such will be hit.

c continue command

c	continue from the breakpoint last reached or at the
	next instruction if previously single stepping.
cs	as for continue but single stepping 
	( see 'gs' above )
	Note that it is not possible to continue from a
	breakpoint that has just been killed, but you can
	use 'g' instead with 'l' as the contents of the program
	counter, R7.

i change I/O command

	This command has a special syntax, because it is
used to redirect the input and output of MUD from one
terminal to another.
i	change I/O to the console
iv 'n'	change I/O to vdu 'n'
is	sink the output. All output will be thrown away,
	including echoing. Input remains on the same channel.

f find a value

f 'v'	searches the physical memory, stating at
	location zero, for a word containing the value
	'v'. Every so often a message will say
	"still looking" if one has'nt been found
	yet. To stop the search early hit <escape>
	to break in.
f 'v' 'l'
	As above but the search starts at 'l' 
	instead of zero.
                                 29

cont.

e evaluate an expression

	This command evaluates an 18 bit expession, using octal
or decimal numbers or the contents of a relocation register. There
is one accumulator, acc, which holds an 18 bit number. The operations
that can be performed on it are:
^ val	Load the acc with 'val'
+ val	Add 'val' into the acc
- val	Subtract 'val' from the acc
P	Convert the least significant 16 bits of acc into an
	18 bit virtual address
V	Treat the value in acc as an 18 bit physical address and
	convert it into a 16 bit virtual address.
The values are given by:
# octal number	- an 18 bit octal number, the # can be omitted
$ decimal number- a 15 bit decimal number, ie positive only
R num		- the 18 bit value of relocation register 'num',
		  num is 1 by default
	Several commands can appear on one line, seperated by commas
or spaces if it is necessary to avoid ambiguity. The value in acc is
displayed when the end of the line is reached.
	The value in acc is zero the first time the 'e' command is
used. Subsequently the value is that left in it after the last use
of 'e'.
                                 30
2.2.6	Using MUD
   If  RESMUD  is  linked  in  before  EMMPM  and MUD is present as an

overlay, then the debugger will be entered once the system has been loaded. If not the debugger cannot be used.

   When  control  is passed from MUD to the user program, by using the

go or continue commands, the system error traps are set so that any error will be handled by MUD. This remains so until the user program changes them. Note that while MUD is running the traps are set to different handlers which detect whether the debugger itself is in error. If so check that RESMUD has been assembled with the current page configuration.

   We can return to the debugger at any time via the console emulator.

Find the address of the global symbol $MUDIN, it is part of RESMUD and so is permanently resident, it's virtual and physical address is therefore the same. Use the console emulator to jump to this location and MUD will restart.

                                 31
2.3	Linker and Loader Functions
___________________________________
   In  general  a  system's  linker  and  loader perform two logically

distinct functions, though in practice the border between these functions may be less clear. In a virtual memory system it is usually the operating system that allocates physical memory, but in a monitor system this may be done by the linker. There are a number of functions that the linker and loader are required to perform on an EMMOS system. Some of these functions require a degree of intelligence that is not usually found in either linkers or loaders. These intelligent functions may be incorporated in either the linker or the loader, or distributed between them. When presenting these functions we will describe them all as being part of the linker, for ease of understanding. The loader then, just copies an output image file from the linker, word for word into the 128K physical memory. However, linkers are complex pieces of system software, that are difficult to write or modify. Therefore, in practice, one usually takes the best of the functions that the linker has to offer and writes a loader program which incorporates those remaining.The functions that the linker/loader must perform are listed below:

1.	Resolve all global references and relocate the 
	object code.
2.	Allocate physical memory for the system, buffers,
	process code and stacks. This takes into account
	the page configuration scheme.
3.	Construct a Load Map which specifies where processes
	are in physical memory and where the ends of their
	stacks are in virtual memory. This is passed on to the
	loaded system.
2.3.1 The Linker
   We  can consider the input to the linker to be a set of Relocatable

Binary ( RLB ) modules, with some system of reference resolving that need not concern us. A process' body is made up of one or more RLB modules, with sharing allowed. EMMOS is similarly composed. To specify how the system and process bodies are constructed we supply the linker with a Link Control File, which specifies which modules make up the system and process bodies, and the order in which they are to be linked to form the body. We also need a way of uniquely identifying a process body, so that when we create a process we can specify which code body to use.

   Here we give an example of what this file could look like. The line

headed 'SYSTEM' would define the construction of the resident operating system part of EMMOS and lines headed 'BODY' would each describe a process body, it's name given in brackets:

SYSTEM:		EMMPM-EMMTTY-..........-EMMMEM;
BODY(X25):	X25CODE;
BODY(PRINTR):	IOCODE-IOLIB;
END:
                                 32

cont.

   The EMMOS linker must also allocate physical memory for the process

code bodies and stacks. In order to do this it needs to know which code bodies will be incarnated as which processes and how much stack to give those processes.T This information could go in the Link Control File. As an example of how it could be represented, we consider the example body description given above, and create some processes with those bodies:

PROC(X25-A):	"X25"   ,SYSTEM=200
PROC(X25-B):	"X25"	,SYSTEM=200
PROC(PRINTR):	"PRINTR",SYSTEM=100,CORAL=400
END:
   Here  we  describe three processes, called X25-A, X25-B and PRINTR.

The first two are both incarnations of the body "X25" and both have 200 words for their system stack and no CORAL stack. It's not necessary for them to have the same size stacks, though some implementations could impose this as a restriction with no great hardship. The third process, "PRINTR", is the one and only incarnation of the body "PRINTR". It has 100 words for it's system stack and 400 words for it's CORAL stack.

   The  linker  still  requires  information on the page configuration

before it can allocate physical memory. This information is not strictly necessary. We could allocate the code and stacks for each process as one lump of physical store and if it is too large for the configuration, then the system will fail at run time. By knowing the page allocation scheme however, the linker can be quite subtle and avoid unnecessary duplication of code and could even arrange for the code and stacks to be on separate pages if the sizes are right. ( For more details see 3.2.7/8 ). To complete our example we shall give an idea of how the configuration could be specified in the Link Control File:

PROCS 5..6,	BUFFERS 0..2,	EMMOS 3..4;
   Note that page 7 must always be allocated for the I/O page and that

the section using page 0 will lose the first 1000 octal bytes for the vector area.

   In  our  example,  if the body "X25" was less than 4K words then it

would fit on one memory page. Since the stacks for "X25-A" and "X25-B" are quite small, they too fit on one page. The linker should then only put one copy of the body into physical memory and arrange the mapping of both the processes such that page 5 maps onto it. Page 6 would map onto the processes stack areas, which would of course be different physical memory locations.

   Once the linker has finished allocating physical memory it needs to

be able to tell the EMMOS initializer what the memory management window for each process is and where it's stacks are. It does this by including a table with this information in, in the Load Image File. This table must be loaded at a place in memory where the initializer can find it, say somewhere in the global buffer pool.

                                 33
2.3.2 The Loader
   The  Loader  takes  the  Load  Image  file  as  input. This file is

produced by the linker and contains absolute binary data with an indication of where in physical memory to load it. The loader need not know anything about the processes or the system it is loading. However there needs to be some way of telling where the system's entry point is. Note that since the entry point will be a virtual address it is necessary to include an initial memory mapping as part of it's definition.

                                 34
			PART THREE
			__________
		   HOST MACHINE ASPECTS
                                 35
3.1	 Linking Using the RSX Task Builder
___________________________________________
   The  RSX  task  builder( TKB ) is used as the linker. It is because

not all the functions we require are supplied by this linker, that our loader is more complicated then usual. The Task Builder is used to perform the relocating and global reference resolving parts of the required actions. The physical memory allocation, process incarnation and the production of the Load Map are all actions that are performed by our loader. This does mean that the loader is complicated, but less overall effort is required to write it than to completely re-write the linker.

3.1.1 Use of the Task Builder
   The task builder is able to handle overlays in several ways. It can

produce disk resident overlays, in which "paged-out" segments reside on disk, or memory resident overlays, in which all segments reside in physical memory but not all are "paged-in" into virtual memory. TKB will also allow your task to be some combination of the two. Overlays are paged in and out by special calls to RSX which TKB automatically inserts into the object code. It is possible to instruct TKB to leave out this code, allowing the user to handle his own paging. Overlays are characterized by being relocated at the same place in memory. it is this feature that EMMOS exploits to get all the process' bodies relocated at the same base address. We do however require that the linker does not put any auto loading code in for the overlays.

   Amongst  the various tables hidden in the task image, is the window

description table, this does not appear for disk resident overlays, so we chose to use memory resident overlays throughout. Also, when using memory resident overlays, each layer of the overlay tree is relocated on a page boundary, which is another requirement for EMMOS. There is an option, when running a task under RSX, of allowing it to be swapped in and out of physical memory ( checkpointed ). A checkpointable task has a large space in it's task image file, so as EMMOS has no need of it, the task must be built not checkpointable ( /-CP switch ). We will assume then that the task is not checkpointable, with manually loaded ( though we shall not include the calls to do it ) memory resident overlays.

                                 36
   The first snag is met when we try to get information about the size

of the overlays and their position within the task image. The tables that contain this information follow on directly after the code of the root segment, but there is no indication as to how long the root is, except in these tables. Normally their position is known because the overlay loading mechanism references them, but we've done away with this. Ideally the root segment would be the EMMOS system, but there is no clean way of finding out how long it is, so we chose to not allocate page 0 to the system. Instead the root segment does not contain any code, only some tables of known length used by the loader. Unfortunately, because TKB will relocate the next overlay layer on page 1, we have used up 4K of virtual space just for the interrupt vectors etc. To avoid this we allocate the global buffer pool space to pages 0,1…. because no code is loaded for it. The problem remains to persuade TKB to relocate EMMOS at the end of the buffers. We could do this by extending the root segment to occupy all these pages and then just ignore it all when loading, but this would mean the task image file would be enormous. Instead we use dummy overlay layers. Although each layer contains only one word, it is sufficient to persuade TKB to relocate the next layer at the next page boundary.

   It  is  because  of  such  devious  means  of  getting  the overlay

relocated to the desired base, that all the calculations performed by TKB to produce physical memory load addresses are useless. Instead of wasting large areas of physical memory the loader does the calculations again. This also means that we can handle the process' stacks nicely.

   Finally  there is the problem of uniquely identifying each overlay,

so a process can specify which is to be it's code body. TKB gives each overlay a name, which is the first six characters of the first object file's name in the overlay, however this may not be unique. We have then two options; use the name given by TKB or use a position numbering scheme. Despite it's drawbacks of non-uniqueness, the former was chosen. Names are easier to understand then numbers, and the possible inclusion of the debugger may well upset the numbering, though it is a special case. The use of six character names for the overlays led us to adopt the <name> field of the $CREAP macro to specify which code body to use. Other schemes may require another field in the macro call.

                                 37
3.1.2 The Format of the Task Image File
   The  information required to complete the Overlay Description Table

is scattered across the first few blocks of the task image file. The format is dependant on various switches and the form of the ODL file. There follows a brief description of the parts relevant to the loader, assuming it was built in the required manner.

BLOCK 0:

     Contains no useful information, except perhaps the creation
date of the task image.

BLOCK 1:

No useful information.

BLOCK 2:

     This  is  the  start  of  the task header. The word at byte
offset  72  octal  is  the  "header  guard  word  pointer". This
contains  the  byte offset, within the next block, of the "guard
word", this immediatly preceeds the code of the root segment and
is how the loader finds the root.

BLOCK 3:

     This contains the start of the root segment. The first word
of  the  root follows the "guard word". The root segment is used
to  transmit  configuration  details and the process description
table  to  the  loader.  Immediatly following these tables is 29
words  of  rubbish ( code used to load the overlays under RSX ),
the  segment  descriptor table, one word of rubbish and then the
window  descriptor  table.  These  two tables are used by RSX to
define the positions of the memory resident overlays in physical
memory,  EMMOS  uses  them  to construct the Overlay Description
Table, as follows:
segment descriptor table -
each entry is 9 words long, there is one entry for each overlay and
the root
	word offset 1	the virtual base address
	word offset 6&7	the segment's name in radix 50 format.
window descriptor table -
each entry is 10 words long, there is one entry for each overlay
	word offset 2	overlay's size in memory blocks (64bytes)

NEXT BLOCK BOUNDARY:

     The  code  of the first overlay begins at the first word of
the   next  disk  block.  Subsequent  overlays  start  on  block
boundaries following on in order.
                                 38
3.1.3 The Overlay Description Language
   The  Task  Builder  takes  a  series of object files and links them

together to produce a Task Image. This is the exact image of what is to be loaded at run time ( under RSX ). EMMOS exploits the memory resident overlay capabilities of the Task Builder in order to relocate the system kernel and processes' bodies in the desired place. The arrangement of the overlays is described in the Overlay Description Language ( ODL ) file, the structure of which is as follows:

module1 - module2 - ............ - moduleN
   specifies  that  the  N modules are to be concatenated in the given

order

( module1, module2, ............ , moduleN )
   specifies  that  the  N  modules will overlay each other in virtual

memory. The modules not paged-in will be resident on disk or, if they are memory resident overlays ( indicated by a ! before the open bracket ), in physical memory.

   These concatenation and overlay operations are combined to describe

the overlay tree structure, eg:

A - B - !( C - D - !( E , F ) , G - !( H - !( I , J ) , K ) , L )

this specifies the following tree

E   F    I   J
|___|    |___|
  D        H     K
  |        |_____|
  C           G          L
  |___________|__________|
	   B
	   |
	   A
   At  any  one  time  during  runtime,  the memory will contain those

modules lying on one of the paths from the root to a leaf.

   For  ease  of  writing, it is possible to give parts of the overlay

tree a name, using the factor command ( .FCTR ). so a complete ODL file for the above example could be:

.ROOT		A - B - !( OV1 , OV2 , L )
OV1:	.FCTR	C - D - OV11
OV2:	.FCTR	G - !( OV22 , K )
OV11:	.FCTR	!( E , F )
OV22:	.FCTR	H - !( I , J )
.END
                                 39
3.1.4 The EMMOS ODL File
.ROOT		ROOT - !( OV1 - !( OV2 - !( EMMOS - !( PROCS ) ) ) )
EMMOS:	.FCTR	EMMPM - FIRST - ......................
PROCS:	.FCTR	BODY1 , BODY2 , ..............., BODYn
   The  factor  EMMOS  describes all the system modules, which are all

concatenated. The factor PROCS describes each of the different code bodies for the processes. Normally only one code body is required at a time so the modules overlay each other, but it is possible to concatenate processes which are to access common routines.

   For  example,  suppose  we have four code bodies BODY1, ... , BODY4

which are each $CREAPed only once to give processes PROC1, … , PROC4. If these processes are completely independant then we write:

PROCS:	.FCTR	BODY1 , BODY2 , BODY3 , BODY4
   Suppose  we have a library module, LIB, that is shared by PROC1 and

PROC2, then we could write:

PROCS:	.FCTR	BODY1 - LIB , BODY2 - LIB , BODY3 , BODY4
   This  would  cause  two  copies of LIB to be in physical memory. We

could however write:

PROCS:	.FCTR	BODY1 - BODY2 - LIB , BODY3 , BODY4
   which  would cause one copy of LIB to be put in the task image, but

PROC1 and PROC2 would now be paged in and out of virtual memory together, which may not be desirable.

   Note that if the total size of BODY1, BODY2, LIB and the stacks for

PROC1 and PROC2 exceeds the space allocated to processes, then the loader will duplicate some or all of the code in an attempt to fit it in. It may be then, that more physical memory would be used in trying to share LIB than if we specified duplication in the first place.

                                 40

cont.

   The modules ROOT, OV1, OV2, ..... OVn are required by the system to

ensure that the operating system and the process bodies are relocated in the correct place. ROOT also transmits information from the $CREAP macro calls to the loader. The Task Builder relocates the first "layer" of the overlay tree ( the root ) at location zero. Successsive layers are relocated at the start of the next page boundary ( a page is 4K words long ). So, because each of these modules is quite small, ROOT is relocated at 0, OV1 at 20000, OV2 at 40000 etc. If we require the operating system to be relocated at 60000, we put it in the next layer ( as in the above example ). Suppose we want EMMOS to start on page 4 ( location 100000 ), and it is over one page long so the processes will be on page 6 ( location 140000 ), we would need to pack out pages 0..3 thus

.ROOT ROOT - !( OV1 - !( OV2 - !( OV3 - !( EMMOS - !( PROCS ) ) ) ) )

   For  technical  reasons  it  was not possible to have the operating

system at the low numbered end of memory, so that is where the buffers have been put. Dummy overlays are used to pad out the buffer area because they cause the task image to be much smaller than if we had a .BLKW . The latter would cause it to contain about 12K of zeroes!

                                 41
3.1.5 Installation of the Debugger
   The  EMMOS  debugger,  MUD, is in two parts, a resident part RESMUD

and an overlay part MUD. To be able to use the debugger both parts must be included in the ODL file. RESMUD must appear before EMMOS but MUD can be put anywhere amongst the PROCS, eg:

.ROOT ROOT - !( OV1 - !( OV2 - !( RESMUD - EMMOS - !( PROCS , MUD ) ) ) )

3.1.6 Creating the Task Image File
   Once  the form of the ODL file has been worked out the task builder

is run under RSX-11 by:

TKB	EMMOS , EMMOS /NOSP = EMMOS /MP
   The  first  output  file  is  for the Task Image ( .TSK ) file, the

second is the task map ( .MAP ) file, which is spooled by default. The MP switch specifies that the input file is an .ODL file.

   The  .MAP  shows  the  structure  of  the  task image and gives the

virtual addresses of the modules, global symbols and .PSECTs. This link map should be used in conjunction with the loader's load map, which specifies physical addresses of code and stacks, when debugging. The .TSK file now needs to be copied to the RT-11 format disk which contains the loader using FLX. The system is now ready for loading and running.

                                 42
3.2	The EMMOS RSX Task Image Loader
_______________________________________
   This section describes the operation of the loader for the Extended

Memory MOS system. The loader itself is loaded from a floppy disk in RT-11 format, using a fairly standard bootstrap loader. The EMMOS loader then loads the system from an RSX-11 Task Builder Image file ( stored in RT-11 format ) on the same floppy disk. Control is then passed to the operating system or to the EMMOS debugger, MUD, if it is loaded.

                                 43
3.2.1 Inputs and Outputs
   The input to the loader is an RSX-11 Task Builder Image file stored

on a floppy disk in RT-11 format. This file contains:

1.	The Configuration Table 	( see 3.2.3 )
2.	The Overlay Description Table	( see 3.2.4 )
3.	The Process Description Table	( see 3.2.5 )
4.	Absolute binary code for the operating system
		and processes.
   The  loader  produces  a  table,  the  load map, which it places in

memory and displays on the console, that describes the positioning of all the processes in physical memory.

   The  address of the entry point to the loaded system must be stored

in the first word of the system. This is set by MOSPM or, if the debugger is included, RESMUD.

                                 44
3.2.2 The Console Load Map
   This  is  a human readable form of the LOAD MAP that is supplied to

the loaded system. There is one line for each process describing the position of it's code and stacks.

   The  first  column  gives  the process id ( pid ), the next two the

limits of the code and the fourth the limits of the two stacks combined.

   If  none  of  the pages containing code, contain any stack then the

third column will read "unshared", that is, there is no page that contains both stack and code. However, if there is only one page for processes then this must contain all the code and all the stack, in this case the third column will read "page is shared".

   If  the code is longer than one page, and it is necessary to have a

page containing both stack and code, then the third column will be the limits of the piece of code that is sharing with the stacks. This may be a copied piece of code.

   The limits are given in terms of physical memory blocks, so a limit

of

	001200 - 001250
   for  the  code  would  mean that the code starts at physical memory

location 120000 and ends somewhere between 125000 and 125077.

   If  the task image does not contain an overlay for a process' body,

then the load map entry for that process will be of the form:

pid  -  ABCDEF has'nt got a body
   If  one  or  more  processes  lack  bodies  then  the  load will be

abandoned, once the load map has been printed, with the message:

"process requires body"   (see ERRORS)
   Note:  the  printing  of  the  Load Map can be abandoned by hitting

<escape>.

                                 45
3.2.3 The Configuration Table
   This  table  resides  in  the  root  segment  of  the  task  image,

immediately before the Process Description Table. It is used to tell the loader the page allocation scheme and the maximum number of processes. The information is passed to it at run time so that the loader need not be re-compiled for every change in the configuration.

The format is:
VAR config_table :RECORD
			max_number_of_processes,
			load_map_address,
			first_mos_page, last_mos_page,
			first_process_page, last_process_page
				: INTEGER
		END;
                                 46
3.2.4 The Overlay Description Table
   This  table  is  constructed  by  the  loader from the task image's

window and segment descriptor tables, that follow the root segment. Each overlay named by the task image ( except the dummy ones and MOS ) has an entry of the form:

TYPE overlay_description = RECORD
				name : ascii;
				size : mem_blocks;
				disk_address : INTEGER
			END;
TYPE ascii = ARRAY[ 1:6 ] OF CHAR;
TYPE mem_blocks = 0..number_of_process_pages * #200 -1;

The table is given by

odt : ARRAY[ 1..number_of_overlays ] OF overlay_description;

odt[ ov ].size contains the size of the overlay segment ( that is of the code ) in 32 word memory blocks. odt[ ov ].name is the overlay's name, taken from the overlay description language file at build time. odt[ ov ].disk_address is the number of the disk block where the overlay starts.

   Note that the disk address field is not used in the current version

of the loader. It remains as a left-over from a version that would not support down line loading.

                                 47
3.2.5 The Process Description Table
   This table resides in the root segment of the task image, following

the Configuration Table. It is produced by the $CREAPs used to create the processes. It is used to tell the loader which processes are required, which overlay will supply the code and the size of their stacks. There is one entry in the table per process, each of the form:

TYPE process_description = RECORD
				pid : INTEGER;
				name : ascii;
				r0_size, r6_size : mem_blocks
			END;
TYPE ascii = ARRAY [ 1..6 ] OF CHAR;
TYPE mem_blocks = 0..number_of_process_pages * #200 - 1;

The table is given by

VAR pdt : ARRAY [ 1..number_of_processes ] OF process_description;

pdt[ i ] is the entry for the ith process, so pdt[ i ].pid = i for all i IN 1..number_of_processes. The name field contains the six character name of the overlay which is the code for the process. This is taken from the second parameter of the $CREAP macro call. pdt[ i ].r0_size and pdt[ i ].r6_size contain the required sizes, in 32 word memory blocks, of the stacks. Note that the macro converts the sizes from words to memory blocks before placing the values in the table, using

size_in_blocks := ceiling( size_in_words / 32 ).
                                 48
3.2.6 The Load Map
   The  load map describes the loaded system to the initializer. It is

placed in memory by the loader at an agreed address before it gives control to the system. There is one entry in the table for each process, and a special one for the debugger if it is included in the task image. The form of a table entry is:

TYPE load_description = RECORD
				pid,
				r0_low, r0_hi,
				r6_low, r6_hi : INTEGER;
				window : ARRAY 
					[ first_page..last_page ]
					OF window_type
			END;
TYPE window_type = RECORD par, pdr : INTEGER END;

The map is given by:

map : ARRAY [ 1..number_of_processes + 1 ] OF load_description
   map[i] is the entry for the ith process, so map[i].pid = i, for all

i IN 1..number_of_processes. map[i].pid = -1 for the debugger entry. Any unused entries have pid = 0.

   r0_low  and  r0_hi contain the virtual byte addresses of the lowest

and highest numbered words in the R0 ( CORAL ) stack for the process. Similarly for r6_low, r6_hi.

   window describes the mapping from virtual memory onto this process.

There are two words for each page allocated to a process, the page address register and the page descriptor register.

                                 49
3.2.7 The Physical Memory Allocation Algorithm
   The  loader should attempt to arrange the loading of the Load Image

into physical memory in a way that will reduce the amount of physical memory required. The total amount of virtual memory allocated to a process for it's code body and stacks will be fixed ( = num_pp memory pages ), but for each process the sizes of it's code and the total size of it's two stacks will vary.

   If  at  all possible the loader will arrange for the stacks and the

code to be on different pages in virtual memory, allowing the process to put read-only protection on it's code. This is only possible if:

pages( code_size ) + pages( stack_size ) <= num_pp
   where  pages(  i  )  = the size i rounded up into a whole number of

memory pages. Of course if pages( code_size + stack_size ) > num_pp then not enough virtual memory is availiable for this process. However if we have:

pages( code_size + stack_size ) = num_pp
   then some of the code and some of the stack space must share one of

the pages. The shared page cannot then have read-only protection placed on it. If a process has already been created with this code body then physical memory following the code body will have been allocated as a stack. We cannot then have the stack for the next process following directly on from the code, so it may be necessary to copy part of the code body and arrange that the memory mapping maps the two parts of the code onto consecutive virtual memory locations.

                                 50

E.G.

   Suppose  we have two pages for a process, num_pp = 2, and we have a

code body which is $CREAPed twice, as PROC1 with stacks S1 and as PROC2 with stacks S2. If the code body is larger than one page then the second page must be shared between the stacks and some of the code.

   First the loader copies the code body into physical memory, then it

allocates the stacks for PROC1, the window for PROC1 will be (a..b,b..d).

  1. -a—————b—-c——d—-f—————

: code size : S1 : ….physical memory

  1. ————————————————–

|← 4K words –>|←- 4K words –>|

   Now  for PROC2. If pages( code size + S1 + S2 ) <= num_pp, then the

loader can allocate the stack following on directly from S1, thus the window for PROC2 will be (a..b,b..e).

  1. –a————–b—-c——d–e-f—————

: code size : S1 :S2:

  1. ————————————————–
   Note  that  PROC2's  window  allows  it to ( illegally ) access the

stack of S1. If however pages( code size + S1 + S2 ) > num_pp then the loader must take a copy of the piece of code that overlaps the second page, (copy from b..c to d..g), thus the window for PROC2 will be ( a..b,d..h ), and the copy will be mapped into the same virtual locations as the original.

  1. –a————–b—-c——d–e-fg–h————

: code size : S1 :copy :S2:

  1. —————————————————
   Now  PROC1's stack is not in the window of PROC2 so neither process

can use the other's stack.

   The  loader  will  try  and fit as many stacks as possible into the

page without copying a piece of code, but it wont try to optimize the ordering. If the code size is smaller than a page and the stacks are bigger then the whole of the code will be copied.

                                 51
3.2.8 The Logical Operation of the Loader
get process description table from task image;
construct overlay description table;
load EMMOS;
FOR each overlay
DO
    load the code;
    FOR each process
    DO
	IF name of process = name of overlay
	THEN
	    IF size of code in pages + size of stacks in pages
		<= number of pages for processes
	    THEN
		code and stacks are on completely different pages;
		set up the window for the process;
		leave room for the stacks in physical memory
	    ELSF (size of code + stacks) in pages
		= number of pages for processes
	    THEN
		one page contains stack and code;
		IF stack will fit on the end of the last
		   incarnation's window
		THEN
		    leave room for the stacks after last stack;
		    set up the process' window
		ELSE
		    make a copy of the piece of code that must
		    share a page with some stack;
		    leave room for the stacks after the copied code;
		    set up the window for the process
		FI
	    ELSE
		process is too large
	    FI
	FI
    OD
OD
                                 52
3.2.9 Error Messages
i. System Traps
     These  indicate  something  has gone drastically wrong with
the  loader,  hopefully they will only occur after modifications
have been made and not thoroughly tested. They may also occur if
the  loaded  system  goes  wrong  before  it  has  set  the trap
locations to its own trap handlers.

cpu error: pc = nnnnnn ps = mmmmmm reserved instruction: pc = nnnnnn ps = mmmmmm

     These  messages  occur  when a trap to #004 and #010 occur.
The contents of the program counter and processor staus word, as
placed on the stack by the trap, are displayed.

memory management trap 250 nnnnnn at mmmmmm

     This  message  occurs  when  something  goes wrong with the
memory  management. The contents of the two status registers are
displayed.  nnnnnn  =  SR0  which  is  further  expanded in full
following  the  error  message  ,  and mmmmmm = SR2, the virtual
address   associated  with  the  error.  Following  the  written
expansion  of  SR0,  the  contents  of  the  kernel  mode memory
management registers are displayed.

power failed, start again

     the  power  to the processor failed, re-boot the system. NB
this has not been tested!!!!
                                 53
ii. Loader Errors and Messages

test: eof

The end of the task builder image file has been reached prematurely.

test: get has failed

An error occurred in the transfer of data from the floppy disk. Try
again.

test: rewind

The loader requires the input task image file to be rewound. This
is only an error if the file is not on a rewindable device, eg down
line loading. It is caused by an error in the loader.

cant find 'task file name'

The file is not on the floppy disk, check the file name specified
in the loader is the same as you copied to the disk, and the disk
is in the correct drive ( drive 0 ). Remember that the name specified
in the loader must include any blanks necessary to pack the name to
10 chars, in the form ABCDEF.EXT

too short!

The file is less than 4 blocks long, so cannot possibly be the 
correct task image.

first dummy overlay not found

the dummy overlay that is used to pack out the buffer area is not
present in the task image. Check the file is a task image and that
the ODL file correctly specifies the dummy overlays.

mos is larger than nn pages long

the resident part of EMMOS is too large, more than the nn pages
specified in the configuration file. Make sure that the processes
have been put in as overlays and not in the resident  part.

window and segment descriptors of unequal size

The loader has lost it's place in the task image, or the ODL file
does not specify the overlays in the correct format.

ABCDEF is too large

The named overlay is too large for the configuration, even without
stacks. Check that the overlay names are seperated by commas in the ODL
file.
                                 54
Errors and Messages - cont.

process nn too large

The process with pid = nn has stack and code too large for the
configuration. Check that stack sizes have been given in words in
the $CREAP macro call.

ABCDEF has'nt been made a process

The named overlay does'nt appear in any of the $CREAPs. The overlay
is loaded but otherwise ignored, this is not a fatal error.

process nn: code duplicated

The loader was forced to make a copy of some of the process' code
because the code had already been $CREAPed and the stack and code
needs to share a page. This is not an error.

debugger has been loaded nnnnnn - mmmmmm

The overlay part of the debugger has been loaded, starting at
memory block nnnnnn and finishing in block mmmmmm. This is not an
error.

ABCDEF has incorrect base address = nnnnnn

The named overlay has been relocated at nnnnnn by the Task Builder.
This does not agree with the virtual page allocation scheme given.
Check that the ODL file is correct, that the right number of dummy
packing overlays are present and that all overlays are not too
large for the configuration.

debugger is too large

The debugger takes up more space than is allowed for processes. it
is still loaded into physical memory, but only those pages normally
allowed for processes will be paged into virtual memory, this will
almost certainly cause MUD to crash. Use the console emulator to
enter the system instead of MUD. Alternatively, remove the debugger
or allow more pages for processes. This is not a fatal error.
                                 55
Errors and Messages - cont.

the debugger cannot be used

Either the resident part or the overlay part of the debugger are
missing and hence the debugger cannot be used. If the debugger is
needed then include the missing name in the ODL file. This is not a 
fatal error.

non-unique overlay ABCDEF

The ODL file describes a configuration such that two overlays have
the same name. The loader requires all overlays to have unique
names so it can unambiguously select the code body for a process.
Try re-ordering the overlay description in the ODL file. If the
effending overlay is a common library, put it's name after the code
body, otherwise the file may need to be copied to one of a
different name.

process requires body

at least one process has specified a body name that does not
appear in the Task Image as an overlay. An indication of which
process is causing the problem can be found from the Console Load
Map (qv). Make sure the name specified in the $CREAP macro call
is that of one of the object files, check in the task map for
the names actually used. If the debugger is included, make sure
it, and all other overlays, are separated by commas in the ODL
file.
                                 56
3.2.10 Tracing
   The  loader and it's libraries are liberally scattered with code to

produce vast amounts of output, that describes in detail what is being processed. These pieces of code are normally excluded by surrounding them in comment brackets. The start of a piece of trace code is indicated by {trace and the end is followed by ecart}. To include the trace statements change {trace to {trace} and ecart} to {ecart}. This will make it easy to restore them later.

   The   output  produced  should  be  self  explanatory,  except  for

PROCEDURE waiting; which displays the message 'waiting' and waits until any character is typed in at the keyboard. All this does is slow down the output, since <cntrl-s> and<cntrl-q> are not implemented.

3.2.11 Changing The System
   The   loader   need  not  be  re-compiled  every  time  the  system

configuration is changed. This is because the configuration information is placed in the root segment of the task image file at link time.

                                 57
3.3	Process Creation
________________________
   Processes  are  created at compile time by calling the $CREAP macro

in the system configuration module. The $CREAP macro defines the attributes of a process; it's entry point, the name of the overlay that contains it's code body, the devices associated with it and the size of the CORAL and System stacks.

   Note, the name given is the name of the overlay in which the code (

and therefore the entry point ) is found. This name is that of the first module given in the overlay subtree. E.g. If we have a library module LIB and a process body module BODY1, then in the ODL file we could write:

PROCS:	.FCTR	BODY1 - LIB , .........

in which case the $CREAP call would be

$CREAP	$ENTRY , <BODY1> , ............

or alternatively

PROCS	.FCTR	LIB - BODY1 , .........

in which case the $CREAP call would need to be

$CREAP	$ENTRY , <LIB   > , ...........
   For  this reason all the overlay names must be unique, othewise the

loader would not know which code body to give a process. Now suppose we have another process body module, BODY2, which also uses LIB. We could not write:

PROCS:	.FCTR	LIB - BODY1 , LIB - BODY2 , .........

since both overlays would be called LIB. Instead write either

PROCS:	.FCTR	BODY1 - LIB , BODY2 - LIB , .........

with

$CREAP	$ENT1 , <BODY1 > , ......
$CREAP	$ENT2 , <BODY2 > , ......

or

PROCS:	.FCTR	LIB - BODY1 - BODY2 , ......

with

$CREAP	$ENT1 , <LIB   > , ......
$CREAP	$ENT2 , <LIB   > , ......

or some other combination.

   Remember  that  the name must be of exactly six characters. Pad out

with blanks if necessary.

   The stack sizes are specified using named parameters, these are the

only extra parameters of $CREAP due to the EMMOS extensions. The sizes are given in words, but are automatically rounded up into memory blocks ( 32 words ). A zero length R0 stack is acceptable for a non-CORAL process, but the R6 stack should always have some space. If the size is not specified it is set to 32 words by default. Note the stack sizes are given seperately for each process, not for each code body, so two incarnations of the same body could have different stack sizes, though it would be a bit odd.

                                 58
			APPENDICES
			__________
                                 59
APPENDIX A	Pitfalls.

A.1 Summary

   Processes  must  ensure that all communication is performed via the

global buffer pool.

   Shared  overlaid  library  procedures must not use the CORAL global

vector for their entry.

   CORAL  code  bodies  incarnated  more  than  once should not preset

variables.

   All  local  variables  must be taken from the stack, in code bodies

incarnated more than once.

   All  IORBs  and  their  data buffers must be located in permanently

resident memory (ref 2.1.4).

A.2.1	Use of local space for inter-process communication
   Obviously  processes  with  different  code bodies cannot use their

local static space ( ie not the stack ) for communication, but at first sight it would appear that different incarnations of the same body could communicate using variables in the body. Although sometimes possible, this is extremely configuration ( and even loader ) dependant. The problem arises when there is not enough room to fit all the code body ( which includes local static data ) onto process pages of it's own. The loader may need to take a copy of some of the code, which may include local data. In this case there would be more than one copy of the local data, so if two processes use it for communicating, they would actually be using two different locations.

   Therefore,   processes   must  ensure  that  all  communication  is

performed via the global buffer pool.

                                 60

A.2.2 Use of shared, overlaid CORAL libraries.

   All  CORAL  procedures,  common  variables  etc.,  must  use unique

indices to the global vector, to define themselves ( unless some trick is being performed ). This remains true even if the procedure is defined in an overlay, because there is only one global vector. If a library module containing some CORAL procedures only appears in one overlay, then the global vector will contain the virtual addresses of the entry points. However, the procedures will only be in virtual memory when a process which is an incarnation of that overlay is running, so only these processes can use the procedures.

   This  is  ok,  the problem arises when two or more overlays use the

library module. If the overlays are such that when relocated, the entry points of the procedures are at different virtual locations, the global vector mechanism wont work. The entry point recorded will probably be that for the last overlay processes by the linker, so the first overlay will crash when it tries to call the procedure.

   To  prevent this all copies of the library module must be relocated

at the same virtual locations, either by placing it on permanently resident EMMOS pages or putting it first in the overlay, though this may cause problems with the overlay naming convention. A more satisfactory solution in many ways, would be to not use the global vector for the procedure entry. The offending procedures would use genuine names for their entry points, not vector indices. This is achieved in CORAL by using a large index, outside the bounds of the global vector.

A.2.3 Use of static variables

   Any  data  space allocated in a code body, which is incarnated more

than once, will be used by more than one incarnation, unless the loader causes a copy to be made. The processes cannot then use this space for private local variables, because another may use the same physical memory for it's private local variables. Neither can it be used for shared variables, because the loader may have copied them ( see 'use of local space for inter process communication' ). Constants are ok since all incarnations will require them to remain constant. Beware of string constants though, although they really should remain constant, some unhealthy procedures actually change some parts of them. Such practices should be banned.

   In  particular,  note  that  the  local variables can be created in

devious ways, in CORAL a preset variable becomes an OWN variable by being allocated not on the stack, but in the local static space. Presetting and variables in the outermost block must be avoided for multi-incarnate processes.

Always take local variables from the stack.

                                 61
		Don't Panic !
                                 62
/data/webs/external/dokuwiki/data/pages/rfc/ien/ien136.txt · Last modified: 2001/06/25 19:09 by 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki