-- File: BufferMgr.mesa - last edit:
-- AOF 2-Feb-88 17:16:27
-- MI 1-Aug-86 15:23:15
-- SMA 27-May-86 11:44:29
-- Copyright (C) 1984, 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved.
DIRECTORY
Buffer USING [
AccessHandle, AccessObject, Buffer, BufferObject, Function,
Queue, Type, PoolType],
BufferOps,
CommFlags USING [doDebug, doStats],
CommHeap USING [zone],
CommPriorities USING [realtime],
CommunicationInternal USING [],
CommUtil USING [AllocateBuffers, FreeBuffers],
Driver USING [Glitch, Device, nilDevice],
Environment USING [bitsPerWord, bytesPerPage, bytesPerWord],
Frame USING [GetReturnFrame, ReadGlobalLink],
Inline USING [BITAND, BITNOT],
Process USING [
Abort, DisableAborts, DisableTimeout, EnableAborts, InitializeCondition,
SetPriority, SetTimeout, TicksToMsec, GetPriority, Priority],
Stats USING [StatIncr],
System USING [GetClockPulses, MicrosecondsToPulses];
BufferMgr: MONITOR
IMPORTS
CommHeap, CommUtil, Driver, Frame, Inline, Process,
Stats, System
EXPORTS Buffer, BufferOps, CommunicationInternal, Driver =
BEGIN
<<
This should be ample space for any encapsulation. Be careful - changing it to
something that is not a multiple of 4 word (8 bytes) will make the alignment
fall apart.
>>
dataLinkReserve: PUBLIC <<Buffer>> CARDINAL ← 32; -- Bytes
maxUserDataSize: PUBLIC <<Buffer>> CARDINAL ← 1500; -- Bytes
<<
** Degree of 'size' is now byte **
** Following constant should be even number **
These constants is used multiple buffer sizes implementation
>>
-- In bytes of the respective buffers at the PHYSICAL layer.
bufferSize: ARRAY SizeHint OF CARDINAL = [
smallBuffer: 274, -- maxSNAPacket
mediumBuffer: 576, -- maxIDPBytesPerPacket
largeBuffer: 1024, -- arbitrary intermediate
maxBuffer: 1500]; -- maxEthernetPacket
bpw: CARDINAL = Environment.bytesPerWord;
bpp: CARDINAL = Environment.bytesPerPage;
phonyQueue: Buffer.Queue = LOOPHOLE[LAST[LONG CARDINAL]];
maxBuffersPerCluster: NATURAL[1..Environment.bitsPerWord] = 5;
fudgeFactor: CARDINAL = 8; -- Bytes. Extra bytes in buffers
d0Alignment: CARDINAL = 8; -- Bytes. Quad word (almost) required
daybreakAlignment: CARDINAL = 54; -- Bytes. Don't page break closer than this (Daybreak)
Device: PUBLIC TYPE = Driver.Device;
Cluster: PUBLIC TYPE = LONG POINTER TO ClusterObject;
-- Field doomed has been removed since only SetSizes used to set this value
ClusterObject: TYPE = MACHINE DEPENDENT RECORD[
next(0): Cluster ← NIL, -- Objects are linked together
order(2): CARDINAL, -- And they are ordered
time(3): LONG CARDINAL ← TRASH, -- System.GetClockPulses last returned
useMap(5), mask(6): UseMap, -- Map and Mask of buffers free/used
hint(7): SizeHint ← TRASH, --size if buffers in this cluster
slot(8): SEQUENCE buffers(8): CARDINAL OF Buffer.Buffer];
SizeHint: TYPE = BufferOps.SizeHint;
BasePointer: TYPE = LONG BASE POINTER;
Use: TYPE = MACHINE DEPENDENT{used(0), free(1)};
UseMap: TYPE = PACKED ARRAY CARDINAL[0..Environment.bitsPerWord) OF Use;
biasing: BOOLEAN = TRUE; -- Keep 'n' buffers free at all times
<<
Since bufferSize defined as constance, syspool.bufferSize has been removed.
And buffersAvailable changed its type in order to support multiple buffer size.
>>
syspool: PUBLIC RECORD[
cache: ARRAY SizeHint OF Cluster, -- Best chance to find free buffer
cluster: RECORD[head, tail: Cluster, count: CARDINAL], -- List of clusters
send, receive, sendInUse, receiveInUse, freeBias, -- Current events
buffersRequested, buffersAllocated: CARDINAL,
buffersAvailable: ARRAY SizeHint OF CARDINAL ← ALL[0],
access: Buffer.AccessHandle, -- Head of linked list
scanInterval, lastTime: LONG CARDINAL, -- Times in Pulses,
clusterAgeLimit, lostBufferLimit: LONG CARDINAL, -- Times in Pulses & msecs
defaultSendBuffers, defaultReceiveBuffers, leaked: CARDINAL,
freeBuffer, fault: CONDITION, flag: BOOLEAN, hint: SizeHint,
allocator: PROCESS];
QueueScrambled: ERROR = CODE;
PoolTypeBroken: ERROR = CODE;
NotInitialized: ERROR = CODE;
QueueSealBroken: ERROR = CODE;
AccessSealBroken: ERROR = CODE;
BufferSealBroken: ERROR = CODE;
BufferNeverFreed: ERROR = CODE;
BufferFreedTwice: ERROR = CODE;
ClusterNotOrdered: ERROR = CODE;
OtherClientsActive: ERROR = CODE;
ClusterNotDeletable: ERROR = CODE;
RequestedSizeTooLarge: ERROR = CODE;
SystemBufferPoolConfused: ERROR = CODE;
DontKnowHowToAllocateBuffer: ERROR = CODE;
-- Procedures which added for multiple buffer size support
SizeToSizeHint: PROC [size: CARDINAL] RETURNS [hint: SizeHint] = INLINE
BEGIN
SELECT size FROM
<= bufferSize[smallBuffer] => hint ← smallBuffer;
<= bufferSize[mediumBuffer] => hint ← mediumBuffer;
<= bufferSize[largeBuffer] => hint ← largeBuffer;
<= bufferSize[maxBuffer] => hint ← maxBuffer;
ENDCASE => Driver.Glitch[RequestedSizeTooLarge];
END; --SizeToSizeHint
SizeHintToSize: PROC [hint: SizeHint] RETURNS [size: CARDINAL] = INLINE BEGIN
RETURN[bufferSize[hint]];
END; --SizeToSizeHint
AllUsed: INTERNAL PROC [c: Cluster] RETURNS [BOOLEAN] = INLINE BEGIN
RETURN [Inline.BITAND[c.useMap, c.mask] = 0];
END; --AllUsed
AllFree: INTERNAL PROC [c: Cluster] RETURNS [BOOLEAN] = INLINE BEGIN
RETURN [Inline.BITAND[Inline.BITNOT[c.useMap], c.mask] = 0];
END; --AllFree
MakeBufferAvailable: INTERNAL PROC [hint: SizeHint] = INLINE BEGIN
syspool.buffersAvailable[hint] ← syspool.buffersAvailable[hint] + 1;
END; --MakeBufferAvailable
MakeBufferUnavailable: INTERNAL PROC [hint: SizeHint] = INLINE
{syspool.buffersAvailable[hint] ← syspool.buffersAvailable[hint] - 1};
GetSizes: PUBLIC PROC RETURNS [ARRAY BufferOps.SizeHint OF CARDINAL] =
{RETURN[bufferSize]};
<< SetSizes has been removed when multiple buffers supported >>
GetIntervals: PUBLIC PROC
RETURNS[ARRAY BufferOps.Intervals OF LONG CARDINAL] = BEGIN
RETURN[[
scan: syspool.scanInterval,
aging: syspool.clusterAgeLimit,
lost: syspool.lostBufferLimit]];
END; --GetIntervals
SetIntervals: PUBLIC PROC[
interval: ARRAY BufferOps.Intervals OF LONG CARDINAL] = BEGIN
syspool.scanInterval ← interval[scan];
syspool.clusterAgeLimit ← interval[aging];
syspool.lostBufferLimit ← interval[lost];
END; --SetIntervals
GetDefaults: PUBLIC PROC
RETURNS[ARRAY BufferOps.Defaults OF CARDINAL] = BEGIN
RETURN[[
syspool.defaultSendBuffers,
syspool.defaultReceiveBuffers,
syspool.freeBias]];
END; --GetDefaults
SetDefaults: PUBLIC PROC[default: ARRAY BufferOps.Defaults OF CARDINAL] = BEGIN
syspool.defaultSendBuffers ← default[send];
syspool.defaultReceiveBuffers ← default[receive];
syspool.freeBias ← default[bias];
END; --SetDefaults
GetStatistics: PUBLIC PROC
RETURNS[ARRAY BufferOps.Statistics OF CARDINAL] = BEGIN
totalBuffersAvailable: CARDINAL ← 0;
-- Get sum of available buffers
FOR h: SizeHint IN SizeHint DO
totalBuffersAvailable ← totalBuffersAvailable + syspool.buffersAvailable[h];
ENDLOOP;
RETURN[[
syspool.buffersRequested, syspool.buffersAllocated,
totalBuffersAvailable, syspool.send, syspool.receive,
syspool.sendInUse, syspool.receiveInUse]];
END; --GetStatistics
BuffersLeft: PUBLIC PROC[aH: Buffer.AccessHandle] RETURNS[CARDINAL] =
{RETURN[(aH.receive - aH.receiveInUse) + (aH.send - aH.sendInUse)]};
SendBuffersLeft: PUBLIC PROC[aH: Buffer.AccessHandle] RETURNS[CARDINAL] =
{RETURN[(aH.send - aH.sendInUse)]};
ReceiveBuffersLeft: PUBLIC PROC[aH: Buffer.AccessHandle] RETURNS[CARDINAL] =
{RETURN[(aH.receive - aH.receiveInUse)]};
DataBytesPerRawBuffer: PUBLIC PROC [b: Buffer.Buffer] RETURNS [CARDINAL] = {
c: Cluster ← b.fo.cluster;
RETURN[bufferSize[c.hint]]};
--THE QUEUE MANAGEMENT PACKAGE
CreditReceiveBuffer: PUBLIC ENTRY PROC[
aH: Buffer.AccessHandle, b: Buffer.Buffer]
RETURNS [gotCredit: BOOLEAN] =
BEGIN
IF CommFlags.doDebug THEN
BEGIN
IF aH.seal ~IN Buffer.PoolType[normalPool..listenPool] THEN
Driver.Glitch[PoolTypeBroken];
IF b.fo.currentOwner # NIL THEN Driver.Glitch[QueueScrambled];
END;
IF (gotCredit ← aH.receiveInUse < aH.receive) THEN
BEGIN
aH.receiveInUse ← aH.receiveInUse + 1;
syspool.receiveInUse ← syspool.receiveInUse + 1;
b.fo.currentOwner ← aH;
b.fo.function ← receive;
IF CommFlags.doDebug THEN
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
END
ELSE IF CommFlags.doStats THEN Stats.StatIncr[statNoCredit];
END; --CreditReceiveBuffer
Dequeue, DequeuePup, DequeueNS, DequeueSpp: PUBLIC PROC[
q: Buffer.Queue] RETURNS [b: Buffer.Buffer] =
BEGIN
IF CommFlags.doDebug THEN
SELECT TRUE FROM
(q = NIL) => Driver.Glitch[QueueScrambled];
(q.seal # queueSeal) => Driver.Glitch[QueueSealBroken];
(q.length > 256) => Driver.Glitch[QueueScrambled];
ENDCASE;
IF (b ← q.first) = NIL THEN
BEGIN
IF CommFlags.doDebug AND q.length # 0 THEN Driver.Glitch[QueueScrambled];
IF CommFlags.doStats THEN Stats.StatIncr[statDequeueNIL];
RETURN;
END;
IF (q.first ← q.first.fo.next) = NIL THEN q.last ← NIL;
q.length ← q.length - 1;
IF CommFlags.doStats THEN Stats.StatIncr[statDequeue];
IF CommFlags.doDebug THEN
BEGIN
SELECT TRUE FROM
(b.fo.queue # q) => Driver.Glitch[QueueScrambled];
(b.fo.seal # bufferSeal) => Driver.Glitch[BufferSealBroken];
(q.length # 0 AND (q.first = NIL OR q.last = NIL)) =>
Driver.Glitch[QueueScrambled];
ENDCASE;
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
END;
b.fo.queue ← NIL;
b.fo.next ← NIL;
END; --Dequeue, DequeuePup, DequeueNS, DequeueSpp
Enqueue, EnqueuePup, EnqueueNS, EnqueueSpp: PUBLIC PROC[
q: Buffer.Queue, b: Buffer.Buffer] =
BEGIN
IF CommFlags.doDebug THEN
SELECT TRUE FROM
(q.seal # queueSeal) => Driver.Glitch[QueueSealBroken];
(b.fo.seal # bufferSeal) => Driver.Glitch[BufferSealBroken];
(b.fo.queue # NIL) => Driver.Glitch[QueueScrambled];
(q.length # 0 AND (q.first = NIL OR q.last = NIL)) =>
Driver.Glitch[QueueScrambled];
(q.length > 256) => Driver.Glitch[QueueScrambled];
ENDCASE => b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
b.fo.next ← NIL;
IF CommFlags.doStats THEN Stats.StatIncr[statEnqueue];
IF q.first = NIL THEN q.first ← b ELSE q.last.fo.next ← b;
q.last ← b;
b.fo.queue ← q;
q.length ← q.length + 1;
END; --Enqueue
ExtractFromQueue, ExtractPupFromQueue, ExtractNSFromQueue,
ExtractSppFromQueue: PUBLIC PROC[q: Buffer.Queue, b: Buffer.Buffer]
RETURNS [Buffer.Buffer] =
BEGIN
previousB, currentB: Buffer.Buffer;
IF CommFlags.doDebug THEN
SELECT TRUE FROM
(q = NIL) => Driver.Glitch[QueueScrambled];
(b = NIL) => Driver.Glitch[QueueScrambled];
(q.seal # queueSeal) => Driver.Glitch[QueueSealBroken];
(b.fo.seal # bufferSeal) => Driver.Glitch[BufferSealBroken];
ENDCASE;
previousB ← NIL;
currentB ← q.first;
UNTIL currentB = b DO
IF currentB = NIL THEN EXIT;
previousB ← currentB;
currentB ← currentB.fo.next;
ENDLOOP;
IF currentB # NIL THEN
BEGIN
--remove this buffer from the queue
IF CommFlags.doDebug AND currentB.fo.seal # bufferSeal THEN
Driver.Glitch[BufferSealBroken];
IF currentB = q.first THEN q.first ← currentB.fo.next;
IF currentB = q.last THEN q.last ← previousB;
IF previousB # NIL THEN previousB.fo.next ← currentB.fo.next;
q.length ← q.length - 1;
currentB.fo.queue ← NIL;
currentB.fo.next ← NIL;
IF CommFlags.doDebug THEN
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
IF CommFlags.doStats THEN Stats.StatIncr[statXqueue];
END
ELSE IF CommFlags.doStats THEN Stats.StatIncr[statXqueueNIL];
RETURN[currentB];
END; --ExtractFromQueue
QueueCleanup: PUBLIC PROC[q: Buffer.Queue] =
{b: Buffer.Buffer; UNTIL (b ← Dequeue[q]) = NIL DO ReturnBuffer[b]; ENDLOOP};
QueueInitialize: PUBLIC PROC[q: Buffer.Queue] =
{q↑ ← [length: 0, first: NIL, last: NIL, seal: queueSeal]};
--BUFFER MANAGEMENT ROUTINES
AllocateNewClusterLocked: ENTRY PROC[tail: Cluster] =
BEGIN
order: NATURAL;
-- Link new cluster to end of list and set its order
IF syspool.cluster.tail = NIL THEN
{syspool.cluster.head ← tail; order ← 1}
ELSE {syspool.cluster.tail.next ← tail;
order ← syspool.cluster.tail.order + 1};
syspool.cluster.tail ← tail;
tail.order ← order;
-- Keep up bookeeping
syspool.cluster.count ← syspool.cluster.count + 1;
syspool.buffersAllocated ← syspool.buffersAllocated + tail.buffers;
-- Make all created buffers in cluster available
FOR index: NATURAL IN[0..tail.buffers) DO
MakeBufferAvailable[tail.hint];
ENDLOOP;
syspool.flag ← FALSE; -- Reset to avoid looping
IF syspool.cache[syspool.hint] = NIL THEN
syspool.cache[syspool.hint] ← tail; --first time with this size
BROADCAST syspool.freeBuffer; -- Tell the world, "New buffers!"
END; --AllocateNewClusterLocked
AllocateNewClusterUnlocked: PROC[hint: SizeHint] RETURNS[tail: Cluster] =
BEGIN
-- Define buffer combinations for cluster assuming homogeneous cluster
ClusterInfo: TYPE = RECORD [count: CARDINAL, hint: SizeHint];
clusterInfoArray: ARRAY SizeHint OF ClusterInfo = [
smallBuffer: [5, smallBuffer],
mediumBuffer: [5, mediumBuffer],
largeBuffer: [3, largeBuffer],
maxBuffer: [3, maxBuffer]];
base: BasePointer;
clusterInfo: ClusterInfo;
clusterByteSize, surplus, index: CARDINAL;
offsets: ARRAY [0..maxBuffersPerCluster) OF
BasePointer RELATIVE POINTER TO Buffer.BufferObject;
IF syspool.flag THEN BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statAddCluster];
<<
This is buffer alignment. It is not really documented anywhere else but
is implemented here a way that satisfies D0s (aka Dolphins), DLions and
Daybreaks.
D0s require that the user data starts on a 3 MOD 4 boundary.
DLions don't seem to care one way or the other
Daybreaks can't have the buffer start less than 54 bytes from the next
page break.
Note that we are safe in only doing the quad word alignment for the D0
once, since the added values 'bytesPerPage / bytesPerWord' and
'dataLinkReserve / bytesPerWord' are multiples of 4 word. If
dataLinkReserve is changed so that this is no longer true, the
alignment will fall apart!
>>
-- Determine cluster size and each buffer's offset
clusterInfo ← clusterInfoArray[hint];
clusterByteSize ← SIZE[ClusterObject[clusterInfo.count]] * bpw;
FOR index IN [0..clusterInfo.count) DO
-- Add fixed fields of buffer
clusterByteSize ← clusterByteSize + SIZE[Buffer.BufferObject] * bpw;
-- Check D0 alignment
clusterByteSize ← clusterByteSize + d0Alignment -
clusterByteSize MOD d0Alignment;
-- Check Daybreak alignment
surplus ← clusterByteSize MOD bpp;
IF surplus > bpp - daybreakAlignment THEN
clusterByteSize ← clusterByteSize + (bpp - surplus); --skip page+
-- Assign offsets by WORDs. If clusterByteSize is odd number,
-- it doesn't work...
offsets[index] ← LOOPHOLE[clusterByteSize / bpw -
SIZE[Buffer.BufferObject]];
-- Add buffer sizes
clusterByteSize ← clusterByteSize + dataLinkReserve +
bufferSize[clusterInfo.hint] + fudgeFactor;
ENDLOOP;
-- Allocate Cluster by WORDs. If clusterByteSize is odd number,
-- it doesn't work...
tail ← base ← CommUtil.AllocateBuffers[nbytes: clusterByteSize];
-- Initialize cluster here
tail.next ← NIL;
tail.time ← System.GetClockPulses[];
tail.useMap ← tail.mask ← ALL[used];
-- Set buffer count. Same as @tail.buffers ← buffersPerCluster
-- Which is not allowed by compiler
LOOPHOLE[@tail.buffers, LONG POINTER TO CARDINAL]↑ ← clusterInfo.count;
tail.hint ← clusterInfo.hint;
-- Set hint and buffer pointer for each buffer slot
-- And make it available
FOR index IN [0..clusterInfo.count) DO
tail.slot[index] ← @base[offsets[index]];
tail.useMap[index] ← tail.mask[index] ← free;
ENDLOOP;
-- Initialize buffers
FOR index IN [0..tail.buffers) DO
InitializeBuffer[tail, index, tail.slot[index]];
ENDLOOP;
END;
END; --AllocateNewClusterUnlocked
AllocateNewCluster: PROC =
BEGIN
<<
This broken in three parts.
The first part is the procedure that is forked. Since it is always around
we try to keep the local frame small.
The second part is the one that does the most work. It has a fairly large
local frame, but doesn't get called all that often.
The third part is monitored. It gets called only to link the resources to
the global state.
>>
WaitForRequest: ENTRY PROC = INLINE
{ENABLE UNWIND => NULL; UNTIL syspool.flag DO WAIT syspool.fault; ENDLOOP};
--UNTIL ABORTED-- DO
ENABLE ABORTED => EXIT;
WaitForRequest[]; --small monitored proc to get rolling
AllocateNewClusterLocked[AllocateNewClusterUnlocked[syspool.hint]];
ENDLOOP;
END; --AllocateNewCluster
InitializeBuffer: PROC[c: Cluster, i: CARDINAL, b: Buffer.Buffer] = BEGIN
b.fo ← [
next: NIL, slot: i, currentOwner: NIL, queue: phonyQueue,
status: pending, cluster: c, seal: bufferSeal, network: Driver.nilDevice,
context: NIL, time: [0], allNets: FALSE, bypassZeroNet: FALSE,
type: vagrant, function: free, tries: 0, debug: 0,
driver: [length: 0, iocb: NIL, faceStatus: other[0]]];
END; --InitializeBuffer
DestroyPool: PUBLIC ENTRY PROC[aH: Buffer.AccessHandle] =
BEGIN
<<
Delete the accounting object pointed to by aH, after making sure that
all the buffers being used by that object have been returned. If after
a period of time all the buffers cannot be accounted for - Glitch!
>>
match: Buffer.AccessHandle;
previous: Buffer.AccessHandle ← NIL;
seal: Buffer.PoolType ← aH.seal; --save the current seal
start: LONG CARDINAL ← System.GetClockPulses[]; --start now
IF CommFlags.doDebug AND
(seal ~IN Buffer.PoolType[normalPool..listenPool]) THEN
Driver.Glitch[AccessSealBroken];
aH.seal ← unsealed; --smash it so it can't be used
Process.DisableAborts[@aH.clientFreeBuffer]; --don't let him hurt us
Process.SetTimeout[@aH.clientFreeBuffer, 10]; --solicit wakeups
FOR looptime: CARDINAL ← 0, looptime + Process.TicksToMsec[10]
UNTIL (aH.sendInUse + aH.receiveInUse) = 0 DO
WAIT aH.clientFreeBuffer;
SELECT TRUE FROM
(looptime < CARDINAL[syspool.lostBufferLimit]) => NULL;
(CommFlags.doDebug) => Driver.Glitch[BufferNeverFreed];
ENDCASE => {SmashTheRequeueProc[aH]; EXIT};
ENDLOOP;
FOR match ← syspool.access, match.next UNTIL match = NIL DO
IF match = aH THEN EXIT ELSE previous ← match;
REPEAT FINISHED => Driver.Glitch[SystemBufferPoolConfused];
ENDLOOP;
IF previous = NIL THEN syspool.access ← aH.next
ELSE previous.next ← aH.next; --delink from list
IF seal # listenPool THEN
BEGIN
syspool.send ← syspool.send - aH.send;
syspool.receive ← syspool.receive - aH.receive;
syspool.buffersRequested ← syspool.buffersRequested - aH.send - aH.receive;
END;
CommHeap.zone.FREE[@aH]; --gone
END; --DestroyPool
DestroySystemBufferPool: PUBLIC --ENTRY-- PROC =
BEGIN
LockAndDestroyClusters: ENTRY PROC =
BEGIN
UNTIL syspool.cluster.count = 0 DO
c: Cluster ← syspool.cluster.head;
IF ~AllFree[c] THEN {WAIT syspool.freeBuffer; LOOP};
syspool.cluster.count ← syspool.cluster.count - 1;
syspool.cluster.head ← c.next; CommUtil.FreeBuffers[c];
ENDLOOP;
syspool.cluster ← [NIL, NIL, 0];
END; --LockAndDestroyClusters
IF syspool.access = NIL THEN Driver.Glitch[SystemBufferPoolConfused];
IF syspool.access.next # NIL THEN Driver.Glitch[OtherClientsActive];
Process.Abort[syspool.allocator]; JOIN syspool.allocator;
Process.DisableAborts[@syspool.freeBuffer];
DestroyPool[syspool.access];
LockAndDestroyClusters[];
syspool.access ← NIL; syspool.cache ← ALL[NIL];
END; --DestroySystemBufferPool
GetInputBuffer: PUBLIC <<Driver>> ENTRY PROC[
wait: BOOLEAN, minimumSize: CARDINAL] RETURNS[b: Buffer.Buffer] =
BEGIN
<<
Used by drivers to get buffers without accounting. It also will not
try to allocate more buffers from the Pilot Space machinery.
>>
ENABLE UNWIND => NULL;
minimumSize ← minimumSize - dataLinkReserve; --we'll give it back later
SELECT TRUE FROM
(b ← GetBufferInternal[SizeToSizeHint[minimumSize]]) # NIL =>
GOTO buffer;
(wait) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statBufferWaits];
WAIT syspool.freeBuffer; --wait for someone to return buffer
b ← GetBufferInternal[SizeToSizeHint[minimumSize]];
IF b # NIL THEN GOTO buffer
ELSE Stats.StatIncr[statBufferNIL];
END;
ENDCASE => IF CommFlags.doStats THEN Stats.StatIncr[statBufferNIL];
EXITS
buffer =>
BEGIN
IF CommFlags.doDebug THEN
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
b.fo.type ← vagrant; b.fo.function ← driver;
IF CommFlags.doStats THEN Stats.StatIncr[statGetFullBuffer];
END;
END; --GetInputBuffer
GetBuffer: PUBLIC ENTRY PROC[
type: Buffer.Type ← vagrant, aH: Buffer.AccessHandle,
function: Buffer.Function, wait: BOOLEAN ← TRUE, size: CARDINAL]
RETURNS[b: Buffer.Buffer ← NIL] =
BEGIN
<<
Basic mechanism to get buffers with accounting involved. This routine will
WAIT if the accounting shows no buffer should be made available. It will
append a new cluster to the list if need it must. This routine may return
NIL if wait is FALSE and the client is attempting to exceed his allocation.
>>
<<
Argument 'size' is the minimum index to use looking at the clusters.
Buffer that will be returned will satisfy this 'size'.
>>
ENABLE UNWIND => NULL;
system: LONG POINTER TO CARDINAL;
reserved, inUse: LONG POINTER TO CARDINAL;
IF CommFlags.doDebug THEN
BEGIN
IF syspool.cache = ALL[NIL] THEN Driver.Glitch[NotInitialized];
IF aH.seal ~IN Buffer.PoolType[normalPool..listenPool] THEN
Driver.Glitch[AccessSealBroken];
END;
SELECT function FROM
send =>
BEGIN
reserved ← @aH.send;
inUse ← @aH.sendInUse;
system ← @syspool.sendInUse;
END;
receive =>
BEGIN
reserved ← @aH.receive;
inUse ← @aH.receiveInUse;
system ← @syspool.receiveInUse;
END;
ENDCASE => Driver.Glitch[DontKnowHowToAllocateBuffer];
--UNTIL CODE EXITS-- DO
SELECT TRUE FROM
(reserved↑ > inUse↑) =>
BEGIN
IF (b ← GetBufferInternal[SizeToSizeHint[size]]) # NIL THEN GOTO go;
IF wait THEN WAIT syspool.freeBuffer ELSE GOTO jail;
IF CommFlags.doStats THEN Stats.StatIncr[statClusterWait];
END;
(wait) => WAIT aH.clientFreeBuffer;
ENDCASE => GOTO jail; --couldn't take it, huh?
REPEAT
go =>
BEGIN
inUse↑ ← inUse↑ + 1; system↑ ← system↑ + 1;
b.fo.function ← function; b.fo.type ← type; b.fo.currentOwner ← aH;
IF CommFlags.doDebug THEN
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
IF CommFlags.doStats THEN Stats.StatIncr[IF b.fo.slot = 0
THEN statGetSmallBuffer ELSE statGetFullBuffer];
END;
jail => IF CommFlags.doStats THEN Stats.StatIncr[statBufferNIL];
ENDLOOP;
END; --GetBuffer
GetBufferInternal: INTERNAL PROC[hisHint: SizeHint]
RETURNS[b: Buffer.Buffer ← NIL] =
BEGIN
<<
Do not seach cluster chain if it is clear that there's no buffer of that
size. However, we can give the client a larger buffer than he asked for.
>>
c: Cluster;
index, order: CARDINAL;
FOR hint: SizeHint IN[hisHint..LAST[SizeHint]] DO
IF (c ← syspool.cache[hint]) = NIL THEN LOOP; --not going win here
IF CommFlags.doDebug THEN order ← c.order;
IF syspool.buffersAvailable[hint] > 0 THEN
UNTIL c = NIL DO
-- This statement is assuming homogeneous cluster
-- If cluster contain different kinds of buffer,
-- code "c.slot[0].hint = hint" have to be removed
IF c.hint = hint AND ~AllUsed[c] THEN
FOR index IN[0..c.buffers) DO
IF c.useMap[index] = free THEN GOTO success;
ENDLOOP;
c ← c.next; --go to next element of link
IF CommFlags.doDebug THEN
SELECT TRUE FROM
(c = NIL) => NULL;
(c.order <= order) => Driver.Glitch[ClusterNotOrdered];
ENDCASE => order ← c.order;
ENDLOOP;
REPEAT
success =>
BEGIN
b ← c.slot[index]; c.useMap[index] ← used; --check it out
IF CommFlags.doDebug THEN
BEGIN
IF b.fo.queue # phonyQueue THEN Driver.Glitch[QueueScrambled];
IF b.fo.currentOwner # NIL THEN Driver.Glitch[AccessSealBroken];
b.fo.queue ← NIL; --now make it NIL so the client can use it
END;
b.requeueProcedure ← ReturnBuffer; --reset requeue proc
b.requeueData ← 0; --and smash that
b.fo.network ← Driver.nilDevice; --remove previous network reference
b.fo.context ← NIL; --doesn't have context at this time
b.fo.next ← NIL; --cleanup forward pointers
b.fo.driver.iocb ← NIL; --initialize this field
b.linkLayer ← [LOOPHOLE[@b.bufferBody], 0, dataLinkReserve];
b.highLayer ← [
b.linkLayer.blockPointer + (dataLinkReserve / bpw),
0, bufferSize[hint]]; --set's the real buffer's length
b.fo.driver.length ← dataLinkReserve + bufferSize[hint];
MakeBufferUnavailable[hint]; -- Decrement available buffer count
RETURN; --with the sweet smell of success
END;
ENDLOOP;
<<
If we exited this loop, the we failed to allocate a buffer. Is there
anything that can be done about it without jeopradizing the system?
We may have enough buffers allocated, but there's no buffer large
enough to satisfy this guy's request. That means we could allocate
quite a few more buffers than requested, but ...
>>
IF syspool.buffersRequested > syspool.buffersAllocated THEN
BEGIN
IF ~syspool.flag THEN --ONLY FIRST REQUEST CAN INPUT HINT
{syspool.hint ← hisHint; syspool.flag ← TRUE};
NOTIFY syspool.fault;
END;
END; --GetBufferInternal
MakePool: PUBLIC <<Buffer>> PROC[
send: CARDINAL, --number of send buffers permitted for client use
receive: CARDINAL, --number of receive buffers permitted for client use
type: Buffer.PoolType] --type of pool => type of pool seal
RETURNS [aH: Buffer.AccessHandle] =
BEGIN
<<
Creates the accounting object for client access to the buffer machinery.
It does not actually allocate any buffers, but does bump the counts so they
may be allocated if the need should arise.
>>
Monitored: ENTRY PROC[] = INLINE
BEGIN
ENABLE UNWIND => NULL;
aH.next ← syspool.access; syspool.access ← aH; --link to begining of list
IF type # listenPool THEN
BEGIN
syspool.send ← syspool.send + send;
syspool.receive ← syspool.receive + receive;
syspool.buffersRequested ← syspool.buffersRequested + send + receive;
END;
END; --Monitored
IF syspool.access = NIL THEN Driver.Glitch[NotInitialized];
aH ← CommHeap.zone.NEW[Buffer.AccessObject];
aH.seal ← type;
aH.send ← send; aH.receive ← receive;
aH.sendInUse ← 0; aH.receiveInUse ← 0;
aH.frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
Process.InitializeCondition[@aH.clientFreeBuffer, 0];
Process.DisableTimeout[@aH.clientFreeBuffer];
Process.EnableAborts[@aH.clientFreeBuffer];
Monitored[]; --link the new object up with global
IF CommFlags.doStats THEN Stats.StatIncr[statPoolCreated];
END; --MakePool
MakeSystemBufferPool: PUBLIC PROC[extraBuffers: CARDINAL] =
BEGIN
LockWaitAndSetCache: ENTRY PROC = INLINE
BEGIN
UNTIL syspool.cluster.head # NIL DO WAIT syspool.freeBuffer; ENDLOOP;
syspool.cache[maxBuffer] ← syspool.cluster.head;
END; --LockWaitAndSetCache
IF syspool.access # NIL THEN Driver.Glitch[SystemBufferPoolConfused];
--CREATE ACCESS OBJECT FOR SYSTEM USE
syspool.access ← CommHeap.zone.NEW[Buffer.AccessObject];
syspool.access.next ← NIL; --this is the only one has .next = NIL
syspool.access.seal ← systemPool;
syspool.access.send ← syspool.defaultSendBuffers;
syspool.access.receive ← syspool.defaultReceiveBuffers + extraBuffers;
syspool.access.sendInUse ← syspool.access.receiveInUse ← 0;
Process.InitializeCondition[@syspool.access.clientFreeBuffer, 0];
Process.DisableTimeout[@syspool.access.clientFreeBuffer];
--INITIALIZE SYSTEM COUNTERS, ETC
syspool.send ← syspool.access.send;
syspool.receive ← syspool.access.receive;
syspool.lastTime ← System.GetClockPulses[];
syspool.sendInUse ← syspool.receiveInUse ← syspool.leaked ← 0;
syspool.access.frame ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
syspool.buffersAllocated ← 0;
syspool.buffersRequested ← syspool.access.send + syspool.access.receive;
syspool.buffersAvailable ← ALL[0];
Process.EnableAborts[@syspool.freeBuffer];
IF CommFlags.doStats THEN Stats.StatIncr[statPoolCreated];
<<SPECIAL CASE -- ACTUALLY COMMITTING THE SPACES BEHIND THE POOL>>
BEGIN
prio: Process.Priority ← Process.GetPriority[];
syspool.hint ← maxBuffer; --this is what the driver is doing
syspool.flag ← TRUE; -- So it allocates below
Process.SetPriority[CommPriorities.realtime];
syspool.allocator ← FORK AllocateNewCluster[]; --will allocate real buffers
Process.SetPriority[prio]; --back to normal
END;
LockWaitAndSetCache[];
END; --MakeSystemBufferPool
ReturnBuffer, ReturnFreeBuffer: PUBLIC ENTRY PROC[b: Buffer.Buffer] =
BEGIN
<<ENABLE UNWIND => NULL; --generates and passes no signals>>
hint: SizeHint;
current: Cluster;
slot: CARDINAL = b.fo.slot;
aH: Buffer.AccessHandle =b.fo.currentOwner;
IF CommFlags.doDebug THEN
BEGIN
IF b.fo.function = free THEN Driver.Glitch[BufferFreedTwice];
IF b.fo.queue # NIL THEN Driver.Glitch[QueueScrambled];
b.fo.debug ← Frame.ReadGlobalLink[Frame.GetReturnFrame[]];
b.fo.queue ← phonyQueue; --make it something we know
END;
SELECT b.fo.function FROM
send =>
BEGIN
aH.sendInUse ← aH.sendInUse - 1;
syspool.sendInUse ← syspool.sendInUse - 1;
NOTIFY aH.clientFreeBuffer;
END;
receive =>
BEGIN
aH.receiveInUse ← aH.receiveInUse - 1;
syspool.receiveInUse ← syspool.receiveInUse - 1;
NOTIFY aH.clientFreeBuffer;
END;
<<driver => NULL; --in use by a driver - no access control>>
ENDCASE;
current ← b.fo.cluster; --copy cluster value from buffer
hint ← current.hint; --get size hint
IF CommFlags.doDebug AND current.useMap[slot] = free THEN
Driver.Glitch[SystemBufferPoolConfused];
current.useMap[slot] ← free; --he's available
b.fo.currentOwner ← NIL; --nobody owns this sucker
b.fo.function ← free; --buffer in cluster and free
BROADCAST syspool.freeBuffer; --tell everyone something has happened
--GENERAL POOL MAINTAINANCE
-- Buffer's back. Make returnd buffer available
MakeBufferAvailable[hint];
IF (current.order < syspool.cache[hint].order) THEN
syspool.cache[hint] ← current; -- Set cache value back to oldest buffer
current.time ← System.GetClockPulses[];
IF (syspool.lastTime - current.time) > syspool.scanInterval THEN
GeneralPoolMaintainance[hint];
END; --ReturnBuffer, ReturnFreeBuffer
GeneralPoolMaintainance: INTERNAL PROC[hint: SizeHint] =
BEGIN
<<
Maybe we want to keep 'n' free buffers laying around at any time. This
would concievably be to handle burst traffic on gateways, and might be
reasonalbe on machines that don't believe in virtual memory.
>>
Bias: PROC RETURNS[BOOLEAN] = INLINE {
RETURN[
INTEGER[syspool.buffersAvailable[hint]] < INTEGER[syspool.freeBias]]};
<<
--This doesn't seem to be needed with homogeneous clusters.
ClusterHaveThisHint: PROC RETURNS[BOOLEAN] = INLINE {
RETURN[current.hint = hint]};
>>
previous: Cluster ← NIL;
current, search: Cluster;
syspool.lastTime ← System.GetClockPulses[]; --record for next time
FOR search ← syspool.cluster.head, search UNTIL search = NIL DO
current ← search; --copy cluster to be tested
search ← current.next; --and get next record in list
<<ASSERTION: previous.order < current.order < search.order>>
IF CommFlags.doDebug THEN
SELECT TRUE FROM
(previous = NIL) => NULL; --first time through loop;
(previous.order >= current.order) => Driver.Glitch[ClusterNotOrdered];
(search = NIL) => NULL;
(current.order >= search.order) => Driver.Glitch[ClusterNotOrdered];
ENDCASE;
SELECT TRUE FROM
~AllFree[current] => previous ← current; -- All buffers not free
((System.GetClockPulses[] - current.time) < syspool.clusterAgeLimit) =>
previous ← current; -- Not old enough
(biasing AND Bias[] <<AND ClusterHaveThisHint[]>>) =>
previous ← current; -- Just don't give it back
ENDCASE =>
BEGIN
syspool.buffersAllocated ← syspool.buffersAllocated - current.buffers;
FOR slot: CARDINAL IN [0..current.buffers) DO
MakeBufferUnavailable[current.hint];
ENDLOOP;
IF previous = NIL THEN syspool.cluster.head ← search --head of list
ELSE previous.next ← search; --pull 'previous' out of middle
IF search = NIL THEN syspool.cluster.tail ← previous; --or the end
<<
Deleting the oldest order cluster may cause us to monotonically
increase our cluster count until we overflow 16 bits. That would
take a while and would require a lot of changes in the sizes of
buffers being used. If it gets to be a problem, then we should
take time in this scan to normalize the cluster's order number
back to zero when we delete the cluster with the lowest order.
>>
FOR hint: SizeHint IN SizeHint DO
IF current = syspool.cache[hint] THEN --did we just blow him away?
FOR it: Cluster ← current.next, it.next UNTIL it = NIL DO
IF it.hint = hint THEN {syspool.cache[hint] ← it; EXIT};
REPEAT FINISHED => syspool.cache[hint] ← NIL;
ENDLOOP;
ENDLOOP;
CommUtil.FreeBuffers[current]; --delete the buffers
syspool.cluster.count ← syspool.cluster.count - 1;
END;
ENDLOOP;
END; --GeneralPoolMaintainance
SmashTheRequeueProc: INTERNAL PROC[aH: Buffer.AccessHandle] =
BEGIN
FOR cluster: Cluster ← syspool.cluster.head, cluster.next
UNTIL cluster = NIL DO
FOR i: NATURAL IN[0..cluster.buffers) DO
IF cluster.slot[i].fo.currentOwner = aH THEN
cluster.slot[i].requeueProcedure ← ReturnBuffer;
ENDLOOP;
ENDLOOP;
syspool.leaked ← syspool.leaked + aH.receiveInUse + aH.sendInUse;
END; --SmashTheRequeueProc
--INITIALIZATION
syspool.access ← NIL;
syspool.cache ← ALL[NIL];
syspool.cluster ← [NIL, NIL, 0];
syspool.freeBias ← syspool.defaultSendBuffers ← 0;
syspool.lostBufferLimit ← 6D4; --msecs
syspool.defaultReceiveBuffers ← maxBuffersPerCluster + 1;
syspool.clusterAgeLimit ← System.MicrosecondsToPulses[30D6];
syspool.scanInterval ← System.MicrosecondsToPulses[5D6];
Process.EnableAborts[@syspool.fault];
Process.DisableTimeout[@syspool.fault];
Process.DisableTimeout[@syspool.freeBuffer];
END.....
LOG
time - by - action
14-May-84 13:36:38 AOF Post Klamath
25-Jul-85 9:11:35 AOF NIL context pointer when allocating new buffers
5-Nov-85 11:26:31 AOF Buffer alignment for D0s, DLions & Daybreaks
15-Apr-86 16:56:01 AOF Smash the requeueProcedure on leaked buffers
17-Apr-86 15:45:55 AOF Correct order in checking for doomed packets
16-May-86 9:24:04 SMA Changes for new encapsulation scheme.
23-May-86 16:01:31 SMA Buffer alignment for new encapsulation scheme
6-Jun-86 15:52:18 AOF Initialize the IOCB in GetBufferInternal
17-Jun-86 18:41:32 AOF Driver.GetInputBuffer takes size
14-Jul-86 15:17:34 MI Changed unit of 'size' from word to byte
18-Jul-86 11:32:43 MI Support multiple buffer size.
23-Jul-86 11:09:25 MI Removed SetSizes and cluster.doomed.
31-Jul-86 14:43:56 MI Changed buffer combination for cluster.
1-Aug-86 15:22:57 MI Changed degree of driver.length from word to byte.
20-Aug-86 11:54:41 AOF dataLinkReserve & maxUserDataSize
17-Nov-86 13:35:10 AOF Renaming of process priorities
20-Nov-86 11:51:22 AOF Allocate larger than requested buffers if available
21-Nov-86 18:23:35 AOF Set highLayer.stopIndex to be allocated buffer's length
21-Dec-86 13:49:25 AOF maxUserDataSize set to 1518 (includes physical layer).
7-Jan-87 16:36:38 AOF Tweeks for MDS relief
12-Jan-87 18:05:49 AOF More debugging code
19-Jan-87 15:16:23 AOF Bytes, not words for AllocateBuffers
12-Feb-87 19:17:24 AOF Tweeking .cache and fudgeFactor
6-May-87 18:39:58 AOF Use of phonyQueue for debugging
20-Aug-87 9:03:52 AOF Don't include "fudgeFactor" in b.fo.driver.length
31-Aug-87 8:51:08 AOF Don't hold monitor and allocate to make pools
31-Aug-87 8:51:08 AOF Adjust buffer lengths (again).