OK gang, how are you doing? I hope last months little session was
a good one for you. As promised, I will talk a bit more (not a byte
more!) on reentrant and reuseable code and give you a better idea how
you can easily create this type of code. Also, new monitor calls, new
instructions, and ADDRESSING will be touched upon. Put on your
thinking caps, tip a brew (this makes it easier), and put on your
waders (for obvious reasons), and let's get on with it!!
Memory Independent Code
When your computer first boots up, it looks out on disk for
AMOSL.MON and your AMOSL.INI file. These two files then create what
is called a 'resident monitor', and this process also stores some
programs into memory (so you don't always have to read the disk to
run these programs). The amount of memory that is 'gobbled up' during
this portion of the .INI depends on how big you have defined your
system. However, the computer starts to grab the memory starting at a
low memory location and moves upward from there (see last months
session if you are confused). After the line in your .INI that says
SYSTEM, the computer is then ready to distribute the remaining memory
to all of the JOBs you have defined. When you start to ATTACH the
terminals to the defined JOBs and give them memory, the computer will
grab the memory starting at the first 'free' memory location and
continue 'upwards' until the amount of memory you assigned the JOB is
allocated. This is repeated until all the JOBs are defined, and any
'left over' memory is then allocated to the boot-up JOB (called JOB 0
in this article). The key idea to grab here is that memory allocation
is 'dynamic' and somewhat 'random' in the sense that YOUR system -
which has 5 JOBS defined on it - will have different starting (or
'base') memory locations for each job than, say, HIS system - which
has 3 jobs defined, even though you both have the same amount of
memory bytes. Graphically:
...........................................................................
memory allocation from the .INI
+----------+ +----------+
| | | | Notice that the base address for
| JOB 0 | | | JOB 1 on both systems is about the
| | | JOB 0 | same in this example, but the rest
|----------| | | of the JOBs have different starting
| JOB 4 | | | locations (base addresses).
|----------| | |
| JOB 3 | |----------|
|----------| | JOB 2 |
| JOB 2 | |----------|
|----------| | JOB 1 |
| JOB 1 | | |
|----------| |----------| <--- 'base address' for JOB1
| MONITOR | | MONITOR |
+----------+ +----------+
What needs to be done when designing code is to have the code
NON memory dependent. In other words, the code should work for any
one user no matter what the value is of their 'base address'. In
order to do this, you must be careful to have all allocated variables
and storage (or anything that is temporarily 'written' to a byte)
represented as 'offsets' in your available user memory partition.
AMOS/L has a very slick way of handling this process (we will see
how it works in just a bit). Code written in this manner will not be
memory dependent and it will work for any one JOB.
Learning by Examples
Again, I cannot stress how important it is that you examine other
people's code. TONS of information can be picked up very quickly.
Let's take a close look at the following little utility called
JOBLST. JOBLST will simply print to your terminal all of the JOBs you
have defined in your AMOSL.INI. The utility itself is not much, but
the theory behind this code is very important. If you didn't read
last month's assembler article, you should do so now so you won't get
too lost looking at this program listing.
;***************************************
;* JOBLST.M68
;*
;* Scans the system and prints
;* every defined job to the screen.
;*
;* Usage: JOBLST
;*
;* by Dave Heyliger - AMUS Staff
;***************************************
OBJNAM JOBLST.LIT ;Define the final product
SEARCH SYS ;Grab all MACRO definitions
SEARCH SYSSYM
SEARCH TRM
;--- The following will define a 'version number' that will be displayed
; when you do a DIR/V JOBLST.LIT. These numbers are 'made-up' by the
; programmer, and can be changed when the code is changed.
;
VMAJOR=1. ;Major version number of the program
VMINOR=0. ;Minor version number of the program
VEDIT=100. ;the edit number of the program
;--- This small portion of the code templates a little memory space in YOUR
; memory partition for a variable I called "BUFFER".
;
.OFINI ;OFfset INItialization:
.OFDEF BUFFER,7. ;OFfset DEFinition for BUFFER
.OFSIZ IMPSIZ ;IMPSIZ is the final size of bytes (7)
;--- Start the program with a header, and also get the size of our memory
; buffer - the 7 bytes where byte "0" is the 1st byte of 7 in "BUFFER".
;
PHDR -1,0,PH$REE!PH$REU ;Program is Re-entrant & Re-useable
GETIMP IMPSIZ,A5 ;Reg. A5 now ^'s to byte "0" of BUFFER
;--- Now get the ADDRESS of the start of the JOB table and plop it into A0
;
GETTBL: MOV JOBTBL,A0 ;Get base of JOB TABLE into A0
;--- The following loop will 1) get all names from each JCB (in RAD50)
; 2) unpack the RAD50 charaters, and
; 3) print the names to the screen
;
LOOP: MOV (A0)+,A4 ;and let A4 point to each JBC
MOV A4,D0 ;this will set the flags 2 B checked
BMI EXIT ;end of the JOB TABLE on a "-1"
BEQ LOOP ;goto the top on a "0"
LEA A1,JOBNAM(A4) ;else ^ A1 to the start of a JOB name
LEA A2,BUFFER(A5) ;and ^ A2 to byte "0" of "BUFFER"
UNPACK ;Find the ASCII letters in JOBNAM
UNPACK ; ...for up to 6 characters
CLRB @A2 ;Place a "null" after the characters
LEA A2,BUFFER(A5) ;Repoint A2 to byte "0" of "BUFFER"
TTYL @A2 ;Print out all chars until a null
CRLF ;Carriage return & line feed
JMP LOOP ;Time for the next name in the table
;--- Finally, let the user know you are through
;
EXIT: TYPE <End of Job listing for this system>
CRLF
EXIT ;return to AMOS
END
Line-by-Line, Byte-by-Byte
WOW! This program has a bunch of neat stuff in it! I have placed
quite a few comments throughout this file and 'blocked' portions of
the program to show you the different steps etc., but let's take a
closer look.
OBJNAM & SEARCH
These two items were covered in last months article, so only a
brief mention here. Again, OBJect file NAMe will create the '.LIT'
filename we want, and the SEARCH will grab all of the slick monitor
calls unique to AMOS.
VMAJOR,VMINOR,VEDIT
As promised, I will go into detail on the 'Program Header' that
you, the creator of the file, has control over. The three lines in
the program (VMAJOR etc.) will define a version number of the program
you are creating. You could think of these numbers as version numbers
just like the version numbers on your operating system. If you were
to log to the SYS: account and to a DIR/V AMOSL.MON, the file name
and version number would be reported. Same thing here: after your
JOBLST.M68 file has been assembled, a DIR/V will report JOBLST.LIT
1.0(100). Where did 1.0(100) come from? From VMAJOR=1, VMINOR=0, and
VEDIT=100. Although not necessary, they can be used as a nice utility
(PHDR will further explain the Program HeaDeR).
.OFINI, .OFDEF, .OFSIZ
THIS STUFF IS IMPORTANT STUFF TO UNDERSTAND. Earlier, I made a
big hoop-la about memory independent code. Here is the heart of this
type of coding. The JOBLST program makes use of some bytes in your
memory partition when it is run (we will see where later). I have
defined an absolute OFFSET in memory and even given it a name (think
of this as basically 'templating' some bytes). This is all due to the
above three AMOS/L monitor calls. The .OFINI could be thought of as
OFfset INItialization, informing AMOS that what is to follow is a
'template' of how all of the variables used in this program are going
to be arranged when the program RUNS (sorry folks, only one variable
this month!). The .OFDEF statement is an OFfset DEFinition with the
name "BUFFER". Immediately following the defintion name is the size
in bytes - notice the "." after the number - this indicates 'byte'.
If there were more variables to be defined, more .OFDEF statements
could follow, with new names and sizes. This is a very handy way of
'variable mapping'. Finally, the .OFSIZ IMPSIZ, or in general, .OFSIZ
{name}, grabs the size of this chunk of memory you have templated.
The size of the chunk is in the {name} IMPSIZ (for IMPure SIZe). An
important note to remember is that the program will NOT PHYSICALLY
GRAB ANY MEMORY for the variable BUFFER when it is assembled. The
only thing this section does is inform AMOS of HOW the variables will
be eventually stored in memory during program execution.
PHDR -1,0,PH$REE!PH$REU
This is the Program HeaDeR macro. The PHDR will define to AMOS
how this program was constructed. It will also grab the version
number we defined earlier in the program. See the December .LOG.
GETIMP IMPSIZ,A5
A very important instruction (aren't they all?). GETIMP was
designed to GET the base address of the IMPure memory allocation (in
this case, our 'template'). This 'get' will occur during run time.
For each JOB on the system, this memory base will differ. The basic
format is GETIMP size,index. So, in this case, we already know that
IMPSIZ contains our size, and we are going to use the ADDRESS
REGISTER A5 as the INDEX TO THE BASE OF THIS TEMPLATE. A5 will
contain some address (we don't care too much what the VALUE of this
address is) that 'points' to the base of where the variables used in
this program 'live' in YOUR particular memory partition. You will see
how this is used in the main loop of this program.
GETTBL: MOV JOBTBL,A0
Remember, the purpose of this utility is to print all of the JOBS
you have defined in your .INI to the screen. The big question is,
where does AMOS keep all of these JOB definitions?? The answer is
(yes, you guessed it) the JOB CONTROL BLOCK or the JCB (there is one
for each JOB). Think of a JCB as a dynamic accounting status for each
JOB on your system. It knows when you are sleeping...it knows when
your awake (do-dado... oops, a month too late!). AND, one of the
items accessable to the programmer that is related to the JCBs is an
address value called the JOBTBL (think 'JOB TaBLe'). The JOBTBL is a
'pointer',i.e., it points to an actual physical location in memory.
This memory location is the base of list of addresses that in turn
point to each JCB (each address in this list is 4 bytes in length).
It's really not that bad. The instruction above will MOVe the
JOBTBL 'pointer' into the address register A0. So now, not only does
JOBTBL point to the beginning of the JCB list, but A0 also points to
the base of the JCB list too. The address register A0 will play an
important part in the main loop (boy, all this talk about 'the main
loop'... I bet you can't wait!).
LOOP: MOV (A0)+,A4
The main loop!!! Notice the label LOOP (a nice label for
looping). Then a MOVe. We saw a MOVe last month, but this type of
MOVe we haven't seen. This line translates to 'MOVe whatever is in
memory pointed to by register A0 into register A4, and right after
you move this value, increment the value in A0'. Now all I need to do
is tell you WHY we did this. OK, remember that A0 points to the base
of the JBCs (the list of addresses). After the MOVe, register A4 will
now contain the address of the base of a JCB (each time through the
loop, a new JCB pointer will be in A4). Register A0 will now be
pointing to the next address of the next JCB in the list. Remember,
the MOVe instruction can be of the type MOVB (move byte) MOVW (move
word), and MOVe (for move long word). Since the computer knows we are
moving a full long word (it takes 4 bytes to hold a memory address),
the computer will 'increment the value in A0' BY FOUR. Your computer
is very smart when it comes to correct incrementation (the "+"). The
four count incrementation is what leaves A0 pointing to the next
valid address.
MOV A4,D0
This MOVe will move whatever is in A4 into the DATA register D0.
The list of addresses (the JCB list) is terminated with a negitive
value. The reason for this move is to check for this condition. This
MOVe will actually set some flags in the STATUS REGISTER (see
December .LOG). The important key here is that ONLY MOVES TO DATA
REGISTERS affect the STATUS REGISTER. A simple " MOV A0,A1" will not
affect the status register. This is easy to forget but very
important.
BMI EXIT
Branch if MInus to the label 'EXIT'. The computer has a SIGN FLAG
in the status register, and if the flag is set to 'minus'(via the MOV
just executed), the program will jump to label EXIT. If it is set to
'plus', then don't jump to EXIT and just continue. Remember, the last
value in the JOBTBL list is a negitive value - this tells AMOS that
you are at the end of all of the defined JOBs. This is why we check
for a negitive result.
BEQ LOOP
Branch if EQual to label LOOP. Again the status flags are
checked. If the zero flag is set, then this jump would occur. When
would the flag be set? Well, sometimes a JOB could be defined in a
.INI and then 'wiped out', yet there are still more JOBs defined on
the system. If so, a zero will be in the register A4, and we would
like to grab the next defined JOB, all of which starts at label LOOP.
LEA A1,JOBNAM(A4)
Load Effective Address which is calculated with JOBNAM(A4) into
the register A1. Basically, inside each JCB for each job there are a
series of bytes. These bytes contain the JOBs status, the memory
size, the P,PN, the you-name-it. Every JCB is 'templated' the same
way, i.e., the very first word in each JCB is the Job Status Word.
The next one word is the Job Type, etc.,etc. Well, there is a
two-word set inside the JCB that contains the RAD50 representation of
the JOBs name, and that's the puppy we want. Now get this: since the
'template' is the same for every JCB, AMOS/L has given pre-defined
NAMES to the OFFSETS (the number of bytes from the 'base' of the JCB)
for every property in the JCB. So instead of trying to remember that
the bytes that contain the JOB's name start XYZ bytes from the start
of the JCB, we just 'point' a register to the base of the JCB (here
it is A4), and then place the pre-defined OFFSET, in this case it's
"JOBNAM", before the register that points to the base, and then
enclose the base pointer register with (). This notation has the
effect of ADDING the OFFSET "JOBNAM" to the value in (A4), and when
we load this address into register A1, A1 now 'points' to the START
of the two-word set that contains the JOBs RAD50 name (A4 is
unchanged in value...the add is a 'pseudo-add'). Pretty darn slick!
LEA A2,BUFFER(A5)
This should look familiar. It is doing the same type of operation
as the previous instruction, but this time it is adding the offset of
BUFFER (which just so happens
to be equal to zero) to the address in
register (A5) and loading this effective address into register A2.
Remember, A5 was pointing to the base of our 'memory template'.
Although this addition doesn't change the value of register A5, the
PROCECURE is worth noting. If there were, for example, three
different variables, then using LEA A2,{var-name}(A5) would produce
the correct effective address for "{var-name}" in A2.
UNPACK
UNPACK
'Unpack (unpack = RAD50 --> ASCII) the RAD50 characters pointed
to by register A1 and move each ASCII character value into the byte
pointed to by A2 WHILE automatically incrementing A2 to point to the
next byte after each character is stored AND automatically have A3
point to the next word of RAD50 data'. Some kind-a monitor call,
no??!? Check the diagram below to see why we wanted A1 to point to
the RAD50 representation of the JOB name, why A2 was pointed to the
base of the variable BUFFER, and then UNPACK executed.
Before first UNPACK (the LEA instructions)
|013573|122653| <-- the RAD50 representation of the JOB's name
^ (two words: one word contains three characters)
|
A1 = a memory location in a JCB : location JOBNAM(A4)
|xxxxxxx| <--- the 'variable' BUFFER
^ (each "x" equals one byte)
|
A2 = memory location in YOUR memory partition : BUFFER(A5).
After the first UNPACK
|012573|122653| Note now that A1 is pointing to the next word
^
|
A1
|DUMxxxx| The first three letters have been placed in the first
^ three bytes of BUFFER (JOBNAM is assumed "DUMMY")
|
A1
CLRB @A2
CLeaR the Byte in memory that register A2 is pointing to. After
the two UNPACKS, the A2 register is pointing to the seventh byte in
the 'variable' BUFFER (if the JOBs name was 6 characters in length).
We want to place a "null" in this byte (you will see why in a
moment). After this instruction, register A2 is still pointing to the
seventh byte, and this byte contains a "0".
LEA A2,BUFFER(A5)
This instruction will 'repoint' A5 to the base of the bytes
defined as BUFFER. The bytes inside of BUFFER are unchanged.
TTYL
This is a fancy AMOS/L monitor call - TeleTYpe Line. This will
type to the screen a line of ascii characters stored in consecutive
bytes that are pointed to by register A2 and end in a null byte. NOW
we see why we placed a null in the seventh byte of BUFFER and
repointed A2 to the start of this 'variable'. We did this all for the
TTYL command.
CRLF
Carriage Return Line Feed. Simulates a return on your screen.
JMP LOOP
Now JuMP back to the label loop, where we will either be at the
end of the JCB list, or we will process another JOBs name.
EXIT: TYPE <End of Job listing for this system>
CRLF
EXIT
END
This series is fairly self explainatory. The monitor call TYPE
will type ascii characters that are 'bounded' by the <>. CRLF
simulates a return to the screen. EXIT will return you to AMOS, and
END defines the end of the source.
Type this guy in (you will learn syntax while you type) and then
assemble the code and run JOBLST. See how the program output looks,
and then re-read the source code to fully understand what the program
does. Remember.. read those manuals! It's the only way you can learn
this stuff (not counting the help from the Fonz). One last note, this
file is also available on the Network. Type 'NEW' for location.