MODULE Caches;
IMPORT KernelLog, Objects, Disks;
CONST
LockedBit = 0; DirtyBit = 1;
CacheUpdateTime = 5*1000;
Trace = TRUE;
TYPE
Buffer* = OBJECT
VAR
data*: POINTER TO ARRAY OF CHAR;
dev*: Disks.Device;
block*: LONGINT;
state: SET;
nextHash, prevHash, nextLRU, prevLRU, nextDirty: Buffer;
PROCEDURE &Init*(size: LONGINT);
BEGIN
NEW(data, size)
END Init;
END Buffer;
Cache* = OBJECT
VAR
blockSize*: LONGINT;
hashTable: POINTER TO ARRAY OF Buffer;
lru: Buffer;
lruClean: LONGINT;
syncNow: BOOLEAN;
timer: Objects.Timer;
PROCEDURE Acquire*(dev: Disks.Device; block: LONGINT; VAR buffer: Buffer; VAR valid: BOOLEAN);
VAR done: BOOLEAN; buf: Buffer; n, m: LONGINT;
BEGIN {EXCLUSIVE}
ASSERT(dev # NIL);
REPEAT
n := Hash(dev, block); buf := hashTable[n];
WHILE (buf # NIL) & ((buf.block # block) OR (buf.dev # dev)) DO
buf := buf.nextHash
END;
IF buf # NIL THEN
AWAIT(~(LockedBit IN buf.state));
done := (buf.dev = dev) & (buf.block = block);
valid := TRUE
ELSE
AWAIT(lruClean # 0);
buf := lru.nextLRU;
WHILE DirtyBit IN buf.state DO syncNow := TRUE; buf := buf.nextLRU END;
ASSERT(buf # lru);
m := Hash(buf.dev, buf.block);
IF m # n THEN MoveBuffer(buf, m, n) END;
buf.dev := dev; buf.block := block;
done := TRUE; valid := FALSE
END
UNTIL done;
buf.prevLRU.nextLRU := buf.nextLRU; buf.nextLRU.prevLRU := buf.prevLRU;
IF ~(DirtyBit IN buf.state) THEN DEC(lruClean) END;
INCL(buf.state, LockedBit);
buffer := buf
END Acquire;
PROCEDURE Release*(buffer: Buffer; modified, written: BOOLEAN);
BEGIN {EXCLUSIVE}
EXCL(buffer.state, LockedBit);
IF written THEN EXCL(buffer.state, DirtyBit); INC(lruClean)
ELSIF modified THEN INCL(buffer.state, DirtyBit)
ELSIF ~(DirtyBit IN buffer.state) THEN INC(lruClean)
ELSE
END;
buffer.prevLRU := lru.prevLRU; buffer.nextLRU := lru;
buffer.prevLRU.nextLRU := buffer; buffer.nextLRU.prevLRU := buffer
END Release;
PROCEDURE Synchronize*;
VAR list, buf: Buffer; res, num, count: LONGINT;
BEGIN
AcquireDirty(list); count := 0;
WHILE list # NIL DO
buf := list; list := buf.nextDirty; buf.nextDirty := NIL;
ASSERT(blockSize MOD buf.dev.blockSize = 0);
num := blockSize DIV buf.dev.blockSize;
buf.dev.Transfer(Disks.Write, buf.block, num, buf.data^, 0, res);
IF res # Disks.Ok THEN ReportError(buf.dev, buf.block, num, res) END;
ReleaseDirty(buf); INC(count)
END;
IF Trace & (count # 0) THEN
KernelLog.Enter; KernelLog.String("Caches: "); KernelLog.String(buf.dev.name);
KernelLog.Char(" "); KernelLog.Int(count, 1); KernelLog.Exit
END
END Synchronize;
PROCEDURE AcquireDirty(VAR list: Buffer);
VAR buf, tail: Buffer;
BEGIN {EXCLUSIVE}
list := NIL; tail := NIL; buf := lru.nextLRU;
WHILE buf # lru DO
IF buf.state * {LockedBit, DirtyBit} = {DirtyBit} THEN
IF list = NIL THEN list := buf ELSE tail.nextDirty := buf END;
tail := buf; buf.nextDirty := NIL;
INCL(buf.state, LockedBit)
END;
buf := buf.nextLRU
END
END AcquireDirty;
PROCEDURE ReleaseDirty(buffer: Buffer);
BEGIN {EXCLUSIVE}
ASSERT(buffer.state * {LockedBit, DirtyBit} = {LockedBit, DirtyBit});
buffer.state := buffer.state - {LockedBit, DirtyBit};
INC(lruClean)
END ReleaseDirty;
PROCEDURE AwaitSync;
BEGIN {EXCLUSIVE}
AWAIT(syncNow); syncNow := FALSE
END AwaitSync;
PROCEDURE Hash(dev: Disks.Device; block: LONGINT): LONGINT;
BEGIN
RETURN block MOD LEN(hashTable)
END Hash;
PROCEDURE MoveBuffer(buf: Buffer; from, to: LONGINT);
BEGIN
IF buf.prevHash # NIL THEN
buf.prevHash.nextHash := buf.nextHash
ELSE
hashTable[from] := buf.nextHash
END;
IF buf.nextHash # NIL THEN buf.nextHash.prevHash := buf.prevHash END;
buf.prevHash := NIL; buf.nextHash := hashTable[to]; hashTable[to] := buf;
IF buf.nextHash # NIL THEN buf.nextHash.prevHash := buf END
END MoveBuffer;
PROCEDURE HandleTimeout;
BEGIN {EXCLUSIVE}
syncNow := TRUE;
Objects.SetTimeout(timer, SELF.HandleTimeout, CacheUpdateTime)
END HandleTimeout;
PROCEDURE &Init*(blockSize, hashSize, cacheSize: LONGINT);
VAR buf: Buffer; i, n: LONGINT;
BEGIN
ASSERT(hashSize <= cacheSize);
NEW(hashTable, hashSize);
NEW(lru, 0); lru.dev := NIL; lru.block := -1;
lru.nextLRU := lru; lru.prevLRU := lru;
lruClean := cacheSize; syncNow := FALSE;
SELF.blockSize := blockSize;
FOR i := 0 TO cacheSize-1 DO
NEW(buf, blockSize);
buf.dev := NIL; buf.block := i;
buf.state := {}; buf.nextDirty := NIL;
n := Hash(buf.dev, buf.block);
buf.prevHash := NIL; buf.nextHash := hashTable[n]; hashTable[n] := buf;
IF buf.nextHash # NIL THEN buf.nextHash.prevHash := buf END;
buf.prevLRU := lru.prevLRU; buf.nextLRU := lru;
buf.prevLRU.nextLRU := buf; buf.nextLRU.prevLRU := buf
END;
NEW(timer); Objects.SetTimeout(timer, SELF.HandleTimeout, CacheUpdateTime)
END Init;
BEGIN {ACTIVE, SAFE} (* cache periodically synchronizes automatically *)
LOOP AwaitSync; Synchronize END
END Cache;
PROCEDURE ReportError(dev: Disks.Device; block, num, res: LONGINT);
BEGIN
KernelLog.Enter; KernelLog.String("Caches: Error "); KernelLog.Int(res, 1);
KernelLog.String(" on disk "); KernelLog.String(dev.name); KernelLog.Int(num, 1);
KernelLog.String(" blocks at "); KernelLog.Int(block, 1); KernelLog.Exit
END ReportError;
END Caches.