Linux中的load average相关代码和测试过程

1. 1969年开始的TENEX操作系统中的load average相关源代码

https://github.com/PDP-10/tenex/blob/e788ef5bcdadfc0580e5df5d9af20d6f4e736902/134-tenex/SCHED.MAC
SCHED.MAC
这是使用LISP语言编写的,LISP语言起源于1958年,是现今第二悠久且仍广泛使用的高级编程语言,只有FORTRAN编程语言比它更早一年,所以看不懂说明你是一个正常人。

438行:NRJAVS==3 ;NUMBER OF LOAD AVERAGES WE MAINTAIN

;<135-TENEX>SCHED.MAC;343    17-DEC-75 12:12:00    EDIT BY ALLEN
; PROTECT AGAINST RE-ENTERING SCHEDULER BY ACCIDENT
;<ALLEN>SCHED.MAC;5     4-NOV-75 11:45:51    EDIT BY ALLEN
; FIX ERROR IN ASSFK LEADING TO NCP BUFFER SCREWUPS
;<134-TENEX>SCHED.MAC;339    30-SEP-75 10:29:30    EDIT BY ALLEN
;<134-TENEX>SCHED.MAC;338    26-SEP-75 16:27:31    EDIT BY ALLEN
; DO SIMPLE INSERT ON PLACING FORK ON COMPQ, RATHER THAN RESORTING
;<134-TENEX>SCHED.MAC;337    14-AUG-75 16:34:29    EDIT BY ALLEN
; AVOID LOCKUP IF BSYS OR SOME SUCH USER CODE UNLOCKS A DIRECTORY
; OR SUB-INDEX LOCK ON WHICH A CONFLICT HAS OCCURRED
;<134-TENEX>SCHED.MAC;336     5-AUG-75 11:38:26    EDIT BY CLEMENTS
; CHANGE TAGS P1 AND P2 TO BE PATCH1, PATCH2, TO AVOID CONFLICT WITH
; NEW ACCUMULATOR NAMES
;<134-TENEX>SCHED.MAC;335    12-JUN-75 15:18:53    EDIT BY ALLEN
; MINOR FIXES
;<134-TENEX>NSCHED.MAC;21    11-JUN-75 10:13:27    EDIT BY ALLEN
; FIX TO RESORT
;<134-TENEX>NSCHED.MAC;20    10-JUN-75 13:11:13    EDIT BY ALLEN
; LIST ROUTINES ARE NOW SUBROUTINES AGAIN INSTEAD OF MACROS
; FIX NECESSITY TO RESKED ON RELHIQ
;<134-TENEX>SCHED.MAC;334     4-JUN-75 13:09:12    EDIT BY ALLEN
; MORE FIXES TO NEW REGULATOR
;<134-TENEX>SCHED.MAC;332     3-JUN-75 15:59:55    EDIT BY ALLEN
; VARIOUS FIXES TO NEW REGULATOR
;<134-TENEX>NSCHED.MAC;13     3-JUN-75 11:27:03    EDIT BY ALLEN
; MINOR FIXES TO SPECIAL SCHEDULING FOR LOCKERS TO AVOID RESPONSE
; PROBLEMS AFTER CLEARING LOCK
;<134-TENEX>NSCHED.MAC;10    20-MAY-75 21:36:47    EDIT BY ALLEN
;<134-TENEX>NSCHED.MAC;9    20-MAY-75 16:49:14    EDIT BY ALLEN
; PARAMETERIZE UTILIZATION AVERAGE TIME CONSTANT
;<134-TENEX>NSCHED.MAC;8    20-MAY-75 16:23:57    EDIT BY ALLEN
; SHORTEN UTILIZATION AVERAGE TIME CONSTANT TO ABOUT
; 4 SECONDS
;<134-TENEX>NSCHED.MAC;7    20-MAY-75 08:26:17    EDIT BY ALLEN
;<134-TENEX>NSCHED.MAC;6    19-MAY-75 17:24:28    EDIT BY ALLEN
; REVISED PIE-SLICE REGULATOR
;<134-TENEX>SCHED.MAC;331    15-MAY-75 08:24:44    EDIT BY TOMLINSON
;<134-TENEX>SCHED.MAC;330    15-MAY-75 08:17:05    EDIT BY TOMLINSON
;<134-TENEX>SCHED.MAC;329    15-MAY-75 08:06:02    EDIT BY TOMLINSON
; ADDED CALL TO TNTCHK IN SCHEDULER
;<134-TENEX>SCHED.MAC;328     2-MAY-75 18:14:13    EDIT BY CLEMENTS
; INTERNED TTPSR1. XLISTED JTDVC1.
;<134-TENEX>SCHED.MAC;327    28-APR-75 12:10:32    EDIT BY CLEMENTS
;<134-TENEX>SCHED.MAC;326    14-APR-75 13:40:38    EDIT BY ALLEN
;<134-TENEX>SCHED.MAC;325    14-APR-75 13:28:24    EDIT BY ALLEN
;<134-TENEX>SCHED.MAC;324    11-APR-75 18:28:29    EDIT BY ALLEN
; FIXES TO NEW SPECIAL SCHEDULING FOR LOCKERS
;<134-TENEX>SCHED.MAC;323    10-APR-75 22:03:08    EDIT BY ALLEN
; REVISED HANDLING OF HIGH PRIORITY FOR LOCKERS
;<134-TENEX>SCHED.MAC;320     8-APR-75 23:44:35    EDIT BY ALLEN
; ADD SPECIAL SCHEDULING QUEUE. REDUCE BEHIND SCHED QUANTUM
;<134-TENEX>SCHED.MAC;318    12-MAR-75 13:08:18    EDIT BY PLUMMER
; CALLS TO ROUTINES IN SIGNAL.MAC
;<134-TENEX>SCHED.MAC;317    25-FEB-75 14:26:47    EDIT BY CLEMENTS
; MOVE DEFINITION OF MAXQ UP BEFORE USE OF IT TO AVOID PASS1 "V" ERRS
;<134-TENEX>SCHED.MAC;316    14-FEB-75 17:51:40    EDIT BY OPERATOR
; FIX BUG IN PSSKD2 LEADING TO LONG QUANTA ON Q0
;<134-TENEX>SCHED.MAC;315     5-FEB-75 15:32:43    EDIT BY ALLEN
;<134-TENEX>SCHED.MAC;314     4-FEB-75 20:07:02    EDIT BY ALLEN
; VARIOUS FIXES TO NEW LIST STUFF
;<134-TENEX>SCHED.MAC;313     4-FEB-75 12:56:11    EDIT BY ALLEN
;<134-TENEX>SCHED.MAC;312     4-FEB-75 12:30:10    EDIT BY ALLEN
; REORGANIZE LIST MANIPULATION STUFF
;<134-TENEX>SCHED.MAC;310    30-JAN-75 15:00:04    EDIT BY ALLEN
;DETAIL CHANGES TO NEW WAITLIST STUFF
;<134-TENEX>SCHED.MAC;309    30-JAN-75 00:12:03    EDIT BY ALLEN
; PLACE FORK DIRECTLY ON RUNLIST IN ASSFK
;<134-TENEX>SCHED.MAC;308    29-JAN-75 23:24:05    EDIT BY ALLEN
; WAITLST NOW DOUBLE LINKED LIST. FORK ON WAITLIST NOW INDICATED
;BY PRESENCE OF WTLS BIT IN FKFLGS, NOT LH(FKPT)=WTLST.
;NEW ROUTINE, 'PRWAKE', TRANSPARENT TO ACS, TAKES FORKX IN AC7
;AND AWAKENS SAID FORK.
;<134-TENEX>SCHED.MAC;301    17-JAN-75 15:12:30    EDIT BY ALLEN
; CORRECT NON-TRANSPARENCY OF HIQ
;<134-TENEX>SCHED.MAC;300    16-JAN-75 12:17:57    EDIT BY ALLEN
; NEWST HIQ'S UNCONDITIONALLY IF WAKEUP FROM TCI OR TCOTST
;FIX ERROR IN INCREMENTING NAPROC IN WTSCAN
;<134-TENEX>SCHED.MAC;299    12-JAN-75 21:17:14    EDIT BY ALLEN
; CORRECT RADIX ERROR IN CHGQ
;<134-TENEX>SCHED.MAC;298    10-JAN-75 11:54:28    EDIT BY ALLEN
; ADD HIQ ROUTINE FOR PIE-SLICE SCHEDULER
; PIE-SLICE PSSKD2 NOW HI-QUEUES
;<134-TENEX>SCHED.MAC;297    10-JAN-75 11:08:34    EDIT BY ALLEN
; HI-Q THE NCP ON WAKEUP
;<133-TENEX>SCHED.MAC;295    21-DEC-74 11:47:52    EDIT BY ALLEN
; MOVE CHKWT ROUTINE TO SCHED. UNDO PREVIOUS DUMB CHANGE I.E.
; DISMT IS NOW A LOCAL AGAIN.
;<133-TENEX>SCHED.MAC;294    19-DEC-74 17:53:04    EDIT BY ALLEN
; MAKE DISMT A GLOBAL
;<133-TENEX>SCHED.MAC;293    19-DEC-74 13:29:48    EDIT BY ALLEN
; REMOVED INIT OF PIEGRP TABLE TO -1
;<133-TENEX>SCHED.MAC;292    16-DEC-74 14:06:41    EDIT BY ALLEN
; MAKE NEWFKF A GLOBAL
;<133-TENEX>SCHED.MAC;291    11-DEC-74 16:58:17    EDIT BY ALLEN
; TREAT DDMP SAME AS NCP WITH RESPECT TO SCHEDULING
;<133-TENEX>SCHED.MAC;290    10-DEC-74 10:52:04    EDIT BY ALLEN
; INITIALIZE PIEGRP TO -1
;<133-TENEX>SCHED.MAC;289     9-DEC-74 19:32:16    EDIT BY ALLEN
; FIX BUG IN RMPROC UNCOVERED BY CHANGE TO MAXBSH LOGIC
;<133-TENEX>SCHED.MAC;288     9-DEC-74 13:21:11    EDIT BY ALLEN
; HOLD FORK IN BALSET FOR MAXBSH IF PREVIOUS DISMISS WAS OF DURATION
; NOT GREATER THAN MAXBSH
;<133-TENEX>SCHED.MAC;287    12-NOV-74 16:35:41    EDIT BY CLEMENTS
;<133-TENEX>SCHED.MAC;286     5-NOV-74 17:09:36    EDIT BY CLEMENTS
; PART OF CRJOB JSYS
;<133-TENEX>SCHED.MAC;285     1-NOV-74 13:37:28    EDIT BY ALLEN
; REMOVE UNNECESSARY GCCORS IN BKGND
;<133-TENEX>SCHED.MAC;284    29-OCT-74 16:56:28    EDIT BY PLUMMER
; RTISW CONDITIONS CALL TO GCCOR
;<133-TENEX>SCHED.MAC;283    18-OCT-74 09:11:20    EDIT BY BTHOMAS
; FIX JTENQ AND FRZPSI TO CALL ENSKED IN CORRECT PLACE
;<133-TENEX>SCHED.MAC;282    11-OCT-74 16:13:33    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;281     8-OCT-74 16:16:47    EDIT BY ALLEN
; TEST FOR VALID UNDERFLOW AT AVERG2+1
;<133-TENEX>SCHED.MAC;280     8-OCT-74 10:18:53    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;279     7-OCT-74 17:40:25    EDIT BY ALLEN
; PIE-SLICE SCHEDULER QUANTA SCALED BY TARGET
; UTILIZATION. PERIODIC SCAN OF AHEAD SCHEDULE QUEUE
;DONE TO DISCOVER NOW-BEHIND-SCHEDULE PROCESSES.
;<133-TENEX>SCHED.MAC;274    25-SEP-74 20:17:05    EDIT BY CLEMENTS
; MISSING CONDITIONAL ON PIESLC CODE FIXED
;<133-TENEX>SCHED.MAC;273    25-SEP-74 14:03:24    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;272    25-SEP-74 13:36:22    EDIT BY ALLEN
; UNLOCK GRPLOK AT HLTFK1 FOR PIE-SLICE SYSTEM
;<133-TENEX>SCHED.MAC;271    24-SEP-74 17:06:07    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;270    24-SEP-74 16:45:20    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;268    20-SEP-74 20:40:58    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;267    20-SEP-74 20:24:43    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;266    19-SEP-74 17:21:14    EDIT BY ALLEN
; VARIOUS PIE-SLICE REPAIRS AND ADDITION TO LOCK COLLISION LOGIC
; TO AVOID POSSIBLE DEADLOCK
;<133-TENEX>SCHED.MAC;264    16-SEP-74 20:04:35    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;263    16-SEP-74 19:34:37    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;262     5-SEP-74 15:27:24    EDIT BY ALLEN
; ADD ROUTINE FOR UNLOCKING LOCKS ON WHICH CONFLICT HAS OCCURRED
;<133-TENEX>SCHED.MAC;261     1-AUG-74 11:43:49    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;260     1-AUG-74 10:46:42    EDIT BY ALLEN
; MORE EFFICIENT HANDLING OF COLLISIONS ON NON-RESIDENT LOCKS
;<133-TENEX>SCHED.MAC;258    22-JUL-74 14:42:37    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;257    22-JUL-74 10:29:09    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;256    19-JUL-74 17:04:40    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;255    18-JUL-74 16:23:02    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;254    18-JUL-74 16:17:32    EDIT BY ALLEN
; ADD CODE FOR PIE-SLICE SCHEDULER REGULATOR
;<133-TENEX>SCHED.MAC;242    16-JUL-74 17:00:19    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;241    16-JUL-74 16:39:43    EDIT BY ALLEN
;<133-TENEX>SCHED.MAC;232    12-JUL-74 11:06:58    EDIT BY ALLEN
; CORRECT BLUNDER IN SCDRUN CAUSING OVERLOADING OF BALSET
;<133-TENEX>SCHED.MAC;231    10-JUL-74 11:26:12    EDIT BY ALLEN
; ELIMINATE RACE AT SCHED4, ELIMINATE RESCHEDULE ON CHAR ARRIVAL
; AND TEST FOR PENDING BALSET REMOVAL IN RMPROC
;<TENEX-132>SCHED.MAC;230    17-JUN-74 20:19:37    EDIT BY ALLEN
; CORRECT BUG IN MAINTENANCE OF OLDSUM

    SEARCH PROLOG
    TITLE SCHED

;TENEX SCHEDULER - D. MURPHY

;LINKAGE TO OTHER PARTS OF MON -- PAGEM AND PISRV

EXTERN ASSPT,BUGCHK,BUGNTE,BUGHLT,DCHKSW,DDTPRS,DESPT,DRMFRE,DRMIN0
EXTERN DSKRT,GCALC,GCCOR,XGC,ICAPT,IOIP,MENTR,MONCOR,MRETN
EXTERN NXTDMP,PIAPRX,POSTPG,PRELD,PRELDF,POSPGF,SETPPG
EXTERN SETPT,SPTC,SWPIN0,SWPINT,SWPRT
EXTERN SETMPG,JDSPTP

PGR==24 ;I/O DEVICE NUMBER FOR PAGER

    EXTERN AUTONX
    EXTERN TTCH7,TTBIGC,TTPSI,TADSEC,LSTERR,FACTSW,FLOGO
    EXTERN TCITST,TCOTST,TTEMES,FRZWT,CAPMSK,CAPENB
    EXTERN KSELF,LOGTOT,LOGDES
    EXTERN TTFORK,TTFRK1,EXEC0
    EXTERN BHC,BITS,SVN,CH6TAB
    EXTERN GFKH,SETLF1

    INTERN PSISV2,RSKEDN
    INTERN BLOCK0,BLOCK1
    INTERN ITRAP,DISGE,DISGET,DISL,DISLT,RSKP,R,JRET,JSKP,NJOBS
    INTERN PJMPG,PJMA,PPMPG,PPMA,PSB,JSB
    INTERN FREJPA,FREJP,JFNPC0,RJFN,MJFN,SJFN,SWPMA0
    INTERN NNAMES,SCDIN,ILIST,SCHEDP,.DISMS,SCHED0
    INTERN SCDRQ7,JOBSRT,TTPSRQ,PSIT1A,GETCHA,.DEBRK
    INTERN DISE,DISET,DISN,DISNT
    INTERN ASSFK,WTFPGS,WTSPT,SUSFKR,SUSWT,ITRAP1
    INTERN NTASKT,NLOADT,NEVENT
    INTERN STIME,ETIME,SETOVF,MAXQ,DISG,DISGT,BLOCKW
    INTERN SUPERP,PARALP,INFERP,PSILOB,TMZONE,DISLE,DISLET
    INTERN FFF,PATCH1,PATCH2,INDFLG
    INTERN .HALTF,EDISMS,HALTF1,HALTT
    INTERN HLTJB,HLTFK1,CLRM0,FRZB1,FRZB2,FRZBB,PSIJTR
    INTERN PSIRQ0,PSIRQF,PSIRQB,CHNSON,PSIR4,FORCTM,PSIRQ
    INTERN P7POV,P7FOV,P7OV,PI7P,WTSPTT,SCHEDR,APCLK1,MINNR,MPEINT
    INTERN SCDVE,PISC7
    INTERN JTULCK,PSIWTF,JTMCN,JTFRZB,JTDVC1,FRZBAL,TRPSI5
    INTERN NEWFKF
    INTERN TTFRKP
    IFDEF RTISW,<INTERN .SOLO,.TUTTI>

NSKDP==40       ;LOCAL PUSH LIST
NSCDRQ==20      ;SIZE OF SCHEDULER REQUEST QUEUE

MINNR==3        ;MIN SIZE OF RPLQ FOR LOADING FORK

;MACROS FOR TIMING SUBROUTINES

DEFINE STMR
<    SKIPL BKGFLG
    JSP 4,STIME
>

DEFINE ETMR(CLK)
<    SKIPGE BKGFLG       ;BACKGROUND MODE?
    JRST .+4        ;YES
    JSP 4,ETIME
    ADDM 1,CLK
    AOS CLK+1
>

;STORAGE

LS SKDPDL,NSKDP     ;SCHEDULER LOCAL PDL
LS MSCNT,1      ;INDEX TO MSEC/TICK TABLE
LS APCLKC,1     ;COMMUNICATION TO CH7 FROM CH1 CLOCK INTERRUPT
LS CLKAC2,1     ;CLOCK ROUTINE TEMP
LS CLKAC1,1     ;  "
LS SYNCC,1      ;  "
LS OLDTCK,1     ;  "
LS PISC7R,1
LS FKPT6M,1     ;-FKPT(6)

GS FORKX,1      ;INDEX OF CURRENTLY RUNNING FORK

GS FKPGS,NFKS       ;UPT,,PSB   SPT NUMBERS
GS FKSTAT,NFKS      ;FORK WAIT TEST
GS FKWSP,NFKS       ;NO. PGS NEEDED TO PRELOAD,,NO. PGS IN CORE
GS FKPGST,NFKS      ;TEST WORD WHILE IN PAGE WAIT
            ;IF ON WTLST, TIME WAIT BEGAN
GS FKQ,NFKS     ;QUEUE NUMBER,,TIME REMAINING IN QUANTUM
GS FKPT,NFKS        ;IF ON WTLST, =WTLST,,ADDRESS OF NEXT FKPT OR 0
            ;IF ON A RUNLST, ADDRESS OF PREV FKPT,, ADDRESS
            ;OF NEXT FKPT
            ;IF DELETED FORK, B0=1
GS FKINT,NFKS       ;FORK INTERRUPT COMMUNICATION REG
;BITS IN FKINT --
; B0 = REQUEST FOR PSI PROCESSING
; B1 = PSI PROCESSING IN PROGRESS - DEFER FURTHER REQUESTS
; REST DEFINED AND COMMENTED AT TAG "PIRQ"
GS FKINTB,NFKS      ;INTERRUPT CHANNELS REQUEST
GS FKJOB,NFKS       ;JOB NUMBER ,, JSB
GS FKNR,NFKS        ;AGE,,BALANCE SET SIZE (RESERVE)
GS FKFLGS,NFKS      ;PER PROCESS FLAGS,,CORE NUMBER

;FLAG BIT DEFS FOR ABOVE TABLE
WTFK==:1B18 ;PROCESS IN BALANCE SET AND WAITING
NOSK==:1B19 ;PROCESS IS NOSKED
FORCEM==:1B20   ;PROCESS IS TO BE REMOVED FROM BS 
BLST==:1B21 ;PROCESS IS IN BALANCE SET
RNLS==:1B22 ;PROCESS IS ON A RUNLIST
ZIFA==:1B23 ;ZERO IFAV AT NEXT PAGE-FAULT (PLEASE)
NOFLT==:1B24    ;INDICATES ADJUSTMENT OF FKNR DUE TO LACK OF PAGE-FAULTING HAS BEEN DONE
WTCLCT==:1B25   ;FORK HAS LEFT BAL SET BUT GCCOR NOT YET RUN

IFN PIESLC,<
NOCNT==:1B26    ;FORK BEING BORN OR DYING. DON'T INCLUDE IN NAPROC
        ;AS FORK IS NOT RECORDED IN SYSFK
> ;END PIE-SLICE SCHEDULER CONDITIONAL

HOLD==:1B27 ;HOLD FORK IN BALANCE SET FOR MAXBSH AFTER DISMISS
WTLS==:1B28 ;FORK IS ON THE WAIT LIST

IFN PIESLC,<
HIQFK==:1B29    ;FORK MUST REMAIN ON A HIGH QUEUE
SPQFK==:1B30    ;FORK MUST REMAIN ON THE SPECIAL QUEUE
PHIQFK==:1B31   ;USED TO REMEMBER THAT FORK WAS ALREADY ON INTERQ AT
        ;TIME OF CALL TO SETHIQ
>

FKCNO=:FKFLGS       ;CORE NUMBER IN RIGHT HALF OF FKFLGS

GS FKJTQ,NFKS       ;JSYS TRAP QUEUE - BACK PTR,,FWRD PTR

IFN PIESLC,<
GS FKSOLD,NFKS      ;SYSTEM SOLD TIME WHEN FKUTIL LAST UPDATED
GS FKUTIL,NFKS      ;PROCESSOR UTILIZATION PER FORK (FLOATING POINT)
GS FKPRT,NFKS       ;PROCESS TIME SINCE FKUTIL LAST UPDATED
GS FKUDT,NFKS       ;REAL TIME SINCE FKUTIL UPDATE

> ;END PIE-SLICE SCHEDULER CONDITIONAL

LS FREFK,1      ;LIST OF FREE FORKS
GS SYSIFG,1     ;SYSTEM HAS BEEN INITIALIZED IF NOT 0
GS ACCIFG,1     ;ACCOUNTING INITIALIZED WHEN NON-0
GS ENTFLG,1     ;PERMIT NEW JOB ON ^C IF NON-0
GS PWRDWN,1     ;POWER FAILURE DETECTED IF .G. 0, DONE IF .L. 0

GS JOBDIR,NJOBS     ;ATTACHED DIRECTORY,,LOGIN DIRECTORY
GS JOBNAM,NJOBS     ;JOB SUBSYSTEM NAME INDEX FOR SETNM
GS JOBRT,NJOBS      ;JOB RUN TIME
GS JOBPT,NJOBS      ;CONTROL TTY,,TOP FORK
LS FREJOB,1     ;LIST OF FREE JOBS

IFE PIESLC,<
MAXQ==:4            ;HIGHEST NUMBERED QUEUE
>
IFN PIESLC,<
MAXQ==:2            ;HIGHEST NUMBERED QUEUE
>

;RH(RUNLST(I)) CONTAINS A POINTER TO THE FKPT ENTRY OF THE HIGHEST
;PRIORITY PROCESS ON Q(I) REQUIRING SERVICE. SUBSEQUENT ENTRIES IN
;THE LIST MAY BE FOUND BY FOLLOWING THE POINTER IN RH(FKPT).

;LH(RUNLSB(I)) CONTAINS A POINTER TO THE FKPT ENTRY OF THE LOWEST
;PRIORITY PROCESS ON Q(I) REQUIRING SEERVICE. THE LIST MAY BE
;FOLLOWED VIA THE POINTERS IN LH(FKPT).

LS RUNLST,MAXQ+1    ;RUNNING FORK LISTS (DESCENDING PRIORITY)
LS RUNLSB,MAXQ+1        ;RUNNING FORK LISTS (ASCENDING PRIORITY)

LS WTLST,1  ;WAITING FORK LIST
LS WTLSTB,1 ;WAIT LIST TAIL POINTER
LS WAITFS,1 ;WAIT LIST FULL SCAN FLAG
LS WAITLS,1 ;TIME OF LAST FULL WAIT LIST SCAN

LS JTLST,1  ;JSYS TRAP QUEUE
LS JTLSTL,1 ; ... LAST

LS JB0FLG,1     ;RUN JOB 0 REQUEST
LS FRECB,1      ;FREE CORE NUMBER BITS

;BALANCE SET VARIABLES

LS NBPROC,1     ;NUMBER OF PROCESSES IN BAL SET
LS NBRUN,1      ;NUMBER RUNNABLE FORKS IN BALSET 

GS TOTRC,1      ;TOTAL NUMBER REAL CORE PAGES
GS SUMNRX,1     ;SUM(FKNR FOR BALSET PROCS)+SUM(FKWSP FOR ALL OTHERS)
            ;+PAGES UNASSIGNED AND AWAITING COLLECTION
GS NRPMIN,1     ;MINIMUM VALUE OF NRPLQ
GS MAXNRX,1     ;MAX VALUE OF SUMNRX
GS CGFLG,1      ;DEASSIGNED PAGES MAY EXIST IF >0
LS PANIC,1      ;COUNT OF PANIC MODE GCCORS
LS RLBITS,1     ;FOR GCCOR -- PROC USE BITS OF RUNLIST PROCS
LS BSBITS,1     ;PROC USE BITS OF BAL SET PROCS
LS RUNT1,1      ;RUNTIME SINCE FORK BEGAN EXECUTION

GS NRPLQ,1      ;NUMBER OF PAGES ON REPLACABLE QUEUE
GS RPLQ,1       ;REPLACABLE QUEUE    END,,BGN
GS PNDING,1     ;NO. UNASSIGNED PAGES AWAITING COLLECTION

GS NPMAX,1      ;MAX NUMBER OF PAGES IN CORE FOR ONE PROCESS
GS SNPMAX,1     ;SMALL NPMAX FOR LOADED CONDITIONS
GS SJSIZ,1      ;'SMALL' JOB SIZE
GS IRJAV,1      ;NEAREST INTEGER TO RJAV

;SCHEDULER VARIABLES

LS SKEDF1,1     ;START PROCESS VIA CH7 BREAK IN 1
LS SKEDF3,1     ;PROCESS CLOCK COUNTED TO 0
LS INSKED,1     ;IN SCHEDULER IF NON-ZERO
LS SSKED,1      ;LAST JOB RUNNING WAS NOSKED

GS PSKED,1      ;PAGE TRANSFER COMPLETED OR PSI NEEDS ATTENTION
GS ISKED,1      ;SCHEDULE REQUEST FLAG
GS FSHBAL,1     ; FLUSH BALSET REQUEST FLAG
GS BKGFLG,1     ;WHEN = 0 INDICATES BACKGROUND

LS NGPROC,1     ;NUMBER OF FORKS WAITING TO ENTER BAL SET
LS OLDSUM,1     ;SUMNRX AT TIME OF LAST ATTEMPT TO ADD TO
            ;BALANCE SET. -1 IF THIS VALUE HAS BEEN
            ;INVALIDATED BY SOME CHANGE SUCH AS A FORK
            ;ENTERING THE RUNLISTS.
LS POTEN,1      ;UPPER BOUND ON NUMBER OF PAGES TO BE GAINED
            ;BY DOING GCCOR

LS RJAVS1,1     ;RJTSUM AT LAST RJAV UPDATE

;TABLES FOR SETNM

NNAMES==^D50        ;NUMBER OF NAMES ALLOWED

GS SNAMES,NNAMES    ;SIXBIT NAME OF SUBSYSTEM
GS STIMES,NNAMES    ;ACCUMULATED RUNTIME OF SUBSYSTEM
GS SPFLTS,NNAMES    ;ACCUMULATED PAGE FAULTS OF SUBSYSTEM
GS SWAKES,NNAMES    ;WAKEUPS 0-14, SIZE INTEGRAL 15-35
GS SBLKTM,NNAMES    ; BLOCKED FOR TTY TIME

;"QTIMES" GETAB TABLE
GS QSUM,5       ;ACCUMULATED TIME OF JOBS OF RESPECTIVE Q'S

;PROCESSOR TASK GETAB TABLE ("TASKTB")
;ACCOUNTS FOR WHERE PROCESSOR TIME IS BEING SPENT
;EACH CATEGORY CONSISTS OF TWO WORDS; FIRST WORD IS TOTAL TIME
;SPENT PERFORMING THE TASK OR ROUTINE, SECOND IS A COUNT OF INVOKATIONS
;OF THAT TASK OR ROUTINE.

GS SOLD,2       ;PROCESS-LEVEL TIME AND NUMBER OF PROCESS
            ;DISPATCHINGS
GS IDLE,2       ;IDLE TIME (NO PROCESSES REQUESTING SERVICE)
            ;AND NUMBER OF TIMES WE GO IDLE
GS SWAPWT,2     ;SWAP-WAIT TIME (NO RUNNABLE PROCESSES) AND
            ;NUMBER OF TIMES WE HAD TO WAIT
GS NSKWT,2      ;NOSKED WAIT TIME (NOSKED PROCESS PAGE FAULTS
            ;WHILE THERE ARE OTHER RUNNABLE PROCESSES) AND
            ;NUMBER OF TIMES THIS HAPPENS
GS SKMAIN,2     ;TOTAL TIME SPENT IN AND ENTRIES TO SCHEDULER
            ;MAIN ROUTINE
GS SKBAL,2      ;TIME SPENT IN AND ENTRIES TO BALANCE SET SCHED.
GS SKWAIT,2     ;TIME SPENT IN AND ENTRIES TO WAIT-LIST SCAN
GS SKLDPR,2     ;TIME SPENT IN AND ENTRIES TO LDPROC ROUTINE
            ;(ROUTINE THAT PROMOTES ACTIVE PROCESSES TO
            ;THE BALANCE SET)
GS GCCR,2       ;TIME SPENT IN AND ENTRIES TO GLOBAL GARBAGE
            ;COLLECTOR (GCCOR)
GS PPG,2        ;TIME SPENT IN AND ENTRIES TO POST-PURGE ROUTINE
GS PTRAP,2      ;TIME SPENT IN AND COUNT OF PAGER TRAPS
            ;PLEASE NOTE THAT PAGER TRAP TIME IS ALSO
            ;INCLUDED IN SOLD TIME
IFN PIESLC,<GS SORTIM,2  ;TIME SPENT IN AND ENTRIES TO RESORT>

IFE PIESLC,<NTASKT==^D22>
IFN PIESLC,<NTASKT==^D24>

;SYSTEM LOAD GETAB TABLE ("LOADTB")
GS RJTSUM,1     ;INTEGRAL OF NBPROC+NGPROC DT
GS BSTSUM,1     ;INTEGRAL OF NBPROC DT
GS NBRSUM,1     ;INTEGRAL OF NBRUN DT
NRJAVS==3       ;NUMBER OF LOAD AVERAGES WE MAINTAIN
GS RJAV,NRJAVS      ;EXPONENTIAL AVERAGES OF NUMBER OF ACTIVE PROCESSES

NLOADT==NRJAVS+3

;SYSTEM MISCELLANEOUS EVENT COUNTERS ("EVENTS")
GS DRMRD,1      ;NUMBER OF DRUM READS
GS DRMWR,1      ;NUMBER OF DRUM WRITES
GS DSKRD,1      ;NUMBER OF DISK READS
GS DSKWR,1      ;NUMBER OF DISK WRITES
GS TOPTRP,1     ;COUNT OF TOP-LEVEL PAGER TRAPS
GS WAKEUP,1     ;NUMBER OF PROCESS WAKE-UPS
GS TTINTS,1     ;NUMBER OF TERMINAL INTERRUPTS
GS NTTYIN,1     ;TOTAL NUMBER TERMINAL INPUT CHARS
GS NTTYOT,1     ;TOTAL NUMBER TERMINAL OUTPUT CHARS
GS ENTDMS,1     ;ENTRIES TO SCHED DUE TO FORKX DISMS
GS ENTPGF,1     ;ENTRIES TO SCHED DUE TO FORKX PAGE-FAULT
GS NGCLCT,1     ;NUMBER OF PAGES COLLECTED BY GCCOR
GS PPCLCT,1     ;NUMBER OF PAGES COLLECTED BY POSTPG
GS NREMJ,1      ;COUNT OF FORCED BALSET REMOVALS
GS HSYST1,1     ;TIME OF SYSTEM SHUTDOWN PENDING (GTAD FORMAT)
GS HSYST4,1     ;AND GTAD TIME SYSTEM SCHEDULED BACK UP (OR 0)

NEVENT==^D15

;CLOCKS COUNTED DOWN AND TESTED, PARALLEL TO PCLKT, DON'T REORDER

LS RJQNT,1      ;RUNNING JOB REMAINING QUANTUM
GS TIM2,1       ;SECOND CLOCK, 100 MS

LS JOBRTT,1     ;JOB RUNTIME SINCE LAST UPDATE

LS SCDRQI,1     ;SCHEDULER REQUEST QUEUE
LS SCDRQO,1
LS SCDRQB,NSCDRQ

GS TODCLK,1     ;MILLISECOND CLOCK, MONOTONICALLY INCREASING
GS CHKTIM,1     ;FOUR MINUTES PAST LAST JOB 0 CHECK
GS CHKTM1,1     ;TWO MINUTES PAST LAST JOB 0 CHECK
GS DDMPFK,1     ;INDEX OF FORK RUNNING DDMP. -1 IF NON-EXISTANT

LS SCDRN1,1         ;RUN ONLY FORK N IF N > -1

LS GLOCK,1          ;USED TO AVOID UNNECESSARY WAKEUPS
                ;AFTER COLLISION ON NON-RESIDENT LOCK
LS RESDNT,1     ;ANYTHING BELOW THIS ADDRESS IS RESIDENT

;RESIDENT STORAGE FOR CRJOB JSYS

GS CRJLCK,1     ;PROCESS LOCK FOR USE OF CRJOB JSYS
GS CRJANS,1     ;ANSWER FROM JOBSRT. 0=WAITING, -1=SUCCESS,
            ; +N IS AN ERROR NUMBER.
GS CRJJNO,1     ;JOB NUMBER ASSIGNED BY JOBSRT

IFN PIESLC,<
;RESIDENT STORAGE FOR PIE-SLICE SCHEDULER
GS NAPROC,NGRPS     ;NUMBER OF ACTIVE PROCESSES PER GROUP
GS DSHARE,NGRPS     ;FRACTION (INCLUDING WINDFALL) PER GROUP
GS PIEGRP,NJOBS         ;GROUP INDEX PER JOB
GS DEFGP,1          ;DEFAULT PIE-SLICE GROUP
GS PASSES,1         ;LOCAL COUNT OF PASSES PER ENTRY TO RESORT
GS EXCHGS,1         ;LOCAL COUNT OF EXCHANGES IN RESORT
GS SRTBEG,1         ;PLACE TO BEGIN NEW SORT PASS
GS SRTEND,1         ;PLACE TO END IT
GS SRTTMP,1         ;TEMPORARY FOR SORT

>; END PIE-SLICE SCHEDULER CONDITIONAL

;PATCH SPACE FOR RES MON

SCDV1==1        ;IF DEFINED MEANS ASSEMBLING MON
SCDVE==.-1      ;UPPER LIMIT FOR CORE CLEAR ON STARTUP

FFF:
PATCH1:
PATCH2: XLIST   ;REPEAT 300,<0>
    REPEAT 300,< 0>
    LIST

INDFLG: 0           ; .NE. 0 MEANS LOCK JSB
                ; .L. 0 MEANS USE NO INDIRECT PTRS

;SCHEDULER INITIALIZATION

SCDIN:  SETZM SYSIFG
    SETZM AUTONX
    SETZM PWRDWN
    SETZM ENTFLG
    SETZM WAITLS
    MOVE 1,[JRST SCDRQ0]
    MOVEM 1,SCDRQ+1     ;DISPATCH FOR JSR-CALLED ROUTINE
    MOVEI 1,JTLST
    MOVEM 1,JTLSTL      ;SET UP JSYS TRAP WAIT LIST
    MOVNI 1,FKPT        ;BECAUSE MACRO DOESN'T HAVE NEG RELOC'N
    HRLI 1,6
    MOVEM 1,FKPT6M      ;-FKPT(6)
    MOVEI 1,SCDRQB
    MOVEM 1,SCDRQI
    MOVEM 1,SCDRQO
    MOVEI 1,FKPT
    MOVEI 2,NFKS
    CALL ILIST      ;INIT FREE FORK LIST
    MOVEM 1,FREFK
IFDEF SIGIPC,<   CALL SIGINI##   ;INIT SIGNL/WTFOR TABLES>
    MOVEI 1,JOBPT
    MOVEI 2,NJOBS
    CALL ILIST
    MOVEM 1,FREJOB      ;INIT FREE JOB LIST
    SETOM JOBRT
    MOVE 1,[XWD JOBRT,JOBRT+1]
    BLT 1,JOBRT+NJOBS-1

    SETZM 20
    SETOM SCDRN1
    SETOM GLOCK
    SETOM FORKX
    SETOM TADSEC
    SETOM OLDSUM
    SETOM SSKED
    SETOM DDMPFK
    SETOM CRJLCK        ;FREE LOCK ON CRJOB CELLS

    HRLOI 1,377777
    MOVEM 1,CHKTM1          ;AVOID JOB0 BUGHLT ON STARTUP

    MOVSI 1,(777B8!CORMB)   ;INIT RLBITS NOT TO CLEAR AGE AND CORMB
    MOVEM 1,RLBITS

    HRLOI 1,377     ;BITS IN PROC. USE REG.
    MOVEM 1,FRECB
    MOVEM 1,INSKED
    MOVE 1,[FACTON]
    MOVEM 1,FACTSW      ;FACT FILE ON AND INIT THE REST=0

    MOVEI 1,MAXQ+1      ;CLEAR RUNLST AND RUNLSB
SCDIN2: MOVEI 2,RUNLST(1)
    MOVEI 3,RUNLSB(1)
    HRLZM 2,0(3)
    HRRZM 3,0(2)
    SOJGE 1,SCDIN2

    MOVEI 1,WTLST
    HRLZM 1,WTLSTB
    MOVEI 1,WTLSTB
    HRRZM 1,WTLST

    MOVE 1,MONCOR
    LSH 1,^D9
    MOVEM 1,RESDNT

    RET

ILIST:  ADDI 1,-1(2)
    SETZM 0(1)      ;INIT FREE LIST, BLOCK ADR IN AC1,
    SOJLE 2,ILIST1      ;  SIZE OF BLOCK IN 2
    MOVEM 1,-1(1)
    SUBI 1,1
    SOJG 2,.-2
ILIST1: RET

;CHANNEL 7 INTERRUPT
;CLOCK, POSSIBLE RESCHEDULING, OR START PROCESS FROM SCHEDULER

PISC7:  XWD PISC7R,.+1
IFN KIFLG,<          ;PROTECT SOME AREAS ON KI-10.
    CLSB SCDCHN     ;THE ISB MUST BE EXPLICITLY CLEARED
    MOVEM 1,KIP7A       ;STASH AN AC TO DO THE RANGE CHECK
    SKIPE KIP7Q     ;REQUEST FROM KI RETURN?
    JRST [  MOVE 1,KIP7P    ;YES. GET PC AND FLAGS
        MOVEM 1,PISC7R  ;SET TO GO THERE
        SETZM KIP7Q ;CLEAR REQUEST
        SETZM KIP7F
        JRST .+1]
    MOVE 1,PISC7R       ;SEE IF WE WERE IN USER MODE
    TLNE 1,UMODF        ; ..
    JRST KIP71      ;YES. OK TO RESCHEDULE
    MOVEI 1,0(1)        ;EXEC MODE. JUST THE ADDRESS. CLR FLAGS
    CAIL 1,KIBGN        ;CRITICAL SECTION RANGE CHECK
    CAIL 1,KIEND        ; ..
KIP71:  SKIPA 1,KIP7A       ;NO. OK TO BREAK HERE. GET THE AC BACK.
     JRST [ AOS KIP7F   ;CRITICAL. FLAG INTERRUPT NEEDED, BUT
        MOVE 1,KIP7A    ; FOR NOW JUST RESTORE AC AND GO AWAY
        JEN @PISC7R]    ;DISMISS FROM CHANNEL 7
>;              ;END OF KI-10 SAFETY CHECK.
    SKIPG PSKED     ;RESCHEDULE ON PAGE ARRIVAL .. DCA
    SKIPE ISKED
    AOSA SKEDF3     ;RESKED REQUEST
    SKIPE APCLKC        ;CLOCK TICK?
    JRST APCLK      ;SERVICE IT
APCLKX: SKIPE SKEDF1        ;INITIATED BY SCHEDULER?
    JRST SCDR       ;YES, GO START PROCESS
    SKIPG INSKED        ;IN SCHEDULER NOW, OR
    SKIPG SKEDF3        ;NO SCHEDULING REQUESTS?
    JEN @PISC7R     ;IGNORE INTERRUPT
    SKIPE ENSKR     ; ABOUT TO ENTER SCHEDULER AT PROC LVL?
     JEN @PISC7R        ; YES. NO NEED TO DO IT HERE
    SKIPN TRAPPC        ;PAGER TRAP STARTING?
    SKIPE NSKED     ;OK TO RESCHEDULE?
    JRST SCDW       ;NO, GO SET TRAPS
    ENTSKD          ;ENTER SCHEDULER ENVIRONMENT
    MOVE 1,PISC7R
    MOVEM 1,PPC
    JEN @[SCHED0]

SCDW:   MOVEM 1,RSKED       ;SAVE AC1
    MOVE 1,RSKEDT       ;GET TRAP INSTRUCTION
    EXCH 1,RSKED        ;LEAVE IT TO GET EXECUTED
    JEN @PISC7R

RSKEDN: JFCL 0          ;NO-TRAP CONTENTS OF RSKED
RSKEDT: JSYS RSKD0      ;TRAP CONTENTS OF RSKED

;SETUP AND RESUME PROCESS

SCDR:   SETZM SKEDF1        ;CLEAR LOCAL FLAG
    SETZM INSKED        ;NO LONGER IN SCHEDULER
IFN KIFLG,<
    JSP 7,KISLOD>        ;RELOAD STUFF PECULIAR TO KI-10
IFE JTRPSW-1,<           ;IF MAPPING RES MON FOR TRAPS
    HRRZ 1,JTMNW        ;1=FORKS MONITOR
    CAIN 1,7777     ;NULL FORK?
    JRST .+5        ;YES, DON'T MAP RES MON
    SKIPN 1,PSB+JDVPG   ;NO, IS FORK INIT'D?
    JRST .+3        ;NO
    MOVEM 1,MMAP+1      ;YES, SET MON MAP AND
    CONO PGR,7      ;MAP RES MON
         >
    MOVE 1,PSB40
    MOVEM 1,40
    MOVSI 17,PAC        ;RESTORE PROCESS AC'S
    BLT 17,17
    JEN @PPC        ;RUN PROCESS

;VARIOUS WAYS OF ENTERING SCHEDULER

;JSYS HALTF - DISMISS FORK UNTIL INTERRUPT OR EXTERNALLY RESTARTED

.HALTF: JSYS MENTR
HALTF1: CALL FKTMI      ;FORK TERM INTERRUPT
HALTX:  MOVEI 1,HALTT
    JSYS EDISMS
    JRST MRETN      ;IF CONTINUED

HALTT:  JRST 0(4)       ;IDENTIFIABLE TEST FOR HALTED FORK

;EXEC DISMISS - AC1 CONTAINS  XWD DATA,TEST ROUTINE ADR

EDISMS: XWD FPC,.+1
    ENTSKD          ;ENTER SCHEDULER
DISMS1: MOVE 2,FPC      ;USE JSYS RETURN AS PPC
    MOVEM 2,PPC
DISMSE: SKIPE NSKED     ;CHECK FOR BUGGY DISMISS
    BUG(HLT,<DISMISS WHILE NOSKED>)
    AOS ENTDMS  ;COUNT DISMISSES
    MOVEM 1,FKSTAT(7)       ;STORE ACTIVATION TEST WORD
    LSH 1,-^D9
    ANDI 1,777
    CAML 1,MONCOR
    BUG(HLT,<DISMISS WITH NON-RES TEST ADDRESS>)
    CALL SAVRT
    SETZM JOBCK0        ;INIT MEASURING INTERVAL
    SETZ 2,
    MOVSI 1,HOLD        ;THIS FORK TO BE KEPT IN BALSET?
    TDNE 1,FKFLGS(7)
    MOVE 2,MAXBSH       ;YES
    HRRZ 1,FKSTAT(7)
    CAIE 1,BLOCKM
    CAIN 1,BLOCKT       ;DISMISSED FOR WAIT .G. 500 MS?
    SETZ 2,         ;YES, DON'T RETAIN IN BALSET
    JUMPE 2,[ CALL REMBSJ   ;IF NO HOLD TIME REMOVE FROM
          SETOB 7,FORKX ;BAL SET IMMED.
          JRST SCHED0 ]
    MOVE 1,TODCLK
    ANDI 1,377777
    ADDI 1,0(2)
    MOVSI 1,0(1)
    HRRI 1,DISMT
    MOVEM 1,FKPGST(7)
    JRST SCHP3

MAXBSH: ^D100       ;MAX BALSET 'HYSTERESIS'

;RESCHEDULE ON PAGE WAIT

SCHEDP: XWD SKDPC,.+1
    ENTSKD
SCHP1:  AOS ENTPGF  ;COUNT ENTRIES DUE TO PAGE-FAULT
    MOVEM 1,FKPGST(7)

    CALL SAVRT

    MOVE 1,SKDPC
    MOVEM 1,PPC
SCHP3:  MOVSI 1,WTFK
    SOS NBRUN       ;FORK NO LONGER RUNNABLE .. COUNT
    CALL SCHP2
    JRST SCHED0

SCHP2:  SKIPE NSKED
     JRST [MOVEM 7,SSKED    ;SAVE NOSKED FORK INDEX
        TLO 1,NOSK
        JRST .+1]
    IORM 1,FKFLGS(7)
    SETOB 7,FORKX
    RET

;DO OKSKED AND RESCHEDULE

SCHEDR: XWD SKDPC,.+1
    ENTSKD
    SOSGE NSKED
    BUG(HLT,<OKSKED WHEN NOT NOSKED>)
    JRST SCHP1

;DEFERRED SCHEDULING REQUEST TRAP

RSKD0:  XWD SKDPC,.+1
    ENTSKD          ;ENTER SCHEDULER
    MOVE 1,SKDPC
RSKD2:  MOVEM 1,PPC
RSKD3:  MOVE 1,RSKEDN
    MOVEM 1,RSKED
    JRST SCHED0

;COMMON SCHEDULER ENTER ROUTINE, SAVE AC'S AND SET INSKED FLAG
;UPDATES PROCESS CLOCKS. SMASHES ACS 15 AND 16 IN DOING SO!!

ENSKED: XWD ENSKR,.+1
    SKIPE INSKED
    BUG(HLT,<CALL TO SCHEDULER WHEN ALREADY IN SCHEDULER>)
    AOS INSKED      ;PREVENT ACTION BY CH7 BREAK
    MOVEM 17,PAC+17     ;SAVE PROCESS AC'S
    MOVEI 17,PAC
    BLT 17,PAC+16
    MOVE 7,40
    MOVEM 7,PSB40
IFN KIFLG,<
    JSP 7,KISSAV>        ;SCHEDULER SAVE ROUTINE FOR KI-10 HWARE
    MOVE 7,FORKX        ;GET INDEX OF CURRENT FORK
    MOVE P,PI7P     ;GET PDL POINTER
    PUSH P,ENSKR        ; SAVE ENSKR
    SETZM ENSKR

;UPDATE PROCESS CLOCKS
UCLOCK: MOVE 16,JOBNO
    SETZ 15,
    EXCH 15,JOBRTT      ;RUN TIME SINCE LAST UPDATE
    JUMPE 15,R      ;RETURN NOW IF NO CHANGE
    ADDM 15,SOLD        ;ACCUMULATE ALL SOLD TIME
    ADDM 15,JOBRT(16)       ;ACCOUNT FOR JOB
    ADDM 15,FKRT        ;ACCOUNT FOR FORK

IFN PIESLC,<
    ADDM 15,FKPRT(7)    ;RUNTIME SINCE LAST FKUTIL UPDATE
> ;END PIE-SLICE SCHEDULER CONDITIONAL

    ADDM 15,RUNT1       ;LOCAL RUNTIME
    ADDM 15,IFTIM       ;INTERFAULT TIME
    HRRZ 16,JOBNAM(16)  ;GET SUBSYSTEM INDEX
    ADDM 15,STIMES(16)  ;ACCUMULATE SUBSYSTEM TIME
    ADDB 15,PGTIM       ;CORE MGT CLOCK
    CAIGE 15,AGTICK     ;TIME TO TICK AGE CLOCK?
    RET         ;NO
    IDIVI 15,AGTICK     ;YES, COMPUTE NUMBER TICKS IN INTERVAL
    MOVEM 16,PGTIM      ;LEAVE REMAINDER
    CAILE 15,AGSEC      ;STRANGELY LONG INTERVAL?
    MOVEI 15,AGSEC      ;YES, NORMALIZE TO A SECOND
    LDB 16,[POINT 9,FKNR(7),17] ;CURRENT AGE
    ADDI 15,0(16)       ;ADD TICKS
    TRNE 15,777000      ;OVERFLOW 9 BIT FIELD?
    SUBI 15,1000-100        ;YES, WRAPAROUND
    DPB 15,[POINT 9,FKNR(7),17] ;SET NEW AGE
    DPB 15,[POINT 9,PGR72,8]    ;FOR PAGER
    PGRLAG          ;LOAD NEW AGE INTO PAGER
    RET

;INSTRUCTION TRAP - TRAP PC IN FPC, ASSUMED TO BE I +1

ITRAP1: MOVEM 1,LSTERR      ;SAVE ERROR CODE GIVEN IN 1
ITRAP:  SKIPE INSKED
    BUG(HLT,<INSTRUCTION TRAP WHILE IN SCHEDULER>)
    SKIPL FORKX     ;NO FORK RUNNING, OR
    CONSZ PI,177B27     ;PI IN PROGRESS?
    BUG(HLT,<INSTRUCTION TRAP WHILE PI IN PROGRESS OR IN SCHEDULER>)
    SKIPGE SLOWF        ;NOW IN SLOW CODE?
    JSYS MENTR      ;NO, ENTER
ITR3:   MOVE 1,MPP      ;STACK PTR ON ENTERING THIS CONTEXT
    MOVE 2,0(1)     ;RETURN PC
    TLNN 2,UMODF        ;PREVIOUS CONTEXT INTERRUPTABLE?
    SKIPGE -2(1)        ;I.E. USER MODE, OR INTDF .L. 0
    JRST ITR2       ;YES, OK
    BUG(CHK,<INSTRUCTION TRAP AND PREVIOUS CONTEXT WAS NOINT>)
ITR2:   SETZM NSKED
    SETOM TRAPC     ;CLEAR FLAGS AND COUNTERS
    SETZM INTDF     ;SET TO 1 LEVEL NOINTERRUPT
    MOVE 1,CHNSON
    ANDCAM 1,PSIBW      ;FLUSH PREVIOUS PANIC BREAKS
    MOVEI 1,^D15        ;INITIATE CHANNEL 15 INTERRUPT
    CALL PSIRQ0
    RESKD1          ;GET THE INTERRUPT "SEEN"
    OKINT           ;INTERRUPT SHOULD TAKE HERE
    MOVE P,UPP      ;RETURN TO USER IF CONTINUED
    ADD P,BHC+2
    JRST MRETN

;BLOCK UNTIL CONDITION SATISFIED
;BLOCK0 - STAYS IN BALSET,  BLOCK1 - LEAVES BALSET

BLOCK0: XWD SKDPC,.+1
    ENTSKD
    CALL BLOCKS
    JRST SCHP1

BLOCK1: XWD SKDPC,.+1
    ENTSKD
    CALL BLOCKS
    MOVE 2,SKDPC
    MOVEM 2,PPC
    JRST DISMSE

BLOCKS: MOVNI 1,^D100
    ADDM 1,RJQNT        ;CHARGE Q TO PREVENT HOGGING
    MOVNI 1,2
    ADDM 1,SKDPC        ;MAKE RETURN TO INSTRUCTION BEFORE CALL
    MOVE 1,TODCLK
    ANDI 1,377777
    ADDI 1,^D100        ;ADD 100 MILLISECS
    MOVSI 1,0(1)
    HRRI 1,BLOCKW
    RET

BLOCKM: JFCL            ;SCHED TEST FOR .5 TO 64 SEC.
BLOCKW: MOVE 2,TODCLK       ;SCHEDULER TEST, GET TIME
    ANDI 2,377777
    SUB 1,2         ;DESIRED - NOW = WAIT LEFT
BLK2:   JUMPLE 1,1(4)       ;NO WAIT TIME LEFT
    CAIGE 1,200000      ;BIG DIFFERENCE?
    JRST 0(4)       ;NO, KEEP WAITING
    SUBI 1,400000       ;YES, COMPENSATE FOR WRAPAROUND
    JRST BLK2

;DEFAULT LOCK FAILURE ROUTINE
CNTLCK::
LCKTST::
    PUSH P,1        ;SAVE AC1
    PUSH P,1        ;DO IT AGAIN
    MOVE 1,-2(P)
    MOVE 1,-2(1)        ;GET THE FAILING AOSE
    EXCH 1,0(P)     ;PUT IT ON THE STACK AND RESTORE AC1
    HRRZI 1,@0(P)       ;AC1=ADDRESS OF LOCK
    CAML 1,RESDNT       ;RESIDENT?
     JRST [SETZM GLOCK  ;NO, INDICATE COLLISION
        MOVEI 1,SLOCKT  ;ACTIVATION ROUTINE
        JSYS EDISMS ;SLEEP
        MOVNI 1,2   ;BACK UP THE PC
        ADDM 1,-2(P)
        SUB P,BHC+1 ;ADJUST STACK
        POP P,1     ;RESTORE AC1
        RET]        ;TRY AGAIN
    HRLZS 1         ;ADDRESS TO LEFT HALF
    HRRI 1,RLOCKT       ;ACTIVATION TEST
    JSYS EDISMS     ;SLEEP
    SUB P,BHC+1
    POP P,1         ;RESTORE AC1
    RET

;ACTIVATION TEST FOR PROCESS WAITING FOR RESIDENT LOCK
RLOCKT: AOSE 0(1)
    JRST 0(4)
    JRST 1(4)

;ACTIVATION TEST FOR PROCESS WAITING FOR A SWAPPABLE LOCK
SLOCKT: SKIPL GLOCK     ;NOW A GOOD TIME TO TRY AGAIN?
    JRST 0(4)       ;NO
    JRST 1(4)       ;YES

;HERE ON UNLOCKING A LOCK ON WHICH A CONFLICT HAS OCCURRED
;NOTE THAT SPECIAL CARE IS TAKEN TO PRESERVE THE STATE OF
;ALL ACS BUT P WHEN THE EFFECTIVE ADDRESS COMPUTATION IS DONE
;TO ADDRESS THE LOCK. THUS, TABLES OF LOCKS MAY BE INDEXED VIA
;ANY AC EXCEPT P.
CNFLCT:: PUSH P,1
    MOVE 1,-1(P)        ;GET PC
    MOVE 1,-2(1)        ;GET THE SOSL THAT DIDN'T SKIP
    EXCH 1,0(P)     ;PUT IT ON THE STACK AND RESTORE 1
    SETOM @0(P)     ;UNLOCK THE LOCK
    PUSH P,1
    HRRZI 1,@-1(P)      ;GET ADDRESS OF LOCK
    CAMGE 1,RESDNT      ;RESIDNET?
     JRST CNFL2     ;YES
    SETOM GLOCK     ;INDICATE A CONFLICT RESOLVED
    SETZM WAITLS        ;REQUEST IMMEDIATE FULL WAITLIST SCAN
    AOS ISKED       ;TO AVOID POSSIBLE DEADLOCK
CNFL2:  POP P,1
    SUB P,BHC+1
    RET

;DISMISS UNTIL WORD .GE. 0

DISGE:  PUSH P,1
    HRLI 1,DISGET       ;GIVEN MON ADDRESS IN 1
DISXE:  MOVS 1,1
    JSYS EDISMS
    POP P,1
    RET

DISGET: SKIPGE 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS UNTIL WORD .L. 0

DISL:   PUSH P,1
    HRLI 1,DISLT
    JRST DISXE

DISLT:  SKIPL 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS UNTIL WORD .G. 0

DISG:   PUSH P,1
    HRLI 1,DISGT
    JRST DISXE

DISGT:  SKIPG 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS UNTIL WORD .LE. 0

DISLE:  PUSH P,1
    HRLI 1,DISLET
    JRST DISXE

DISLET: SKIPLE 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS UNTIL WORD .E. 0

DISE:   PUSH P,1
    HRLI 1,DISET
    JRST DISXE

DISET:  SKIPE 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS UNTIL WORD .N. 0

DISN:   PUSH P,1
    HRLI 1,DISNT
    JRST DISXE

DISNT:  SKIPN 0(1)
    JRST 0(4)
    JRST 1(4)

;DISMISS FOR SPECIFIED TIME JSYS

.DISMS: JSYS MENTR
    JUMPLE 1,MRETN
    CAIL 1,100000       ;LONG OR SHORT TIME?
    JRST TDIS1      ;LONG
    MOVE 2,TODCLK
    ANDI 2,377777
    ADDI 2,0(1)     ;COMPUTE TIME TO RESTART
    CAIGE 1,^D500       ;USE BLOCKW FOR WAIT .L. 500 MS
    SKIPA 1,[BLOCKW]
    MOVEI 1,BLOCKM      ;BLOCKM OTHERWISE
    HRLI 1,0(2)
TDIS2:  JSYS EDISMS     ;DISMISS WITH SPECIFIED TEST
    JRST MRETN

TDIS1:  CAML 1,BITS+^D9
    MOVSI 1,(1B9)       ;APPROX 17 HRS IS MAX PERMITTED
    MOVE 2,TODCLK
    TLZ 2,777000
    ADD 2,1         ;COMPUTE TIME TO RESTART
    LSH 2,-^D10     ;DIVIDE BY 1024
    MOVSI 1,0(2)
    HRRI 1,BLOCKT
    JRST TDIS2      ;GO COMPLETE DISMISSAL

;SCHEDULER WAIT TEST FOR LONG WAIT

BLOCKT: LSH 1,^D10      ;RESTORE WAKEUP TIME TO FULL SIZE
    MOVE 2,TODCLK       ;GET TIME NOW
    TLZ 2,777000
    SUB 1,2         ;DESIRED-NOW = TIME LEFT TO WAIT
BLKT1:  JUMPLE 1,1(4)       ;WAKEUP IF NEGATIVE
    CAMG 1,BITS+^D9     ;VERY LARGE DIFFERENCE?
    JRST 0(4)       ;NO, KEEP WAITING
    SUB 1,BITS+^D8      ;COMPENSATE FOR WRAPAROUND OF TODCLK
    JRST BLKT1      ;CHECK AGAIN

;SCHEDULER

SCHED0: SETZ 0,         ;FOR PEOPLE WATCHING LIGHTS

SCH0:   CONSZ PI,177B27     ;ANY PI IN PROGRESS?
    BUG(HLT,<ENTERED SCHEDULER WITH PI IN PROGRESS>)

IFE JTRPSW-1,<           ;IF MAPPING RES MON FOR TRAPPED FORK
    CONO PGR,6      ;UNMAP IT
    >

    MOVE 1,NBPROC
    ADD 1,NGPROC
    LSH 1,^D9
    ADD 1,NBPROC
    HRLI 0,0(1)     ;DISPLAY NRUN, NBPROC IN LH 0
    MOVEM 0,22      ;AND 22

    MOVSI 16,-NPCLKS    ;SCAN PROCESS CLOCKS
    SKIPG RJQNT(16)     ;EXHAUSTED? (RJQNT IS FIRST IN TABLE)
    XCT PCLKT(16)       ;YES, SERVICE WHATEVER
    AOBJN 16,.-2

    SKIPLE TTBIGC       ;TTY BIG BUFFER NEED SERVICE?
    CALL TTCH7      ;YES
IFDEF TNTCHN,<
    SKIPE TNTFLG##
     CALL TNTCHK##>

    SETZM SKEDF3

    MOVE 4,SCDRQO
    CAME 4,SCDRQI       ;ANY REQUESTS?
    CALL SCDRQ1     ;YES

    SKIPN ISKED     ;ANY REQUESTS FOR RESCHEDULING?
    SKIPE PSKED
     CALL DISMSJ        ;YES

    SKIPE ISKED     ;SCHED REQUEST?
    CALL WTSCAN     ;YES, CHECK WAITING FORKS

    SKIPE PWRDWN        ;POWER FAIL DETECTED?
    JRST SCHPRF     ;YES

    SKIPE 1,20      ;REQUEST FROM SWITCHES?
    JSP 4,SWTST     ;YES

    SKIPGE 7,FORKX      ;JOB TO CONTINUE?
     CALL SKPROC        ;NO, GO FIND ONE. 

SCHED3: SKIPGE 1,FKINT(7)   ;INTERRUPT REQUEST?
    TLNE 1,200000       ;AND NOT ONE IN PROGRESS
    JRST SCHED4
    MOVSI 1,200000      ;CLEAR WORD EXCEPT FOR PI IN PROG
    EXCH 1,FKINT(7)
    MOVEM 1,PIMSK       ;PASS REQUEST WORD TO SERVICE ROUTINE
    MOVEI 1,PIRQ        ;PSEUDO-INTERRUPT SERVICE
IFN KIFLG,<
    TLO 1,(1B6)>     ;SO UXCT GOES TO USER SPACE
    EXCH 1,PPC
    MOVEM 1,PIPC        ;SAVE OLD PC

SCHED4: SETZ 1,
    EXCH 1,JOBRTT

    AOSG BKGFLG     ;COMING OUT OF BACKGROUND?
     JRST [ASH 1,-1     ;YES, CHARGE SCHED AND SWAPWT EQUALLY
        ADDM 1,SWAPWT
        JRST .+1]
    ADDM 1,SKMAIN
    AOS SKMAIN+1

    AOS SKEDF1      ;TELL CH7 TO START PROCESS
    ISB SCDCHN      ;LET IT START PROCESS
    JRST .-1        ;JUST IN CASE ....

    DEFINE ECALL (D)
<IFDEF D'CHN,<EXTERN D'CHK
    CALL D'CHK
    GS D'TIM
>>

;TABLE OF SERVICE CALLS FOR PROCESS CLOCKS

PCLKT:  CALL DISMSJ     ;RUNNING JOB QUANTUM OVERFLOW
BKGNDT: CALL CLK2       ;SECOND LEVEL CLOCK
NPCLKS==.-PCLKT
NBKR==.-BKGNDT      ;THE (LAST) N OF THESE TO RUN ANYTIME

;SECOND PROCESS CLOCK, LESS PRECISE, UPDATES EVERY 100 MS

CLK2:   SKIPL SCDRN1        ;RUNNING SPECIFIC JOB?
     JRST CLK22     ;YES, DON'T DO JOB 0 CHECK

    MOVE 1,TODCLK
    CAMGE 1,CHKTM1      ;JOB 0 NOT RUN FOR TWO MINS.?
     JRST CLK22     ;EVERYTHING'S OKAY

    SKIPGE 7,DDMPFK     ;GET INDEX OF DDMP FORK
     JRST CLK22     ;DOESN'T EXIST

    HLRZ 2,FKQ(7)
    JUMPE 2,CLK23       ;FORK ALREADY ON HI-Q

    HRRZS FKQ(7)        ;PLACE ON Q0

    MOVE 2,FKFLGS(7)    ;SEE IF CURRENTLY ACTIVE
    TLNN 2,RNLS
     JRST CLK23     ;NOT ACTIVE

    CALL CHGRLS         ;MOVE TO NEW RUNLIST
    JRST CLK22

CLK23:  CAML 1,CHKTIM   ;DDMP 4 MIN. OVERDUE?
    BUG(HLT,<JOB 0 NOT RUN FOR TOO LONG, PROBABLE SWAPPING HANGUP>)

CLK22:  MOVEI 15,^D100
    SUBM 15,TIM2        ;ACTUAL TIME SINCE LAST UPDATE
    EXCH 15,TIM2

;UPDATE ACTIVE-PROCESS TIME INTEGRAL
    MOVE 1,NBPROC
    ADD 1,NGPROC
    IMUL 1,15
    ADDM 1,RJTSUM

    MOVN 15,15      ;NEGATE TIME

    MOVSI 14,-N2CLKS    ;SET TO SCAN SECOND LEVEL CLOCKS
CLK21:  ADDM 15,DSKTIM(14)  ;UPDATE CLOCK
    SKIPG DSKTIM(14)    ;COUNTED OUT?
    XCT CLK2CL(14)      ;YES, DO WHATEVER
    AOBJN 14,CLK21

    AOS ISKED       ;DO COMPLETE RESKED AT LEAST THIS OFTEN
IFDEF DIALLN,<
    PUSHJ P,DILCHK##>    ; TEMP FOR BBN AUTODIALLER
    RET

;TABLE OF CALLS FOR SECOND LEVEL CLOCKS

CLK2CL: ECALL DSK       ;DISK RE-QUEUE CHECK
                ;NOTE THAT DSKCHK MUST BE FIRST --
                ;SEE CLK21 ABOVE.
IFDEF IMPCHN,<
    EXTERN IMPCHK
    CALL IMPCHK
    GS IMPTM2,1
>
    ECALL TNT       ;TELENET
    ECALL MTA       ;MAG TAPE
    ECALL PTP       ;PAPER TAPE PUNCH
    ECALL PLT       ;PLOTTER
    ECALL DTA       ;DECTAPE
    ECALL LPT       ;PHYSICAL LPT
    CALL SKDCHK     ;DO PERIODIC SCHEDULER FUNCTIONS
    GS SKDTIM,1

IFN PIESLC,<
    CALL RESORT     ;REORDER COMPUTATION QUEUE
    GS RESTIM,1
> ;END PIE-SLICE SCHED CONDITIONAL

    CALL DORJAV     ;UPDATE LOAD AVS
    GS RJATIM,1
    ECALL DLS       ;DLS (TTY) BACKGROUND STUFF
N2CLKS==.-CLK2CL

RSKP:   AOS 0(P)        ;RETURN (VIA PDL) SKIPPING
R:  RET

PI7P:   IOWD NSKDP,SKDPDL

;UPDATE RUNNABLE JOB AVERAGES

DORJAV: MOVEI 2,^D5000
    MOVEM 2,RJATIM      ;SET TIME OF NEXT UPDATE
    MOVE 4,RJTSUM       ;CURRENT INTEGRAL OF NBPROC+NGPROC
    SUBM 4,RJAVS1       ;DIFFERENCE FROM LAST UPDATE
    EXCH 4,RJAVS1
    FSC 4,233       ;FLOAT IT
    FDVR 4,[5000.0]     ;AVERAGE OVER LAST 5000 MS
    JOV .+1         ;CLEAR OV FLAG
    MOVSI 2,-NRJAVS
SCHC1:  MOVE 3,EXPFF(2)
    FMPRM 3,RJAV(2)     ;SUM*EXP(-T/C) -) SUM
    JOV [   SETZM RJAV(2)   ;THAT MAY HAVE UNDERFLOWED,
        JRST .+1]   ;IF SO, CLEAR IT TO 0
    MOVE 3,4
    FMPR 3,EXPGF(2)
    FADRM 3,RJAV(2)     ;TERM*(1-EXP(-T/C)) + SUM -) SUM
    AOBJN 2,SCHC1
    MOVE 2,RJAV     ;1 MIN AV.
    FADRI 2,(0.5)       ;ROUND
    MULI 2,400      ;FLOAT-TO-FIX
    ASH 3,-243(2)
    MOVEM 3,IRJAV       ;MAINTAIN INTEGER VALUE
    RET

;TABLE OF EXP(-T/C) FOR T = 5 SEC.

EXPFF:  EXP 0.920043902 ;C = 1 MIN
    EXP 0.983471344 ;C = 5 MIN
    EXP 0.994459811 ;C = 15 MIN

;TABLE OF 1-EXP(-T/C) FOR T = 5 SEC

EXPGF:  EXP 0.0799560979    ;C = 1 MIN
    EXP 0.0165286558    ;C = 5 MIN
    EXP 0.00554018893   ;C = 15 MIN

; PERIODIC SCHEDULER FUNCTIONS

;CHECK SUMNRX
SKDCHK: MOVSI 2,-NFKS
    SETZ 1,
SUMLUP: MOVE 3,FKFLGS(2)
    TLNE 3,BLST
    TLNE 3,FORCEM
     SKIPA 4,FKWSP(2)
    MOVE 4,FKNR(2)
    ADDI 1,0(4)
SUMNXT: AOBJN 2,SUMLUP

    PIOFF           ;PREVENT PNDING FROM CHANGING
    ADD 1,PNDING
    CAMN 1,SUMNRX
     JRST SUMEXT

    BUG (NTE,<SUMNRX INCORRECT>)
    MOVEM 1,SUMNRX

SUMEXT: PION

;CHECK PNDING
    SKIPE IOIP      ;DON'T CHECK IF ANY WRITES IN PROGRESS
     JRST UDINT

    MOVE 1,SWPCOR##
    SETZ 2,
    MOVSI 4,(77B5)      ;MASK TO CHECK AGE

PNDCHK: TDNN 4,CST0(1)      ;ON RPLQ??
     JRST PNDCH2        ;YES, DON'T INSPECT FURTHER

    MOVE 3,CST3(1)
    TLC 3,7777
    TLNN 3,7777     ;PAGE UNOWNED AND NOT ON RPLQ?
    ADDI 2,1        ;YES, COUNT IT
PNDCH2: CAME 1,NHIPG##      ;HAVE WE JUST DONE LAST PAGE?
    AOJA 1,PNDCHK       ;NO, DO ANOTHER

    CAMN 2,PNDING       ;PNDING OK?
     JRST UDINT     ;YES

    BUG (CHK,<PNDING INCORRECT>)  ;NO

    SUB 2,PNDING
    ADDM 2,PNDING
    ADDM 2,SUMNRX       ;ADD SAME AMT TO SUMNRX WE JUST ADDED TO PNDING

;UPDATE INTERESTING TIME INTEGRALS
UDINT:  MOVEI 1,^D60000     ;UPDATE EVERY 60 SECONDS
    SUBM 1,SKDTIM
    EXCH 1,SKDTIM

    MOVE 2,NBPROC
    IMUL 2,1
    ADDM 2,BSTSUM       ;INTEGRAL NBPROC DT

    MOVE 2,NBRUN
    IMUL 2,1
    ADDM 2,NBRSUM       ;INTEGRAL NBRUN DT

; GUARD AGAINST POSSIBLE LOCKUP IN NON-RESIDENT LOCK CONFLICT
; RESOLUTION STRATEGY
    SETOM GLOCK     ; WAKE EVERYBODY JUST IN CASE
    SETZM WAITLS        ; A DIRECTORY OR SUBINDEX GOT LOCKED BY 
    AOS ISKED       ; USER CODE WHICH UNLOCKS BUT CAN'T 
                ; SETOM GLOCK

    RET
IFN PIESLC,<

;ROUTINE WHICH PERIODICALLY REORDERS THE COMPUTATION QUEUE

RESORT: STMR            ;TIME THIS ROUTINE

    MOVEI 1,^D1000      ;DO THIS EVERY SECOND
    MOVEM 1,RESTIM
    SETOM OLDSUM
    MOVEI 1,RUNLST+COMPQ    ;INIT SRTBEG
    MOVEM 1,SRTBEG
    MOVEI 1,RUNLSB+COMPQ    ;INIT SRTTMP
    MOVEM 1,SRTTMP

    SETZM PASSES        ;ZERO COUNT OF PASSES PER ENTRY
RESOR3: SETOM EXCHGS        ;ZERO COUNT OF EXCHANGES PER PASS

    MOVE 1,SRTTMP       ;INIT SRTEND
    MOVEM 1,SRTEND

    MOVE 6,SRTBEG       ;PLACE TO START THIS PASS
    HRLOI 5,377777      ;5=LARGEST FLOATING POINT NUMBER
    JRST RESOR8

RESOR6: MOVEI 7,@FKPT6M     ;GET FORKX OF LOWER FORK
    SKIPN PASSES        ;FIRST PASS?
     CALL UPDUT     ;YES, UPDATE UTILIZATION
    CALL CDIST      ;AND GET DISTANCE FROM TARGET
    CAMGE 5,1       ;IN PROPER ORDER?
     JRST RESOR7        ;NO, EXCHANGE THEM
    MOVE 5,1        ;YES, MOVE LOWER FORK'S DIST TO AC5
RESOR8: HRRZ 6,0(6)     ;NEXT FKPT
    CAMN 6,SRTEND       ;LAST?
     JRST RESOR5        ;YES
    JRST RESOR6     ;NO

;EXCHANGE THE FORK POINTED TO BY AC6 WITH THE ONE ABOVE IT.
;DENOTE THE FORK IN 6 BY "C" IN THE SEQUENCE "A,B,C,D".
;WE WISH TO COME OUT WITH "A,C,B,D".

RESOR7:             ;6=C
    HLRZ 3,(6)      ;3=B
    HLRZ 2,(3)      ;2=A
    HRRZ 4,(6)      ;4=D

    MOVEM 3,SRTTMP      ;NEXT PASS MAY END AFTER COMPARING
                ;A&C IF NO EXCHANGES FOLLOW

    AOSN EXCHGS     ;FIRST EXCHANGE THIS PASS?
     JRST [HLRZ 1,(2)   ;YES, NEXT PASS BEGINS BY COMPARING A&C
        JUMPE 1,.+1 ;CAN'T DO IF AT TOP OF LIST
        MOVEM 1,SRTBEG
        JRST .+1]

    HRRM 6,(2)      ;A FORWARD TO C
    HRLZM 2,(6)     ;C BACK TO A

    HRRM 3,(6)      ;C FORWARD TO B
    HRLZM 6,(3)     ;B BACK TO C

    HRRM 4,(3)      ;B FORWARD TO D
    HRLM 3,(4)      ;D BACK TO B

    HRRZ 6,0(6)     ;6 MUST NOW POINT TO B

    JRST RESOR8     ;DONE

RESOR5: SKIPL EXCHGS        ;ANY EXCHANGES THIS PASS?

    AOSA PASSES     ;COUNT PASSES PER ENTRY TO RESORT
    CAIA
    JRST RESOR3
    ETMR SORTIM     ;COMPLETE TIMING OF THIS ROUTINE
    RET

;ROUTINE TO COMPUTE DISTANCE FROM TARGET. LEAVES RESULT IN AC1.
;SMASHES AC2.
CDIST:  HLRZ 2,FKJOB(7)     ;GET JOB NO.
    MOVE 2,PIEGRP(2)    ;GET GROUP INDEX
    MOVE 1,DSHARE(2)
    FDVR 1,NAPROC(2)    ;AC1=TARGET
    FSBR 1,FKUTIL(7)    ;AC1=TARGET-UTIL
    RET

> ;END PIE-SLICE SCHED CONDITIONAL

;TEST WORD DEPOSITED BY SWITCHES IN 20

SWTST:  SETZM 20
    JFFO 1,.+1
    CAIGE 2,NSWTT
    XCT SWTT(2)
SWTST1: JRST 0(4)       ;RESUME SCHEDULER

SWTT:   JRST SWHLT      ;HALT T.S.
    JRST SWRUN1     ;RUN ONLY SPECIFIED JOB
    JRST SWCRSH     ;INITIATE JOB0 FUNCTION
NSWTT==.-SWTT

SWHLT:  CALL DISMSJ     ;DISMISS CURRENT JOB
    PUSH P,DCHKSW
    SETOM DCHKSW
    MOVNI 0,1
    BUG(CHK,<SCHED HALTED>)   ;GO TO DDT
SWHLT1: POP P,DCHKSW
    SKIPE DDTPRS
    JRST .
    JRST SCHED0

SWRUN1: HRREI 1,0(1)        ;-1 OR JOB NUMBER IN RH
    JUMPL 1,SWRUN2      ;-1 MEANS RESTORE TO NORMAL
    CAIGE 1,NJOBS       ;LEGAL JOB NUMBER?
    SKIPGE JOBRT(1)     ;RIGHT HALF OF SWITCHES SPECIFIES JOB
    JRST SWTST1     ;EXCEPT THAT JOB DOESN'T EXIST
SWRUN2: MOVEM 1,SCDRN1      ;ALLOW ONLY THAT JOB TO RUN
    CALL DISMSJ     ;DISMISS CURRENT FORK
    JRST SWTST1

SWCRSH: SETZM NXTDMP        ;DO DDMP
    SETZB 1,DDTIME##    ;HUSTLE UP THE DISK TRICKLER
    AOS JB0FLG      ;DO JOB 0
    JRST SWTST1

;POWER FAIL DETECTED

SCHPRF: CALL DISMSJ     ;FLUSH CURRENT FORK
    MOVE 1,TODCLK       ;WAIT A COUPLE MS FOR IO TO STOP
    ADDI 1,2
    CAMLE 1,TODCLK
    JRST .-1
    PIOFF 610000        ;CLEAR WORLD
    CONO APR,1B19
    SETOM PWRDWN        ;SAYS WE FINISHED PWR DWN SEQUENCE
    JRST 4,.        ;SYSTEM SHOULD BE RESTARTABLE AT SYSRST

;SCHEDULER REQUEST PROCESSOR

;SCDRQ7 CALLED BY ROUTINES HAVING PDL POINTER IN P

SCDRQ7: PIOFF
    JSR SCDRQ
    PION
    RET

;SCDRQ CALLED BY JSR AFTER HAVING TURNED OFF PI SYSTEM

LS SCDRQ,2

SCDRQ0: MOVEM 1,@SCDRQI
    AOS 1,SCDRQI
    CAIE 1,SCDRQB+NSCDRQ
    JRST @SCDRQ
    MOVEI 1,SCDRQB
    MOVEM 1,SCDRQI
    JRST @SCDRQ

;PROCESS SCHEDULER REQUESTS

SCDRQ1: CAMN 4,SCDRQI
    RET
    MOVE 2,0(4)     ;WORD CONTAINS DATA,,DISPATCH ADR
    HLRZ 1,2
    CALL 0(2)
    AOS 4,SCDRQO
    CAIE 4,SCDRQB+NSCDRQ
    JRST SCDRQ1
    MOVEI 4,SCDRQB
    MOVEM 4,SCDRQO
    JRST SCDRQ1

;SCHEDULER REQUESTS
;PUSHJ TO THESE ROUTINES ON SCHED LEVEL WHEN SCDRQ QUEUE NON-EMPTY.
; CALL WITH DATUM IN AC1

;JOB STARTUP ROUTINE. CALL WITH TTY NUMBER OR 0,,-1 IN AC 1

JOBSRT: HRRE 1,1        ;FULL WORD IN CASE DETACHED STARTUP
    MOVE 2,SPTC     ;CURRENT SPT COUNT
    CAIL 2,SSPT-NOFN-20 ;NEARLY FULL?
    JRST JOBSR1     ;YES, DON'T PERMIT LOGIN
    MOVE 2,DRMFRE
    CAIG 2,100      ;ENOUGH FOR THE EXEC, IF NOT A JOB?
    JRST JOBSR2     ;NO
    SKIPN FREJOB        ;ROOM FOR NEW JOB
    JRST JOBSR3     ;NO JOBS
    SKIPN FREFK     ;AND NEW FORK?
    JRST JOBSR4     ;NO
    MOVE 2,@FREJOB      ;GET THE FREE JOB OFF THE FREE LIST
    EXCH 2,FREJOB       ; ..
    SUBI 2,JOBPT        ;CONVERT TO JOB NUMBER FROM LIST ADR
    PUSH P,2        ;SAVE JOB NUMBER
    PUSH P,1        ;TTY NUMBER (FROM TTY SRV)
    CALL ASSFK      ;GET A FORK
    POP P,1
    POP P,2         ;JOB NUMBER AGAIN
    HRLM 2,FKJOB(7)     ;PUT IT IN FORK TABLE, TO RE-FIND JOB NUMBER
    HRLI 1,NEWJBF       ;ADD TTY #, NEWJOB TO B0&NEWFKF
    IORM 1,FKINT(7)     ;LEAVE TTY NUMBER IN RH FOR STARTUP ROUTINE

IFN PIESLC,<
    SKIPE 1,SYSIFG      ;SYSTEM INITIALIZED?
    MOVE 1,DEFGP        ;GET DEFAULT PIE-SLICE GROUP
    MOVEM 1,PIEGRP(2)
> ;END PIE-SLICE SCHEDULER CONDITIONAL

    RET

JOBSR4: MOVEI 2,[ASCIZ / FORKS FULL
/]
    JRST JOBSR0
JOBSR3: MOVEI 2,[ASCIZ / JOBS FULL
/]
    JRST JOBSR0
JOBSR2: MOVEI 2,[ASCIZ / DRUM FULL
/]
    JRST JOBSR0
JOBSR1: MOVEI 2,[ASCIZ / SPT FULL
/]
JOBSR0: JUMPL 1,JOBSRC      ;JUMP IF FROM CRJOB
    EXCH 1,2        ;MSG TO 1, TTY TO 2
    HRRZS 2         ;JUST LINE NUMBER
    HRLI 1,440700       ;STRING POINTER
    CALL TTEMES     ;GIVE USER BAD NEWS
    SETOM TTFORK(2)     ;CLEAR TTY
    RET
JOBSRC: MOVEI 1,CRJBX6##    ;FAIL RETURN FROM CRJOB
    MOVEM 1,CRJANS      ;RETURN IT
    RET

;ASSIGN FORK SLOT

ASSFK:  HRRZ 7,@FREFK
    EXCH 7,FREFK        ;GET FORK, UPDATE LIST
    SUBI 7,FKPT
    MOVEI 1,JSKP
    MOVEM 1,FKSTAT(7)   ;MAKE STATUS RUNNABLE AT NEXT TEST

IFE PIESLC,<
    MOVE 1,QBASE+1
    MOVEM 1,FKQ(7)      ;ESTABLISH QUEUE
> ;END NON-PIE-SLICE SCHEDULER CONDITIONAL

IFN PIESLC,<
    MOVE 1,SOLD
    MOVEM 1,FKSOLD(7)
    SETZM FKUTIL(7)
    SETZM FKPRT(7)
    MOVE 1,TODCLK
    MOVEM 1,FKUDT(7)
    MOVSI 1,NOCNT       ;INITIALIZE FKFLGS
    HLLM 1,FKFLGS(7)
    MOVEI 2,INTERQ
    CALL CHGQ
> ;END PIE-SLICE SCHEDULER CONDITIONAL

    CALL WTCONC     ;PUT ON WAITLIST
    MOVSI 1,400000+NEWFKF
    MOVEM 1,FKINT(7)    ;LEAVE INTERRUPT REQUEST
    MOVEI 2,(7)     ;PSIR4 TAKES FORKX IN AC2
    CALL PSIR4      ;AND GET IT NOTICED
    SETZM FKINTB(7)
    SETZM FKPGS(7)      ;CLEAR PT AND PSB WORD
    SETZM FKJOB(7)
    SETZM FKWSP(7)
    MOVE 1,[XWD 100100,3]   ;INIT AGE TO 100, W.S. TO 3
    MOVEM 1,FKNR(7)
    RET

;PROCESSOR INTERRUPTS REFERRED FROM CHANNEL 1

P7OV:   MOVEI 2,6       ;OVERFLOW, FLOATING OVERFLOW
    JRST P7PI1

P7FOV:  MOVEI 2,7       ;FLOATING OVERFLOW CHANNEL
    JRST P7PI1

P7POV:  MOVEI 2,^D9     ;PDL OVERFLOW
P7PI1:  EXCH 1,2        ;FORK NUMBER LEFT BY APR ROUTINE
    CALL PSIRQ
    RET

MPEINT: MOVEI 2,^D11        ;GIVES IO ERROR INTERRUPT
    JRST P7PI1

;BALANCE SET SCHEDULER
;CALLED TO SELECT PROCESS TO RUN

SKPROC: STMR        ;BEGIN TIMING THIS ROUTINE

    SETZM 21    ;CLEAR JOB RUNNING DISPLAY
    SETZM PSKED

    SKIPE FSHBAL    ;ANYBODY WANT TO FLUSH THE BALANCE SET?
     CALL FSHBS ;YES, GO DO IT

SKPR1:  SKIPGE SSKED        ;ARE WE NOSKED?
    SKIPN NBRUN     ;NO, ANYBODY TO RUN?
SKPR5:  SKIPA 1,NRPLQ       ;NOBODY TO RUN OR NOSKED
     JRST SKPR3     ;SOMEBODY TO RUN, GO DO IT.
    ADD 1,IOIP
    CAMLE 1,NRPMIN
     JRST SKPR3     ;NO

    ADD 1,POTEN     ;ANY POINT IN DOING A GCCOR?
    CAMG 1,NRPMIN
     JRST SKPR4     ;NO, TRY TO REMOVE A PROCESS

    CALL GCCOR      ;YES, TRY GARBAGE COLLECTING

    MOVE 1,NRPLQ        ;DID THAT HELP?
    ADD 1,IOIP
    CAMLE 1,NRPMIN
     JRST SKPR3     ;YES

SKPR4:  CALL RMPROC     ;NO, REMOVE A PROCESS FROM BALANCE SET
     JRST [PUSH P,BSBITS    ;COULDN'T FIND ONE, DO PANIC GCCOR
        SETZM BSBITS    ;MAKE GCCOR COLLECT ALL UNLOCKED PAGES
        CALL GCCOR
        POP P,BSBITS
        AOS PANIC   ;COUNT OCCURRENCES
        JRST SKPR3]

    JRST SKPR5      ;FOUND ONE, RECHECK CORE SITUATION

SKPR3:  SKIPL 7,SSKED   ;ARE WE NOSKED?
     JRST SKPR11        ;YES

    SKIPN 1,NGPROC  ;ANY PROCS WAITING TO ENTER BAL SET?
     JRST SKPR7 ;NO

    SKIPL SCDRN1        ;RUNNING SPECIAL JOB?
     JRST SKPR6     ;CALL LDPROC

    SKIPL 1,OLDSUM      ;VALID OLD SUMNRX?
    CAMLE 1,SUMNRX      ;YES, HAS SUMNRX GOTTEN SMALLER?
SKPR6:  CALL LDPROC ;TRY TO LOAD SOME PROCESSES

SKPR7:  SKIPN 10,NBPROC ;NUMBER OF BALSET PROCS
     JRST BKGND1        ;THERE AREN'T ANY

    MOVSI 11,-<MAXQ+1>        ;INIT QUEUE LIST LOOP AOBJN POINTER

SKPR8:  HRRZ 6,RUNLST(11)   ;GET FKPT ADDRESS FOR FIRST FORK

SKPR9:  CAIN 6,RUNLSB(11)   ;END OF LIST?
     JRST SKPR10        ;YES, GO TO NEXT ONE

;WE HAVE A PROCESS TO LOOK AT, GET ITS FORK INDEX
    MOVEI 7,@FKPT6M ;FORK INDEX TO 7
    HRRZ 6,0(6)     ;GET POINTER TO NEXT PROCESS

    MOVE 2,FKFLGS(7)    ;GET FLAGS

    TLNN 2,BLST ;IS IT IN THE BALANCE SET?
     JRST SKPR9 ;NO, GET NEXT PROCESS

    TLNN 2,WTFK+FORCEM  ;RUNNABLE?
     JRST SKPR2 ;YES, GIVE HIM CONTROL

    CALL WAITT  ;INSPECT WAITING FORK
     JRST [ SOJG 10,SKPR9   ;GO ON TO NEXT PROC, IF ANYMORE IN BS
        JRST BKGND1]    ;NO NEED TO LOOK FURTHER, GO IDLE

    JRST SKPR2

;MOVE ON TO THE NEXT LIST
SKPR10: AOBJN 11,SKPR8      ;ANOTHER LIST?
     JRST BKGND1    ;NO, NOBODY TO RUN

;TEST NOSKED FORK
SKPR11: CALL WAITT      ;NOSKED FORK MUST BE WAITING
     JRST BKGND1        ;STILL WAITING

    SETOM SSKED     ;NO LONGER WAITING, RUN HIM
    ETMR SKBAL
    JRST SETRT

;TEST WAITING BALANCE SET PROCESS

WAITT:  HRRZ 2,FKPGST(7)    ;GET ADDRESS OF TEST ROUTINE
    HLRZ 1,FKPGST(7)    ;GET TEST DATA
    JSP 4,0(2)      ;CALL IT
     RET            ;STILL WAITING

;FORK NO LONGER WAITING
WAITT2: MOVSI 4,WTFK+NOSK
    ANDCAB 4,FKFLGS(7)  ;TURN OFF WAIT AND NOSKED BITS

    AOS NBRUN       ;COUNT RUNNABLE FORKS

    TLNN 4,FORCEM   ;IS THIS A DELAYED FORCED BS REMOVAL?
     JRST WAITT3        ;NO

    HRRZ 1,FKPGST(7)    ;YES
    CAIN 1,SWPINT   ;WAS THIS PROCESS ENTERING BALANCE SET?
     CALL UDNRP     ;YES, CLEAN UP

    CALL REMBSF     ;KICK HIM OUT

    RET

WAITT3: HRRZ 1,FKPGST(7)
    CAIE 1,SWPINT   ;ENTERING BALANCE SET?
     JRST RSKP      ;SKIP RETURN

    SKIPN PRELDF        ;PRELOADING DESIRED?
     JRST WAITT4        ;NO

    HLRZ 1,FKWSP(7) ;IS THERE ENOUGH CORE TO PRELOAD?
    CAMLE 1,NRPLQ
     JRST [SKIPG IOIP   ;NO, FREE PAGES ON THE WAY?
        JRST .+1    ;NO, LOAD AS MUCH AS POSSIBLE NOW
        MOVSI 1,WTFK    ;YES, MAKE HIM WAIT AGAIN
        IORM 1,FKFLGS(7)
        SOS NBRUN
        RET]

    PUSH P,6
    PUSH P,10
    CALL PRELD      ;PRELOAD THE WS
    POP P,10
    POP P,6

WAITT4: CALL UDNRP
    JRST RSKP

;UPDATE FOR PROCESS FINISHED ENTERING BALSET
UDNRP:  SKIPN PRELDF        ;PRELOADING?
    RET

    HLRZ 1,FKWSP(7)     ;PRELOAD SIZE AS DETERMINED BY LDPROC
    MOVN 1,1
    ADDM 1,NRPMIN       ;REMOVE FROM REPLQ RESERVE
    RET

;TEST EDISMS FORK FOR ELAPSED GRACE PERIOD

DISMT:  PUSH P,4    ;SAVE AC4

    MOVE 2,FKFLGS(7)    ;GET FLAG WORD
    TLNE 2,FORCEM       ;DELAYED FORCED REMOVAL?
     JRST DISMT2        ;YES

;SEE IF EDISMS WAIT IS OVER
    MOVE 2,FKSTAT(7)
    HLRZ 1,2
    JSP 4,0(2)  ;CALL ACTIVATION ROUTINE
    CAIA            ;STILL WAITING
    JRST RSKP       ;NO LONGER WAITING

;STILL BLOCKED, SEE IF PSI IS PENDING
DISMT4: SKIPGE 1,FKINT(7)   ;INTERRUPT PENDING?
    TLNE 1,(1B1)        ;AND ACCEPTABLE?
     JRST DISMT3        ;NO, SEE IF GRACE PERIOD IS OVER

    MOVSI 1,PSIWTF      ;YES, REMEMBER FORK WAS WAITING
    IORM 1,FKINT(7)

    JRST RSKP

;SEE IF GRACE PERIOD IS OVER
DISMT3: HLRZ 1,FKPGST(7)
    JSP 4,BLOCKW        ;GRACE PERIOD OVER?
    RET     ;NO

DISMT2: CALL REMBSJ

    RET

;ROUTINE TO DETERMINE WHETHER FORK IS WAITING
; CHECKS FOR FORK ON WAIT-LIST OR BEING HELD IN BALANCE SET
;TAKES FORK INDEX IN AC7. CLOBBERS NO ACS.
;SKIP RETURNS IF FORK IS WAITING, RETURNS +1 OTHERWISE.
CHKWT:: PUSH P,1

    MOVE 1,FKFLGS(7)    ;SEE IF FORK ON WAIT-LIST
    TLNE 1,WTLS
     JRST CHKWT1        ;IT IS, SKIP RETURN

;SEE IF BEING HELD IN BALANCE SET
    TLC 1,WTFK+RNLS+BLST
    TLNE 1,WTFK+RNLS+BLST
     JRST CHKWT2        ;NOT BEING HELD
    HRRZ 1,FKPGST(7)    ;ITS IN BALANCE SET AND NOT RUNNABLE
    CAIN 1,DISMT        ;BEING HELD?
CHKWT1: AOS -1(P)       ;YES
CHKWT2: POP P,1         ;NO, RESTORE AC1
    RET         ;AND RETURN

;GIVE CONTROL TO SELECTED PROCESS

SKPR2:  ETMR SKBAL  ;FINISH TIMING BALANCE SET SCHEDULER

    SKIPL SCDRN1        ;RUN SPECIAL JOB?
     JRST SKDSP     ;YES

SETRT:  HRRZM 7,FORKX
    CALL SETPPG     ;SETUP PAGER FOR THIS PROCESS

IFE PIESLC,<
    HLRZ 2,FKQ(7)       ;SETUP REMAINING TIME
    HRRZ 3,FKQ(7)
    SUB 3,QBASE(2)
> ;END NON-PIE-SLICE SCHEDULER CONDITIONAL

IFN PIESLC,<
    HRRZ 3,FKQ(7)       ;GET REMAINING TIME ON THIS QUEUE
> ;END PIE-SLICE SCHEDULER CONDITIONAL

    MOVEM 3,RJQNT
    SETZM RUNT1
    AOS SOLD+1      ;COUNT TOTAL RESCHEDULES
    HLRZ 1,FKQ(7)       ;QUEUE LEVEL OF FORK
    HLRZ 2,FKJOB(7)     ;JOB NUMBER OF FORK
    CAIG 2,^D32     ;DONT DISPLAY IF GT JOB 32
    IOR 1,BITS(2)       ;BIT POSITION DESIGNATES JOB NUMBER
    MOVEM 1,21      ;FORK NUMBER AND STATUS FOR LIGHTS

IFN KIFLG,<
    JRST SETOVF>     ;SET UP FLAGS IN OTHER MODULE FOR KI10
IFN KAFLG,<
SETOVF: MOVEI 2,5B29+5B32   ;CLEAR OV AND FOV FLAGS
    MOVE 1,PSICHM       ;GET THIS FORKS CHANNEL MASK
    TLNE 1,(1B6)        ;CHANNEL 6?
    TRC 2,6B32      ;YES, ENABLE OVERFLOW
    TLNE 1,(1B7)        ;CHANNEL 7?
    TRC 2,6B29      ;YES, ENABLE FLOATING OVERFLOW
    CONO APR,APRCHN(2)  ;SET APR ACCORDING TO FORGOING
    RET         ;RETURN (+2 IF CALLED FROM SCHED)
>
;RUN SPECIAL JOB ONLY
SKDSP:  HLRZ 1,FKJOB(7)     ;GET SELECTED FORK'S JOB NO.
    CAMN 1,SCDRN1       ;IS IT THE RIGHT JOB?
     JRST SETRT     ;YES

    CALL RMPROC     ;NO, THROW OUT A PROCESS
    BUG (HLT,<COULDN'T FLUSH BAL SET FOR SPECIAL JOB>)

;BACKGROUND ACTIVITIES, IF NO PROCESS TO RUN

BKGND1: SKIPGE BKGFLG
    JRST .+3    ;ALREADY BACKGROUND
    JSP 4,ETIME ;FINISH TIMING ROUTINE
    ADDM 1,JOBRTT   ;BUT ADD BACK INTO JOBRTT SO SWAPWT GETS CHARGED

    MOVSI 16,-NBKR      ;PERFORM ANY PERIODIC ROUTINES
    XCT BKGNDT(16)      ;WHICH CAN BE RUN MORE OFTEN THAN
    AOBJN 16,.-1        ;WHEN THEIR CLOCK RUN OUT

    SETZ 1,
    EXCH 1,JOBRTT       ;GET TIME SINCE LAST UPDATE
    SKIPG 2,NBPROC      ;ANYONE IN BALANCE SET?
    IOR 2,NGPROC        ;OR WAITING TO ENTER BAL SET?
     JUMPE 2,[ADDM 1,IDLE   ;NO .. TREAT AS IDLE TIME
        SKIPL BKGFLG
        AOS IDLE+1
        JRST BKGND2]

    SKIPL SSKED     ;NOSKED?
     JRST [SKIPG NBRUN  ;ANYBODY RUNNABLE?
        JRST .+1    ;NO ,CHARGE TO IOWT
        ADDM 1,NSKWT    ;CHARGE TO NOSKED WAIT
        SKIPL BKGFLG
        AOS NSKWT+1
        JRST BKGND2]

    ADDM 1,SWAPWT       ;CHARGE TO SWAPWT
    SKIPL BKGFLG
    AOS SWAPWT+1

BKGND2: SETOM BKGFLG        ;INDICATE BACKGROUND
    SETZM WAITLS        ;INSURE FULL SCAN OF WAIT LIST

    SKIPE CGFLG     ;EXPLICIT GCCOR REQUEST?
     CALL GCCOR     ;YES

SCHRST: MOVE P,PI7P     ;REINITIALIZE STACK POINTER
    AOJA 0,SCH0     ;TRY AGAIN

;CORE OVER RESERVED, SELECT PROCESS TO REMOVE
;SELECT PROCESS WITH LOWEST PRIORITY THAT CAN BE REMOVED

RMPROC: SKIPL SCDRN1        ;ARE WE RUNNING SCECIAL JOB?
     JRST RMPR2     ;YES, DONT CHECK FOR ONE PROCESS

    MOVE 1,NBPROC
    CAIG 1,1    ;ONLY ONE PROCESS IN BALSET?
     JRST RMPR1 ;YES, MUST BE A BIG ONE

RMPR2:  MOVEI 1,MAXQ

RMPR3:  HLRZ 6,RUNLSB(1)

RMPR4:  CAIN 6,RUNLST(1)    ;END OF LIST?
     JRST RMPR5     ;YES, GO TO NEXT ONE

    MOVEI 7,@FKPT6M     ;GET FORK INDEX
    HLRZ 6,0(6)     ;GET POINTER TO NEXT FKPT

    MOVE 2,FKFLGS(7)
    TLNN 2,BLST     ;IS THIS A BAL SET PROCESS?
     JRST RMPR4     ;NO

    SKIPL SCDRN1    ;ARE WE RUNNING A SPECIAL JOB?
     JRST [HLRZ 10,FKJOB(7)
        CAMN 10,SCDRN1  ;IS THIS THE RIGHT ONE?
        JRST RMPR4      ;YES, DONT KICK HIM OUT
        JRST .+1]

    TLNE 2,NOSK     ;CAN THIS GUY BE KICKED OUT?
     JRST RMPR4 ;NO

    TLNE 2,FORCEM       ;WAITING FOR FORCED BALSET REMOVAL?
     JRST [PUSH P,1     ;YES, TRY TO KICK HIM OUT
        CALL WAITT
        POP P,1
        JRST RMPR4]

    TLNE 2,WTFK ;IS THIS FORK WAITING?
     JRST [MOVSI 10,FORCEM+WTCLCT   ;INDICATE FORCED OUT AND AWAITS COLLECTION
        IORM 10,FKFLGS(7)
        HRRZ 10,FKWSP(7)
        ADDM 10,POTEN   ;INCREASE POTENTIALLY COLLECTABLE
        HRRZ 2,FKNR(7)
        SUBI 10,0(2)
        ADDM 10,SUMNRX
        AOS NREMJ
        MOVE 2,FKCNO(7)
        MOVE 2,BITS(2)
        ANDCAM 2,BSBITS     ;BAL SET MASK FOR GCCOR
        JRST RSKP]

    CALL REMBSF     ;KICK HIM OUT NOW

    AOS NREMJ

    JRST RSKP

;END OF LIST, GO TO NEXT ONE
RMPR5:  SOJGE 1,RMPR3
    RET

RMPR1:  MOVE 1,NRPLQ    ;TIGHT ON CORE?
    CAMLE 1,NRPMIN
     RET        ;NO, LEAVE BALANCE SET AS IS

    JRST RMPR2

;REMOVE PROCESS FROM BALANCE SET

REMBSF: TDZA 3,3
REMBSJ: SETO 3,
    MOVE 1,FKFLGS(7)    ;SEE IF DELAYED FORCED REMOVAL
    TLNE 1,FORCEM
     JRST REMBS4        ;IT IS, FOLLOWING HAS BEEN DONE

    TLO 1,WTCLCT        ;INDICATE AWAITS COLLECTION
    MOVEM 1,FKFLGS(7)
    HRRZ 2,FKWSP(7) ;DECREASE SUMNRX BY FKNR-FKWSP
    ADDM 2,POTEN    ;INCREASE POTENTIALLY COLLECTABLE
    MOVE 1,FKNR(7)
    SUBI 2,0(1)
    ADDM 2,SUMNRX
    MOVE 2,FKCNO(7)
    MOVE 2,BITS(2)
    ANDCAM 2,BSBITS     ;MASK FOR GCCOR

;IF NEITHER PRELOADING NOR POSTPURGING, AVOID POSTPG CALL
REMBS4: SKIPN 1,PRELDF
    IOR 1,POSPGF
    JUMPE 1,REMBS3
    PUSH P,10
    PUSH P,6
    PUSH P,3
    MOVE 1,3
    CALL POSTPG
    POP P,3
    POP P,6
    POP P,10

REMBS3: HLRZ 2,FKJOB(7)     ;MAINTAIN SUBSYS INFO
    HRRZ 2,JOBNAM(2)
    HRRZ 1,FKNR(7)
    HRLI 1,(1B14)       ;COUNT REMOVALS FROM BALSET
    ADDM 1,SWAKES(2)    ;INTEGRATE SIZE

    MOVSI 2,-PLKV

    SKIPN INDFLG        ;ARE WE LOCKING JSB'S?
     JRST REMB1     ;NO

    HRRZ 1,FKJOB(7) ;GET SPT INDEX OF JSB
    MOVE 1,SPT(1)   ;GET CORE INDEX
    ADDM 2,CST1(1)  ;REDUCE LOCK COUNT

REMB1:  HRRZ 1,FKPGS(7) ;PSB SPT INDEX
    MOVE 1,SPT(1)
    ADDM 2,CST1(1)  ;UNLOCK PSB

    HLRZ 1,FKPGS(7) ;UPT SPT INDEX
    JUMPE 1,.+3     ;NO UPT
    MOVE 1,SPT(1)
    ADDM 2,CST1(1)  ;UNLOCK UPT

    MOVSI 1,BLST+FORCEM
    ANDCAB 1,FKFLGS(7)  ;TURN OFF BAL SET BIT

    TLZE 1,WTFK     ;WAITING FORK?
    JRST [ANDM 1,FKFLGS(7)  ;TURN OFF WAIT BIT
        JRST REMBS2]
    SOS NBRUN       ;WE ARE REMOVING A RUNNABLE FORK

REMBS2: SOS NBPROC

    JUMPGE 3,[AOS NGPROC
        RET]

;REMOVE PROCESS FROM RUNLST AND PLACE ON WTLST
    CALL REMRUN

    SETOM OLDSUM        ;INDICATE OLDSUM INVALID

    MOVE 1,FKCNO(7)
    MOVE 1,BITS(1)
    ANDCAM 1,RLBITS     ;MAINTAIN MASK FOR GCCOR

IFN PIESLC,<
    MOVE 1,FKFLGS(7)
    TLNE 1,NOCNT        ;ARE WE TO DECREMENT NAPROC?
     JRST REMBS5        ;NO
    HLRZ 1,FKJOB(7)
    MOVE 1,PIEGRP(1)    ;GET PIESLICE GROUP INDEX
    MOVSI 2,(-1.0)
    FADRM 2,NAPROC(1)
REMBS5:
> ; END PIE-SLICE SCHEDULER CONDITIONAL

    HRRZ 1,0(P) ;SEE IF WE SHOULD DO WTCONC CALL
    CAIE 1,HLTFK3   ;IF FROM HLTFK3 DONT DO IT
    CALL WTCONC
    RET

;ADD PROCESS TO BALANCE SET IF POSSIBLE

LDPROC: STMR        ;BEGIN TIMING THIS ROUTINE

    CALL SCDRUN     ;SELECT BEST RUNNABLE FORK

    JRST LDPRT  ;RETURN IF NONE FOUND

;PROMOTE TO THE BALANCE SET
LDPR2:  MOVE 3,NRPLQ        ;SEE IF ENOUGH PAGES ON RPLQ TO LOAD PSB&UPT
    CAIGE 3,MINNR
     JRST [ADD 3,IOIP   ;ADD WRITES IN PROGRESS
        CAIL 3,MINNR    ;NNOW ENOUGH?
        JRST LDPRT      ;YES, JUST RETURN WITHOUT GCCOR
        PUSH P,7
        CALL GCCOR  ;TRY TO GET SOME PAGES
        POP P,7
        MOVE 3,NRPLQ    ;NOW TEST
        CAIGE 3,MINNR   ;ENOUGH NOW?
        JRST LDPRT      ;NO
        JRST LDPR3]

LDPR3:  HRRZ 1,FKNR(7)
    SKIPE 2,FKWSP(7)
    SUBI 1,0(2)     ;FKNR-FKWSP
    ADDM 1,SUMNRX       ;UPDATE SUMNRX

    MOVSI 10,BLST+WTFK+ZIFA ;INDICATE WAITING, BAL SET FORK AND REQUEST
            ;CLEAR OF IFAV

    IORB 10,FKFLGS(7)

    TLNN 10,WTCLCT      ;FORK BEEN GCCOR'D?
     JRST .+3
    MOVNI 2,0(2)        ;NO
    ADDM 2,POTEN

    SETOM OLDSUM        ;INDICATE OLDSUM INVALID
    SOS NGPROC
    AOS NBPROC

    SKIPN PRELDF        ;PRELOADING WANTED?
     JRST LDPR4     ;NO

;NOTE THAT IT IS ASSUMED HERE THAT FKNR-FKWSP IS STILL IN AC1
;LEFT OVER FROM LDPR3!!
    HRLM 1,FKWSP(7) ;SAVE
    ADDM 1,NRPMIN   ;MAKE SURE WE HAVE THEM ON RPLQ

LDPR4:  HRRZ 1,FKPGS(7)     ;GET PSB INTO CORE
    JUMPN 1,.+3
    CALL ASSPT      ;NO PSB, GO ASSIGN ONE
    HRRZM 1,FKPGS(7)
    CALL SWPIN0
    HLRZ 1,FKPGS(7)
    JUMPN 1,.+3
    CALL ASSPT      ;NO UPT, ASSIGN ONE
    HRLM 1,FKPGS(7)
    CALL SWPIN0
    HRRZ 1,FKJOB(7)     ;GET JSB SPTN
    JUMPN 1,.+3     ;IS ONE?
    CALL ASSPT      ;NO, ASSIGN FOR NEW JOB
    HRRM 1,FKJOB(7)
    SKIPE INDFLG        ;LOCKING JSB'S?
    CALL SWPIN0     ;SWAP IT IN AND LOCK IT
    MOVEI 1,SWPINT
    MOVEM 1,FKPGST(7)   ;SET TEST TO WAIT FOR PSB AND PT

    MOVE 1,FKCNO(7)
    MOVE 1,BITS(1)
    IORM 1,BSBITS       ;MAINTAIN MASK FOR GCCOR

    ETMR SKLDPR     ;FINISH TIMING THIS ROUTINE
    RET         ;AND EXIT

LDPRT:  ETMR SKLDPR     ;COMPLETE TIMING OF THIS ROUTINE
    RET         ;NO SKIP RETURN

;SELECT PROCESS TO PROMOTE TO BALANCE SET

SCDRUN: MOVSI 1,-<MAXQ+1>
    SETZ 4,     ;4 USED TO ACCUMULATE NR OF BALSET PROCS
SCDR1:  MOVEI 6,RUNLST(1)   ;ADDRESS OF CURRENT LIST POINTER
SCDR2:  HRRZ 6,0(6) ;GET ADDRESS OF NEXT FKPT ENTRY

    CAIN 6,RUNLSB(1)    ;END OF LIST?
     JRST SCDR3     ;YES, GO TO NEXT ONE

    MOVEI 7,@FKPT6M ;GET FORK INDEX

    MOVE 2,FKFLGS(7)    ;GET FLAG WRD

    TLNE 2,BLST ;IN BALANCE SET?
     JRST [MOVE 2,FKNR(7)   ;YES, ACCUMULATE NR
        ADDI 4,0(2)
        JRST SCDR2]

    SKIPL SCDRN1    ;RUNNING A SPECIAL JOB?
     JRST [HLRZ 10,FKJOB(7) ;YES, ONLY LOAD ITS FORKS
        CAME 10,SCDRN1
        JRST SCDR2  ;THIS FORK NOT PART OF SPEC. JOB
        JRST .+1]

;SEE IF HE CAN ENTER BY DISPLACING LOWER PRIORITY BALSET FORKS
    HRRZ 5,FKNR(7)      ;SPACE HE WILL NEED
    SKIPE 3,FKWSP(7)    ;SPACE HE CURRENTLY OCCUPIES
    SUBI 5,0(3)     ;DEDUCT FROM FKNR IF NON-ZERO
    ADDI 5,0(4)     ;PLUS SPACE REQUIRED BY HIGHER PRIORITY FORKS
    CAMGE 5,MAXNRX      ;DOES THIS EXCEED MAXNRX?
     JRST RSKP      ;NO, OKAY TO LOAD
SCDR4:  MOVE 5,SUMNRX       ;YES, SAVE SUMNRX
    MOVEM 5,OLDSUM
    RET

;NEXT LIST
SCDR3:  AOBJN 1,SCDR1
    JRST SCDR4

;LIST MAINTENANCE SUBROUTINES

; REMOVE FORK FROM CURRENT LIST
REMOVE: MOVE 3,FKPT(7)
    HLLM 3,(3)      ;NEXT POINTS BACK TO PREVIOUS
    MOVS 3,3
    HLRM 3,(3)      ;PREVIOUS POINTS FORWARD TO NEXT
    RET

; INSERT FORK ON LIST
;RH(3) POINTS TO EXISTING ENTRY ON LIST. FORK WILL BE INSERTED ABOVE IT.
INSERT: HRLI 3,FKPT(7)      ;LH(3)=ADDRESS(NEW FKPT)
    HLRZ 4,(3)      ;RH(4)=ADDRESS(OLD FKPT)
    HLLM 3,(3)      ;TAIL POINTS BACK TO NEW
    HLRM 3,(4)      ;OLD POINTS FORWARD TO NEW
    HRLZM 4,FKPT(7)     ;NEW POINTS BACK TO OLD
    HRRM 3,FKPT(7)      ;NEW POINTS FORWARD TO TAIL
    RET

; REMOVE A FORK FROM THE RUNLIST IT IS ON
REMRUN: CALL REMOVE
    MOVSI 3,RNLS
    ANDCAM 3,FKFLGS(7)
    RET

; REMOVE FORK FROM WAITLIST
REMWT:  CALL REMOVE
    MOVSI 3,WTLS
    ANDCAM 3,FKFLGS(7)
    RET

; APPEND FORK TO RUNLIST
APPRUN: HLRZ 3,FKQ(7)
    MOVEI 3,RUNLSB(3)
    CALL INSERT
    MOVSI 3,RNLS
    IORM 3,FKFLGS(7)
    RET

; APPEND FORK TO WAITLIST
APPWT:  MOVEI 3,WTLSTB
    CALL INSERT
    MOVSI 3,WTLS
    IORM 3,FKFLGS(7)
    RET

;REMOVE FORK FROM CURRENT RUNLIST AND PLACE ON RUNLIST SPECIFIED BY
;CURRENT CONTENTS FOR FKQ
CHGRLS: CALL REMOVE     ;REMOVE FROM CURRENT LIST
    HLRZ 3,FKQ(7)       ;QUEUE

IFN PIESLC,<
    CAIN 3,COMPQ        ;GOING TO COMPQ?
    JRST REQUE      ;PLACE ON COMPQ
> ;END PIE-SLICE CONDITIONAL

    MOVEI 3,RUNLSB(3)   ;TAIL POINTER
    CALL INSERT
    RET

IFN PIESLC,<
;TRANSPARENT ROUTINE TO INSERT A FORK IN ITS PROPER PLACE ON THE COMPQ
REQUE:  ADD P,BHC+7
    JUMPGE P,MSTKOV##
    MOVEM 7,-6(P)
    MOVEM 6,-5(P)
    MOVEM 5,-4(P)
    MOVEM 4,-3(P)
    MOVEM 3,-2(P)
    MOVEM 2,-1(P)
    MOVEM 1,(P)

    CALL UPDUT      ;UPDATE FORK'S UTIL AVERAGE
    CALL CDIST      ;GET DISTANCE FROM TARGET
    MOVE 5,1        ;SAVE IT IN AC5

    MOVEI 6,RUNLST+COMPQ    ;PREPARE TO CHASE DOWN COMPQ
REQUE1: HRRZ 6,0(6)     ;NEXT FORKPT
    CAIN 6,RUNLSB+COMPQ ;ARE WE AT THE END?
     JRST REQUE2        ;YES
    MOVEI 7,@FKPT6M     ;NO
    CALL CDIST      ;GET THIS FORK'S DISTANCE FROM TARGET
    CAMGE 5,1       ;SAVED DISTANCE .GE. THIS ONE?
     JRST REQUE1        ;NO, DON'T WANT TO PUT HIM HERE

REQUE2: MOVE 3,6        ;GET FKPT TO INSERT AT
    MOVE 7,-6(P)        ;AND FORKX WE SAVED
    CALL INSERT     ;AND STUFF HIM ONTO COMPQ

    MOVE 1,(P)
    MOVE 2,-1(P)
    MOVE 3,-2(P)
    MOVE 4,-3(P)
    MOVE 5,-4(P)
    MOVE 6,-5(P)
    SUB P,BHC+7
    RET
> ;END PIE-SLICE SCHED CONDITIONAL

;PUT PROCESS ON THE WAITLIST

WTCONC: MOVE 1,TODCLK       ;SAVE TIME FORK WAS PUT INTO WAITING
    MOVEM 1,FKPGST(7)

    HRRZ 1,FKWSP(7)
    JUMPN 1,WTCON1      ;NO PAGES IN CORE?
    HRRZ 1,FKCNO(7)     ;YES, HAS CORE NUMBER?
    JUMPE 1,WTCON1
    MOVE 1,BITS(1)      ;YES, DEASSIGN IT
    IORM 1,FRECB
    HLLZS FKCNO(7)
WTCON1: CALL APPWT          ;APPEND TO WAITLIST

    RET

;PERFORM WAIT LIST SCAN FOR UNBLOCKED PROCESSES
WTSCAN: STMR        ;TIME WAIT LIST SCAN

    SETZM ISKED
    SETZM WAITFS        ;RESET FULL-SCAN FLAG
    MOVE 6,TODCLK       ;GET TIME-NOW
    SUB 6,WAITLS        ;LESS TIME OF LAST FULL SCAN
    CAIL 6,^D750        ;HAS IT BEEN 3/4 SEC OR MORE?
     JRST   [AOS WAITFS ;YES, INDICATE FULL SCAN DESIRED
        ADDM 6,WAITLS   ;WAITLS=TIME NOW
        JRST .+1]

    HLRZ 6,WTLSTB       ;BEGIN BACKWARDS SCAN
WTSCN1: CAIN 6,WTLST        ;DONE YET?
     JRST WTSCN2        ;YES
    MOVEI 7,@FKPT6M     ;FORK INDEX

    HLRZ 6,0(6)     ;GET FKPT ADDRESS OF NEXT ENTRY

    MOVE 1,TODCLK       ;TIME NOW
    SUB 1,FKPGST(7)     ;LESS TIME THIS PROCESS ENTERED WAIT LIST
    CAIL 1,^D3000       ;HAS HE BEEN THERE 3 SEC OR MORE?
     JRST [SKIPN WAITFS ;FULL SCAN REQUIRED?
        JRST WTSCN2 ;NO, TERMINATE THE SCAN HERE
        JRST .+1]   ;YES, CONTINUE

    SKIPGE 1,FKINT(7)   ;INTERRUPT REQUESTED?
    JRST [  TLNE 1,(1B1)    ;AND ACCEPTABLE?
        JRST .+1    ;NOW DEFERRING...
        MOVSI 1,PSIWTF
        IORM 1,FKINT(7) ;REMEMBER WAS IN WAIT STATE
        IFN PIESLC,<CALL INCNAP>
        CALL PSIAWK ;AWAKEN WITHOUT NEWST
        JRST WTSCN1]    ;NEXT FORK
    MOVE 2,FKSTAT(7)    ;GET ACTIVATION TEST
    HLRZ 1,2
    JSP 4,0(2)      ;CALL TEST ROUTINE
    JRST WTSCN1     ;NO SKIP => STILL NOT RUNNABLE
    CALL AWAKEN     ;FORK IS ARISING

    JRST WTSCN1     ;NEXT ONE

WTSCN2: ETMR SKWAIT     ;FINISH TIMING WAITLIST SCAN
    RET

;TRANSPARENT CALL TO AWAKEN.
PRWAKE:: PUSH P,1
    PUSH P,2
    PUSH P,3
    PUSH P,4
    CALL AWAKEN
    POP P,4
    POP P,3
    POP P,2
    POP P,1

    RET

;ROUTINE TO MOVE FORK FROM WAIT LIST TO RUNLIST. FORKX IN 7.
;ALTERNATE ENTRY POINT 'PSIAWK' USED FOR SLEEPING FORK THAT'S BEEN
;PSI'D.
AWAKEN:
IFN PIESLC,<CALL INCNAP>
    AOS WAKEUP      ;NOW RUNNABLE, COUNT UNBLOCKS
    CALL NEWST      ;ESTABLISH NEW QUEUE STATUS
PSIAWK: MOVSI 1,HOLD        ;PREPARE TO SEE IF THIS FORK
                ;SHOULD BE HELD IN BALSET AFTER NEXT
    MOVE 2,TODCLK       ;DISMISS
    SUB 2,FKPGST(7)     ;AC2=DURATION OF DISMISS
    CAMLE 2,MAXBSH      ;LESS THAN OR EQUAL TO MAXBSH?
    ANDCAB 1,FKFLGS(7)  ;NO, TURN OFF HOLD BIT
    IORB 1,FKFLGS(7)    ;YES, SET HOLD BIT. NOP IF WE FALL
                ;THRU FROM ANDCAB.
    TLZN 1,WTLS     ;MAKE SURE THIS GUY'S ON WAITLIST
    BUG (HLT,<ATTEMPT TO WAKE-UP FORK NOT ON WAITLIST>)
    TLO 1,RNLS      ;TURN ON RUNLIST BIT
    MOVEM 1,FKFLGS(7)   ;AND STUFF THE WHOLE THING IN FKFLGS

;PLACE PROCESS ON PROPER QUEUE LIST

    CALL CHGRLS     ;PLACE ON RUNLIST
    AOS NGPROC
    SETOM OLDSUM        ;INDICATE OLDSUM INVALID

;ASSIGN A CORE NUMBER
    HRRZ 2,FKCNO(7)
    JUMPN 2,[MOVE 2,BITS(2) ;ALREADY ASSIGNED
        IORM 2,RLBITS   ;MASK FOR GCCOR
        RET]

    MOVE 2,FRECB
    JFFO 2,.+2      ;NONE AVAILABLE
    MOVEI 3,^D35        ;YES, USE 35
    MOVE 2,BITS(3)
    ANDCAM 2,FRECB
    IORM 2,RLBITS       ;MASK FOR GCCOR
    HRRM 3,FKCNO(7)

    RET

IFN PIESLC,<
; ROUTINE TO INCREMENT NAPROC

INCNAP: MOVE 1,FKFLGS(7)
    TLNE 1,NOCNT        ;ARE WE TO INCREMENT NAPROC
     RET            ;NO
    HLRZ 1,FKJOB(7)     ;GET JOB NUMBER
    MOVE 1,PIEGRP(1)    ;GET PIE-SLICE GROUP INDEX
    MOVSI 2,(1.0)       ;UPDATE ACTIVE PROCESS COUNT
    FADRM 2,NAPROC(1)
    RET
> ;END PIE-SLICE SCHEDULER CONDITIONAL

;DISMISS JOB FOR RESCHEDULING OR ON QUANTUM OVERFLOW

DISMSJ: SKIPGE 7,FORKX
     RET            ;NOTHING IN FORKX
    CALL SAVRT      ;UPDATE QUEUE AND QUANTUM
    SETZ 1,
    JRST SCHP2

;UPDATE QUEUE NUMBER AND TIME USED VALUE

SAVRT:  MOVE 1,IFTIM        ;HAS HE GONE .5 SEC. WITHOUT FAULTING?
    CAIGE 1,^D500
     JRST SAVRT2        ;NO

    MOVE 1,FKFLGS(7)
    TLOE 1,NOFLT        ;HAVE WE ALREADY DONE FKNR ADJUSTMENT?
     JRST SAVRT2        ;YES, DON'T KEEP DOING IT

    MOVEM 1,FKFLGS(7)   ;REPLACE FKFLGS WITH NOFLT BIT ON

    HRRZ 1,FKNR(7)      ;MAKE FKNR=FKWSP+1
    HRRZ 2,FKWSP(7)
    SUBI 2,-1(1)
    ADDM 2,SUMNRX
    ADDM 2,FKNR(7)

SAVRT2: HLRZ 2,FKQ(7)       ;CURRENT QUEUE NUMBER
    HRLOI 1,377777      ;LARGEST NUMBER
    EXCH 1,RJQNT        ;GET REMAINING QUANTUM
    MOVE 3,RUNT1        ;RUNTIME THIS RUNNING
    ADDM 3,QSUM(2)      ;ACCUMULATE STATISTIC
    JUMPG 1,DISMJ1      ;NOT EXHAUSTED
    SETOM OLDSUM        ;INDICATE OLDSUM INVALID

IFE PIESLC,<
    MOVE 1,JOBNO
    SKIPE JOBCK0        ;GUARANTEE WORDS INIT'ED?
    JRST SAVRT7     ;YES, GO CHECK CURRENT PERFORMANCE
    MOVE 3,TODCLK       ;INITIALIZE TIME QUARANTEE WORDS
    MOVEM 3,JOBCK0
    MOVE 3,JOBRT(1)
    MOVEM 3,JOBCK1
SAVRT7: LDB 3,[POINT 7,JOBBIT,17] ;GET GUARANTEE PERCENTAGE
    JUMPE 3,SAVRT8      ;NOT SPECIAL
    LDB 4,[POINT 6,JOBBIT,23]   ;DOES FORK HAVE TEMP MINQ?
    JUMPN 4,SAVRT8      ;YES, IGNORE PCT. PRIORITY
    MOVE 4,JOBRT(1)     ;COMPUTE RUNTIME DURING TEST INTERVAL
    SUB 4,JOBCK1
    MOVE 1,TODCLK       ;COMPUTE REAL TIME OF TEST INTERVAL
    SUB 1,JOBCK0
    IMULI 4,^D100       ;COMPUTE RUNTM*100%/PCT TO GET
    IDIV 4,3        ;EXPECTED REAL TIME
    SUB 4,1         ;EXCESS OR DEFICIT OF REAL TIME
    MOVEM 4,23      ;FOR LIGHT WATCHERS
    HRLM 2,23       ; ..
    JUMPGE 4,SAVRT5     ;.G. 0 MEANS BETTER THAN GUARANTEE
    MOVEI 2,0       ;KEEP ON QUEUE 0 TO GET EXCLUSIVE TIME
    JRST SAVRT9

SAVRT8: LDB 3,[POINT 6,JOBBIT,35] ;GET MAX Q FOR THIS JOB
    CAIL 2,0(3)     ;IF REACHED MAX Q, AND 
    JUMPN 3,[LDB 2,[POINT 6,JOBBIT,29] ;PRIORITY WORD NOT 0,
        MOVEM 7,23  ;FOR DISPLAY
        JRST SAVRT9]    ;SET NEW Q AS SPECIFIED
    CAIGE 2,MAXQ        ;NOW ON MAX Q?
    AOJA 2,SAVRT9       ;NO, GO TO NEXT ONE
SAVRT9: LDB 3,[POINT 6,JOBBIT,23]   ;TEMP MINQ??
    JUMPE 3,.+3     ;NO
    CAILE 2,0(3)        ;IS NEW QUEUE HIGHER OR SAME?
    MOVEI 2,0(3)        ;NO, USE MINQ
    MOVSM 2,FKQ(7)
    CALL CHGRLS     ;APPEND FORK TO APPROPRIATE RUN LIST
    SKIPA 1,QBASE+1(2)  ;NEXT QUEUE TIME
DISMJ1: ADD 1,QBASE(2)      ;INCREASE BY BASE TIME
DISMJ2: HRRM 1,FKQ(7)
    RET

SAVRT5: CAIGE 4,LOWQT   ;IS HE AHEAD BY MORE THAN LOWQT?
     JRST SAVRT8    ;NO

    LDB 2,[POINT 6,JOBBIT,35]   ;GET MAXQ FOR THIS FORK
    SKIPE 2     ;NO MAXQ
    CAIL 2,MAXQ
    MOVEI 2,MAXQ
    MOVSM 2,FKQ(7)
    CALL CHGRLS
    MOVE 1,QBASE+1  ;GIVE HIM HI-Q QUANTUM
    JRST DISMJ2

;THIS ROUTINE ACCEPTS AN ARGUMENT IN AC1. IF THIS ARGUMENT IS
;NON-ZERO, THE CALLING FORK IS PLACED ON QUEUE 0. IN ANY CASE,
;THE ARGUMENT IS DEPOSITED IN JOBBIT IN THE SIX BIT BYTE ENDING AT
;BIT 23. THE SCHEDULER TREATS THIS BYTE AS A MINIMUM QUEUE IF NON-ZERO.
;THIS ROUTINE IS INTENDED FOR USE BY MONITOR ROUTINES WHICH ARE
;ABOUT TO USE A CRITICAL RESOURCE (SUCH AS LOCKING A DIRECTORY)
;AND WANT TO INSURE PROMPT COMPLETION.
STMINQ:: DPB 1,[POINT 6,JOBBIT,23]  ;STASH THE ARG
    JUMPE 1,R       ;IF ZERO NOTHING MORE TO DO.

    PUSH P,7        ;SAVE AC7
    MOVE 7,FORKX        ;GET INDEX OF THIS FORK

    NOSKED
    HRRZS FKQ(7)        ;PUT HIM ON Q 0.
    PUSH P,3
    PUSH P,4
    CALL CHGRLS     ;PUT HIM ON Q0 RUNLIST
    POP P,4
    POP P,3
    MOVE 1,QBASE+1      ;GET Q0 QUANTUM
    HRRM 1,FKQ(7)       ;STASH IN FKQ
    MOVEM 1,RJQNT       ;AND IN RJQNT
    OKSKED

    POP P,7         ;RESTORE 7
    RET

> ;END NON-PIE-SLICE SCHEDULER CONDITIONAL

IFN PIESLC,<
    MOVE 3,FKFLGS(7)
    TLNE 3,HIQFK        ;FORK NEED SPECIAL SCHEDULING?
     JRST [TLNE 3,SPQFK ;SPECIAL QUEUE?
        SKIPA 2,[SPECQ] ;YES
        MOVEI 2,INTERQ  ;NO
        TLZE 3,PHIQFK   ;FORGET THAT HE WAS ORIGINALLY ON INTERQ
        MOVEM 3,FKFLGS(7)
        CALL CHGQ   ;SET UP FKQ
        CALL CHGRLS
        RET]

    MOVEI 2,COMPQ       ;HE GOES ON COMPQ
    CALL CHGQ   ;SET UP FKQ
    CALL CHGRLS         ;APPEND TO NEW RUNLST
    RET

DISMJ1: HRRM 1,FKQ(7)       ;SAVE TIME LEFT ON THIS QUEUE
    RET

> ;END PIE-SLICE SCHEDULER CONDITIONAL

;CLOCK ROUTINES

;CALLED FROM APR INTERRUPT, 60 CY CLOCK INITIATES BREAK ON CH7
;FOR SERVICE

APCLK1: CONO APR,1000+APRCHN    ;TURN OFF FLAG
    AOS APCLKC      ;FOR CH7 ROUTINE
    ISB 7
    SOSLE MSCNT     ;THIRD TICK?
    JEN @PIAPRX
    MOVEM 1,MSCNT       ;THIRD TICK (50 MS.), SYNC 1 MS. CLOCK
    SKIPE 1,SYNCC       ;COUNTED 50 MS.?
    JRST [  ADDM 1,TODCLK   ;NO, FINISH UP LAST TICKS
        ADDM 1,JOBRTT
        JRST .+2]   ;AND LEAVE IT RUNNING
    MSCKON          ;TURN CLOCK BACK ON
    MOVEI 1,^D50
    MOVEM 1,SYNCC       ;SET TO SYNC AFTER 50 TICKS
    MOVEI 1,3       ;AND 3 TICKS OF 60 HZ CLOCK
    EXCH 1,MSCNT
    JEN @PIAPRX

;SCHEDULER CLOCK UPDATE

APCLK:  SETZM APCLKC        ;CLEAR REQUEST FLAG
    MOVEM 1,CLKAC1      ;SAVE COUPLE AC'S
    MOVEM 2,CLKAC2
    MOVE 1,TODCLK       ;CLOCK UPDATED BY 1MS INTERRUPT
    SUBM 1,OLDTCK       ;COMPUT NUMBER MS. SINCE LAST UPDATE
    EXCH 1,OLDTCK       ;SAVE 'NOW' IN OLDTCK
    MOVN 1,1
    MOVSI 2,-NPCLKS     ;UPDATE PROCESS CLOCKS
APCLK3: ADDM 1,RJQNT(2)     ;UPDATE (RJQNT IS FIRST OF TABLE)
    SKIPG RJQNT(2)      ;TIMED OUT?
    AOS SKEDF3      ;YES, NOTIFY SCHED
    AOBJN 2,APCLK3
    MOVE 2,CLKAC2       ;RESTORE AC2
    MOVE 1,CLKAC1
    JRST APCLKX

;TIMER ROUTINES

;   JSP 4,STIME ;STARTS TIMING
;   ..      ;PROGRAM
;   JSP 4,ETIME ;ENDS TIMING, RETURNS TIME IN 1
;   ADDM 1,CLOCK    ;ADD TIME TO APPROPRIATE CLOCK

STIME:  SETZ 1,
    EXCH 1,JOBRTT       ;GET AND RESET RUNTIME
    PUSH P,1
    JRST 0(4)

ETIME:  POP P,1         ;OLD RUNTIME
    EXCH 1,JOBRTT       ;RESTORE OLD RUNTIME, GET RUNTIME OF
    JRST 0(4)       ;TIMED CODE AND RETURN IT

JSKP:   JRST 1(4)
JRET:   JRST 0(4)

;FLUSH ALL JOBS FROM THE BALANCE SET, THEN CALL GCALC
FSHBS:  SKIPL SSKED     ;ARE WE NOSKED?
     JRST SKPR1     ;YES, DONT TRY TO FLUSH BALSET

    MOVSI 10,-<MAXQ+1>
FSHBS1: MOVEI 6,RUNLST(10)
FSHBS2: HRRZ 6,0(6)

    CAIN 6,RUNLSB(10)   ;END OF LIST?
     JRST FSHNL     ;YES, GET NEXT ONE

    MOVEI 7,@FKPT6M ;GET FORK INDEX

FSHBSD: MOVE 2,FKFLGS(7)    ;GET FLAG WORD

    TLNN 2,BLST ;IS PROCESS IN BALANCE SET?
     JRST FSHBS2    ;NO

    TLNE 2,WTFK ;IS IT WAITING?
     JRST FSHBSW    ;YES

FSHBSN: CALL REMBSF

    JRST FSHBS1

FSHBSW: HRRZ 5,FKPGST(7)    ;GET WAIT TEST

    CAIE 5,DISMT        ;EDISMS?
     JRST FSHBSS        ;NO

    CALL REMBSJ

    JRST FSHBS1

FSHBSS: HLRZ 1,FKPGST(7)
    JSP 4,0(5)      ;CALL WAIT ROUTINE
     JRST FSHBSD        ;STILL WAITING

    CAIN 5,SWPINT       ;WAS HE ENTERING BAL SET?
     CALL UDNRP     ;YES, UPDATE NRPMIN

    JRST FSHBSN

;NEXT LIST
FSHNL:  AOBJN 10,FSHBS1
     JRST GCALC

;QUEUE PARAMETER TABLES

IFE PIESLC,<
    RADIX 10

QBASE:  0
    100
    500
    2500
    4500
    14500

LOWQT=10000         ;TIME QUANTUM OF LOWEST QUEUE
    RADIX 8
> ;END NON-PIE-SLICE SCHEDULER CONDITIONAL

IFN PIESLC,<
SPECQ==0            ;QUEUE FOR SPECIAL SCHEDULING
INTERQ==SPECQ+1         ;INTERACTIVE QUEUE
COMPQ==INTERQ+1     ;COMPUTATION QUEUE

QUANTS: ^D100
    ^D100
    777777          ;ESSENTIALLY INFINITE QUANTUM FOR COMPQ

; ROUTINE TO SET UP FKQ WITH PROPER QUANTUM AND QUEUE NUMBER
;QUEUE IN AC2
CHGQ:   HRL 2,QUANTS(2)     ;GET QUANTUM
    MOVSM 2,FKQ(7)      ;STASH IT AWAY
    RET

> ;END PIE-SLICE SCHEDULER CONDITIONAL

IFE PIESLC,<
;HEURISTIC FOR ADJUSTING QUEUE LEVEL AFTER I/O WAIT

;THIS ROUTINE IS THE PRINCIPLE CONTROL OVER THE EXTENT TO WHICH
;'INTERACTIVE' OR 'COMPUTE-BOUND' JOBS ARE FAVORED.  IT GIVES
;PRIORITY 'CREDIT' TO A FORK AS A RESULT OF WAITING.  THE MORE
;CREDIT GIVEN FOR A CERTAIN LENGTH WAIT (OR THE SHORTER THE WAIT
;REQUIRED TO BECOME HIGH-Q), THE MORE THE SYSTEM WILL FAVOR
;INTERACTIVE FORKS, AND THE GREATER THE CHANCE THAT FREQUENT OR
;WELL-TIMED INTERACTIONS WILL GIVE A PROCESS AN EXCESSIVELY LARGE
;SHARE OF COMPUTE TIME.  IT HAS BEEN DEMONSTRATED HOWEVER, THAT
;A COMPLETELY 'FAIR' ALGORITHM HERE, I.E. ONE WHICH PREVENTS AN
;INTERACTIVE FORK FROM GETTING ANY GREATER SHARE OF THE MACHINE
;THAN A COMPUTE-BOUND FORK, IS HIGHLY UNSATISFACTORY TO INTERACTIVE
;USERS UNDER MEDIUM AND HEAVY LOADS (AND ALL USERS ARE INTERACTIVE
;SOMETIMES), AND RESULTS IN EXPONENTIALLY INCREASING LEVELS OF
;FRUSTRATION, CURSING AND BEATING OF TERMINALS, ETC.  THEREFORE
;THIS ROUTINE IS GENUINELY A HEURISTIC, MODIFIED AS A RESULT OF
;PRESSURES BROUGHT TO BEAR ON SYSTEM PROGRAMMERS.

;THE FOLLOWING DESCRIBES THE CURRENT PRACTICE:
; 1. TTY INPUT WAITS OF .GE. 1 SEC GIVE HIGH-Q.  GREATLY REDUCES
;    USER FRUSTRATION LEVEL.
; 2. WAITS BY FORKS ON QUEUE 0 RESULT IN NO CHANGE TO Q VALUE
; 3. FORKS ON QUEUES 1 TO MAXQ-1 WILL BE HIGH-Q IF WAITING TIME IS
;    LONGER THAN LAST RUNTIME AS IMPLIED BY Q LEVEL.  THIS LIMITS
;    'WELL-TIMED' INTERACTIONS TO ABOUT 1/2 THE CPU.
; 4. FORKS ON MAXQ WILL BE HIGH-Q IF WAITING TIME IS LONGER THAN
;    THE MAXQ QUANTUM, AND WILL BE MOVED UP TO MAXQ-1 IF WAITING
;    TIME IS LONGER THAN SOME 'MINIMAL' TIME (500 MS)

;POSSIBLE ADJUSTMENTS INCLUDE:
; 1. INCLUDE NJOB WEIGHTING BY INCLUDING IDIV AT NEWST+3
; 2. REDUCE CPU LIMIT IN 3 ABOVE BY INCLUDING IDIVI 1,M AT NEWST2
;    MAX CPU USE WILL THEN BE 1-M/(M+1), E.G. 1/3 FOR M=2

;COMPUTE NEW Q VALUE

NEWST:  MOVE 1,TODCLK       ;CALCULATE ACTUAL WAITING TIME
    SUB 1,FKPGST(7)
;   IDIV 1,IRJAV        ;DIVIDE BY AV NUMBER RUNNABLE FORKS
    CAIGE 1,^D1000      ;ABOVE MIN WAIT TIME?
    JRST NEWST2     ;NO FOLLOW REGULAR ALGORITHM
    HRRZ 2,FKSTAT(7)
    CAIN 2,TCITST       ;TTY INPUT?
    JRST NEWST1     ;YES, BE MORE GENEROUS
NEWST2: HLRZ 2,FKQ(7)       ;CURRENT Q
    JUMPE 2,R       ;NO CHANGE IF HIGHEST
    CAIGE 2,MAXQ        ;LOW Q?
    JRST NEWST4     ;NO, DO Q'S 1 TO MAXQ-1 ALGORITHM
    CAIL 1,LOWQT        ;LOW Q - WAITED FULL QUANTUM?
    JRST PSSKD1     ;YES, CAN BE HI-Q
    CAIGE 1,^D500       ;.GE. MINIMAL WAIT?
    RET         ;NO
    MOVEI 1,MAXQ-1      ;YES, REQUE ON MAXQ-1
    JRST NEWST3

NEWST4: HRRZ 3,FKQ(7)       ;COMPUTE TIME USED ON CURRENT QUEUE
    MOVN 3,3
    ADD 3,QBASE+1(2)
    CAMGE 1,3       ;WAIT TIME LONGER THAN THAT?
    JRST [  ADDM 1,FKQ(7)   ;NO, GIVE CREDIT FOR WAIT, LEAVE
        RET]        ;FORK ON SAME QUEUE
    ADD 3,QBASE(2)      ;ADD TIME USED ON EARLIER QUEUES
    CAML 1,3        ;WAIT TIME LONGER THAN THAT?
    JRST PSSKD1     ;YES, HI-QUEUE THE FORK
    MOVEI 1,-1(2)       ;NO, BUMP FORK UP ONE LEVEL
    JRST NEWST3

PSSKD1:             ;REINIT ON QUEUE 0 AND CLEAR WS
NEWST1: MOVEI 2,6       ;TTYIN COMPLETED -- IF LONG WAIT,
    HRRZ 1,FKWSP(7)
    CAIGE 2,0(1)        ;CHOOSE MIN(6,SIZE)
    MOVEI 2,0(1)
    HRRM 2,FKNR(7)
    SETZ 1,         ;INIT ON QUEUE 0
NEWST3: MOVE 2,QBASE+1(1)
    HRLI 2,0(1)     ;CONSTRUCT NEW Q WORD
    MOVEM 2,FKQ(7)
    RET

> ;END NON-PIE-SLICE SCHEDULER CONDITIONAL

IFN PIESLC,<
;ROUTINE CALLED BY PIE-SLICE SCHEDULER FOR DETERMINING QUEUE LEVEL
;AFTER A PERIOD DURING WHICH A PROCESS HAS BLOCKED.
NEWST:  MOVEI 2,INTERQ
    MOVE 3,FKFLGS(7)
    TLNE 3,HIQFK            ;SPECIAL SCHEDULING?
     JRST [TLNE 3,SPQFK     ;SPECIAL QUEUE
        MOVEI 2,SPECQ
        JRST NEWST2]

    HRRZ 3,FKSTAT(7)
    CAIE 3,TCITST           ;WAS WAITING FOR TTY INPUT?
    CAIN 3,TCOTST##         ;OR OUTPUT?
     JRST NEWST2            ;YES, HI-Q

    MOVEI 2,COMPQ
NEWST2: CALL CHGQ       ;GET QUANTUM AND UPDATE FKQ
    RET

;ROUTINE TO MAINTAIN EXPONENTIAL AVERAGE OF PROCESS UTILIZATION
UPDUT:  MOVE 1,FKUTIL(7)    ;GET CURRENT VALUE
    MOVE 2,TODCLK
    SUB 2,FKUDT(7)      ;TIME SINCE LAST UPDATE
    CAIG 2,<1B<35-<TCEXP-4>>-1>   ;ENOUGH TIME ELAPSED TO UPDATE?
     RET                ;NO, JUST RETURN
    ADDM 2,FKUDT(7)         ;TODCLK TO FKUDT

    CONO APR,APRCHN+550 ;CLEAR AND DISABLE OV AND FOV

    SKIPE 4,FKPRT(7)    ;GET INCREMENTAL RUNTIME
     JRST UPDUT3        ;NON-ZERO
    MOVE 3,SOLD
    MOVEM 3,FKSOLD(7)
UPDUT2: CALL AVERAG     ;COMPUTE AVERAGE
    MOVEM 1,FKUTIL(7)
    RET

UPDUT3: SETZM FKPRT(7)
    FSC 4,233   ;CONVERT TO FLOATING POINT
    MOVE 3,SOLD
    SUBM 3,FKSOLD(7)
    EXCH 3,FKSOLD(7)
    FSC 3,233
    FDVR 4,3    ;COMPUTE RECENT UTILIZATION
    JFCL 11,UPDUTE  ;ERROR IF OVERFLOW
    JRST UPDUT2
UPDUTE: BUG (CHK,<OVERFLOW COMPUTING PROCESSOR UTILIZATION>)
    SETZ 4,
    JRST UPDUT2

;ROUTINE TO COMPUTE EXPONENTIAL AVERAGE
;ACCEPTS IN AC1: A(I) FLOATING POINT FORMAT
;       AC2: T  TIME SINCE LAST UPDATE IN MILLISECS (FIXED PT)
;           AC4: K  (THE SAMPLE VALUE) IN FLOATING POINT FORMAT

;RETURNS +1 ALWAYS WITH A(I+1)=((A(I)-K)*E^-(T/C))+K  IN AC1
;           WHERE C=TC*LOG E
;                                     2
;   SMASHES AC3
TCEXP==^D12     ;GIVES ABOUT 4 SEC TIME CONSTANT

TC==1B<35-TCEXP>      ; =2^TCEXP

AVERAG: FSBR 1,4            ;AC1=A(I)-K

    CAILE 2,<TC-1>            ;INTEGER PART NON-ZERO?
     JRST   [ASHC 2,-TCEXP      ;NO, GET INT(T/TC)
        MOVNS 2         ;NEGATE
        FSC 1,(2)           ;AC1=(A(I)-K)*2^-INT(T/TC)
        JFCL 11,AVEROV      ;FSC CAN UNDERFLOW
        ASHC 2,4        ;GET BACK FRACTIONAL PART
        ANDI 2,17       ;TAKE OUT ALL OTHER BITS
        JRST AVERG2]

    ASH 2,-<TCEXP-4>          ;GET TOP 4 BITS OF FP

AVERG2: FMPR 1,EXPTAB(2)        ;AC1=(A(I)-K)*2^-(T/TC)
     JFCL 11,AVEROV         ;FMPR COULD HAVE UNDERFLOWED
    FADR 1,4            ;PLUS K GIVES A(I+1)
    RET

AVEROV: MOVE 1,4        ;NEW VALUE = K
    RET

EXPTAB: 1.0     ;2^0
     .957603281 ;2^-(1/16)
     .917004043 ;2^-(2/16)
     .87812608  ;...
     .840896415
     .805245166
     .771105413
     .738413073
     .707106781
     .677127773
     .648419777
     .620928906
     .594603558
     .569394317
     .545253866
     .522136891 ;2^-(15/16)
> ;END PIE-SLICE SCHEDULER CONDITIONAL

;NON-PIESLICE SYSTEM: IF NOT ON ONE OF TOP TWO QUEUES, MOVE PROCESS UP A QUEUE
;PIESLICE SYSTEM: HI-QUEUE THE FORK

PSSKD2: PUSH P,3
    PUSH P,4

IFE PIESLC,<
    HLRZ 2,FKQ(7)       ;GET PRESENT QUEUE
    CAIG 2,1        ;.GT. 1?
     RET            ;NO, DO NOTHING
    SUBI 2,1        ;BUMP HIM UP A QUEUE

    HRLM 2,FKQ(7)
    MOVE 2,QBASE+1(2)
    HRRM 2,FKQ(7)       ;SET UP HIS QUANTUM
> ;END NON-PIE-SLICE CONDITIONAL

IFN PIESLC,<
    MOVSI 2,SPQFK       ;SPECIAL QUEUE?
    TDNE 2,FKFLGS(7)
    SKIPA 2,[SPECQ]     ;YES
    MOVEI 2,INTERQ      ;NO
PSSKD4: CALL CHGQ
> ;END PIE-SLICE CONDITIONAL

    NOSKD1
    MOVE 2,FKFLGS(7)
    TLNN 2,RNLS     ;IS FORK NOW ACTIVE?
     JRST PSSKD3        ;NO
    CALL CHGRLS     ;APPEND TO NEW LIST
    SKIPN INSKED        ;IN SCHEDULER?
    CAME 7,FORKX        ;NO, PROCESS LEVEL
     JRST PSSKD3        ;IN SCHED, OR OBJECT FORK NOT THIS ONE
    HRRZ 4,FKQ(7)       ;PUT NEW QUANTUM
    MOVEM 4,RJQNT       ;IN RJQNT
PSSKD3: OKSKD1
    POP P,4
    POP P,3

    RET

IFN PIESLC,<

;ROUTINE TO REPLACE FORK ON COMPQ
SETCQ:  PUSH P,3
    PUSH P,4
    MOVEI 2,COMPQ
    JRST PSSKD4

;TRANSPARENT ROUTINE TO PLACE CURRENT FORK ON ONE OF THE HI QUEUES
SETHIQ:: AOSE HIQCNT        ;ALREADY HI-QUEUED?
     RET            ;YES
    PUSH P,1
    PUSH P,2
    PUSH P,7
    MOVE 7,FORKX
    HLRZ 1,FKQ(7)       ;IS HE NOW ON INTERQ?
    CAIN 1,INTERQ
     JRST [MOVSI 1,PHIQFK   ;YES, MAKE NOTE OF THAT
        IORM 1,FKFLGS(7)
        JRST SETHI2]
    MOVSI 2,HIQFK
    IORM 2,FKFLGS(7)    ;SET IN FKFLGS
    CALL PSSKD2
SETHI2: POP P,7
    POP P,2
    POP P,1
    RET

;ROUTINE TO RESUME NORMAL SCHEDULING IF THE OUTERMOST
;SUCH REQUEST
RELHIQ:: SKIPGE HIQCNT
    BUG (HLT,<ATTEMPT TO OVERDECREMENT HIQCNT>)
    SOSL HIQCNT     ;OUTERMOST REQUEST?
     RET
    PUSH P,1
    PUSH P,2
    PUSH P,7
    MOVE 7,FORKX
RELHI2: MOVSI 1,SPQFK+HIQFK ;TURN OFF SPECIAL SCHED BITS
RELHI3: ANDCAB 1,FKFLGS(7)
    TLZN 1,PHIQFK       ;WAS HE PREVIOUSLY ON INTERQ?
     JRST [CALL SETCQ   ;REPLACE ON COMPQ
        JRST .+2]
    MOVEM 1,FKFLGS(7)   ;YES, CLEAR THE BIT BUT NO RESCHED
    POP P,7
    POP P,2
    POP P,1
    RET

; ROUTINE TO OBTAIN TOP PRIORITY SCHEDULING FOR RUNNING FORK
SETSPQ:: AOSE SPQCNT        ;FIRST SUCH REQUEST?
     RET        ;NO, JUST NOTE THAT IT HAPPENED
    PUSH P,1
    PUSH P,2
    PUSH P,7
    MOVE 7,FORKX
    MOVSI 1,SPQFK+HIQFK
    IORM 1,FKFLGS(7)
    AOSN HIQCNT
     JRST [HLRZ 1,FKQ(7)    ;IF NOT INTERQ AS A RESULT OF PRIOR
                ;CALL TO SETHIQ, THEN NOTE IF INTERQ NOW
        CAIN 1,INTERQ
         JRST [MOVSI 1,PHIQFK
            IORM 1,FKFLGS(7)
            JRST .+1]
        JRST .+1]
    CALL PSSKD2
    POP P,7
    POP P,2
    POP P,1
    RET

;ROUTINE TO TURN OFF TOP PRIORITY SCHEDULING IF OUTERMOST
;SUCH REQUEST
RELSPQ:: SKIPGE SPQCNT
    BUG (HLT,<ATTEMPT TO OVERDECREMENT SPQCNT>)
    SOSL SPQCNT
     RET        ;NESTED REQUEST
    PUSH P,1
    PUSH P,2
    PUSH P,7
    MOVE 7,FORKX
    SKIPGE HIQCNT
    BUG (HLT,<ATTEMPT TO OVERDECREMENT HIQCNT>)
    SOSGE HIQCNT
     JRST RELHI2
    MOVSI 1,SPQFK
    JRST RELHI3

>; END PIE-SLICE SCHEDULER CONDITIONAL

;HALT JOB

HLTJB:  HRRE 6,CTRLTT
    JUMPL 6,HLTJB1      ;IF JOB DETACHED
    SETZM TTPSI(6)      ;CLEAR TTY WORDS
    SETOM TTFORK(6)
HLTJB1: MOVE 5,JOBNO
    SETZM CTRLTT        ;CLEAR CONTROL TTY WORDS
    HRRZS JOBPT(5)
    MOVEI 1,400000
    SETO 2,
    DIC         ;DEACTIVATE ALL INTERRUPTS
    MOVNI 1,1
    CLOSF
    JFCL
    RELD            ;RELEASE ALL DEVICES
    JFCL
    MOVEI 1,-4
    KFORK           ;KILL ALL INFERIOR FORKS
IFDEF SIGIPC,<   CALL RMJBRF##   ;REMOVE REFERENCES TO THIS JOB IN SIGS>
    MOVE 7,FORKX        ;THIS FORK.
    HLLZ 2,FKPGS(7)     ;FORKS PT
    MOVSI 6,-1000
    CALL CLRM0      ;CLEAR UPT
    MOVE 6,[XWD PJMPG-PPMPG,JOBMAP-JSB]
    HRLZ 2,FKJOB(7)     ;GET SPTN OF JSB
    CALL CLRM0      ;CLEAR JDV AND PAGES IN JOB AREA
    SKIPGE JOBPMF
    JRST HLTJB4     ;NO PMF
    SETO 1,
    HRLZ 2,JOBPMF
    MOVSI 6,-1000
    HRRI 2,0(6)
    PMAP            ;DELETE CONTENTS OF PMF
    AOBJN 6,.-2
    HRRZ 1,JOBPMF
    SETOM JOBPMF        ;ENABLE CLOSE OF PMF
    CLOSF           ;CLOSE PMF - NO JSYS' AFTER HERE
    JFCL
HLTJB4: MOVE 1,ACBAS
    CAIGE 1,<EUACB>B39    ;AC BLOCKS IN PSB?
    SETZM PSB+UACPG     ;YES, CLEAR MAP ENTRY FOR UACPG

HLTJB5: MOVE 6,[XWD CPTPG+1-UPTPG,CPTPG+1]
    HRLZ 2,FKPGS(7)
    CALL CLRM0      ;CLEAR PAGES IN PP AREA
    HRRZ 1,FKJOB(7)     ;JSB
    CALL WTSPT      ;WAIT FOR IT TO BE UNSHARED
    CALL WTFPGS     ;WAIT FOR PSB AND UPT TO BE IN NO MAPS
    ENTSKD          ;ENTER SCHED
    MOVE 1,JOBNO        ;RELEASE JOB NUMBER
    SETZM JOBDIR(1)     ;CLEAR DIRECTORY NUMBER
    SETOM JOBRT(1)      ;INDICATE JOB NUMBER NOT IN USE
    ADDI 1,JOBPT
    EXCH 1,FREJOB       ;PUT SLOT ON FREE LIST
    MOVEM 1,@FREJOB
    JRST HLTFK2     ;FLUSH THIS LAST FORK

HLTFK1: ENTSKD      ;ENTER SCHEDULER
HLTFK2: CALL REMBSJ     ;REMOVE FORK FROM BALANCE SET
HLTFK3:     ;THIS LABEL MUST IMMEDIATELY FOLLOW CALL TO
        ;REMBSJ. DO NOT SEPARATE!!!

    MOVEI 1,(1B0)
    HRLM 1,FKPT(7)      ;NOTE FORK NOT IN BALSET
    HRRZ 1,FKJOB(7)     ;JSB
    LDB 2,[POINT 14,SPT(1),13] ;SHARE COUNT NOW 1?
    CAIE 2,1        ;LAST USE OF JSB?
    JRST [  MOVSI 2,-1B31   ;NO, REDUCE SHARE COUNT
        ADDM 2,SPT(1)
        JRST .+2]
    CALL DESPT      ;YES, DELETE IT (LOGOUT CASE)
    HLRZ 1,FKPGS(7)     ;UPT
    CALL DESPT      ;DELETE IT
    HRRZ 1,FKPGS(7)
    CALL DESPT      ;DEASSIGN PSB
    SETOM FORKX
    PUSH P,7
    ADDI 7,FKPT
    EXCH 7,FREFK        ;PUT FORK NUMBER ON FREE LIST
    TLO 7,400000
    MOVEM 7,@FREFK
    CALL GCCOR      ;CLEAN UP PAGES
    POP P,7
    HRRZ 1,FKWSP(7)     ;MAKE SURE FORK CLEANED UP
    HRRZ 2,FKCNO(7)
    CAIN 1,0
    CAIE 2,0
    BUG(CHK,<FORK NOT PROPERLY DELETED>)
    JRST SCHED0     ;NOW THERE IS NOTHING LEFT OF JOB...

CLRM0:  SETZ 1,
CLRM1:  HRRI 2,0(6)     ;PUT PAGE NUMBER WITH PTN
    CALL SETPT
    AOBJN 6,CLRM1
    RET

;WAIT FOR PSB AND UPT TO HAVE SHARE COUNT OF 1

WTFPGS: HRRZ 1,FKPGS(7)     ;PSB
    CALL WTSPT
    HLRZ 1,FKPGS(7)     ;UPT
WTSPT:  PUSH P,4
WTSPT2: JSP 4,WTSPTT        ;TEST PAGE NOW
    JRST WTSPT1     ;MUST WAIT
    POP P,4         ;NOW OK
    RET

WTSPT1: MOVSI 1,0(1)
    HRRI 1,WTSPTT
    JSYS EDISMS
    HLRZ 1,1
    JRST WTSPT2

WTSPTT: LDB 2,[POINT 14,SPT(1),13]  ;GET SHARE COUNT
    CAIE 2,1
    JRST 0(4)
    JRST 1(4)

;PRELIMINARY FORK INIT
;HERE ON PROCESS LEVEL FROM PIRQ IF NEWFKF IS SET IN FKINT
; IF NEW JOB, TTY # IS IN PIMSK AND RH 7

FKSET:  MOVE 1,PSB+PSBPG    ;SETUP USER MAP WORD
    MOVEM 1,PSB+UACPG   ;SAME AS PSB UNTIL OVERFLOW
    MOVE 1,[IOWD NUPDL,UPDL]
    MOVEM 1,UPP     ;MON ROUTINES PDL
    MOVE 1,[IOWD 1000,PSIPGA]
    MOVEM 1,PSIPT       ;PSI STORAGE STACK
    MOVEI 1,<UACB>B39 ;SETUP AC BASE
    MOVEM 1,ACBAS
    MOVEM 1,ACBAS1
    SETACB 1
    MOVE 1,ICAPT
    MOVEM 1,CAPT
    MOVE 1,INTDF0       ;INTERRUPT SWITCHES
    MOVEM 1,INTDFF
    MOVE 1,MJRST0
    MOVEM 1,MJRSTF
    SETZM NSKED
    MOVE 1,RSKEDN
    MOVEM 1,RSKED
    MOVSI 1,(<MOVEM 1,0>)
    MOVEM 1,PATU40      ;SETUP INSTRUCTION PART FOR COMPAT
    MOVEM 1,PATUPC      ;ENTRY PROCEDURE

IFN PIESLC,<
    SETOM HIQCNT        ;INIT SPEC SCHEDULING REQUEST COUNTS
    SETOM SPQCNT
> ;END PIE-SLICE CONDITIONAL
    SETOM SLOWF
    SETOM INTDF
    SETOM TRAPC
    SETOM FKTAB
    MOVEI 1,FKTAB+1
    HRLI 1,-1(1)
    BLT 1,FKTAB+NLFKS/2-1
    SETOM JTLCK     ;INIT JSYS TRAP LOCK
    MOVE 2,[XWD 77,7777]    ;INIT JTMNW TO NO CHANNEL, NO MONITOR
    MOVEM 2,JTMNW
    MOVE 1,JDSPTP
    MOVE 2,FORKX
    HRL 2,FKPGS(2)
    HRRI 2,JDVPG
    MOVSI 3,RWX     ;SET JSYS DISPATCH TO STANDARD
    CALL SETPT      ;NON-MONITORED DISPATCH
    MOVE 2,FORKX
    HLRZ 1,FKPGS(2)     ;GET SPTN OF PAGE TABLE
    LSH 1,^D9       ;CONSTRUCT SHARE POINTER
    TLO 1,RWXB-XCTB+SHRBIT
    MOVEM 1,PSB+UPTPG
    MOVE 6,FORKX
    TLNE 7,NEWJBF       ;NEW JOB TOO?
    JRST FKSET1     ;YES
    HRRZ 1,FKJOB(6)     ;GET JSB
    MOVSI 2,1B31
    ADDM 2,SPT(1)       ;BUMP SHARE COUNT
    LSH 1,^D9
    TLO 1,RWXB+SHRBIT   ;CONSTRUCT SHARE POINTER
    MOVEM 1,PSB+JSBPG
    MOVEI 1,FKSET2
FKSET3: MOVEM 1,PIPC
    SETZM PIOLDS
    MOVE 1,PSB+JSBPG    ;GET JSB POINTER
    TLC 1,SHRBIT+INDBIT ;MAKE INTO INDIRECT POINTER
    ADDI 1,JOBMAP-JSB+JSBPG-PJMPG+1 ;FIRST WORD OF JOB PT
    MOVEI 2,JSBPG+1     ;STARTING AFTER JSB,
    MOVEM 1,PSB(2)      ;FILL MON MAP WITH IND POINTERS
    ADDI 2,1
    CAIGE 2,PPMPG
    AOJA 1,.-3
    JRST PIRQR      ;DEBREAK - RUN IN NORMAL MODE

;INIT NEW JOB

FKSET1: HRRE 2,7        ;GET CONTROLLING TTY #, IF ANY
    HLRZ 1,FKJOB(6)     ;GET JOB NUMBER STORED BY JOBSRT
    MOVEM 1,JOBNO
    MOVSM 2,JOBPT(1)    ;TTY ASSIGNED TO JOB, OR -1, TO LH JOBPT
    SKIPL 2         ;UNLESS DETACHED, SET TTFORK TOO
    HRLM 1,TTFORK(2)    ;JOB CONTROLLED BY TTY
    MOVE 2,FORKX
    HRRM 2,JOBPT(1)     ;TOP FORK OF JOB
    SETZM JOBRT(1)      ;JOB RUNTIME
    HRRZ 1,FKJOB(6)     ;JSB
    LSH 1,^D9
    TLO 1,RWXB+SHRBIT
    MOVEM 1,PSB+JSBPG   ;SETUP JSB
    MOVE 1,JOBNO
    HLRE 2,JOBPT(1)     ;CONTROLLING TTY OR -1
    MOVEM 2,CTRLTT      ;IN JSB
    MOVEI 1,EXEC0
    HRRZS FKTAB     ;FORK 400000 IN TOP FK IS JOB FK 0
    JRST FKSET3

FKSET2: SETZ 0,         ;START WITH 0 AC'S
    MOVEI 17,1
    BLT 17,16
    SETZ 17,
    ENTSKD
    MOVSI 1,UMODF
    MOVEM 1,PPC
    MOVEI 1,HALTT
    JRST DISMSE

INTDF0: SOS INTDF       ;NORMAL CONTENTS OF INTDFF
MJRST0: JRSTF @FPC      ;NORMAL CONTENTS OF MJRSTF
CHNSON: EXP 1B9+1B11+1B15+1B16+1B17+1B18+1B20   ;ALWAYS ON PSI CHANS

IFDEF RTISW,<

;SOLO -- CAPTURE THE WHOLE MACHINE

.SOLO:  JSYS MENTR
    MOVE 1,JOBNO
    MOVEM 1,SCDRN1      ;TELL SCHED TO RUN THIS JOB ONLY
    MOVEI 1,FSHBAL
    MOVEM 1,FSHBAL      ;TELL SCHED TO FLUSH BALANCE SET
    CALL DISE       ;WAIT TIL DONE
    JRST MRETN

;TUTTI -- ALLOW ALL JOBS TO RUN

.TUTTI: JSYS MENTR
    MOVSI 1,(1B1)
    MOVEM 1,CHKTIM
    SETOM SCDRN1
    JRST MRETN

>;END IFDEF RTISW

;PSEUDO-INTERRUPT SYSTEM

;BITS IN LH FKINT, LH PIMSK

;B18=INT REQUEST
;B19=INTERRUPT HANDLER RUNNING AT PROCESS LEVEL
NEWFKF==:1B20           ;INITIATE NEW FORK - PI FLAG
NEWJBF==1B21            ;INITIATE NEW JOB - PI FLAG
PSIIF==1B22         ;CHANNEL INTERRUPT REQUESTED IN FKINTB
PSIT1F==1B23            ;TERMINAL CODE INTERRUPT, PHASE 1
PSIT2F==1B24            ;TERMINAL CODE INTERRUPT, PHASE 2
SUSFKR==1B25            ;SUSPEND FORK REQUEST
PSIWTF==1B26            ;JOB WAS IN WAIT STATUS
PSILOB==1B27            ;LOGOUT JOB REQUEST
FRZB1==1B28         ;DIRECT FREEZE HAS BEEN DONE
FRZB2==1B29         ;INDIRECT FREEZE HAS BEEN DONE
PSIJTR==1B30            ;JSYS TRAP REQUEST
JTFRZB==1B31            ;JSYS TRAP FREEZE
FRZBB==FRZB1+FRZB2      ;BOTH BITS FOR EXTERNAL REFS
FRZBAL==JTFRZB+FRZBB        ;FOR EXTERNAL REFS

;SCHEDULER CAUSES JOB TO BE STARTED HERE ON PI REQUEST
;SAVED PC IN PIPC
;PIMSK CONTAINS INTERRUPT REQUEST WORD

PIRQ:   MOVEM P,PIAC+17
    MOVEI P,PIAC        ;SAVE USER AC'S
    BLT P,PIAC+16
    MOVE P,PIPDL        ;SET UP LOCAL STACK
    PUSH P,PGURET       ;SAVE UNTRAP RETURN ON LOCAL STACK
    MOVE 7,PIMSK        ;INTERRUPT REQUEST WORD
    MOVE 6,FORKX
    SETZ 2,
    TLNE 7,PSIWTF       ;WAS JOB IN WAIT STATUS?
    MOVE 2,FKSTAT(6)    ;YES, GET OLD STATUS
    MOVEM 2,PIOLDS      ;SAVE OLD STATUS, OR 0 IF WAS RUNNING
    TLNE 7,NEWFKF       ;START NEW FORK?
    JRST FKSET      ;YES
    TLNE 7,PSIT1F
    JRST PSIT1      ;TERMINAL, PHASE 1
    TLNE 7,PSIT2F
    JRST PSIT2      ;TERMINAL, PHASE 2
PSITR1: TLNE 7,PSIIF+SUSFKR+PSILOB+PSIJTR
    JRST PSII       ;CHANNEL INTERRUPT SPEC. BY FKINTB
PIRQR:  JSYS UNPIR      ;LEAVE PI STATE
PSIDF1: SKIPN 1,PIOLDS      ;WAS RUNNING BEFORE PSI?
     JRST SCHED3
    JRST DISMSE     ;NO, REPLACE ON WAIT LIST

;NOTE THAT THIS ROUTINE IS NOW CALLED VIA
;JSYS UNPIR. THIS WILL NOT WORK ON THE KI10.
UNPIR:  XWD ENSKR,.+1
    SKIPE INSKED
    BUG(HLT,<CALL TO SCHEDULER WHEN ALREADY IN SCHEDULER>)
    AOS INSKED      ;ENTER SCHEDULER
    MOVE P,PI7P     ;SCHEDULER STACK
    PUSH P,ENSKR
    SETZM ENSKR
    MOVE 1,PIPDB        ;RESTORE PGURET
    MOVEM 1,PGURET
    MOVE 1,[XWD PIAC,PAC]
    BLT 1,PAC+17        ;PUT AC'S BACK
IFN KIFLG,<
    JSP 7,KISSAV>        ;SAVE STUFF FOR KI-10 HARDWARE
    MOVE 1,PIPC
    MOVEM 1,PPC
    MOVSI 1,200000
    MOVE 7,FORKX
    ANDCAM 1,FKINT(7)
    JRST UCLOCK     ;CHARGE PROCESS TIME AND RETURN

PIPDL:  IOWD NPIPDL,PIPDB   ;INTERRUPT ROUTINES LOCAL PDL

;REQUEST INTERRUPT
;AC1 CONTAINS INTERRUPT NUMBER
;AC2 CONTAINS FORK INDEX

PSIRQ0: MOVE 2,FORKX        ;REQUEST INTERRUPT IN CURRENT FORK
PSIRQF: NOSKED          ;REQUEST INTERRUPT, FORK IN AC2
    CALL PSIRQ
    OKSKED
    RET

;ENTERED FROM SCHEDULER REQUEST PROCESSOR

PSIRQ:  MOVE 1,BITS(1)
PSIRQB: IORM 1,FKINTB(2)    ;SET BIT IN INTERRUPT WAITING BUFFER
PSITQ:  MOVSI 1,400000+PSIIF    ;REGULAR INTERRUPT FLAG
    IORM 1,FKINT(2)
    CAMN 2,FORKX        ;FOR THIS FORK?
    RET         ;YES
PSIR4:  PUSH P,7
    MOVEI 7,0(2)
    NOSKD1
    MOVE 1,FKFLGS(7)    ;NO, GET STATUS OF FORK
    TLNN 1,WTLS     ;ON WAITLIST?
     JRST PSIR6     ;NO
    MOVSI 1,(1B1)       ;INTERRUPT IN PROGRESS?
    TDNE 1,FKINT(7)
     JRST PSIR6     ;YES, LET WTSCAN PICK HIM UP
    MOVSI 1,PSIWTF      ;REMEMBER IT WAS WAITING
    IORM 1,FKINT(7)
IFN PIESLC,<CALL INCNAP>
    PUSH P,3
    PUSH P,4
    CALL PSIAWK     ;AWAKEN HIM
    POP P,4
    POP P,3

;SET NEW SCHED STATUS FOR PSI'D FORK

PSIR6:  OKSKD1
    CALL PSSKD2     ;SET SHORT QUANTUM, HIGH PRIORITY
    MOVEI 2,0(7)
    POP P,7
    RET

;TERMINAL INTERRUPT
;PHASE ONE - CALLED FROM TERM SERVICE ROUTINES
; 2/ LINE NO.,   3/ INTERRUPT CODE
;SEND TO TOP FORK TO FIND PROPER DESTINATION

TTPSRQ: HLRZ 1,TTFORK(2)    ;GET JOB USING THIS TTY
    ANDCMI 1,600000     ;FLUSH EXTRANEOUS BITS
    HLRZ 4,JOBPT(1)     ;4=JOB CTTY LINE #
    CAIE 4,0(2)     ;LINE PSI REQUESTED?
    JRST TTPSR2     ;NO, MUST FIND TOP FORK IN GROUP TO PSI
    HRRZ 2,JOBPT(1)     ;YES, 2=INDEX OF TOP JOB FORK
TTPSR1:: MOVSI 1,1B18+PSIT1F    ;PHASE ONE REQUEST
    IORM 1,FKINT(2)
    HRRM 3,FKINT(2)     ;INTERRUPT CODE
    JRST PSIR4      ;SET NEW STATUS

TTPSR2: MOVEI 1,0(2)        ;1=LINE #
    IDIVI 1,2       ;COMPUTE BYTE PTR TO TTFRK1 ENTRY
    ADD 1,TTFRKP(2)
    LDB 2,1         ;2=INDEX OF TOP FORK IN GROUP
    CAIL 2,NFKS     ;RANGE CHECK
    RET         ;FAILED, ABORT PSI
    JRST TTPSR1

TTFRKP: POINT 18,TTFRK1,17  ;POINTERS TO TTFRK1 ENTRIES
    POINT 18,TTFRK1,35

;ROUTINES TO HANDLE INTERRUPT CONDITIONS AS SPECIFIED BY BITS
;IN LEFT HALF OF FKINT

;TERMINAL INTERRUPT, PHASE ONE
;THIS CODE RUN IN TOP FORK OF PROCESS GROUP ONLY

PSIT1:  MOVE 6,BITS(7)
    HRRZ 1,FORKN        ;START WITH THIS FORK
    HLRZ 4,SYSFK(1)     ;4=DESIGNATOR OF TERM PSI SOURCE
    SETO 5,
    TDNE 6,FKPSIE(1)    ;TERM CODE ON IN FORK?
    MOVEI 5,0(1)        ;YES, REMEMBER FORK
    CALL PSIT1A     ;LOOK AT ALL INFERIORS
    JUMPL 5,PSIT11      ;NOT FOUND, SO TURN OFF CODE
    HRRZ 2,SYSFK(5)     ;GET SYSTEM INDEX OF FORK TO GET INTERPT
    CAMN 2,FORKX        ;THIS FORK?
    JRST PSIT2      ;YES, GO DIRECTLY TO PHASE TWO
    NOSKED
    HRRM 7,FKINT(2)     ;NO, SETUP TO INTERRUPT PROPER FORK
    MOVSI 1,PSIT2F+400000   ;PHASE TWO REQUEST FLAG
    IORM 1,FKINT(2)
    CALL PSIR4
    OKSKED
    JRST PSITR1

PSIT11: CAIN 4,-1       ;SOURCE OF PSI=JOB CTTY?
    JRST PSIT12     ;YES.
    TRZN 4,1B18     ;MAYBE, CONVERT TO LINE #, ASSUMING TTY
    JRST PSITR1     ;DESIGNATOR.  NOT TTY DES. RETURN.
    CAMN 4,CTRLTT       ;CTTY OF JOB?
    JRST PSIT12     ;YES.
    CAIGE 4,NLINES      ;NO, RANGE CHECK LINE #
    CAIGE 4,0
    JRST PSITR1
    JRST PSIT13
PSIT12: ANDCAM 6,TTSPSI     ;CLEAR PSI CODE FOR JOB
    SKIPL 4,CTRLTT
PSIT13: ANDCAM 6,TTPSI(4)   ;CLEAR CODE FOR TTY
    JRST PSITR1

;SEARCH FORK STRUCTURE FOR FORK TO INTERRUPT
;4/ DESIGNATOR OF SOURCE OF THIS PSI

PSIT1A: ADD 1,INFERP        ;LOOK AT INFERIOR LIST
PSIT1B: LDB 1,1         ;GET NEXT IN LIST
    JUMPE 1,R       ;RETURN AT END OF LIST
    HLRZ 2,SYSFK(1)     ;2=FORK'S SOURCE OF TTY PSI'S
    CAIE 2,0(4)     ;=SOURCE OF THIS ONE?
    JRST PSIT1E     ;NO, CONSIDER FORK NO FURTHER.
    HRRZ 2,SYSFK(1)     ;CHECK STATE OF FORK
    PUSH P,7        ;SEE IF FORK FROZEN OR HALTED
    MOVEI 7,0(2)
    CALL CHKWT      ;SEE IF DISMISSED
     JRST [POP P,7      ;ITS NOT
        JRST PSIT1D]
    POP P,7
    HRRZ 3,FKSTAT(2)
    CAIN 3,FRZWT        ;FROZEN?
    JRST PSIT1G     ;YES
    CAIE 3,HALTT        ;HALTED OR FORCED TERM?
    CAIN 3,FORCTM
    JRST PSIT1C     ;YES
PSIT1D: TDNE 6,FKPSIE(1)    ;FORK HAS CODE ENABLED?
    MOVEI 5,0(1)        ;YES, REMEMBER IT
PSIT1E: HRLM 1,0(P)     ;REMEMBER CURRENT FORK
    CALL PSIT1A     ;CHECK INFERIORS
    HLRZ 1,0(P)     ;RECOVER CURRENT
PSIT1C: ADD 1,PARALP        ;DO PARALLELS
    JRST PSIT1B

PSIT1G: MOVSI 3,JTFRZB      ;JSYS TRAP FREEZE?
    TDNN 3,FKINT(2)
    JRST PSIT1C     ;NO
    MOVSI 3,FRZBB
    TDNE 3,FKINT(2)     ;OTHER FREEZE ALSO?
    JRST PSIT1C     ;YES.
    JRST PSIT1D

;FORK STRUCTURE POINTERS

SUPERP: POINT 12,FKPTRS,11  ;SUPERIOR
PARALP: POINT 12,FKPTRS,23  ;PARALLEL
INFERP: POINT 12,FKPTRS,35

;TERMINAL INTERRUPT, PHASE TWO

PSIT2:  MOVEI 1,0(7)
    CALL GETCHA
    LDB 2,2
    MOVE 1,BITS(2)      ;AND SET BIT IN INT. WAITING WORD
    AND 1,PSICHM        ;BUT ONLY FOR ENABLED CHANNELS
    IORM 1,PSIBW
    JRST PSII       ;THEN GO PROCESS IT

;SUSPEND FORK REQUEST

PIRSFK: TLNE 7,PSIJTR       ;JSYS TRAP PSI REQUEST ALSO PRESENT?
    JRST [  MOVE 2,FORKX    ;YES, REMEMBER IT
        MOVSI 1,PSIJTR+400000
        IORM 1,FKINT(2)
        JRST .+1 ]
    MOVE 1,PIPC
    CALL PITEST     ;NOW INTERRUPTABLE?
    JRST PIRSF1     ;NO
PIRSK2: MOVEI 3,SUSWT       ;SUSPENDED FORK TEST
PIRSK1: MOVE 2,FORKX
    MOVSI 1,SUSFKR
    ANDCAM 1,FKINT(2)
    JSYS UNPIR      ;LEAVE INTERRUPT STATE
    IORM 1,FKINT(7)     ;KEEP INTERRUPT STARTING BIT
    MOVEI 1,0(3)        ;SUSWT OR FRZWT
    HRL 1,PIOLDS        ;WITH OLD STATUS
    JRST DISMSE     ;DISMISS

PIRSF1: MOVE 7,FORKX
       NOSKED
    HRRZ 1,FKSTAT(7)
    CAIN 1,JTQWT        ;IN JSYS TRAP QUEUE WAIT?
    JRST PIRSF2     ;YES, ALLOW SUSPENSION
       OKSKED
    MOVSI 1,SUSFKR      ;TURN REQUEST BIT BACK ON
    IORM 1,FKINT(7)
    JRST PSIDFR     ;AND SET DEFERRED INTERRUPTS

PIRSF2: MOVEI 1,FKJTQ(7)    ;FORK IN JSYS TRAP QUEUE WAIT
    CALL JTDEQ      ;REMOVE IT FROM QUEUE
    MOVEI 1,JTRLCK      ;SET RESUME ADDR TO LOCK ROUTINE
    SETZM PIOLDS
    MOVEM 1,PIPC
       OKSKED
    JRST PIRSK2

SUSWT:  JRST 0(4)       ;SCHEDULER TEST FOR SUSPENDED FORK

;LOGOUT REQUEST

PIRLGO: MOVE 1,PIPC
    CALL PITEST     ;OK TO INTERRUPT?
    JRST [  MOVE 7,FORKX    ;NO, REMEMBER REQUEST
        MOVSI 1,PSILOB
        IORM 1,FKINT(7)
        JRST PSIDFR]
    SETZM PIOLDS        ;MAKE FORK RUNNABLE
    MOVEI 1,FLOGO
    EXCH 1,PIPC
    SKIPGE SLOWF
    JRST [  MOVEM 1,FPC ;IN USER MODE, SIMULATE JSYS
        JRST PIRQR]
    MOVE 2,PIAC+17      ;IN MON MODE, SIMULATE PUSHJ
    PUSH 2,1
    MOVEM 2,PIAC+17
    JRST PIRQR

;JSYS TRAP REQUEST

PIRJTP: MOVE 1,PIPC
    CALL PITEST     ;FORK INTERRUPTABLE?
    JRST PIRJT1     ;NO, DEFER IT
    MOVSI 1,PSIJTR
    MOVE 7,FORKX        ;IN CASE THIS PSI WAS DEFERRED
    ANDCAM 1,FKINT(7)   ;CLEAR IT FROM FKINT
    LDB 1,JTMCN     ;GET PSI CHANNEL FOR TRAP
    MOVE 1,BITS(1)
    IORM 1,PSIBW        ;SET BIT IN INT WAITING WORD
    SETZ 7,
    JRST PSII       ;GO PROCESS THE TRAP

PIRJT1: MOVE 7,FORKX        ;DEFER THE JSYS TRAP PSI
    MOVSI 1,PSIJTR
    IORM 1,FKINT(7)
    JRST PSIDFR

;PROCESS INTERRUPT(S) FOR THIS FORK AS SPECIFIED BY FKINTB

PSII:
IFN KIFLG,<
    JRSTF @[1B6+.+1]>    ;TURN ON UXCT FLAG
    MOVE 1,MJRST0       ;NORMALIZE ALL DEFER TRAPS
    MOVEM 1,MJRSTF
    MOVE 1,INTDF0
    MOVEM 1,INTDFF
    TLNE 7,SUSFKR       ;FORK SUSPENSION REQUEST?
    JRST PIRSFK     ;YES
    TLNE 7,PSILOB       ;LOGOUT REQUEST?
    JRST PIRLGO
    TLNE 7,PSIJTR       ;JSYS TRAP REQUEST?
    JRST PIRJTP
    MOVE 2,FORKX
    MOVEI 1,0
    EXCH 1,FKINTB(2)    ;RESET FKINTB TO 0
    IORM 1,PSIBW        ;INCLUDE IN PROCESS WAITING BREAKS
    MOVE 1,PSICHM       ;USERS ENABLED CHANNELS
    IOR 1,CHNSON        ;WITH ALWAYS ON CHANNELS
    IOR 1,SUPCHN        ;WITH SUPERIOR RESERVED CHANNELS
    SKIPE 3,PIOLDS      ;WAS FORK WAITING?
    JRST [  SKIPN FORKN     ;AND NOT TOP FORK?
        JRST .+1    ;NO
        MOVEI 3,0(3)    ;YES, HALT OR FORCED TERM?
        CAIE 3,HALTT
        CAIN 3,FORCTM
        SETZ 1,     ;YES, FLUSH BREAKS
        JRST .+1]
    ANDB 1,PSIBW        ;FLUSH DISABLED CHANS
    JUMPE 1,PIRQR       ;RETURN IF NO BREAKS WAITING
    MOVE 1,PIPC     ;PROCESS PC
    CALL PITEST     ;CAN PROCESS BE INTERRUPTED NOW?
    JRST PSIDFR     ;NO, GO SETUP DEFERRED INTERRUPT
PSIS:   MOVE 1,PSIBW
    TDNE 1,MONCHN       ;MONITOR RESERVED CHANNEL?
    JRST PSIMB      ;YES
    AND 1,SUPCHN        ;LOOK AT SUPERIOR RESERVED CHANS
    JUMPN 1,PSIN1       ;TERMINATE IF ANY
    MOVE 1,PSIBW
    AND 1,CHNSON        ;LOOK AT SPECIAL CHANNELS
    SKIPE PSISYS        ;IF THIS PROCESS NOT TAKING PSI'S,
    JUMPN 1,PSIN1       ;TERMINATE IT IF ANY SPECIALS
    ANDCM 1,PSICHM      ;AND'ING WITH USER'S 'OFF' CHANNELS
    JUMPN 1,PSIN1       ;TERMINATE CAUSE CHANNEL NOT ACTIVE
    SKIPE PSISYS        ;PSI SYSTEM ON?
    JRST PIRQR      ;NO
    SKIPN LEVCHN        ;THIS PROCESS TAKING INTERRUPTS?
    JRST PSIN1      ;NO, GO TRANSMIT THE PSI
    MOVE 1,PSIBW        ;FIND HIGHEST PRIORITY INTERRUPT
    MOVEI 2,0       ;NOW WAITING
    MOVSI 3,1
PSIS1:  JUMPL 1,PSIS2       ;THIS CHANNEL HAS WAITING BREAK?
PSIS4:  LSH 1,1         ;NO, SHIFT TO NEXT CHANNEL
    ADDI 2,1        ;COUNT CHANNEL NUMBER
    JUMPN 1,PSIS1       ;KEEP LOOKING IF ANY BITS LEFT

;AC3 NOW CONTAINS LEVEL OF HIGHEST PRIORITY INTERRUPT FOUND
;AC5 CONTAINS CORRESPONDING CHANNEL NUMBER

    JUMPE 3,PSID1       ;NO LEVEL ASSIGNED? GO XMIT INTERRUPT
    MOVE 1,BITS(3)
    CAMG 1,PSIBIP       ;OK TO BREAK ON THIS LEVEL?
    JRST [  MOVE 2,PSIBW    ;NO, .GE. PRIORITY LEVEL IN PROGRESS
        TDNN 2,CHNSON   ;BREAKS ON PANIC CHNS WAITING?
        JRST PIRQR  ;NO, HOLD WAITING BREAKS
        JRST PSIN1] ;YES, MUST TERMINATE
    IORM 1,PSIBIP       ;YES, REMEMBER  BREAK THIS LEVEL
    HRRZ 1,LEVCHN       ;GET ADR OF USER'S CHANNEL TABLE
    ADDI 1,0(5)     ;COMPUTE ADR OF USER'S CHANNEL WORD
    UMOVE 1,0(1)        ;GET ADR OF USER'S INT ROUTINE
    HRLI 1,UMODF        ;USER MODE ON, OTHER FLAGS OFF
    EXCH 1,PIPC     ;GET OLD PC
    TLNN 1,UMODF        ;WAS IN USER MODE?
    JRST PSISM      ;NO, MUST SAVE MONITOR CONTEXT
PSIS5:  SETZM PIOLDS
    HLRZ 2,LEVCHN       ;GET ADR OF USER'S LEVEL TABLE
    ADDI 2,-1(3)        ;COMPUTE ADR OF USER'S LEVEL WORD
    UMOVE 2,0(2)        ;GET ADR OF PC WORD FOR THIS LEVEL
    TRNN 2,777760       ;ADDRESS IS AC?
    MOVEM 1,PIAC(2)     ;YES, STRANGE BUT ALLOW IT
    TRNE 2,777760       ;NORMALLY,
    UMOVEM 1,0(2)       ;STORE BREAK PC IN USER'S MEMORY
PSID3:  MOVE 1,BITS(5)      ;CLEAR WAITING BREAK BIT FOR THIS CHANNEL
    ANDCAM 1,PSIBW
    JRST PIRQR      ;TO USER

PSIS2:  HRRZ 4,LEVCHN       ;GET ADR OF USER'S CHANNEL TABLE
    ADDI 4,0(2)     ;COMPUTE ADR OF USER'S CHANNEL WORD
    XCTUU [HLRZ 4,0(4)] ;GET LEVEL NUMBER FOR THIS CHANNEL
    CAILE 4,NPILEV      ;LEGAL LEVEL?
    SETZ 4,         ;NO, TREAT AS 0
    CAIG 3,0(4)     ;OLD LEVEL GREATER THAN CURRENT?
    JRST PSIS4      ;NO
    MOVEI 3,0(4)        ;YES, REMEMBER NEW LEVEL
    MOVEI 5,0(2)        ;AND CHANNEL NUMBER
    JRST PSIS4      ;RESUME SCAN

;MONITOR ROUTINE IS SHORTSTOPPING INTERRUPTS - SIMULATE  JSYS MONBK

PSIMB:  HRRZ 1,MONBK        ;ROUTINE ADDRESS
    EXCH 1,PIPC     ;GET OLD PC
    SETZM PIOLDS
    HLRZ 2,MONBK        ;RET LOC
    MOVEM 1,0(2)        ;STORE RETURN
    JRST PIRQR

;SPECIAL ROUTINE TO SAVE STATE OF INTERRUPTED MONITOR
;ROUTINE

PSISM:  MOVS 2,BITS(3)      ;NO, REMEMBER MONITOR INTERRUPT
    IORM 2,PSIBIP       ;IN RH OF BIP WORD
    MOVE 7,PSIPT        ;STORAGE STACK POINTER
    HLRE 6,7        ;SEE IF ENOUGH ROOM
    MOVN 6,6        ;GET POSITIVE COUNT
    MOVE 2,ACBAS
    LSH 2,4         ;2=ADDR OF END OF USER AC BLOCKS
    CAIGE 6,NUPDL-UACB+34(2) ;ENOUGH ROOM?
    BUG(HLT,<PSI STORAGE STACK OVERFLOW>)
    MOVE 10,7       ;SAVE POINTER
    PUSH 7,1
    MOVSI 6,-NSAVC      ;SAVE VULNERABLE CELLS
    PUSH 7,@SAVCT(6)    ; ..
    AOBJN 6,.-1
    MOVSI 6,UPDL        ;STORE ENTIRE MONITOR STACK
    HRRI 6,1(7)
    ADD 7,[XWD NUPDL,NUPDL]
    BLT 6,0(7)
    MOVSI 1,PIAC        ;STORE CURRENT MONITOR AC'S
    HRRI 1,1(7)     ;NOW LIVING IN PIAC
    ADD 7,[XWD 20,20]
    BLT 1,0(7)
IFN KIFLG,<
    MOVEI 1,KIASTK      ;MOVE AC BLOCK 1 TO TOP OF STACK
    XCTUM [BLT 1,KIASTK+17]>
    HRRZ 1,ACBAS        ;STORE ALL AC BLOCKS IN USE
    HRRZ 2,ACBAS1
    SUBI 1,-1(2)        ;COMPUTE NUMBER OF BLOCKS IN USE
    SUBI 2,<PSB-UACPGA>B39    ;FORCE BLT FROM UACPG
    LSH 2,^D18+4
IFN KIFLG,<
    MOVSI 2,KIASTK>      ;ACTUAL TOP OF AC STACK
    MOVE 4,2
    HRRI 2,1(7)
    LSH 1,4
    HRLI 1,0(1)     ;NUMBER OF WORDS BOTH HALFS
    ADD 7,1
    BLT 2,0(7)
    PUSH 7,1        ;SAVE COUNT FOR DEBRK
    PUSH 7,ACBAS        ;AND CURRENT ACBAS
    HRRI 4,PIAC     ;RECOVER USER AC'S AT TIME OF MON CALL
    BLT 4,PIAC+17
    HRRZ 1,ACBAS
    CAIL 1,<EUACB>B39 ;USING PSB FOR AC BLOCKS?
    JRST [  SETZ 1,     ;NO, SWITCH BACK TO PSB
        MOVEI 2,UACPGA
        CALL SETMPG ;UNMAP UACPG
        MOVE 1,PSB+PSBPG
        MOVEM 1,PSB+UACPG   ;RESET MAP ENTRY FOR UACPG
        JRST .+1 ]  ;TO THAT FOR PSB
    MOVE 1,UPDL     ;USER PC AT MONITOR CALL
    PUSH 7,10       ;PSI STACK BEFORE ALL THIS PUSHING
    PUSH 7,1
    PUSH 7,PIPDB        ;SAVE IN CASE PSI DURING PGUTRP
    MOVEM 7,PSIPT
    TLZ 1,UMODF     ;SO HE CAN TELL IT WAS MON INTERRUPT
    SETOM SLOWF
    JRST PSIS5      ;FINISH INTERRUPT START

;XMIT INTERRUPT TO SUPERIOR FORK

PSIT:   HRRZ 2,FORKN
    MOVE 2,FKPTRS(2)    ;POINTERS RELATIVE TO FORK
    LSH 2,-^D24     ;SUPERIOR FORK POINTER
    HRRZ 2,SYSFK(2)     ;SYSTEM FORK INDEX
    JRST PSIRQF     ;REQUEST INTERRUPT

;THIS FORK WON'T TAKE INTERRUPT, DISMISS IT AND RECORD WHY

PSID1:  MOVEI 2,0(5)        ;CHANNEL WITH NO LEVEL ASSIGNED
    JRST PSIN2

PSIN1:  MOVE 1,PSIBW        ;INTERRUPTS OFF OR NO LEVCHN
    JFFO 1,.+1      ;CALCULATE CHANNEL NUMBER
PSIN2:  MOVEM 2,FORCTC      ;SAVE CHANNEL NUMBER FOR STATUS
    MOVE 1,BITS(2)      ;JUST ONE CHANNEL AT A TIME
    ANDCAM 1,PSIBW      ;RESET WAITING BIT
    CALL FKTMI      ;GIVE FORK TERM INTERRUPT
    MOVEI 3,FRZWT       ;FORK FROZEN STATE TEST
    MOVE 1,CAPENB
    TLNE 1,(1B17)       ;SUPERIOR WANTS FROZEN STEAD HALT?
    JRST PIRSK1     ;YES, GO FREEZE
    JSYS UNPIR      ;LEAVE PI STATE, MOVE AC'S ETC.
    MOVEI 1,FORCTM
    JRST DISMSE     ;THIS ONE IS BEING DISMISSED

FORCTM: JRST 0(4)       ;SCHEDULER TEST FOR FORCED TERM FORK

;INTERRUPT SUPERIOR FORK ON TERMINATION

FKTMI:  PUSH P,1
    HRRZ 1,FORKN
    SKIPN 1
    SKIPA 1,[^D35]      ;TERMINATING TOP FORK, GIVE CH 35
    MOVEI 1,^D19        ;19 IS FORK TERMINATED
    CALL PSIT       ;TRANSMIT IT
    POP P,1
    RET

GETCHA: MOVEI 2,0(1)
    IDIVI 2,6
    ADDI 2,PSICHA
    HLL 2,CH6TAB(3)
    RET

;DEFERRED INTERRUPT LOGIC
;SET TRAPS TO RECHECK INTERRUPTS WHEN STATE CHANGES

PSIDFR: MOVE 1,MJRST1
    MOVEM 1,MJRSTF
    MOVE 1,INTDF1
    MOVEM 1,INTDFF
    JSYS UNPIR      ;LEAVE BREAK STARTING STATE
    IORM 1,FKINT(7)     ;BUT LEAVE PENDING BIT
    JRST PSIDF1     ;RESUME

MJRST1: JRSTF @[PSISV0]
INTDF1: JSYS PSISV1

PSISV1: XWD PIPC,.+1
    SOSGE INTDF
    JRSTF @[PSISV2]
    JRSTF @PIPC     ;JUST RETURN, HE'S NOT INTERRUPTIBLE

PSISV0: MOVEM 1,PIPC        ;SAVE AC1
    MOVE 1,FPC      ;FPC NOW CONTAINS USER'S PC
    EXCH 1,PIPC
PSISV2: MOVEM P,PIAC+17     ;SAVE USER'S AC17
    MOVEI P,PIAC        ;AND AC'S 0-NSAC
    BLT P,PIAC+16
    MOVE P,PIPDL        ;RESTORE INTERRUPT STARTING STATE
    PUSH P,PGURET       ;SAVE PGURET
    SETZM PIOLDS
PSISV3: MOVE 2,FORKX
    MOVE 7,FKINT(2)
    JRST PSII       ;ENTER MAIN SEQUENCE

;TEST FOR IMMEDIATE OR DEFERRED INTERRUPT
;SKIP => IMMEDIATE
;NOSKIP => DEFERRED
;CALLED WITH TEST USER PC IN AC1

PITEST: TLNE 1,UMODF        ;USER MODE?
    JRST RSKP       ;YES, IMMEDIATE
    SKIPL SLOWF     ;NO, SLOW CODE?
    SKIPL INTDF     ;YES, INTERRUPTABLE
    RET         ;NO, DEFER
    SKIPN NSKED     ;IN CASE NOSKED W/O NOINT
    SKIPL TRAPC     ;IN PAGER TRAP, OR
    RET         ;YES, DEFER
    JRST RSKP       ;IMMEDIATE

;DEBREAK

.DEBRK: SKIPN PSIBIP        ;ANY BREAKS IN PROGRESS?
    XCT MJRSTF      ;NO, ACTS AS NOP
    MOVEM 1,TW1     ;SAVE USER AC1,2
    MOVEM 2,TW2
    MOVE 2,FORKX
    MOVSI 1,200000
    IORM 1,FKINT(2)     ;SET INTERRUPT STARTING BIT
    MOVE 2,TW2
    MOVE 1,TW1
    MOVEM P,PIAC+17     ;ENTER INTERRUPT STARTING STATE
    MOVEI P,PIAC
    BLT P,PIAC+16
    MOVE P,PIPDL
    PUSH P,PGURET       ;SAVE PGURET ON LOCAL STACK
    SETZM PIOLDS
PSIDBK: MOVE 2,PSIBIP       ;BREAKS NOW IN PROGRESS
    JFFO 2,.+2      ;FIND HIGHEST ONE
    JRST 4,.        ;IMPOSSIBLE
    HLRZ 1,LEVCHN       ;COMPUTE ADDRESS OF RETURN PC
    ADDI 1,-1(3)
    UMOVE 1,0(1)
    TRNN 1,777760       ;ADDRESS IS AC?
    SKIPA 1,PIAC(1)     ;YES
    UMOVE 1,0(1)        ;GET RETURN PC FROM USER MEMORY
    MOVS 2,BITS(3)
    TDNE 2,PSIBIP       ;WAS THIS MONITOR INTERRUPT?
    JRST PSIS7      ;YES, GO UNWIND
PSIS8:
IFN KAFLG,<
    TLZ 1,7637>      ;FLUSH TROUBLESOME BITS
IFN KIFLG,<
    TLZ 1,7037>      ; ..
    TLO 1,UMODF     ;MAKE SURE USER MODE IS ON
    MOVEM 1,PIPC        ;SET TO DEBREAK AT THAT ADDRESS
PSIS6:  MOVE 1,BITS(3)
    ANDCAM 1,PSIBIP     ;CLEAR BIP THIS LEVEL
    JRST PSISV3     ;GO CHECK FOR OTHER INTERRUPTS AND RETURN

PSIS7:  ANDCAM 2,PSIBIP     ;CLEAR MON BREAK FLAG FOR THIS LEVEL
    MOVE 7,PSIPT
    POP 7,PIPDB     ;RESTORE PGURET ON LOCAL STACK
    POP 7,4         ;PC GIVEN TO USER
    POP 7,PSIPT     ;TOP OF THIS BLOCK OF PSI STORAGE
    TLON 1,UMODF        ;IF IT WAS DIDDLED AT ALL,
    CAME 1,4
    JRST PSIS8      ;DON'T RESUME MON ROUTINE
    POP 7,2
    MOVEM 2,ACBAS
    SETACB 2
    POP 7,4
    SUB 7,4
    HRRZ 5,ACBAS1
    LSH 5,4
    CAIL 2,<EUACB>B39 ;PREVIOUSLY USING PSB FOR AC BLOCKS?
    JRST [  SUBI 5,PSB-UACPGA   ;NO, FORCE BLT TO UACPG
        SETZM PSB+UACPG     ;CLEAR UACPG MAP ENTRY
        PUSH P,1
        LDB 1,[POINT 13,PSB+PSBPG,26]
        MOVE 2,[XWD RCW,UACPGA]
        CALL SETMPG     ;SET UP MAP FOR UACPG
        POP P,1
        JRST .+1 ]
IFN KIFLG,<
    MOVEI 5,KIASTK>      ;ACTUAL TO OF KI STACK
    MOVE 2,5        ;SAVE ACB ADDRESS
    HRLI 5,1(7)
    ADDI 4,0(5)
    BLT 5,-1(4)     ;RESTORE AC BLOCKS
    MOVSI 5,PIAC        ;PUT USER CURRENT AC'S INTO TOP BLOCK
    HRRI 5,0(2)
    BLT 5,17(2)
IFN KIFLG,<
    MOVSI 5,KIASTK      ;PUT TOP OF STACK BACK IN AC BLOCK 1
    XCTMU [BLT 5,17]>
    SUB 7,[XWD 20,20]
    HRRZ 5,ACBAS
    CAIGE 5,<EUACB>B39    ;AC BLOCKS IN PSB?
    JRST PSIS7A     ;YES
    HRRZ 5,ACBAS1       ;NO, MUST COPY THEN FROM UACPG
    SUBI 5,<PSB-UACPGA>B39    ;TO PSB
    HRL 5,ACBAS1
    LSH 5,4
    MOVSS 5
    BLT 5,EUACB-1       ;COPY AS MANY AS FIT IN PSB

PSIS7A: MOVEI 2,PIAC
    HRLI 2,1(7)
    BLT 2,PIAC+17       ;RESTORE MONITOR AC'S
    SUB 7,[XWD NUPDL,NUPDL]
    MOVEI 2,UPDL
    HRLI 2,1(7)
    BLT 2,UPDL+NUPDL-1  ;RESTORE STACK
    MOVEI 2,NSAVC-1     ;RESTORE GROUP OF CELLS
    POP 7,@SAVCT(2)
    SOJGE 2,.-1
    POP 7,PIPC      ;ACTUAL MON INTERRUPT PC
    SETZM SLOWF
    SETOM INTDF
    JRST PSIS6      ;NOW DEBRK

;TABLE OF CELLS STORED ON PI STACK AROUND A PSI

SAVCT:  40
    60
    MPP
    FPC
    PIOLDS
    XMENTR
IFN KIFLG,<
    KIMAC1
    KIMAC2>
NSAVC==.-SAVCT      ;LENGTH OF THIS LIST

;TRAP AND PSI ROUTINE EXECUTED WHEN A FORK EXECUTES A TRAPPED JSYS

JTSRVD==400000      ;TRAP SERVICED FLAG

TRAPSI: XWD JTTMP,TRPSI0
TRPSI0: MOVEM 1,TW1     ;SAVE AC1 AND AC2
    MOVEM 2,TW2
    MOVSI 1,200000
    MOVE 2,FORKX        ;PREVENT PSI'S WHILE IN
    IORM 1,FKINT(2)     ;THIS CODE
    HRRZ 1,JTMNW        ;GET HANDLE OF IMMED MON.
    CAIN 1,7777     ;NULL MON FORK?
    BUG(CHK,<UNMONITORED FORK TRAPPED>)

    HRRZ 1,JTTMP        ;DETERMINE JSYS EXECUTING WHEN
    SUBI 1,JTDVC1+1     ;TRAPPED
    DPB 1,JTTJSY        ;SAVE IT
IFE JTRPSW-1,<
    CONO PGR,6      ;UNMAP RES MON
        >
    HRRZ 1,NJDV(1)      ;GET NORMAL DISPATCH
IFE JTRPSW-1,<
    CONO PGR,7      ;REMAP RES MON
        >
    HRRM 1,JTTMP        ;AND SAVE IT.  AT THIS POINT
    MOVE 1,FPC      ;RH(JTTMP)=NORM DISP
    TLNE 1,UMODF        ;CALL FROM USER MODE?
    JRST TRPSI1     ;YES, DETERMINE FORK TO PSI
    MOVSI 1,200000      ;NO, ACCEPT PSI'S,
    MOVE 2,FORKX    
    ANDCAM 1,FKINT(2)
    MOVE 1,TW1      ;RESTORE ACS 1 AND 2,
    MOVE 2,TW2
    JRST @JTTMP     ;AND DO NORMAL DISPATCH

TRPSI1: MOVEM 1,JTFPC
    MOVE 1,TW1      ;RESTORE ACS 1 AND 2
    MOVE 2,TW2

    MOVEM P,XMENT1      ;CALL FROM USER MODE, SET UP
    MOVE P,UPP      ;STACK, ETC. AS IN BECOMING SLOW
    MOVEM P,MPP     ;BUT DON'T BE INTERRUPTABLE
    MOVE P,ACBAS1
    MOVEM P,ACBAS
    SETACB P
    MOVE P,XMENT1
    UMOVEM P,P
    SETZ P,
    XCTMU [ BLT P,P-1 ]
    MOVE P,MPP

    PUSH P,FPC      ;SAVE USER RETRUN FOR RFSTS,ETC
    HRRZ 1,JTMNW        ;GET IMMED MONITOR
    LDB 2,JTTJSY
    IDIVI 2,^D36
    MOVE 3,BITS(3)      ;2=OFFSET INTO BIT TABLE
    TDNE 3,JTBTB(2)     ;3=MASK FOR TRAPPED JSYS.
    JRST TRPSI3     ;HANDLED BY IMMED MONITOR

TRPSI2: JUMPN 1,TRPSIA      ;FORK=TOP JOB FORK?
TRPSIB: TLNN 2,JTSRVD       ;YES, TRAP HANDLED YET?
    BUG(CHK,<NO MONITOR FOR TRAPPED FORK>)
    JRST TRPSI6     ;YES, DO NORMAL DISPATCH.

TRPSIA: MOVEI 11,0(1)
    CALL SETLF1     ;MAP MONITOR'S PSB
    EXCH 1,11
    HRRZ 1,JTMNW(11)    ;1=ITS MONITOR
    CAIN 1,7777     ;NULL MONITOR?
    JRST TRPSIB     ;YES.
    ADD 11,2
    TDNN 3,JTBTB(11)    ;IS THAT FORK HANDLING JSYS?
    JRST TRPSI2     ;NO, TRY ITS MONITOR.

TRPSI3: TLO 2,JTSRVD        ;INDICATE TRAP HANDLED
    MOVEI 11,0(1)
    CALL SETLF1     ;MAP PSB OF THE MONITOR
    EXCH 1,11
TRPSI4: HLRZ 4,JTMNW(11)
    TRZ 4,777700
    CAIN 4,77       ;IS CHANNEL SPECIFIED?
    JRST TRPSI5     ;NO, DON'T PSI
    CALL JTLOCK     ;SYNCH WITH OTHER TRAPPING FORKS
    JRST TRPSI4     ;FORK SUSPENDED AND RESUMED
                ;WHILE QUEUED, RETRY LOCKING
    JSYS FRZPSI     ;FREEZE SELF AND PSI MONITOR

;RESUMED HERE AFTER TRAP HANDLED IF MONITOR DOES NOT CHANGE PC

TRPSI5: JUMPE 1,TRPSI6      ;IF TOP JOB FORK DO DISPATCH
    JRST TRPSIA     ;OTHERWISE LOOK FOR MORE MONS.

TRPSI6: LDB 1,JTTJSY
IFE JTRPSW-1,<
    CONO PGR,6      ;UNMAP RES MON
        >
    HRRZ 1,NJDV(1)      ;GET NORMAL DISPATCH
IFE JTRPSW-1,<
    HRRZ 2,JTMNW
    CAIE 2,7777     ;UNLESS NO LONGER TRAPPED
    CONO PGR,7      ;REMAP RES MON
        >
    MOVEM 1,JTTMP
    MOVE P,JTFPC
    MOVEM P,FPC     ;RESTORE FPC
    TLZ P,UMODF
    HLLM P,JTTMP
    MOVSI 1,200000
    MOVE 2,FORKX
    ANDCAM 1,FKINT(2)   ;ACCEPT PSI'S
    SETZ P,
    XCTUM [ BLT P,P-1 ] ;RESTORE ACS
    UMOVE P,P
    JRSTF @JTTMP        ;DO NORMAL DISP FOR THIS JSYS

;FREEZE AND PSI ROUTINE - FORK INITIATES JSYS TRAP PSI OF
;FORK TRAPPED TO AND THEN FREEZES ITSELF
;1/ JOB INDEX OF FORK TO PSI
;11/ OFFSET TO FORKS PSB

FRZPSI: XWD FPC,FRZPS0

FRZPS0: LDB 4,JTTJSY
    HRL 4,FORKN     ;4=FORK INDEX,,JSYS
    MOVEM 1,JTTMP       ;SAVE FORK TRAPPED FOR UTFRK
    MOVEM 4,JTTRW(11)   ;SET TRAPPED FORK,,JSYS
    HRRZ 4,SYSFK(1)
    ENTSKD          ;ENTER THE SCHEDULER, SAVE ACS,
                ;ETC.
    MOVEI 2,0(4)
    MOVSI 1,400000+PSIJTR
    IORM 1,FKINT(2)     ;PSI MONITORING FORK
    CALL PSIR4      ;MAKE SCHEDULER SEE IT

    MOVSI 1,200000+JTFRZB   ;DO "JSYS TRAP" FREEZE OF SELF
    IORM 1,FKINT(7)     ;7=FORKX SET BY ENSKED
    SETZM PIOLDS        ;"OLD STATE" = RUNNING
    MOVEI 1,FRZWT
    JRST DISMS1     ;DISMS

JTMCN:  POINT 6,JTMNW,17    ;SELECTS PSI CHANNEL
JTTJSY: POINT 9,JTMNW,11    ;PTR TO TEMP STORAGE FOR TRAPPED JSYS

JTDVC1: XLIST   ;REPEAT 1000,<   JSYS TRAPSI >
    REPEAT 1000,<    JSYS TRAPSI >
    LIST
;THE INTERMEDIATE DISPATCH VECTOR.
;TRAPPED JSYS'S DISPATCH THROUGH IT
;TO TRAPSI ROUTINE

;JSYS TRAP LOCK AND UNLOCK ROUTINES
;WHEN A FORK TRIES JTLOCK AND SOME OTHER FORK HAS THE
;LOCK, THE FORK ADDS ITSELF TO A QUEUE (FKJTQ) AND BECOMES BLOCKED.
;WHEN THE LOCK IS CLEARED (BY A MONITORING FORK) THE QUEUE IS
;SCANNED FOR THE FIRST FORK (IF ANY) WAITING ON THE LOCK.  THAT
;FORK IS REMOVED FROM THE QUEUE AND ALLOWED TO RUN.

;LOCK ROUTINE
;ON ENTRY TO JTLOCK:
;1/ JOB FORK INDEX (OF FORK TO FIELD TRAP)
;11/ PTR TO ITS PSB
;RET + 1 IF SUSPENDED AND RESUMED WHILE QUEUED
;RET + 2 WITH LOCK SET

JTLOCK: NOSKED
    AOSE JTLCK(11)      ;TRY TO SEIZE THE LOCK
    JRST JTLOC2     ;SOMEONE ELSE HAS IT
    OKSKED          ;GOT IT
JTLOC1: AOS 0(P)        ;RET + 2
    RET

JTLOC2: JSYS JTENQ      ;PUT SELF ON JSYS TRAP QUEUE
    JRST JTLOC1     ;RETURNS HERE WITH LOCK SEIZED

;IF FORK IS RESUMED AT JTRLCK, IT RETURNS + 1 TO TRAPSI ROUTINE
;FORCING ANOTHER CALL TO JTLOCK AFTER A CHECK TO SEE IF THE TRAP IS
;STILL TO GO TO THE SAME FORK.

JTRLCK: MOVEM 1,TW1     ;SAVE ACS 1 AND 2
    MOVEM 2,TW2
    MOVSI 1,200000
    MOVE 2,FORKX
    IORM 1,FKINT(2)     ;PREVENT PSI'S BEFORE REENTERING
    MOVE 1,TW1      ;TRAP CODE
    MOVE 2,TW2
    RET

JTENQ:  XWD FPC,JTENQ0      ;ROUTINE TO PALCE FORK ON QUEUE
JTENQ0: HRL 1,SYSFK(1)      ;1=FORK WAITING ON
    ENTSKD          ;ENTER SCHEDULER
    SOSE NSKED      ;MATCHED NOSKED IN JTLOCK
    BUG(HLT,<JTENQ WITH BAD NSKED>)
    MOVEI 6,FKJTQ(7)    ;7=FORKX, SET BY ENSKED
    HRRM 6,@JTLSTL      ;ADD THIS FORK TO END OF QUEUE
    EXCH 6,JTLSTL       ;SET NEW END OF QUEUE PTR
    MOVSM 6,FKJTQ(7)    ;SET BACK PTR TO OLD QUEUE END
    HRRI 1,JTQWT
    JRST DISMS1     ;DISMS

;JSYS TRAP QUEUE WAIT TEST

JTQWT:  MOVE 1,FKINT(7)     ;DID A SUSPEND REQUEST OCCUR
    TLNN 1,SUSFKR       ;BEFORE BLOCKING?
    JRST 0(4)       ;NO.
    MOVSI 1,400000+PSIWTF   ;YES, REINITIATE SUSPEND
    IORM 1,FKINT(7)     ;REQUEST PSI
    MOVSI 1,200000
    ANDCAM 1,FKINT(7)   ;ALLOW PSI'S
    HLRZ 1,FKQ(7)
    CAIG 1,1
    JRST 1(4)
    PUSH P,3        ;GIVE FORK SOME PRIORITY
    CALL PSSKD2
    POP P,3
    JRST 1(4)

;UNLOCK ROUTINE
;USES BUT DOES NOT SAVE ACS 1,2,3,4

JTULCK: HRRZ 2,FORKX
       NOSKED
    MOVE 1,JTLST        ;SCAN QUEUE LOOKING FOR FORK
                ;WAITING ON EXECUIING FORK
JTULC1: JUMPE 1,JTULC3      ;NONE FOUND
    MOVEI 4,0(1)
    SUBI 4,FKJTQ        ;4=FORK INDEX OF QUEUED FORK
    HLRZ 3,FKSTAT(4)
    CAMN 3,2        ;THIS FORK WAITING ON EX FORK?
    JRST JTULC2     ;YES, REMOVE IT FROM QUEUE
    HRRZ 1,0(1)     ;NO, TRY NEXT FORK
    JRST JTULC1

JTULC2: CALL JTDEQ      ;REMOVE FORK FROM QUEUE
    MOVEI 3,JSKP        ;SET WAIT TEST TO SKIP
    MOVEM 3,FKSTAT(4)   ;CAUSING FORK TO RUN
    CAIA
JTULC3: SETOM JTLCK     ;NO FORKS ON QUEUE, CLEAR LOCK
       OKSKED
    RET

;REMOVE FORK WHOSE FKJTQ ENTRY IS PT'D TO BY 1 FROM JSYS TRAP QUEUE
;USES BUT DOES NOT SAVE ACS 2,3

JTDEQ:  NOSKED
    HRRZ 3,(1)      ;3=PTR TO NEXT ITEM ON QUEUE
    HLRZ 2,(1)      ;2=PTR TO PREV ITEM
    HRRM 3,(2)
    JUMPE 3,JTDEQ1      ;REMOVING LAST ITEM?
    HRLM 2,(3)      ;NO
    CAIA
JTDEQ1: MOVEM 2,JTLSTL
       OKSKED
    RET

END         ;END OF SCHED.MAC FILE

2. 1992年02月22日的Linux kernel 0.96.a版本中第一次引入这个功能-

https://elixir.bootlin.com/linux/0.96a/source/kernel/sched.c
/kernel/sched.c
344行:

/*
 *  linux/kernel/sched.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#include <signal.h>
#include <errno.h>

int need_resched = 0;

#define _S(nr) (1<<((nr)-1))
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

void show_task(int nr,struct task_struct * p)
{
    int i,j = 4096-sizeof(struct task_struct);

    printk("%d: pid=%d, state=%d, father=%d, child=%d, ",nr,p->pid,
        p->state, p->p_pptr->pid, p->p_cptr ? p->p_cptr->pid : -1);
    i=0;
    while (i<j && !((char *)(p+1))[i])
        i++;
    printk("%d/%d chars free in kstack\n\r",i,j);
    printk("   PC=%08X.", *(1019 + (unsigned long *) p));
    if (p->p_ysptr || p->p_osptr) 
        printk("   Younger sib=%d, older sib=%d\n\r", 
            p->p_ysptr ? p->p_ysptr->pid : -1,
            p->p_osptr ? p->p_osptr->pid : -1);
    else
        printk("\n\r");
}

void show_state(void)
{
    int i;

    printk("\rTask-info:\n\r");
    for (i=0 ; i<NR_TASKS ; i++)
        if (task[i])
            show_task(i,task[i]);
}

#define LATCH (1193180/HZ)

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

union task_union {
    struct task_struct task;
    char stack[PAGE_SIZE];
};

static union task_union init_task = {INIT_TASK,};

unsigned long volatile jiffies=0;
unsigned long startup_time=0;
int jiffies_offset = 0;     /* # clock ticks to add to get "true
                   time".  Should always be less than
                   1 second's worth.  For time fanatics
                   who like to syncronize their machines
                   to WWV :-) */

struct task_struct *current = &(init_task.task);
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&(init_task.task), };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
    long * a;
    short b;
    } stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 */
void math_state_restore()
{
    if (last_task_used_math == current)
        return;
    __asm__("fwait");
    if (last_task_used_math) {
        __asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
    }
    last_task_used_math=current;
    if (current->used_math) {
        __asm__("frstor %0"::"m" (current->tss.i387));
    } else {
        __asm__("fninit"::);
        current->used_math=1;
    }
}

/*
 *  'schedule()' is the scheduler function. It's a very simple and nice
 * scheduler: it's not perfect, but certainly works for most things.
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */
void schedule(void)
{
    int i,next,c;
    struct task_struct ** p;

/* check alarm, wake up any interruptible tasks that have got a signal */

    need_resched = 0;
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
        if (*p) {
            if ((*p)->timeout && (*p)->timeout < jiffies)
                if ((*p)->state == TASK_INTERRUPTIBLE) {
                    (*p)->timeout = 0;
                    (*p)->state = TASK_RUNNING;
                }
            if ((*p)->alarm && (*p)->alarm < jiffies) {
                (*p)->signal |= (1<<(SIGALRM-1));
                (*p)->alarm = 0;
            }
            if (((*p)->signal & ~(*p)->blocked) &&
            (*p)->state==TASK_INTERRUPTIBLE)
                (*p)->state=TASK_RUNNING;
        }

/* this is the scheduler proper: */

    while (1) {
        c = -1;
        next = 0;
        i = NR_TASKS;
        p = &task[NR_TASKS];
        while (--i) {
            if (!*--p)
                continue;
            if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                c = (*p)->counter, next = i;
        }
        if (c) break;
        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) +
                        (*p)->priority;
    }
    switch_to(next);
}

int sys_pause(void)
{
    unsigned long old_blocked;
    unsigned long mask;
    struct sigaction * sa = current->sigaction;

    old_blocked = current->blocked;
    for (mask=1 ; mask ; sa++,mask += mask)
        if (sa->sa_handler == SIG_IGN)
            current->blocked |= mask;
    current->state = TASK_INTERRUPTIBLE;
    schedule();
    current->blocked = old_blocked;
    return -EINTR;
}

/*
 * wake_up doesn't wake up stopped processes - they have to be awakened
 * with signals or similar.
 */
void wake_up(struct task_struct **p)
{
    struct task_struct * wakeup_ptr, * tmp;

    if (p && *p) {
        wakeup_ptr = *p;
        *p = NULL;
        while (wakeup_ptr && wakeup_ptr != task[0]) {
            if (wakeup_ptr->state == TASK_ZOMBIE)
                printk("wake_up: TASK_ZOMBIE\n");
            else if (wakeup_ptr->state != TASK_STOPPED) {
                wakeup_ptr->state = TASK_RUNNING;
                if (wakeup_ptr->counter > current->counter)
                    need_resched = 1;
            }
            tmp = wakeup_ptr->next_wait;
            wakeup_ptr->next_wait = task[0];
            wakeup_ptr = tmp;
        }
    }
}

static inline void __sleep_on(struct task_struct **p, int state)
{
    unsigned int flags;

    if (!p)
        return;
    if (current == task[0])
        panic("task[0] trying to sleep");
    __asm__("pushfl ; popl %0":"=r" (flags));
    current->next_wait = *p;
    task[0]->next_wait = NULL;
    *p = current;
    current->state = state;
    sti();
    schedule();
    if (current->next_wait != task[0])
        wake_up(p);
    current->next_wait = NULL;
    __asm__("pushl %0 ; popfl"::"r" (flags));
}

void interruptible_sleep_on(struct task_struct **p)
{
    __sleep_on(p,TASK_INTERRUPTIBLE);
}

void sleep_on(struct task_struct **p)
{
    __sleep_on(p,TASK_UNINTERRUPTIBLE);
}

/*
 * OK, here are some floppy things that shouldn't be in the kernel
 * proper. They are here because the floppy needs a timer, and this
 * was the easiest way of doing it.
 */
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
static int  mon_timer[4]={0,0,0,0};
static int moff_timer[4]={0,0,0,0};
unsigned char current_DOR = 0x0C;

int ticks_to_floppy_on(unsigned int nr)
{
    extern unsigned char selected;
    unsigned char mask = 0x10 << nr;

    if (nr>3)
        panic("floppy_on: nr>3");
    moff_timer[nr]=10000;       /* 100 s = very big :-) */
    cli();              /* use floppy_off to turn it off */
    mask |= current_DOR;
    if (!selected) {
        mask &= 0xFC;
        mask |= nr;
    }
    if (mask != current_DOR) {
        outb(mask,FD_DOR);
        if ((mask ^ current_DOR) & 0xf0)
            mon_timer[nr] = HZ/2;
        else if (mon_timer[nr] < 2)
            mon_timer[nr] = 2;
        current_DOR = mask;
    }
    sti();
    return mon_timer[nr];
}

void floppy_off(unsigned int nr)
{
    moff_timer[nr]=3*HZ;
}

void do_floppy_timer(void)
{
    int i;
    unsigned char mask = 0x10;

    for (i=0 ; i<4 ; i++,mask <<= 1) {
        if (!(mask & current_DOR))
            continue;
        if (mon_timer[i]) {
            if (!--mon_timer[i])
                wake_up(i+wait_motor);
        } else if (!moff_timer[i]) {
            current_DOR &= ~mask;
            outb(current_DOR,FD_DOR);
        } else
            moff_timer[i]--;
    }
}

#define TIME_REQUESTS 64

static struct timer_list {
    long jiffies;
    void (*fn)();
    struct timer_list * next;
} timer_list[TIME_REQUESTS] = { { 0, NULL, NULL }, };

static struct timer_list * next_timer = NULL;

void add_timer(long jiffies, void (*fn)(void))
{
    struct timer_list * p;

    if (!fn)
        return;
    cli();
    if (jiffies <= 0)
        (fn)();
    else {
        for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
            if (!p->fn)
                break;
        if (p >= timer_list + TIME_REQUESTS)
            panic("No more time requests free");
        p->fn = fn;
        p->jiffies = jiffies;
        p->next = next_timer;
        next_timer = p;
        while (p->next && p->next->jiffies < p->jiffies) {
            p->jiffies -= p->next->jiffies;
            fn = p->fn;
            p->fn = p->next->fn;
            p->next->fn = fn;
            jiffies = p->jiffies;
            p->jiffies = p->next->jiffies;
            p->next->jiffies = jiffies;
            p = p->next;
        }
    }
    sti();
}

#define FSHIFT  11
#define FSCALE  (1<<FSHIFT)
/*
 * Constants for averages over 1, 5, and 15 minutes
 * when sampling at 5 second intervals.
 */
static unsigned long cexp[3] = {
    1884,   /* 0.9200444146293232 * FSCALE,  exp(-1/12) */
    2014,   /* 0.9834714538216174 * FSCALE,  exp(-1/60) */
    2037,   /* 0.9944598480048967 * FSCALE,  exp(-1/180) */
};
unsigned long averunnable[3];   /* fixed point numbers */

void update_avg(void)
{
        int i, n=0;
    struct task_struct **p;

    for(p = &LAST_TASK; p > &FIRST_TASK; --p)
        if (*p && ((*p)->state == TASK_RUNNING || 
               (*p)->state == TASK_UNINTERRUPTIBLE))
            ++n;

    for (i = 0; i < 3; ++i)
        averunnable[i] = (cexp[i] * averunnable[i] +
            n * FSCALE * (FSCALE - cexp[i])) >> FSHIFT;
}

unsigned long timer_active = 0;
struct timer_struct timer_table[32];

void do_timer(long cpl)
{
    unsigned long mask;
    struct timer_struct *tp = timer_table+0;
    static int avg_cnt;

    for (mask = 1 ; mask ; tp++,mask += mask) {
        if (mask > timer_active)
            break;
        if (!(mask & timer_active))
            continue;
        if (tp->expires > jiffies)
            continue;
        timer_active &= ~mask;
        tp->fn();
        sti();
    }

    if (cpl)
        current->utime++;
    else
        current->stime++;

    if (next_timer) {
        next_timer->jiffies--;
        while (next_timer && next_timer->jiffies <= 0) {
            void (*fn)(void);

            fn = next_timer->fn;
            next_timer->fn = NULL;
            next_timer = next_timer->next;
            (fn)();
        }
    }
    if (current_DOR & 0xf0)
        do_floppy_timer();
    if (--avg_cnt < 0) {
        avg_cnt = 500;
        update_avg();
    }
    if ((--current->counter)<=0) {
        current->counter=0;
        need_resched = 1;
    }
}

int sys_alarm(long seconds)
{
    int old = current->alarm;

    if (old)
        old = (old - jiffies) / HZ;
    current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
    return (old);
}

int sys_getpid(void)
{
    return current->pid;
}

int sys_getppid(void)
{
    return current->p_pptr->pid;
}

int sys_getuid(void)
{
    return current->uid;
}

int sys_geteuid(void)
{
    return current->euid;
}

int sys_getgid(void)
{
    return current->gid;
}

int sys_getegid(void)
{
    return current->egid;
}

int sys_nice(long increment)
{
    if (increment < 0 && !suser())
        return -EPERM;
    if (increment >= current->priority)
        increment = current->priority-1;
    current->priority -= increment;
    return 0;
}

void sched_init(void)
{
    int i;
    struct desc_struct * p;

    if (sizeof(struct sigaction) != 16)
        panic("Struct sigaction MUST be 16 bytes");
    set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
    set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
    p = gdt+2+FIRST_TSS_ENTRY;
    for(i=1 ; i<NR_TASKS ; i++) {
        task[i] = NULL;
        p->a=p->b=0;
        p++;
        p->a=p->b=0;
        p++;
    }
/* Clear NT, so that we won't have troubles with that later on */
    __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
    ltr(0);
    lldt(0);
    outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */
    outb_p(LATCH & 0xff , 0x40);    /* LSB */
    outb(LATCH >> 8 , 0x40);  /* MSB */
    set_intr_gate(0x20,&timer_interrupt);
    outb(inb_p(0x21)&~0x01,0x21);
    set_system_gate(0x80,&system_call);
}

3. 1993年09月20日的Linux kernel 0.99.13版本中的sched.c源代码

https://kernelhistory.sourcentral.org/linux-0.99.13/?f=/linux-0.99.13/S/449.html%23L332
/root/kernel/sched.c
338行: (*p)->state == TASK_RUNNING

/*
 *  linux/kernel/sched.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */

#include <linux/config.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/ptrace.h>
#include <linux/segment.h>
#include <linux/delay.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define TIMER_IRQ 0

int need_resched = 0;

/*
 * Tell us the machine setup..
 */
int hard_math = 0;              /* set by boot/head.S */
int x86 = 0;                    /* set by boot/head.S to 3 or 4 */
int ignore_irq13 = 0;           /* set if exception 16 works */
int wp_works_ok = 0;            /* not used currently */

extern int _setitimer(int, struct itimerval *, struct itimerval *);
unsigned long * prof_buffer = NULL;
unsigned long prof_len = 0;

#define _S(nr) (1<<((nr)-1))

#define LATCH ((1193180 + HZ/2)/HZ)

extern void mem_use(void);

extern int timer_interrupt(void);
asmlinkage int system_call(void);

static unsigned long init_kernel_stack[1024];
struct task_struct init_task = INIT_TASK;

unsigned long volatile jiffies=0;
unsigned long startup_time=0;
int jiffies_offset = 0;         /* # clock ticks to add to get "true
                                   time".  Should always be less than
                                   1 second's worth.  For time fanatics
                                   who like to syncronize their machines
                                   to WWV :-) */

struct task_struct *current = &init_task;
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&init_task, };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
        long * a;
        short b;
        } stack_start = { & user_stack [PAGE_SIZE>>2] , KERNEL_DS };
/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 *
 * Careful.. There are problems with IBM-designed IRQ13 behaviour.
 * Don't touch unless you *really* know how it works.
 */
asmlinkage void math_state_restore(void)
{
        __asm__ __volatile__("clts");
        if (last_task_used_math == current)
                return;
        timer_table[COPRO_TIMER].expires = jiffies+50;
        timer_active |= 1<<COPRO_TIMER; 
        if (last_task_used_math)
                __asm__("fnsave %0":"=m" (last_task_used_math->tss.i387));
        else
                __asm__("fnclex");
        last_task_used_math = current;
        if (current->used_math) {
                __asm__("frstor %0": :"m" (current->tss.i387));
        } else {
                __asm__("fninit");
                current->used_math=1;
        }
        timer_active &= ~(1<<COPRO_TIMER);
}

/*
 *  'schedule()' is the scheduler function. It's a very simple and nice
 * scheduler: it's not perfect, but certainly works for most things.
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 *
 * The "confuse_gcc" goto is used only to get better assembly code..
 * Djikstra probably hates me.
 */
asmlinkage void schedule(void)
{
        int c;
        struct task_struct * p;
        struct task_struct * next;

/* check alarm, wake up any interruptible tasks that have got a signal */

        sti();
        need_resched = 0;
        p = &init_task;
        for (;;) {
                if ((p = p->next_task) == &init_task)
                        goto confuse_gcc1;
                if (p->state != TASK_INTERRUPTIBLE)
                        continue;
                if (p->signal & ~p->blocked) {
                        p->state = TASK_RUNNING;
                        continue;
                }
                if (p->timeout && p->timeout < jiffies) {
                        p->timeout = 0;
                        p->state = TASK_RUNNING;
                }
        }
confuse_gcc1:

/* this is the scheduler proper: */
#if 0
        /* give processes that go to sleep a bit higher priority.. */
        /* This depends on the values for TASK_XXX */
        /* This gives smoother scheduling for some things, but */
        /* can be very unfair under some circumstances, so.. */
        if (TASK_UNINTERRUPTIBLE >= (unsigned) current->state &&
            current->counter < current->priority*2) {
                ++current->counter;
        }
#endif
        c = -1;
        next = p = &init_task;
        for (;;) {
                if ((p = p->next_task) == &init_task)
                        goto confuse_gcc2;
                if (p->state == TASK_RUNNING && p->counter > c)
                        c = p->counter, next = p;
        }
confuse_gcc2:
        if (!c) {
                for_each_task(p)
                        p->counter = (p->counter >> 1) + p->priority;
        }
        switch_to(next);
}

asmlinkage int sys_pause(void)
{
        current->state = TASK_INTERRUPTIBLE;
        schedule();
        return -ERESTARTNOHAND;
}

/*
 * wake_up doesn't wake up stopped processes - they have to be awakened
 * with signals or similar.
 *
 * Note that this doesn't need cli-sti pairs: interrupts may not change
 * the wait-queue structures directly, but only call wake_up() to wake
 * a process. The process itself must remove the queue once it has woken.
 */
void wake_up(struct wait_queue **q)
{
        struct wait_queue *tmp;
        struct task_struct * p;

        if (!q || !(tmp = *q))
                return;
        do {
                if ((p = tmp->task) != NULL) {
                        if ((p->state == TASK_UNINTERRUPTIBLE) ||
                            (p->state == TASK_INTERRUPTIBLE)) {
                                p->state = TASK_RUNNING;
                                if (p->counter > current->counter)
                                        need_resched = 1;
                        }
                }
                if (!tmp->next) {
                        printk("wait_queue is bad (eip = %08x)\n",((unsigned long *) q)[-1]);
                        printk("        q = %p\n",q);
                        printk("       *q = %p\n",*q);
                        printk("      tmp = %p\n",tmp);
                        break;
                }
                tmp = tmp->next;
        } while (tmp != *q);
}

void wake_up_interruptible(struct wait_queue **q)
{
        struct wait_queue *tmp;
        struct task_struct * p;

        if (!q || !(tmp = *q))
                return;
        do {
                if ((p = tmp->task) != NULL) {
                        if (p->state == TASK_INTERRUPTIBLE) {
                                p->state = TASK_RUNNING;
                                if (p->counter > current->counter)
                                        need_resched = 1;
                        }
                }
                if (!tmp->next) {
                        printk("wait_queue is bad (eip = %08x)\n",((unsigned long *) q)[-1]);
                        printk("        q = %p\n",q);
                        printk("       *q = %p\n",*q);
                        printk("      tmp = %p\n",tmp);
                        break;
                }
                tmp = tmp->next;
        } while (tmp != *q);
}

static inline void __sleep_on(struct wait_queue **p, int state)
{
        unsigned long flags;
        struct wait_queue wait = { current, NULL };

        if (!p)
                return;
        if (current == task[0])
                panic("task[0] trying to sleep");
        current->state = state;
        add_wait_queue(p, &wait);
        save_flags(flags);
        sti();
        schedule();
        remove_wait_queue(p, &wait);
        restore_flags(flags);
}

void interruptible_sleep_on(struct wait_queue **p)
{
        __sleep_on(p,TASK_INTERRUPTIBLE);
}

void sleep_on(struct wait_queue **p)
{
        __sleep_on(p,TASK_UNINTERRUPTIBLE);
}

static struct timer_list * next_timer = NULL;

void add_timer(struct timer_list * timer)
{
        unsigned long flags;
        struct timer_list ** p;

        if (!timer)
                return;
        timer->next = NULL;
        p = &next_timer;
        save_flags(flags);
        cli();
        while (*p) {
                if ((*p)->expires > timer->expires) {
                        (*p)->expires -= timer->expires;
                        timer->next = *p;
                        break;
                }
                timer->expires -= (*p)->expires;
                p = &(*p)->next;
        }
        *p = timer;
        restore_flags(flags);
}

int del_timer(struct timer_list * timer)
{
        unsigned long flags;
        unsigned long expires = 0;
        struct timer_list **p;

        p = &next_timer;
        save_flags(flags);
        cli();
        while (*p) {
                if (*p == timer) {
                        if ((*p = timer->next) != NULL)
                                (*p)->expires += timer->expires;
                        timer->expires += expires;
                        restore_flags(flags);
                        return 1;
                }
                expires += (*p)->expires;
                p = &(*p)->next;
        }
        restore_flags(flags);
        return 0;
}

unsigned long timer_active = 0;
struct timer_struct timer_table[32];

/*
 * Hmm.. Changed this, as the GNU make sources (load.c) seems to
 * imply that avenrun[] is the standard name for this kind of thing.
 * Nothing else seems to be standardized: the fractional size etc
 * all seem to differ on different machines.
 */
unsigned long avenrun[3] = { 0,0,0 };

/*
 * Nr of active tasks - counted in fixed-point numbers
 */
static unsigned long count_active_tasks(void)
{
        struct task_struct **p;
        unsigned long nr = 0;

        for(p = &LAST_TASK; p > &FIRST_TASK; --p)
                if (*p && (*p)->state == TASK_RUNNING)
                        nr += FIXED_1;
        return nr;
}

static inline void calc_load(void)
{
        unsigned long active_tasks; /* fixed-point */
        static int count = LOAD_FREQ;

        if (count-- > 0)
                return;
        count = LOAD_FREQ;
        active_tasks = count_active_tasks();
        CALC_LOAD(avenrun[0], EXP_1, active_tasks);
        CALC_LOAD(avenrun[1], EXP_5, active_tasks);
        CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}

/*
 * The int argument is really a (struct pt_regs *), in case the
 * interrupt wants to know from where it was called. The timer
 * irq uses this to decide if it should update the user or system
 * times.
 */
static void do_timer(struct pt_regs * regs)
{
        unsigned long mask;
        struct timer_struct *tp = timer_table+0;
        struct task_struct * task_p;

        jiffies++;
        calc_load();
        if ((VM_MASK & regs->eflags) || (3 & regs->cs)) {
                current->utime++;
                /* Update ITIMER_VIRT for current task if not in a system call */
                if (current->it_virt_value && !(--current->it_virt_value)) {
                        current->it_virt_value = current->it_virt_incr;
                        send_sig(SIGVTALRM,current,1);
                }
        } else {
                current->stime++;
#ifdef CONFIG_PROFILE
                if (prof_buffer && current != task[0]) {
                        unsigned long eip = regs->eip;
                        eip >>= 2;
                        if (eip < prof_len)
                                prof_buffer[eip]++;
                }
#endif
        }
        if (current == task[0] || (--current->counter)<=0) {
                current->counter=0;
                need_resched = 1;
        }
        /* Update ITIMER_REAL for every task */
        for_each_task(task_p) {
                if (!task_p->it_real_value)
                        continue;
                if (--task_p->it_real_value)
                        continue;
                send_sig(SIGALRM,task_p,1);
                task_p->it_real_value = task_p->it_real_incr;
                need_resched = 1;
        }
        /* Update ITIMER_PROF for the current task */
        if (current->it_prof_value && !(--current->it_prof_value)) {
                current->it_prof_value = current->it_prof_incr;
                send_sig(SIGPROF,current,1);
        }
        for (mask = 1 ; mask ; tp++,mask += mask) {
                if (mask > timer_active)
                        break;
                if (!(mask & timer_active))
                        continue;
                if (tp->expires > jiffies)
                        continue;
                timer_active &= ~mask;
                tp->fn();
                sti();
        }
        cli();
        while (next_timer && next_timer->expires == 0) {
                void (*fn)(unsigned long) = next_timer->function;
                unsigned long data = next_timer->data;
                next_timer = next_timer->next;
                sti();
                fn(data);
                cli();
        }
        if (next_timer)
                next_timer->expires--;
        sti();
}

asmlinkage int sys_alarm(long seconds)
{
        struct itimerval it_new, it_old;

        it_new.it_interval.tv_sec = it_new.it_interval.tv_usec = 0;
        it_new.it_value.tv_sec = seconds;
        it_new.it_value.tv_usec = 0;
        _setitimer(ITIMER_REAL, &it_new, &it_old);
        return(it_old.it_value.tv_sec + (it_old.it_value.tv_usec / 1000000));
}

asmlinkage int sys_getpid(void)
{
        return current->pid;
}

asmlinkage int sys_getppid(void)
{
        return current->p_pptr->pid;
}

asmlinkage int sys_getuid(void)
{
        return current->uid;
}

asmlinkage int sys_geteuid(void)
{
        return current->euid;
}

asmlinkage int sys_getgid(void)
{
        return current->gid;
}

asmlinkage int sys_getegid(void)
{
        return current->egid;
}

asmlinkage int sys_nice(long increment)
{
        int newprio;

        if (increment < 0 && !suser())
                return -EPERM;
        newprio = current->priority - increment;
        if (newprio < 1)
                newprio = 1;
        if (newprio > 35)
                newprio = 35;
        current->priority = newprio;
        return 0;
}

static void show_task(int nr,struct task_struct * p)
{
        int i, j;
        unsigned char * stack;

        printk("%d: pid=%d, state=%d, father=%d, child=%d, ",(p == current)?-nr:nr,p->pid,
                p->state, p->p_pptr->pid, p->p_cptr ? p->p_cptr->pid : -1);
        i = 0;
        j = PAGE_SIZE;
        if (!(stack = (unsigned char *) p->kernel_stack_page)) {
                stack = (unsigned char *) init_kernel_stack;
                j = sizeof(init_kernel_stack);
        }
        while (i<j && !*(stack++))
                i++;
        printk("%d/%d chars free in kstack\n",i,j);
        printk("   PC=%08X.", *(1019 + (unsigned long *) p));
        if (p->p_ysptr || p->p_osptr) 
                printk("   Younger sib=%d, older sib=%d\n", 
                        p->p_ysptr ? p->p_ysptr->pid : -1,
                        p->p_osptr ? p->p_osptr->pid : -1);
        else
                printk("\n");
}

void show_state(void)
{
        int i;

        printk("Task-info:\n");
        for (i=0 ; i<NR_TASKS ; i++)
                if (task[i])
                        show_task(i,task[i]);
}

void sched_init(void)
{
        int i;
        struct desc_struct * p;

        if (sizeof(struct sigaction) != 16)
                panic("Struct sigaction MUST be 16 bytes");
        set_tss_desc(gdt+FIRST_TSS_ENTRY,&init_task.tss);
        set_ldt_desc(gdt+FIRST_LDT_ENTRY,&default_ldt,1);
        set_system_gate(0x80,&system_call);
        p = gdt+2+FIRST_TSS_ENTRY;
        for(i=1 ; i<NR_TASKS ; i++) {
                task[i] = NULL;
                p->a=p->b=0;
                p++;
                p->a=p->b=0;
                p++;
        }
/* Clear NT, so that we won't have troubles with that later on */
        __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
        load_TR(0);
        load_ldt(0);
        outb_p(0x34,0x43);              /* binary, mode 2, LSB/MSB, ch 0 */
        outb_p(LATCH & 0xff , 0x40);    /* LSB */
        outb(LATCH >> 8 , 0x40);        /* MSB */
        if (request_irq(TIMER_IRQ,(void (*)(int)) do_timer)!=0)
                panic("Could not allocate timer IRQ!");
}

4. 1993年11月29日的Linux kernel 0.99.14版本中的sched.c源代码

/root/kernel/sched.c
https://kernelhistory.sourcentral.org/linux-0.99.14/?f=/linux-0.99.14/S/323.html%23L412
418行: ((p)->state == TASK_RUNNING || (p)->state == TASK_UNINTERRUPTIBLE || (*p)->state == TASK_SWAPPING)

/*
 *  linux/kernel/sched.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */

#include <linux/config.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/ptrace.h>
#include <linux/segment.h>
#include <linux/delay.h>
#include <linux/interrupt.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define TIMER_IRQ 0

#include <linux/timex.h>

/*
 * kernel variables
 */
long tick = 1000000 / HZ;               /* timer interrupt period */
volatile struct timeval xtime;          /* The current time */
int tickadj = 500/HZ;                   /* microsecs */

/*
 * phase-lock loop variables
 */
int time_status = TIME_BAD;     /* clock synchronization status */
long time_offset = 0;           /* time adjustment (us) */
long time_constant = 0;         /* pll time constant */
long time_tolerance = MAXFREQ;  /* frequency tolerance (ppm) */
long time_precision = 1;        /* clock precision (us) */
long time_maxerror = 0x70000000;/* maximum error */
long time_esterror = 0x70000000;/* estimated error */
long time_phase = 0;            /* phase offset (scaled us) */
long time_freq = 0;             /* frequency offset (scaled ppm) */
long time_adj = 0;              /* tick adjust (scaled 1 / HZ) */
long time_reftime = 0;          /* time at last adjustment (s) */

long time_adjust = 0;

int need_resched = 0;

/*
 * Tell us the machine setup..
 */
int hard_math = 0;              /* set by boot/head.S */
int x86 = 0;                    /* set by boot/head.S to 3 or 4 */
int ignore_irq13 = 0;           /* set if exception 16 works */
int wp_works_ok = 0;            /* set if paging hardware honours WP */ 

extern int _setitimer(int, struct itimerval *, struct itimerval *);
unsigned long * prof_buffer = NULL;
unsigned long prof_len = 0;

#define _S(nr) (1<<((nr)-1))

extern void mem_use(void);

extern int timer_interrupt(void);
asmlinkage int system_call(void);

static unsigned long init_kernel_stack[1024];
struct task_struct init_task = INIT_TASK;

unsigned long volatile jiffies=0;

struct task_struct *current = &init_task;
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&init_task, };

long user_stack [ PAGE_SIZE>>2 ] ;

struct {
        long * a;
        short b;
        } stack_start = { & user_stack [PAGE_SIZE>>2] , KERNEL_DS };

/*
 * int 0x80 entry points.. Moved away from the header file, as
 * iBCS2 may also want to use the '<linux/sys.h>' headers..
 */
#ifdef __cplusplus
extern "C" {
#endif

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_olduname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid, sys_sigsuspend, sys_sigpending,
sys_sethostname, sys_setrlimit, sys_getrlimit, sys_getrusage,
sys_gettimeofday, sys_settimeofday, sys_getgroups, sys_setgroups,
sys_select, sys_symlink, sys_lstat, sys_readlink, sys_uselib,
sys_swapon, sys_reboot, sys_readdir, sys_mmap, sys_munmap, sys_truncate,
sys_ftruncate, sys_fchmod, sys_fchown, sys_getpriority, sys_setpriority,
sys_profil, sys_statfs, sys_fstatfs, sys_ioperm, sys_socketcall,
sys_syslog, sys_setitimer, sys_getitimer, sys_newstat, sys_newlstat,
sys_newfstat, sys_uname, sys_iopl, sys_vhangup, sys_idle, sys_vm86,
sys_wait4, sys_swapoff, sys_sysinfo, sys_ipc, sys_fsync, sys_sigreturn,
sys_clone, sys_setdomainname, sys_newuname, sys_modify_ldt,
sys_adjtimex, sys_mprotect, sys_sigprocmask };

/* So we don't have to do any more manual updating.... */
int NR_syscalls = sizeof(sys_call_table)/sizeof(fn_ptr);

#ifdef __cplusplus
}
#endif

/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 *
 * Careful.. There are problems with IBM-designed IRQ13 behaviour.
 * Don't touch unless you *really* know how it works.
 */
asmlinkage void math_state_restore(void)
{
        __asm__ __volatile__("clts");
        if (last_task_used_math == current)
                return;
        timer_table[COPRO_TIMER].expires = jiffies+50;
        timer_active |= 1<<COPRO_TIMER; 
        if (last_task_used_math)
                __asm__("fnsave %0":"=m" (last_task_used_math->tss.i387));
        else
                __asm__("fnclex");
        last_task_used_math = current;
        if (current->used_math) {
                __asm__("frstor %0": :"m" (current->tss.i387));
        } else {
                __asm__("fninit");
                current->used_math=1;
        }
        timer_active &= ~(1<<COPRO_TIMER);
}

#ifndef CONFIG_MATH_EMULATION

asmlinkage void math_emulate(long arg)
{
  printk("math-emulation not enabled and no coprocessor found.\n");
  printk("killing %s.\n",current->comm);
  send_sig(SIGFPE,current,1);
  schedule();
}

#endif /* CONFIG_MATH_EMULATION */

/*
 *  'schedule()' is the scheduler function. It's a very simple and nice
 * scheduler: it's not perfect, but certainly works for most things.
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 *
 * The "confuse_gcc" goto is used only to get better assembly code..
 * Djikstra probably hates me.
 */
asmlinkage void schedule(void)
{
        int c;
        struct task_struct * p;
        struct task_struct * next;

/* check alarm, wake up any interruptible tasks that have got a signal */

        sti();
        need_resched = 0;
        p = &init_task;
        for (;;) {
                if ((p = p->next_task) == &init_task)
                        goto confuse_gcc1;
                if (p->state != TASK_INTERRUPTIBLE)
                        continue;
                if (p->signal & ~p->blocked) {
                        p->state = TASK_RUNNING;
                        continue;
                }
                if (p->timeout && p->timeout <= jiffies) {
                        p->timeout = 0;
                        p->state = TASK_RUNNING;
                }
        }
confuse_gcc1:

/* this is the scheduler proper: */
#if 0
        /* give processes that go to sleep a bit higher priority.. */
        /* This depends on the values for TASK_XXX */
        /* This gives smoother scheduling for some things, but */
        /* can be very unfair under some circumstances, so.. */
        if (TASK_UNINTERRUPTIBLE >= (unsigned) current->state &&
            current->counter < current->priority*2) {
                ++current->counter;
        }
#endif
        c = -1;
        next = p = &init_task;
        for (;;) {
                if ((p = p->next_task) == &init_task)
                        goto confuse_gcc2;
                if (p->state == TASK_RUNNING && p->counter > c)
                        c = p->counter, next = p;
        }
confuse_gcc2:
        if (!c) {
                for_each_task(p)
                        p->counter = (p->counter >> 1) + p->priority;
        }
        switch_to(next);
        /* Now maybe reload the debug registers */
        if(current->debugreg[7]){
                loaddebug(0);
                loaddebug(1);
                loaddebug(2);
                loaddebug(3);
                loaddebug(6);
        };
}

asmlinkage int sys_pause(void)
{
        current->state = TASK_INTERRUPTIBLE;
        schedule();
        return -ERESTARTNOHAND;
}

/*
 * wake_up doesn't wake up stopped processes - they have to be awakened
 * with signals or similar.
 *
 * Note that this doesn't need cli-sti pairs: interrupts may not change
 * the wait-queue structures directly, but only call wake_up() to wake
 * a process. The process itself must remove the queue once it has woken.
 */
void wake_up(struct wait_queue **q)
{
        struct wait_queue *tmp;
        struct task_struct * p;

        if (!q || !(tmp = *q))
                return;
        do {
                if ((p = tmp->task) != NULL) {
                        if ((p->state == TASK_UNINTERRUPTIBLE) ||
                            (p->state == TASK_INTERRUPTIBLE)) {
                                p->state = TASK_RUNNING;
                                if (p->counter > current->counter)
                                        need_resched = 1;
                        }
                }
                if (!tmp->next) {
                        printk("wait_queue is bad (eip = %08lx)\n",((unsigned long *) q)[-1]);
                        printk("        q = %p\n",q);
                        printk("       *q = %p\n",*q);
                        printk("      tmp = %p\n",tmp);
                        break;
                }
                tmp = tmp->next;
        } while (tmp != *q);
}

void wake_up_interruptible(struct wait_queue **q)
{
        struct wait_queue *tmp;
        struct task_struct * p;

        if (!q || !(tmp = *q))
                return;
        do {
                if ((p = tmp->task) != NULL) {
                        if (p->state == TASK_INTERRUPTIBLE) {
                                p->state = TASK_RUNNING;
                                if (p->counter > current->counter)
                                        need_resched = 1;
                        }
                }
                if (!tmp->next) {
                        printk("wait_queue is bad (eip = %08lx)\n",((unsigned long *) q)[-1]);
                        printk("        q = %p\n",q);
                        printk("       *q = %p\n",*q);
                        printk("      tmp = %p\n",tmp);
                        break;
                }
                tmp = tmp->next;
        } while (tmp != *q);
}

static inline void __sleep_on(struct wait_queue **p, int state)
{
        unsigned long flags;
        struct wait_queue wait = { current, NULL };

        if (!p)
                return;
        if (current == task[0])
                panic("task[0] trying to sleep");
        current->state = state;
        add_wait_queue(p, &wait);
        save_flags(flags);
        sti();
        schedule();
        remove_wait_queue(p, &wait);
        restore_flags(flags);
}

void interruptible_sleep_on(struct wait_queue **p)
{
        __sleep_on(p,TASK_INTERRUPTIBLE);
}

void sleep_on(struct wait_queue **p)
{
        __sleep_on(p,TASK_UNINTERRUPTIBLE);
}

static struct timer_list * next_timer = NULL;

void add_timer(struct timer_list * timer)
{
        unsigned long flags;
        struct timer_list ** p;

        if (!timer)
                return;
        timer->next = NULL;
        p = &next_timer;
        save_flags(flags);
        cli();
        while (*p) {
                if ((*p)->expires > timer->expires) {
                        (*p)->expires -= timer->expires;
                        timer->next = *p;
                        break;
                }
                timer->expires -= (*p)->expires;
                p = &(*p)->next;
        }
        *p = timer;
        restore_flags(flags);
}

int del_timer(struct timer_list * timer)
{
        unsigned long flags;
        unsigned long expires = 0;
        struct timer_list **p;

        p = &next_timer;
        save_flags(flags);
        cli();
        while (*p) {
                if (*p == timer) {
                        if ((*p = timer->next) != NULL)
                                (*p)->expires += timer->expires;
                        timer->expires += expires;
                        restore_flags(flags);
                        return 1;
                }
                expires += (*p)->expires;
                p = &(*p)->next;
        }
        restore_flags(flags);
        return 0;
}

unsigned long timer_active = 0;
struct timer_struct timer_table[32];

/*
 * Hmm.. Changed this, as the GNU make sources (load.c) seems to
 * imply that avenrun[] is the standard name for this kind of thing.
 * Nothing else seems to be standardized: the fractional size etc
 * all seem to differ on different machines.
 */
unsigned long avenrun[3] = { 0,0,0 };

/*
 * Nr of active tasks - counted in fixed-point numbers
 */
static unsigned long count_active_tasks(void)
{
        struct task_struct **p;
        unsigned long nr = 0;

        for(p = &LAST_TASK; p > &FIRST_TASK; --p)
                if (*p && ((*p)->state == TASK_RUNNING ||
                           (*p)->state == TASK_UNINTERRUPTIBLE ||
                           (*p)->state == TASK_SWAPPING))
                        nr += FIXED_1;
        return nr;
}

static inline void calc_load(void)
{
        unsigned long active_tasks; /* fixed-point */
        static int count = LOAD_FREQ;

        if (count-- > 0)
                return;
        count = LOAD_FREQ;
        active_tasks = count_active_tasks();
        CALC_LOAD(avenrun[0], EXP_1, active_tasks);
        CALC_LOAD(avenrun[1], EXP_5, active_tasks);
        CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}

/*
 * this routine handles the overflow of the microsecond field
 *
 * The tricky bits of code to handle the accurate clock support
 * were provided by Dave Mills (Mills@UDEL.EDU) of NTP fame.
 * They were originally developed for SUN and DEC kernels.
 * All the kudos should go to Dave for this stuff.
 *
 * These were ported to Linux by Philip Gladstone.
 */
static void second_overflow(void)
{
        long ltemp;
        /* last time the cmos clock got updated */
        static long last_rtc_update=0;
        extern int set_rtc_mmss(unsigned long);

        /* Bump the maxerror field */
        time_maxerror = (0x70000000-time_maxerror < time_tolerance) ?
          0x70000000 : (time_maxerror + time_tolerance);

        /* Run the PLL */
        if (time_offset < 0) {
                ltemp = (-(time_offset+1) >> (SHIFT_KG + time_constant)) + 1;
                time_adj = ltemp << (SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE);
                time_offset += (time_adj * HZ) >> (SHIFT_SCALE - SHIFT_UPDATE);
                time_adj = - time_adj;
        } else if (time_offset > 0) {
                ltemp = ((time_offset-1) >> (SHIFT_KG + time_constant)) + 1;
                time_adj = ltemp << (SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE);
                time_offset -= (time_adj * HZ) >> (SHIFT_SCALE - SHIFT_UPDATE);
        } else {
                time_adj = 0;
        }

        time_adj += (time_freq >> (SHIFT_KF + SHIFT_HZ - SHIFT_SCALE))
            + FINETUNE;

        /* Handle the leap second stuff */
        switch (time_status) {
                case TIME_INS:
                /* ugly divide should be replaced */
                if (xtime.tv_sec % 86400 == 0) {
                        xtime.tv_sec--; /* !! */
                        time_status = TIME_OOP;
                        printk("Clock: inserting leap second 23:59:60 GMT\n");
                }
                break;

                case TIME_DEL:
                /* ugly divide should be replaced */
                if (xtime.tv_sec % 86400 == 86399) {
                        xtime.tv_sec++;
                        time_status = TIME_OK;
                        printk("Clock: deleting leap second 23:59:59 GMT\n");
                }
                break;

                case TIME_OOP:
                time_status = TIME_OK;
                break;
        }
        if (xtime.tv_sec > last_rtc_update + 660)
          if (set_rtc_mmss(xtime.tv_sec) == 0)
            last_rtc_update = xtime.tv_sec;
}

static int lost_ticks = 0;

/*
 * disregard lost ticks for now.. We don't care enough.
 */
static void timer_bh(void * unused)
{
        cli();
        while (next_timer && next_timer->expires == 0) {
                void (*fn)(unsigned long) = next_timer->function;
                unsigned long data = next_timer->data;
                next_timer = next_timer->next;
                sti();
                fn(data);
                cli();
        }
        sti();
}

/*
 * The int argument is really a (struct pt_regs *), in case the
 * interrupt wants to know from where it was called. The timer
 * irq uses this to decide if it should update the user or system
 * times.
 */
static void do_timer(struct pt_regs * regs)
{
        unsigned long mask;
        struct timer_struct *tp = timer_table+0;
        struct task_struct * task_p;

        long ltemp;

        /* Advance the phase, once it gets to one microsecond, then
         * advance the tick more.
         */
        time_phase += time_adj;
        if (time_phase < -FINEUSEC) {
                ltemp = -time_phase >> SHIFT_SCALE;
                time_phase += ltemp << SHIFT_SCALE;
                xtime.tv_usec += tick - ltemp;
        }
        else if (time_phase > FINEUSEC) {
                ltemp = time_phase >> SHIFT_SCALE;
                time_phase -= ltemp << SHIFT_SCALE;
                xtime.tv_usec += tick + ltemp;
        } else
                xtime.tv_usec += tick;

        if (time_adjust)
        {
            /* We are doing an adjtime thing. 
             */

            /* Limit the amount of the step for *next* tick to be
             * in the range -tickadj .. +tickadj
             */
             if (time_adjust > tickadj)
               ltemp = tickadj;
             else if (time_adjust < -tickadj)
               ltemp = -tickadj;
             else
               ltemp = time_adjust;

            /* Reduce the amount of time left by this step */
            time_adjust -= ltemp;

            /* Modify the value of the tick for next time.
             * Note that a positive delta means we want the clock
             * to run fast. This means that the tick should be bigger
             */
            tick = 1000000/HZ + ltemp;
        }
        else
            tick = 1000000/HZ;

        if (xtime.tv_usec >= 1000000) {
            xtime.tv_usec -= 1000000;
            xtime.tv_sec++;
            second_overflow();
        }

        jiffies++;
        calc_load();
        if ((VM_MASK & regs->eflags) || (3 & regs->cs)) {
                current->utime++;
                /* Update ITIMER_VIRT for current task if not in a system call */
                if (current->it_virt_value && !(--current->it_virt_value)) {
                        current->it_virt_value = current->it_virt_incr;
                        send_sig(SIGVTALRM,current,1);
                }
        } else {
                current->stime++;
#ifdef CONFIG_PROFILE
                if (prof_buffer && current != task[0]) {
                        unsigned long eip = regs->eip;
                        eip >>= 2;
                        if (eip < prof_len)
                                prof_buffer[eip]++;
                }
#endif
        }
        if (current == task[0] || (--current->counter)<=0) {
                current->counter=0;
                need_resched = 1;
        }
        /* Update ITIMER_REAL for every task */
        for_each_task(task_p) {
                if (!task_p->it_real_value)
                        continue;
                if (--task_p->it_real_value)
                        continue;
                send_sig(SIGALRM,task_p,1);
                task_p->it_real_value = task_p->it_real_incr;
                need_resched = 1;
        }
        /* Update ITIMER_PROF for the current task */
        if (current->it_prof_value && !(--current->it_prof_value)) {
                current->it_prof_value = current->it_prof_incr;
                send_sig(SIGPROF,current,1);
        }
        for (mask = 1 ; mask ; tp++,mask += mask) {
                if (mask > timer_active)
                        break;
                if (!(mask & timer_active))
                        continue;
                if (tp->expires > jiffies)
                        continue;
                timer_active &= ~mask;
                tp->fn();
                sti();
        }
        cli();
        if (next_timer) {
                if (next_timer->expires) {
                        next_timer->expires--;
                        if (!next_timer->expires)
                                mark_bh(TIMER_BH);
                } else {
                        lost_ticks++;
                        mark_bh(TIMER_BH);
                }
        }
        sti();
}

asmlinkage int sys_alarm(long seconds)
{
        struct itimerval it_new, it_old;

        it_new.it_interval.tv_sec = it_new.it_interval.tv_usec = 0;
        it_new.it_value.tv_sec = seconds;
        it_new.it_value.tv_usec = 0;
        _setitimer(ITIMER_REAL, &it_new, &it_old);
        return(it_old.it_value.tv_sec + (it_old.it_value.tv_usec / 1000000));
}

asmlinkage int sys_getpid(void)
{
        return current->pid;
}

asmlinkage int sys_getppid(void)
{
        return current->p_opptr->pid;
}

asmlinkage int sys_getuid(void)
{
        return current->uid;
}

asmlinkage int sys_geteuid(void)
{
        return current->euid;
}

asmlinkage int sys_getgid(void)
{
        return current->gid;
}

asmlinkage int sys_getegid(void)
{
        return current->egid;
}

asmlinkage int sys_nice(long increment)
{
        int newprio;

        if (increment < 0 && !suser())
                return -EPERM;
        newprio = current->priority - increment;
        if (newprio < 1)
                newprio = 1;
        if (newprio > 35)
                newprio = 35;
        current->priority = newprio;
        return 0;
}

static void show_task(int nr,struct task_struct * p)
{
        static char * stat_nam[] = { "R", "S", "D", "Z", "T", "W" };

        printk("%-8s %3d ", p->comm, (p == current) ? -nr : nr);
        if (((unsigned) p->state) < sizeof(stat_nam)/sizeof(char *))
                printk(stat_nam[p->state]);
        else
                printk(" ");
        /* this prints bogus values for the current process */
        printk(" %08lX ", ((unsigned long *)p->tss.esp)[2]);
        printk("%5lu %5d %6d ",
                p->tss.esp - p->kernel_stack_page, p->pid, p->p_pptr->pid);
        if (p->p_cptr)
                printk("%5d ", p->p_cptr->pid);
        else
                printk("      ");
        if (p->p_ysptr)
                printk("%7d", p->p_ysptr->pid);
        else
                printk("       ");
        if (p->p_osptr)
                printk(" %5d\n", p->p_osptr->pid);
        else
                printk("\n");
}

void show_state(void)
{
        int i;

        printk("                         free                        sibling\n");
        printk("  task             PC    stack   pid father child younger older\n");
        for (i=0 ; i<NR_TASKS ; i++)
                if (task[i])
                        show_task(i,task[i]);
}

void sched_init(void)
{
        int i;
        struct desc_struct * p;

        bh_base[TIMER_BH].routine = timer_bh;
        if (sizeof(struct sigaction) != 16)
                panic("Struct sigaction MUST be 16 bytes");
        set_tss_desc(gdt+FIRST_TSS_ENTRY,&init_task.tss);
        set_ldt_desc(gdt+FIRST_LDT_ENTRY,&default_ldt,1);
        set_system_gate(0x80,&system_call);
        p = gdt+2+FIRST_TSS_ENTRY;
        for(i=1 ; i<NR_TASKS ; i++) {
                task[i] = NULL;
                p->a=p->b=0;
                p++;
                p->a=p->b=0;
                p++;
        }
/* Clear NT, so that we won't have troubles with that later on */
        __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
        load_TR(0);
        load_ldt(0);
        outb_p(0x34,0x43);              /* binary, mode 2, LSB/MSB, ch 0 */
        outb_p(LATCH & 0xff , 0x40);    /* LSB */
        outb(LATCH >> 8 , 0x40);        /* MSB */
        if (request_irq(TIMER_IRQ,(void (*)(int)) do_timer)!=0)
                panic("Could not allocate timer IRQ!");
}

5. 2022年02月22日的最新版Linux kernel中的loadavg.c源代码

https://github.com/torvalds/linux/blob/master/kernel/sched/loadavg.c
注意看loadavg.c文件中的第一段注释:其实这是一个很SB的指标,但是不知道为什么他们都认为这玩意很重要。
349行:void calc_global_load(void)

// SPDX-License-Identifier: GPL-2.0
/*
 * kernel/sched/loadavg.c
 *
 * This file contains the magic bits required to compute the global loadavg
 * figure. Its a silly number but people think its important. We go through
 * great pains to make it work on big machines and tickless kernels.
 */

/*
 * Global load-average calculations
 *
 * We take a distributed and async approach to calculating the global load-avg
 * in order to minimize overhead.
 *
 * The global load average is an exponentially decaying average of nr_running +
 * nr_uninterruptible.
 *
 * Once every LOAD_FREQ:
 *
 *   nr_active = 0;
 *   for_each_possible_cpu(cpu)
 *  nr_active += cpu_of(cpu)->nr_running + cpu_of(cpu)->nr_uninterruptible;
 *
 *   avenrun[n] = avenrun[0] * exp_n + nr_active * (1 - exp_n)
 *
 * Due to a number of reasons the above turns in the mess below:
 *
 *  - for_each_possible_cpu() is prohibitively expensive on machines with
 *    serious number of CPUs, therefore we need to take a distributed approach
 *    to calculating nr_active.
 *
 *        \Sum_i x_i(t) = \Sum_i x_i(t) - x_i(t_0) | x_i(t_0) := 0
 *                      = \Sum_i { \Sum_j=1 x_i(t_j) - x_i(t_j-1) }
 *
 *    So assuming nr_active := 0 when we start out -- true per definition, we
 *    can simply take per-CPU deltas and fold those into a global accumulate
 *    to obtain the same result. See calc_load_fold_active().
 *
 *    Furthermore, in order to avoid synchronizing all per-CPU delta folding
 *    across the machine, we assume 10 ticks is sufficient time for every
 *    CPU to have completed this task.
 *
 *    This places an upper-bound on the IRQ-off latency of the machine. Then
 *    again, being late doesn't loose the delta, just wrecks the sample.
 *
 *  - cpu_rq()->nr_uninterruptible isn't accurately tracked per-CPU because
 *    this would add another cross-CPU cacheline miss and atomic operation
 *    to the wakeup path. Instead we increment on whatever CPU the task ran
 *    when it went into uninterruptible state and decrement on whatever CPU
 *    did the wakeup. This means that only the sum of nr_uninterruptible over
 *    all CPUs yields the correct result.
 *
 *  This covers the NO_HZ=n code, for extra head-aches, see the comment below.
 */

/* Variables and functions for calc_load */
atomic_long_t calc_load_tasks;
unsigned long calc_load_update;
unsigned long avenrun[3];
EXPORT_SYMBOL(avenrun); /* should be removed */

/**
 * get_avenrun - get the load average array
 * @loads:  pointer to dest load array
 * @offset: offset to add
 * @shift:  shift count to shift the result left
 *
 * These values are estimates at best, so no need for locking.
 */
void get_avenrun(unsigned long *loads, unsigned long offset, int shift)
{
    loads[0] = (avenrun[0] + offset) << shift;
    loads[1] = (avenrun[1] + offset) << shift;
    loads[2] = (avenrun[2] + offset) << shift;
}

long calc_load_fold_active(struct rq *this_rq, long adjust)
{
    long nr_active, delta = 0;

    nr_active = this_rq->nr_running - adjust;
    nr_active += (int)this_rq->nr_uninterruptible;

    if (nr_active != this_rq->calc_load_active) {
        delta = nr_active - this_rq->calc_load_active;
        this_rq->calc_load_active = nr_active;
    }

    return delta;
}

/**
 * fixed_power_int - compute: x^n, in O(log n) time
 *
 * @x:         base of the power
 * @frac_bits: fractional bits of @x
 * @n:         power to raise @x to.
 *
 * By exploiting the relation between the definition of the natural power
 * function: x^n := x*x*...*x (x multiplied by itself for n times), and
 * the binary encoding of numbers used by computers: n := \Sum n_i * 2^i,
 * (where: n_i \elem {0, 1}, the binary vector representing n),
 * we find: x^n := x^(\Sum n_i * 2^i) := \Prod x^(n_i * 2^i), which is
 * of course trivially computable in O(log_2 n), the length of our binary
 * vector.
 */
static unsigned long
fixed_power_int(unsigned long x, unsigned int frac_bits, unsigned int n)
{
    unsigned long result = 1UL << frac_bits;

    if (n) {
        for (;;) {
            if (n & 1) {
                result *= x;
                result += 1UL << (frac_bits - 1);
                result >>= frac_bits;
            }
            n >>= 1;
            if (!n)
                break;
            x *= x;
            x += 1UL << (frac_bits - 1);
            x >>= frac_bits;
        }
    }

    return result;
}

/*
 * a1 = a0 * e + a * (1 - e)
 *
 * a2 = a1 * e + a * (1 - e)
 *    = (a0 * e + a * (1 - e)) * e + a * (1 - e)
 *    = a0 * e^2 + a * (1 - e) * (1 + e)
 *
 * a3 = a2 * e + a * (1 - e)
 *    = (a0 * e^2 + a * (1 - e) * (1 + e)) * e + a * (1 - e)
 *    = a0 * e^3 + a * (1 - e) * (1 + e + e^2)
 *
 *  ...
 *
 * an = a0 * e^n + a * (1 - e) * (1 + e + ... + e^n-1) [1]
 *    = a0 * e^n + a * (1 - e) * (1 - e^n)/(1 - e)
 *    = a0 * e^n + a * (1 - e^n)
 *
 * [1] application of the geometric series:
 *
 *              n         1 - x^(n+1)
 *     S_n := \Sum x^i = -------------
 *             i=0          1 - x
 */
unsigned long
calc_load_n(unsigned long load, unsigned long exp,
        unsigned long active, unsigned int n)
{
    return calc_load(load, fixed_power_int(exp, FSHIFT, n), active);
}

#ifdef CONFIG_NO_HZ_COMMON
/*
 * Handle NO_HZ for the global load-average.
 *
 * Since the above described distributed algorithm to compute the global
 * load-average relies on per-CPU sampling from the tick, it is affected by
 * NO_HZ.
 *
 * The basic idea is to fold the nr_active delta into a global NO_HZ-delta upon
 * entering NO_HZ state such that we can include this as an 'extra' CPU delta
 * when we read the global state.
 *
 * Obviously reality has to ruin such a delightfully simple scheme:
 *
 *  - When we go NO_HZ idle during the window, we can negate our sample
 *    contribution, causing under-accounting.
 *
 *    We avoid this by keeping two NO_HZ-delta counters and flipping them
 *    when the window starts, thus separating old and new NO_HZ load.
 *
 *    The only trick is the slight shift in index flip for read vs write.
 *
 *        0s            5s            10s           15s
 *          +10           +10           +10           +10
 *        |-|-----------|-|-----------|-|-----------|-|
 *    r:0 0 1           1 0           0 1           1 0
 *    w:0 1 1           0 0           1 1           0 0
 *
 *    This ensures we'll fold the old NO_HZ contribution in this window while
 *    accumulating the new one.
 *
 *  - When we wake up from NO_HZ during the window, we push up our
 *    contribution, since we effectively move our sample point to a known
 *    busy state.
 *
 *    This is solved by pushing the window forward, and thus skipping the
 *    sample, for this CPU (effectively using the NO_HZ-delta for this CPU which
 *    was in effect at the time the window opened). This also solves the issue
 *    of having to deal with a CPU having been in NO_HZ for multiple LOAD_FREQ
 *    intervals.
 *
 * When making the ILB scale, we should try to pull this in as well.
 */
static atomic_long_t calc_load_nohz[2];
static int calc_load_idx;

static inline int calc_load_write_idx(void)
{
    int idx = calc_load_idx;

    /*
     * See calc_global_nohz(), if we observe the new index, we also
     * need to observe the new update time.
     */
    smp_rmb();

    /*
     * If the folding window started, make sure we start writing in the
     * next NO_HZ-delta.
     */
    if (!time_before(jiffies, READ_ONCE(calc_load_update)))
        idx++;

    return idx & 1;
}

static inline int calc_load_read_idx(void)
{
    return calc_load_idx & 1;
}

static void calc_load_nohz_fold(struct rq *rq)
{
    long delta;

    delta = calc_load_fold_active(rq, 0);
    if (delta) {
        int idx = calc_load_write_idx();

        atomic_long_add(delta, &calc_load_nohz[idx]);
    }
}

void calc_load_nohz_start(void)
{
    /*
     * We're going into NO_HZ mode, if there's any pending delta, fold it
     * into the pending NO_HZ delta.
     */
    calc_load_nohz_fold(this_rq());
}

/*
 * Keep track of the load for NOHZ_FULL, must be called between
 * calc_load_nohz_{start,stop}().
 */
void calc_load_nohz_remote(struct rq *rq)
{
    calc_load_nohz_fold(rq);
}

void calc_load_nohz_stop(void)
{
    struct rq *this_rq = this_rq();

    /*
     * If we're still before the pending sample window, we're done.
     */
    this_rq->calc_load_update = READ_ONCE(calc_load_update);
    if (time_before(jiffies, this_rq->calc_load_update))
        return;

    /*
     * We woke inside or after the sample window, this means we're already
     * accounted through the nohz accounting, so skip the entire deal and
     * sync up for the next window.
     */
    if (time_before(jiffies, this_rq->calc_load_update + 10))
        this_rq->calc_load_update += LOAD_FREQ;
}

static long calc_load_nohz_read(void)
{
    int idx = calc_load_read_idx();
    long delta = 0;

    if (atomic_long_read(&calc_load_nohz[idx]))
        delta = atomic_long_xchg(&calc_load_nohz[idx], 0);

    return delta;
}

/*
 * NO_HZ can leave us missing all per-CPU ticks calling
 * calc_load_fold_active(), but since a NO_HZ CPU folds its delta into
 * calc_load_nohz per calc_load_nohz_start(), all we need to do is fold
 * in the pending NO_HZ delta if our NO_HZ period crossed a load cycle boundary.
 *
 * Once we've updated the global active value, we need to apply the exponential
 * weights adjusted to the number of cycles missed.
 */
static void calc_global_nohz(void)
{
    unsigned long sample_window;
    long delta, active, n;

    sample_window = READ_ONCE(calc_load_update);
    if (!time_before(jiffies, sample_window + 10)) {
        /*
         * Catch-up, fold however many we are behind still
         */
        delta = jiffies - sample_window - 10;
        n = 1 + (delta / LOAD_FREQ);

        active = atomic_long_read(&calc_load_tasks);
        active = active > 0 ? active * FIXED_1 : 0;

        avenrun[0] = calc_load_n(avenrun[0], EXP_1, active, n);
        avenrun[1] = calc_load_n(avenrun[1], EXP_5, active, n);
        avenrun[2] = calc_load_n(avenrun[2], EXP_15, active, n);

        WRITE_ONCE(calc_load_update, sample_window + n * LOAD_FREQ);
    }

    /*
     * Flip the NO_HZ index...
     *
     * Make sure we first write the new time then flip the index, so that
     * calc_load_write_idx() will see the new time when it reads the new
     * index, this avoids a double flip messing things up.
     */
    smp_wmb();
    calc_load_idx++;
}
#else /* !CONFIG_NO_HZ_COMMON */

static inline long calc_load_nohz_read(void) { return 0; }
static inline void calc_global_nohz(void) { }

#endif /* CONFIG_NO_HZ_COMMON */

/*
 * calc_load - update the avenrun load estimates 10 ticks after the
 * CPUs have updated calc_load_tasks.
 *
 * Called from the global timer code.
 */
void calc_global_load(void)
{
    unsigned long sample_window;
    long active, delta;

    sample_window = READ_ONCE(calc_load_update);
    if (time_before(jiffies, sample_window + 10))
        return;

    /*
     * Fold the 'old' NO_HZ-delta to include all NO_HZ CPUs.
     */
    delta = calc_load_nohz_read();
    if (delta)
        atomic_long_add(delta, &calc_load_tasks);

    active = atomic_long_read(&calc_load_tasks);
    active = active > 0 ? active * FIXED_1 : 0;

    avenrun[0] = calc_load(avenrun[0], EXP_1, active);
    avenrun[1] = calc_load(avenrun[1], EXP_5, active);
    avenrun[2] = calc_load(avenrun[2], EXP_15, active);

    WRITE_ONCE(calc_load_update, sample_window + LOAD_FREQ);

    /*
     * In case we went to NO_HZ for multiple LOAD_FREQ intervals
     * catch up in bulk.
     */
    calc_global_nohz();
}

/*
 * Called from scheduler_tick() to periodically update this CPU's
 * active count.
 */
void calc_global_load_tick(struct rq *this_rq)
{
    long delta;

    if (time_before(jiffies, this_rq->calc_load_update))
        return;

    delta  = calc_load_fold_active(this_rq, 0);
    if (delta)
        atomic_long_add(delta, &calc_load_tasks);

    this_rq->calc_load_update += LOAD_FREQ;
}

6. 测试代码及结果

写一段程序,生成N个一直占用CPU资源的进程,运行T秒,然后观察load-average的值。
可以看到运行1分钟以上后,load-average的第一个值和进程数接近。

6.1 测试代码

# cat forkpid.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

//define a signal handler function to terminate child processes
void handler(int sig)
{
    printf("Child process %d received signal %d\n", getpid(), sig);
    exit(0);
}

int main(int argc, char *argv[])
{
    if(argc != 3) //check the number of arguments
    {
        printf("Usage: %s <number of processes> <time to run>\n", argv[0]); //print the usage
        exit(1);
    }

    int N = atoi(argv[1]); //convert the first argument to an integer, indicating the number of processes to create
    int T = atoi(argv[2]); //convert the second argument to an integer, indicating the time to run

    int i;
    for(i = 0; i < N; i++) //loop N times
    {
        pid_t pid; //define a process ID variable
        pid = fork(); //create a child process

        if(pid == -1) //if creation fails, return -1
        {
            perror("fork error");
            exit(1);
        }

        else if(pid == 0) //if creation succeeds, return 0, indicating child process
        {
            printf("This is child process %d, pid is %d\n", i+1, getpid()); //print the child process number and ID
            signal(SIGALRM, handler); //register the signal handler function to receive the SIGALRM signal sent by the parent process
            for(;;) //execute an infinite loop to consume CPU resources
            {
                double x = 3.14159; //define a double variable
                x = x * x; //calculate the square of x
            }
        }
    }

    //parent process waits for T seconds and then sends SIGALRM signal to all child processes to terminate them
    sleep(T); //wait for T seconds
    kill(0, SIGALRM); //send SIGALRM signal to all processes in the same process group
    printf("Parent process sent signal %d to all child processes\n", SIGALRM);
    //parent process waits for all child processes to end and gets their return values

    int status; //define a status variable
    while(wait(&status) != -1) //if there are still child processes not finished, continue to wait
    {
        printf("Child process exited with status %d\n", status); //print the exit status of the child process
    }
    return 0;
}

6.2 测试结果

# gcc -o forkpid forkpid.c
# ./forkpid 500 180

在这里运行了500个进程,运行了180秒,可以看到top中的load-average的第一值为482。

# top
top - 09:32:50 up 13:16,  5 users,  load average: 482.00, 283.16, 128.12
Tasks: 771 total, 501 running, 270 sleeping,   0 stopped,   0 zombie
%Cpu(s): 99.5 us,  0.2 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
KiB Mem :  7989848 total,  5106864 free,  1373304 used,  1509680 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  6162116 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND    
 26609 root      20   0    4216     88      0 R   1.5  0.0   0:01.55 forkpid    
 26503 root      20   0    4216     88      0 R   1.2  0.0   0:01.56 forkpid    
 26505 root      20   0    4216     88      0 R   1.2  0.0   0:01.55 forkpid    
 26506 root      20   0    4216     88      0 R   1.2  0.0   0:01.55 forkpid    
 26507 root      20   0    4216     88      0 R   1.2  0.0   0:01.55 forkpid    
 26509 root      20   0    4216     88      0 R   1.2  0.0   0:01.55 forkpid    
Index
滚动至顶部