1987
In my sophomore year of college I took two interesting classes that helped “make my bones” as a programmer. One was called Assembly Language, and one was called Operating Systems. In the assembly language class we wrote a series of assignments that culminated in writing a (simplified) assembler for the VAX 11/750, in VAX 11/750 assembly language. In the operating systems class, we read Operating System Concepts, Second Edition by Peterson and Silberschatz and studied the theory behind operating systems. But we also had a “practicum” — we implemented an operating system.
Well, sort of. We weren’t Linus Torvalds writing Linux back then. We had a small lab set up with Motorola 68000 “trainer” development boards, each of which contained the same microprocessor that was in the original Apple Macintosh. We used a cross-assembler that ran on the VAX 11/750 to build 68000 assembly-language code for this board, download it, and run it. There was a little low-level “monitor” program in ROM on this board that made this possible.
Why the 68000? We and our instructor were quite interested in the 68000 at the time, as it was a very capable microprocessor, with a larger memory space and wider data bus than the earlier 6502 used in the Apple II or 8088 used in the original IBM PC. Our instructor Simon Tung was especially interested in the illegal opcode exception-handling, as this was used by Apple to implement the early Macintosh Toolbox. I vividly recall him drawing a diagram of the exception-handling logic at the gate level.
The exception-handling logic provided a fast and efficient way to trigger an exit from user code to privileged system code in ROM. This saved a lot of RAM by keeping most of the toolbox code in ROM — remember, RAM was scarce and expensive back then compared to ROM. But this mechanism worked by using a jump table stored in RAM, which allowed code on disc to patch operating system calls, both to fix bugs and to do other neat tricks. I would eventually teach how to write a variety of programs for old MacOS in C and Pascal, and that became a big part of my early career, but that’s another story.
We began with some very simple low-level functions, but the final assignment was to put them together into a “teeny-tiny operating system.” It was a program that ran a timer and read keyboard input using interrupts, launched a number of tasks, and switched periodically between tasks. So we’d press a key, and the program would start spitting out that character to the terminal at a specific rate. We’d press additional keys, and it would start more tasks to do the same thing with different characters at different rates. We could kill processes. This functionality is what an operating system does. Of course, it’s not all a modern operating system does.
I don’t recall all the details (it’s been almost 40 years, after all), but I recall that I had my program working perfectly, with beautiful timing, but I screwed something up, and I was never able to get the timing to work quite that well again. Oh well. If you spot the bug, let me know!
Note that the web version of the code is a bit ugly, because my web page template imposes a strict width restriction on preformatted text, which results in the ends of some comments getting cut off (although you can scroll right to read them). Until I can fix that, I recommend reading the PDF version of this file.
: as3.3.src;
* Original filename120 ;ALIGN NICELY
LLEN
******************************************************************3 PART 3 FOR CS 256 BY PAUL POTTS
*********** ASSIGNMENT 16 PROCESSES CALLED
*********** THIS PROGRAM IMPLEMENTS UP TO ' WHICH SIMPLY PRINT OUT A SPECIFIC CHARACTER
*********** 'USER. THE DELAYS FOR NEW PROCESSES
*********** STORED IN THE PCB1000 UNITDELAYS OF .001 SECONDS,
*********** START OUT BEING OR 1 SECOND. IN ORDER TO KEEP THE PROCESSES
*********** FROM OVERFLOWING EVEN A LARGE QUEUE THE SMALLEST DELAY
*********** 5 UNITDELAYS. THE USER CAN CHANGE THE DELAY
*********** ALLOWED MUST BE '+' TO DOUBLE IT OR '-' TO CUT IT
*********** BY STRIKING IN HALF WHILE THE PROCESS IS RUNNING. THE KEY
*********** .
*********** '#' WILL TERMINATE EXECUTION OF THE PROCESSAND GENERATING NO
*********** THE SYSTEM STARTS OUT RUNNING NO PROCESSES . PROCESSES ARE ADDED TO THE
*********** TIMER INTERRUPTS TO SWAP PROCESSES.
*********** SYSTEM BY STRIKING KEYS OTHER THAN THE ONES MENTIONED ABOVE.
*********** THESE PROCESSES WILL CONTINUE TO CYCLE UNTIL THEY ARE KILLED
*********** NOTE THAT IF THE TIME DELAY OF A PROCESS IS MADE SMALL ENOUGH
*********** IT WILL PRINT SEVERAL REPETITIONS OF ITS CHARACTER IN ITS, SINCE IT WILL CALL "DISPLAY" SEVERAL TIMES
*********** TIME SLICE.
*********** BEFORE IT IS SWAPPED OUT
******************************************************************-DEPENDENT LABELS GIVEN TO THE SPECIAL
* THE FOLLOWING ARE SYSTEM68000 BOARD
* REGISTERS OF THE
******************************************************************
4*29 ;VECTOR FOR ACIA INTERRUPT
ACIAVECTOR EQU 4*26 ;VECTOR FOR TIMER INTERRUPT
TIMERVECTOR EQU 4*10 ;VECTOR FOR A-LINE HANDLER
ALINEVECTOR EQU $10021 ;TIMER CONTROL REGISTER
TCR EQU $10023 ;TIMER INTERRUPT VECTOR
TIV EQU $10025 ;COUNTER PRELOAD AREA
CPR EQU $10040 ;CONTROL/STATUS REGISTER
ACIACS EQU $10042 ;DATA REGISTER
ACIAD EQU 15 ;THE MODE WANTED
ACIAMODE EQU 40 ;40 SPACES IN THE QUEUE
QSIZE EQU $2000 ;INITIAL SR OF EACH PROCESS
SRSTART EQU 100 ;DELAY OF PROCESS=.1 SEC.
DELAY1 EQU 125000 ;STARTING SLICE=1 SEC.
NEWSLICE EQU
******************************************************************
$1000 ;START HERE
TOP ORG JMP START ;BRANCH DOWN
DS.L 1 ;START TIMESLICE AT 10 SEC.
TIMESLICE
*************************************************************************NOT USE INTERNAL LABELS BUT JUST
**** THIS DEFINITION OF THE QUEUE DOES . THUS, QUEUE POINTS TO THE HEAD OFFSET,
**** A POINTER TO THE WHOLE QUEUE+2 POINTS TO THE TAIL OFFSET, QUEUE+4 POINTS TO THE LENGTH, AND
**** QUEUE+6 IS THE FIRST BYTE OF THE QUEUE. THE QUEUE STORAGE TAKES
**** QUEUE , ALL BUT ONE OF WHICH CAN HOLD ACTUAL
**** LENGTH OF QUEUE PLUS ONE BYTESDATA. THE EXTRA IS USED TO FIGURE OUT WHEN THE QUEUE IS FULL.
****
*************************************************************************
DS.W 1 ;THE HEAD POINTER
QUEUE .W 1 ;THE TAIL POINTER
DS.W QSIZE ;THE QUEUE LENGTH
DC.B QSIZE+1 ;THE LENGTH PLUS AN EXTRA
DS
******************************************************************, FROM ZERO TO 16
* PCOUNT HOLDS THE NUMBER OF ACTIVE PROCESSES
******************************************************************
DS.B 1 ;NUMBER OF PROCESSES
PCOUNT
******************************************************************:
* THIS IS THE STRUCTURE OF A PCB IN MEMORY
*
* storage holdsBYTE: ASCII ID CHARACTER FOR THE PROCESS
* BYTE: FOR FUTURE USE
* WORD: FOR HOLDING THE STATUS REGISTER
* : PC
* LONGWORD: D0
* LONGWORD: D1
* LONGWORD: D2
* LONGWORD
*: EACH TAKES 20 BYTES
* TOTAL PCB
*
******************************************************************
DS.B 320 ;SPACE FOR 30 PCBS
PCBTOP
DS.L 1 ;POINTER TO THE CURRENT PCB
PCBPOINTER
*******************************************************OR DISALLOW THE HANDLING OF INTERRUPTS
* CODE TO ALLOW
*******************************************************
UNBLOCKINT MACRO#$F8FF,SR ;ENABLE INTERRUPTS
ANDI
ENDM
********************************************************
BLOCKINT MACRO#$0700,SR ;DISABLE INTERRUPTS
ORI
ENDM
********************************************************DISPLAY FORCES AN A-LINE EXCEPTION TO OCCUR, AND THE
* , AHANDLER, WHEN IT OCCURS.
* SYSTEM JUMPS TO THE CODE BELOW
********************************************************
DISPLAY MACRO
.W $A000 ;A-LINE FOR DISPLAY
DC
ENDM
**************************************************************-LINE HANDLER
* THE A
* THIS ROUTINE SIMPLY COPIES THE CURRENT PROCESS ID CHARACTERINTO D7, THEN CALLS ENQ TO PUT IT IN THE QUEUE.
* , D0, D1, AND D2 ARE NOT DESTROYED, BUT
* THE REGISTERS A6USED IN THE QUEUE ROUTINE.
* OTHERS ARE
**************************************************************
AHANDLER BLOCKINT.L PCBPOINTER,A5 ;ADDRESS POINTED TO
MOVEA.B (A5),D7 ;PUT ID CHAR INTO D7
MOVE;ENQUEUE THE BYTE
JSR ENQ ADD.L #2,$2(A7) ;UPDATE OLD PC
RTE
***************************************************************: INITALINE
* SUBROUTINE-LINE EMULATOR.
* THIS ROUTINE SIMPLY DOES INITIALIZATION OF THE A
***************************************************************
.L #AHANDLER,ALINEVECTOR ;A-LINE VECTOR
INITALINE MOVE
RTS
***********************************************************************: INITPCBS
* SUBROUTINE.
* THIS ROUTINE SETS UP THE INITIAL VALUES OF THE THREE PCBS IN MEMORY
* THIS IS WRITTEN AS CODE INSTEAD OF SIMPLY FILLING MEMORY WITH.W OR DC.L SO THAT THE PROGRAM CAN BE RESTARTED WITHOUT LOSING
* DC.
* THE INITIAL CONTENTS OF THE PCBS
*USED: A0 AND D4 ONLY. THE ORIGINAL VALUES ARE RESTORED
* REGISTERS
***********************************************************************
.L D4/A0,-(A7) ;PUSH REGISTERS
INITPCBS MOVEM.L #PCBTOP,A0 ;HOLD TEMPORARILY
MOVEA.L A0,PCBPOINTER ;SET UP INITIAL VALUE
MOVE.B #0,PCOUNT ;NUMBER OF PROCESSES=0
MOVE.L #15,D4 ;SET UP LOOP COUNTER
MOVE
.B #0,(A0) ;SET ID/OUTPUT CHAR TO NULL
COUNTLOOP MOVEADD.L #2,A0 ;POINT TO SR
.W #SRSTART,(A0) ;COPY INTO PCB
MOVEADD.L #2,A0 ;POINT TO FIRST PC
.L #WHILELOOP,(A0) ;COPY INTO PCB
MOVEADD.L #16,A0 ;POINT TO NEXT PCB
,COUNTLOOP ;BUILD NEXT PCB
DBEQ D4.L (A7)+,D4/A0 ;POP REGISTERS
MOVEM
RTS
***********************************************************************MOD: IT ACCEPTS THE OFFSET STORED IN A WORD
****** THIS ROUTINE IS 1..QSIZE + 1) AND RETURNS THE REAL MEMORY ADDRESS TO PUT
****** (OF OR GET THE DATA BYTE. IT SERVES TO WRAP AROUND THE QUEUE SO
****** 'T POSSIBLE TO WALK OFF THE BOTTOM OF THE DATA
****** THAT IT ISN. IF THE POINTER
****** STRUCTURE BY INCREMENTING YOUR OFFSETS TOO MUCH(MEANING THAT IT CONTAINS QSIZE+1) THE MACRO
****** IS TOO LARGE , OR THE BEGINNING OF THE QUEUE.
****** RESETS IT TO ZERO
***********************************************************************word offset
* The first parameter is the byte is or should go
* The second parameter is the real address of where the
MOD MACRO.L D1-D2/A4,-(A7) ;PUSH STUFF ON
MOVEM.L A6,A4 ;START AT TOP OF QUEUE
MOVEA.W \1,D1 ;HOLD THE VALUE OF THE OFFSET
MOVE.W #0,\1 ;SET THE OLD OFFSET FOR WRAPAROUND
MOVEADD.L #6,A4 ;POINT TO QUEUE DATA
.L A4,\2 ;POINT TO THE START OF QUEUE DATA
MOVECMP.W #QSIZE,D1 ;IS IT WITHIN 1..QSIZE?
\@ ;SHOULD WE WRAP THE BYTE AROUND?
BGT .W D1,\1 ;RESTORE THE VALUE OF THE POINTER
MOVEADD.W \1,\2 ;OR INCLUDE OFFSET
. THE POINTER
* THIS CASE IS EXECUTED IF THE QUEUE HAS WRAPPED AROUNDIN \1 IS THEN LEFT AT ZERO (AS IT WAS SET ABOVE) AND THE ADDRESS IS
* (AS IT WAS SET IN A4.)
* LEFT AT THE START OF THE QUEUE DATA
.L (A7)+,D1-D2/A4 ;POP STUFF OFF
\@ MOVEM
ENDM
****************************************************************
*.001 SECONDS BY
* THE PURPOSE OF THIS MACRO IS TO CONSUME 280 TIMES. THE PASCAL EQUIVALENT CODE IS:
* LOOPING
*AND THUS DESTROYS ITS CONTENTS
* IT USES REGISTER D2 TO COUNT DOWN
*;
* PROCEDURE UNITDELAY: INTEGER;
* VAR X
* BEGIN:=280 DOWNTO 1 DO {NOTHING};
* FOR XEND;
*
*
***************************************************************
UNITDELAY MACRO.L #280,D2 ;COUNT DOWN FROM 280
MOVE,\@ ;LOOP IF NOT DONE
\@ DBEQ D2
ENDM
****************************************************************'S T BY TWO. IT CAN AVOID THE
* THIS MACRO MULTIPLIES THE USERWORD MULTIPLICATION BY SHIFTING BY ONE BIT
* LIMITS OF . THE USER'S T IS PASSED IN D0. THIS MACRO ALSO
* INSTEAD
* ALLOWS YOU TO INCREASE YOUR TIME DELAY EVEN IF DIVIDE_T, SO THAT YOU CAN'T BECOME "STUCK."
* HAS SET IT TO ZERO.
* IT AFFECTS NO OTHER REGISTERS
****************************************************************
MULTIPLY_T MACRO.L #1,D0 ;MULTIPLY D0 BY 2
ASL
ENDM
******************************************************************
* THIS IS WRITTEN AS A MACRO TO AVOID PROBLEMS WITH THE REMAINDERIN THE UPPER WORD OF D0 AND TO SIMPLIFY THE MAIN PROCEDURE.
* 'S DELAY BY TWO.
* THE MACRO SIMPLY DIVIDES THE USER(THE USERS'S DELAY)
* IT USES NO OTHER REGISTERS THAN D0
******************************************************************
DIVIDE_T MACRO.L #1,D0 ;DIVIDE D0 BY 2
ASRCMP.L #5,D0 ;IS DELAY TOO SMALL?
\@ ;IF NOT, SKIP DOWN
BGT .L #5,D0 ;SET IT TO FIVE.
MOVENOP ;FINISH MACRO
\@
ENDM
***********************************************************************
****** THIS SUBROUTINE SETS UP THE TIMER TO BEGIN GENERATING(TIMESLICE) CYCLES--WHEN AN INTERRUPT
****** INTERRUPTS EVERY 15
****** OCCURS IT WILL TRAP TO THE CONTENTS OF VECTOR .
****** TIMESLICE CAN BE ALTERED BY THE PERSON RUNNING THE PROGRAM: THIS IS DONE
* NOTE THAT TIMERINIT NO LONGER ENABLES THE TIMERNOT OCCUR
* WHEN PROCESSES ARE ADDED SO THAT TIMER INTERRUPTS DO .
* WHEN NO PROCESSES ARE RUNNING IN THE SYSTEM.
* IT CHANGES NO REGISTERS PERMANENTLY
***********************************************************************
.L A0/D0,-(A7) ;PUSH
TIMERINIT MOVEM.L #NEWSLICE,TIMESLICE ;INITIAL TIMESLICE
MOVE.L TIMESLICE,D0 ;NUMBER OF CYCLES
MOVE.L #CPR,A0 ;COUNTER PRELOAD REGISTERS
MOVEA.L D0,0(A0) ;HAVE TO USE THIS INSTRUCTION
MOVEP.B #26,TIV ;AUTOVECTOR NUMBER
MOVE.L #MULTIPLEXER,TIMERVECTOR ;VECTOR FOR TIMER
MOVE.L (A7)+,A0/D0 ;POP
MOVEM;RETURN
RTS
***********************************************************************AND SETS UP THE VECTOR
****** THIS SUBROUTINE INITIALIZES THE ACIA1
****** SO THAT WHEN AN INTERRUPT OCCURS THE EXCEPTION HANDLER ACIAHANDLER(FROM THE SCREEN),
****** WILL BE CALLED TO DETERMINE WHETHER IT IS RECEIVE (FROM THE KEYBOARD) OR OVERRUN THAT IS CAUSING THE INTERRUPT.
****** TRANSMIT 29TH
****** WHEN AN INTERRUPT OCCURS FROM THE ACIA IT TRAPS THROUGH THE . IT CHANGES NO REGISTERS PERMANENTLY.
****** VECTOR OF THE TABLE
***********************************************************************
.L D4,-(A7) ;PUSH
INITACIA MOVE.B #3,ACIACS ;RESET ACIA
MOVE.W #$1000,D4 ;TIMING CONSTANT
MOVEWAIT DBRA D4,WAIT ;PAUSE
.B #ACIAMODE,ACIACS ;SET ACIA MODE
MOVE.L #ACIAHANDLER,ACIAVECTOR ;TRAP THROUGH VECTOR 29
MOVE.L (A7)+,D4 ;POP
MOVE
RTS
***********************************************************************
****** THIS PROCEDURE FIGURES OUT WHAT KIND OF ACIA INTERRUPT HAS OCCUREDAND CALLS WHATEVER IT HAS TO TO HANDLE THE SITUATION. THE THREE
****** (FROM THE SCREEN READY
****** POSSIBLE SITUATIONS ARE RECEIVE INTERRUPT ), TRANSMIT INTERRUPT (WHEN A KEY HAS BEEN
****** FOR ANOTHER CHARACTER), OR OVERRUN. IN PRACTICE THIS ROUTINE
****** PRESSED ON THE KEYBOARD.
****** NEVER CALLS THE OVERFLOW"-" WILL DIVIDE THE DELAY VALUE BY 2, A "+" WILL MULTIPLY IT BY 2
* A "#" WILL SIGNIFY THAT THE PROCESS IS NOW DEAD. THE PROCESS WILL RESET
* A 'T HAVE TO WAIT FOR ITS TIMESLICE TO EXPIRE.
* THE TIMER SO THAT IT DOESN, WHEN YOU KILL A PROCESS THE NEXT ONE IS SWAPPED IN WITHIN A FEW
* THUS. I GIVE THE TIMER 25 CYCLES BEFORE THE NEXT INTERRUPT HITS SO
* CYCLESAND
* THAT IT GIVES ACIAHANDLER A CHANCE TO FINISH EXCEPTION PROCESSING .
* RETURN TO THE DELAY LOOP BEFORE THE NEXT PROCESS IS SWAPPED IN, D5, AND D6 ARE DESTROYED AND NOT RESTORED.
* THE REGISTERS A5
***********************************************************************
ACIAHANDLER BLOCKINT
**********************************************************************
* THE FIRST PART OF THE CODE FIGURES OUT FROM THE STATUS BITS OF THE, AND JUMPS ACCORDINGLY.
* ACIA WHAT KIND OF INTERRUPT HAS OCCURED
**********************************************************************
.B #0,ACIACS ;IS IT FROM THE KEYBOARD?
BTST;IF SO, RECEIVE A KEY PRESS
BNE RECEIVE .B #1,ACIACS ;IS THE SCREEN READY FOR MORE?
BTST;IF SO, BRANCH TO SEND
BNE SEND JMP ENDHANDLER ;RETURN IF SPURIOUS INT.
*************************************************************************: STRUCTURALLY, IT
* THIS CODE HANDLES AN INTERRUPT FROM THE KEYBOARD-THEN STATEMENTS. IT IS WRITTEN AS ONE LONG
* IS A WHOLE SERIES OF IFAND TO
* ROUTINE RATHER THAN SUBROUTINES TO SPEED UP EXECUTION TIME .
* PREVENT THE STACK FROM BEING FILLED WITH SUBROUTINE CALLS
*************************************************************************
.L PCBPOINTER,A5 ;HOLD ADDRESS OF POINTER
RECEIVE MOVEA.B ACIAD,D6 ;STORE IT IN D4
MOVECMP.B #'+',D6 ;IF (KEY='+') THEN T:=T*2
;ELSE
BNE NOT_MULTIPLY CMP.B #0,(A5) ;IS IT A NULL PROCESS?
;IF SO, DON'T MULTIPLY
BEQ ENDHANDLER
CALL THE MACRO TO DOUBLE THE USERS DELAY *
*
MULTIPLY_T
JMP ENDHANDLER ;CONTINUE WHILE LOOP
.B #'-',D6 ;IF (KEY='-') THEN DIVIDE T/2
NOT_MULTIPLY CMP;ELSE
BNE NOT_DIVIDE CMP.B #0,(A5) ;IS IT A NULL PROCESS?
;IF SO, DON'T DIVIDE
BEQ ENDHANDLER
DIVIDE_T
CALL THE MACRO DO CUT THE USERS DELAY TIME IN HALF *
*
JMP ENDHANDLER
**************************************************************. TO BE "KILLED"
* THIS CODE HANDLES THE DEATH OF A PROCESSAND PCOUNT REDUCED
* A PROCESS SIMPLY HAS ITS ID CHAR SET TO NULL , AND THEN FORCE A CONTEXT SWITCH IN 25 CYCLES.
* BY ONE, WE DON'T
* NOTE THAT IF THERE ARE NO PROCESSES REMAINING, SO WE LOAD THE STACK WITH THE
* WANT TO DO ANY MORE SWAPPINGAND WHEN THE PROGRAM HITS AN
* ADDRESS OF THE WAIT_ON_P LOOP .
* RTE THE LOOP IS JUMPED BACK TO
**************************************************************
.B #'#',D6 ;IS PROCESS KILLED?
NOT_DIVIDE CMP;OR CHAR IS SOMETHING ELSE
BNE CALL_ADDP CMP.B #0,(A5) ;ONLY KILL A LIVE PROCESS
;NOT A DEAD ONE
BEQ ENDHANDLER .L PCBPOINTER,A5 ;ADDRESS POINTED TO
MOVEA.B #0,(A5) ;MARK PROCESS FOR DEATH
MOVESUB.B #1,PCOUNT ;DECREASE NUMBER OF PROCESSES
CMP.B #0,PCOUNT ;ARE THERE ANY LEFT?
;IF SO, SWAP IN NEXT ONE
BNE SWAP_IN_NEXT .B #0,TCR ;TURN OFF TIMER
MOVE.W (A7)+,D5 ;POP OFF SR
MOVE.L (A7)+,A5 ;POP OFF PC
MOVE.L #WAIT_ON_P,A5 ;LOAD WITH ADDRESS OF WAITLOOP
MOVE.L A5,-(A7) ;PUSH ALTERED PC
MOVE.W A5,-(A7) ;PUSH ALTERED SR
MOVEJMP ENDHANDLER ;RETURN
.B #0,TCR ;TURN OFF TIMER
SWAP_IN_NEXT MOVE.L #25,D5 ;NUMBER OF CYCLES
MOVE.L #CPR,A0 ;COUNTER PRELOAD REGISTERS
MOVEA.L D5,0(A0) ;HAVE TO USE THIS INSTRUCTION
MOVEP.B #$A1,TCR ;RESTART TIMER
MOVEJMP ENDHANDLER ;RETURN
******************************************************************AND THEN FORCES
* THIS CODE CALLS ADDPROCESS TO ADD A NEW PROCESS 25 CYCLES. ADDPROCESS ITSELF TAKES CARE
* A CONTEXT SWITCH WITHIN .
* OF UPDATING THE PCOUNT IN CASE NO PROCESSES CAN BE ADDED
******************************************************************
;ADD A PROCESS WITH KEY=ID
CALL_ADDP JSR ADDPROCESS .B #0,TCR ;TURN OFF TIMER
MOVE.L #25,D5 ;NUMBER OF CYCLES
MOVE.L #CPR,A0 ;COUNTER PRELOAD REGISTERS
MOVEA.L D5,0(A0) ;HAVE TO USE THIS INSTRUCTION
MOVEP.B #$A1,TCR ;RESTART TIMER
MOVE
ENDHANDLER UNBLOCKINT
RTE
****************************************************************
* THIS CODE HANDLES AN INTERRUPT FROM THE SCREEN READY TO RECEIVE. IT CALLS DEQ TO REMOVE A BYTE OF TEXT FROM
* MORE INFORMATIONAND THEN SENDS IT DIRECTLY TO THE ACIA DATA REGISTER.
* THE QUEUE
****************************************************************
;RETURN BYTE IN D7
SEND JSR DEQ .B D7,ACIAD ;SEND BYTE TO SCREEN
MOVE
UNBLOCKINT
RTE
**************************************************************************.
****** THIS SUBROUTINE INITIALIZES THE QUEUE WHICH WILL HOLD LENGTH BYTESBYTE BIGGER THAN THE MAXIMUM NUMBER
****** NOTE THAT THE QUEUE MUST BE ONE , SO THAT YOU CAN TELL WHEN THE QUEUE IS FULL.
****** OF BYTES IT CAN HOLD
**************************************************************************
.L #QUEUE,A6 ;A6 POINTS TO THE QUEUE STRUCTURE
INITQ MOVEA.L A6,-(A7) ;PUSH POINTER
MOVE.W #0,(A6) ;SET HEAD TO ZERO
MOVEADD.L #2,A6 ;POINT TO TAIL OFFSET
.W #0,(A6) ;THE TAIL=HEAD=0 FOR NEW EMPTY QUEUE
MOVE.L (A7)+,A6 ;POP THE QUEUE POINTER
MOVE;RETURN
RTS
**************************************************************************BYTE INTO THE QUEUE
****** THIS SUBROUTINES PUTS A , IT INCREMENTS TAIL AND MODS IT TO FIGURE OUT WHERE THE
****** FIRSTBYTE SHOULD GO. THEN, IT COMPARES THE HEAD AND TAIL TO SEE IF
****** OR NOT. THEN, IT USES A BASE ADDRESS
****** THE QUEUE HAS OVERFLOWED AND THE OFFSET TO CALCULATE WHERE IN MEMORY TO PUT THE BYTE
****** AND FINALLY, THE BYTE GOES IN THE QUEUE!
****** , THE RECIEVE INTERRUPT IS FROM THE ACIA1 IS TURNED ON SO
****** THEN.
****** THE TERMINAL CAN REQUEST CHARACTERS TO BE SENT TO IT,A2,AND A3 ARE DESTROYED.
****** THE CONTENTS OF THE ADDRESS REGISTERS A1
**************************************************************************
.L A6,A1 ;COPY POINTER
ENQ MOVEA(A1),A2 ;FIND REAL ADDRESS OF HEAD
MOD ADD.L #2,A1 ;POINT TO TAIL OFFSET
ADD.W #1,(A1) ;INCREMENT OFFSET
(A1),A3 ;GET THE REAL ADDRESS OF TAIL
MOD .L A2,A3 ;HAS HEAD CRASHED INTO TAIL?
CMPA;EXIT IF IT HAPPENS
BEQ SHUTDOWN .B D7,(A3) ;PUT THE BYTE IN THE QUEUE
MOVE.B #$B5,ACIACS ;TURN ON TRANSMIT (SCREEN) INTERRUPTS
MOVE;RETURN TO MAIN LOOP
RTS
**************************************************************************BYTE OUT OF THE QUEUE. HEAD IS INCREMENTED
****** THIS SUBROUTINE PULLS A AND THE CHARACTER IS RETURNED AS THE PARAMETER. IF THE QUEUE IS
****** , THE INTERRUPTS FROM THE TERMINAL ARE TURNED OFF
****** THEN EMPTYNOT REQUEST ANY MORE CHARACTERS.
****** SO THAT IT DOES USED SO THAT THE INCREMENTS ARE WRAPPED AROUND.
****** MOD IS , A2, AND A3 ARE DESTROYED.
****** THE CONTENTS OF THE ADDRESS REGISTERS A1
**************************************************************************
.L A6,A1 ;COPY POINTER
DEQ MOVEAADD.W #1,(A1) ;INCREMENT HEAD
(A1),A2 ;CALCULATE THE HEAD ADDRESS TO USE
MOD .B (A2),D7 ;GET THE BYTE FROM THE HEAD OF QUEUE
MOVEADD.L #2,A1 ;POINT TO TAIL OFFSET
(A1),A3 ;CALCULATE TAIL ADDRESS TO COMPARE
MOD .L A2,A3 ;IS THE QUEUE EMPTY? (HEAD=TAIL)
CMPA;IF SO, TURN OFF ACIA INTERRUPTS
BNE QNOTEMPTY .B #$95,ACIACS ;TURN OFF TRANSMIT (SCREEN) INTERRUPTS
MOVE;RETURN
QNOTEMPTY RTS
******************************************************************. THE STACK IS
* THE MULTIPLEXER RUNS WHEN A TIMER INTERRUPT OCCURSAND SP FROM THE USER PROCESS WHICH WAS
* USED TO PLACE THE OLD SR , AND THEN A7,D0,AND D1 ARE STORED. NOTE THAT THE
* INTERRUPTEDSTACK POINTER IS STORED AS IT WAS WHEN THE INTERRUPT HANDLER
* , SO THAT THE CONTEXT IS RESTORED PROPERLY BY
* BEGAN EXECUTING. THE CIRCULAR DATA STRUCTURE
* THE RTE AT THE END OF THE HANDLER.
* IS WALKED THROUGH UNTIL THE NEXT ACTIVE PROCESS IS FOUND
* NOTE THAT THIS MULTIPLEXER SHOULD NEVER BE CALLED IF THERE, BECAUSE TIMER INTERRUPTS WOULD BE
* ARE NO ACTIVE PROCESSES.
* SHUT DOWN
*****************************************************************
MULTIPLEXER BLOCKINT.B #$0,TCR ;DISABLE TIMER
MOVE.L PCBPOINTER,A3 ;LOAD ADDRESS HELD THERE
MOVEA.L A3,A5 ;COPY TO TEMPORARY POINTER
MOVEAADD.L #2,A3 ;MOVE TO STORAGE OF SR
.W (A7)+,(A3) ;POP SR INTO PCB
MOVEADD.L #2,A3 ;POINT TO PC
.L (A7)+,(A3) ;POP OLD PC IN
MOVEADD.L #4,A3 ;POINT TO STORAGE OF D0
.L D0,(A3) ;COPY D0 INTO PCB
MOVEADD.L #4,A3 ;POINT TO STORAGE OF D1
.L D1,(A3) ;COPY D1 INTO PCB
MOVEADD.L #4,A3 ;POINT TO STORAGE OF D2
.L D2,(A3) ;COPY D2 INTO PCB
MOVEADD.L #4,A3 ;POINT TO NEXT PCB
*********************************************************************
* THIS PORTION OF THE CODE WALKS THROUGH THE PCBS TO FIND THE NEXT(CIRCULARLY) WHICH IS ACTIVE AND RESTORES IT.
* ONE
************************************************************************
.L #PCBPOINTER,A4 ;COPY POINTER'S ADDRESS
LOOK MOVEA.L A3,A4 ;HAVE WE HIT BOTTOM?
CMPA;IF NOT, TEST FOR ACTIVE
BNE IS_IT_ACTIVE .L #PCBTOP,A3 ;IF SO, WRAPAROUND
MOVEA.B #0,(A3) ;IF NOT,
IS_IT_ACTIVE CMP;KEEP LOOKING
BEQ KEEP_LOOKING JMP RESTORECONTEXT ;OR ELSE SWAP IT IN
.L #20,A3 ;POINT TO NEXT PROCESS
KEEP_LOOKING ADDJMP LOOK ;LOOP AROUND
***********************************************************
* THIS CODE RESTORES THE CONTEXT OF A PROCESS FROM THE PCB
***********************************************************
.L A3,PCBPOINTER ;UPDATE POINTER
RESTORECONTEXT MOVEADD.L #4,A3 ;POINT TO STORAGE OF PC
.L (A3),-(A7) ;PUSH OLD PC
MOVESUB.L #2,A3 ;POINT TO STORED SR
.W (A3),-(A7) ;PUSH OLD SR
MOVEADD.L #6,A3 ;POINT TO STORAGE OF D0
.L (A3),D0 ;RESTORE D0
MOVEADD.L #4,A3 ;POINT TO STORAGE OF D1
.L (A3),D1 ;RESTORE D1
MOVEADD.L #4,A3 ;POINT TO STORAGE OF D2
.L (A3),D2 ;RESTORE D2
MOVE
*********************************************************************AND RETURNS CONTROL TO THE NEXT USER PROCESS
* THIS CODE FINISHES UP
*********************************************************************
.L TIMESLICE,D4 ;NUMBER OF CYCLES
CONTINUE MOVE.L #CPR,A5 ;COUNTER PRELOAD REGISTERS
MOVEA.L D4,0(A5) ;HAVE TO USE THIS INSTRUCTION
MOVEP.B #$A1,TCR ;ENABLE TIMER
MOVE
UNBLOCKINT;CONTINUE EXECUTION OF USER PROCESS
RTE
**********************************************************************ADD PROCESS ADDS A PROCESS TO THE LIST ****************
************** +,-, OR # IS PRESSED.
* THIS SUBROUTINE IS CALLED WHEN A KEY BESIDES THE OR
* IT WALKS THROUGH THE LIST OF PCBS UNTIL IT FINDS A NULL PROCESS . IF IT FINDS A
* HAS WALKED ALL THE WAY AROUND WITHOUT FINDING ONE
* NULL PROCESS THE CHARACTER OF THE KEY PRESSED IS MADE THE ID CHAR OFAND THE PROCESS IS INSERTED INTO THE PCB LIST.
* A NEW PROCESS
**********************************************************************
.L PCBPOINTER,A3 ;LOAD ADDRESS
ADDPROCESS MOVEA.L #PCBPOINTER,A4 ;MEMORY LOCATION
MOVEA.L A3,A5 ;COPY TO TEMPORARY POINTER
MOVEA.L A3,A4 ;HAVE WE HIT BOTTOM
WRAP CMPA;IF NOT CHECK FOR ALL FULL
BNE IS_IT_USED .L #PCBTOP,A3 ;OR ELSE WRAPAROUND
MOVEA.B #0,(A3) ;IS PROCESS USED?
IS_IT_USED CMP;IF NOT, ADD HERE
BEQ ADDIT .L #20,A3 ;POINT TO NEXT PCB
ARE_ALL_USED ADD.L A3,A5 ;HAVE WE GONE AROUND?
CMPA;IF NOT, CONTINUE
BNE PCB_LOOP ;OR QUIT WITHOUT ADDING
RTS ;CONTINUE SEARCH
PCB_LOOP JMP WRAP .B D6,(A3) ;SET PROCESS ID
ADDIT MOVEADD.B #1,PCOUNT ;PCOUNT:=PCOUNT+1
ADD.L #8,A3 ;POINT TO DELAY IN PCB
.L #DELAY1,(A3) ;STORE INITIAL DELAY=1 SEC.
MOVECMP.B #1,PCOUNT ;IS THIS THE FIRST PROCESS
;IF NOT, DON'T SET D0
BNE DOWNHERE .L #DELAY1,D0 ;SET UP D0 FOR FIRST DELAY
MOVE;RETURN TO ACIAHANDLER
DOWNHERE RTS
**********************************************************************
* MAIN PROGRAM, THEN THE QUEUE, THEN THE ACIA,
* THIS FIRST SETS UP THE PCBPOINTER, THEN UNBLOCKS THE INTERRUPTS AND ENTERS
* THEN THE TIMER.
* THE LOOP TO WAIT FOR THE FIRST PROCESS TO BE INVOKED BY ADDPROCESS
**********************************************************************
.L #$7000,A7 ;SET STACK POINTER TO $7000
START MOVEA;SET UP THE I/O QUEUE
JSR INITQ ;SET UP THE 16 PCBS IN RAM
JSR INITPCBS ;SET UP THE A-LINE EMULATOR
JSR INITALINE ;START TIMER INTERRUPTS
JSR TIMERINIT ;SET UP THE ACIA
JSR INITACIA
.B #$95,ACIACS ;TURN ON RECEIVE (KEY) INTERRUPTS
MOVE
UNBLOCKINT
***********************************************************************
.B #0,PCOUNT ;ARE THERE NO PROCESSES?
WAIT_ON_P CMP;IF SO, LOOP HERE
BEQ WAIT_ON_P
***********************************************************************: IT CONSISTS OF THE DISPLAY MACRO (WHICH IS
* THIS IS THE SHARED CODE) AND THE CODE FOR THE DELAY PROCEDURE. THE REGISTERS
* A SYSTEM CALL, D1 AND D2 FOR THE CONSTANT DELAY,
* USED BY THE MAIN PROCEDURE ARE D0, AND UNITDELAY TIMER. THESE REGISTERS ARE
* OUTER LOOP DELAY TIMERAND OUT.
* STORED WHEN THE PROCESS IS SWAPPED IN
***********************************************************************
;CALL THE A-LINE HANDLER TO DISPLAY
WHILELOOP DISPLAY
********************************************************************. I AM LEAVING IT IN THE MAIN PROCEDURE
* THIS IS THE CODE FOR DELAY'T HAVE SUBROUTINE CALLS ON THE
* SO THAT THE CONTEXT SWITCHER WONSTACK TO CONTEND WITH, AND SO EACH PROCESS CAN RESUME PROPERLY.
* .
* SEPARATE STACKS WOULD BE EASIER WITH A MEMORY MANAGEMENT UNIT
********************************************************************
.L D0,D1 ;COPY T TO LOCAL
MOVE;CALL UNITDELAY
DELOOP UNITDELAY ,DELOOP ;REPEAT T TIMES
DBEQ D1JMP WHILELOOP ;GO BACK TO DISPLAY: REPEAT
******************************************************************.
* SHUTDOWN IS CALLED WHEN THE QUEUE OVERFLOWS
******************************************************************
.B #$0,TCR ;DISABLE TIMER
SHUTDOWN MOVE.B #229,D7 ;RETURN CONTROL
MOVE#14 ;TO TUTOR
TRAP END TOP ;END OF THE PROGRAM