home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PROGRAMS / UTILS / DSKCACHE / EVALCACH.ZIP / EVAL.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-10-26  |  21.1 KB  |  719 lines

  1. {
  2. **************************************************************************
  3. EVAL.PAS -- Disk Cache Strategy Performance Evaluator
  4. **************************************************************************
  5.  
  6. This program estimates the effectiveness of disk caching strategies using
  7. a log of up to 8196 actual disk transactions from your first physical hard
  8. disk. The TSR program SNOOP.COM maintains the log in memory.
  9.  
  10. This version of the program compares the performance of a simple write-
  11. through cache to the performance you'd get from no cache at all. It bases
  12. its speed estimates on hard disk characteristics you enter, rather than on
  13. the actual characteristics of your machine, so you can see how drive
  14. characteristics affect both system and cache performance. You can test your
  15. own cache strategies by defining objects of the type CacheStrategy. You can
  16. also observe the performance effects of cache size. As a simple experiment,
  17. try reducing the cache size to 64K or 128K and press G to simulate the
  18. results. You should notice a dramatic drop in cache performance.
  19.  
  20. This program is Copyright (C) 1989 by L. Brett Glass. It may be freely
  21. redistributed for non-commercial purposes only. It may not be made part of any
  22. hardware or software product without the express written consent of the
  23. copyright owner. This program is supplied on an "as-is" basis. The author
  24. assumes no responsibility for its correctness or its fitness for a given
  25. purpose, nor for consequential damages which may arise from its use.
  26. You may contact the author at one of the following electronic mail addresses:
  27.  
  28. Usenet: apple!well!rogue
  29. ARPANET: well!rogue@APPLE.COM
  30. BIX: glass
  31. CompuServe: [72267,3673] (Also reachable from MCIMail)
  32.  
  33. **************************************************************************
  34. }
  35.  
  36. program Eval;
  37. {$A-} {No word alignment}
  38.  
  39. uses DOS, Snoop, CRT, CharScrn, Boxes, HBars, Controls;
  40.  
  41. { The typed constant below contains information on how to draw the box
  42.   that greets the user when the program starts up. }
  43.  
  44. const
  45.   bigBox : Box = (x : 1; y : 1;
  46.                   color : WHITE;
  47.                   dirty : TRUE;
  48.                   title : 'Disk Cache Strategy Performance Evaluator --'+
  49.                           ' Copyright L. Brett Glass 1989';
  50.                   width : 80; height : 24;
  51.                   boxType : THINBOX;
  52.                   hDividers : [6,11,21];
  53.                   vDividers : []);
  54.  
  55. const
  56.   MAXBARWIDTH = SCREENWIDTH - 6; {Maximum width of bar graphs}
  57.  
  58. { Text controls for the user interface. These are simplistic controls
  59.   that can be selected with a single keystroke and can change internal
  60.   variables in a safe manner. See the unit Controls for the code that
  61.   runs them. }
  62.  
  63.   adjSeek : WordCtrl =
  64.                      (x : 3; y : 14;
  65.                       color : WHITE;
  66.                       dirty : TRUE;
  67.                       title: 'T)rack-to-track seek (adjacent): ';
  68.                       next : NIL;
  69.                       value : 5;
  70.                       fieldSize : 2;
  71.                       max : 20;
  72.                       min : 1;
  73.                       increment : 1;
  74.                       bigIncrement: 10;
  75.                       unitStr : 'ms');
  76.  
  77.   avgSeek : WordCtrl =
  78.                       (x : 3; y : 15;
  79.                        color : WHITE;
  80.                        dirty : TRUE;
  81.                        title: 'A)verage seek time (all tracks): ';
  82.                        next : NIL;
  83.                        value : 40;
  84.                        fieldSize : 3;
  85.                        max : 120;
  86.                        min : 1;
  87.                        increment : 1;
  88.                        bigIncrement : 10;
  89.                        unitStr : 'ms');
  90.  
  91.    numCyl : WordCtrl =
  92.                       (x : 3; y : 16;
  93.                        color : WHITE;
  94.                        dirty : TRUE;
  95.                        title : 'N)umber of cylinders: ';
  96.                        next : NIL;
  97.                        value : 615;
  98.                        fieldSize : 4;
  99.                        max : 2048;
  100.                        min : 128;
  101.                        increment : 1;
  102.                        bigincrement : 10;
  103.                        unitStr : '');
  104.  
  105.    rotSpeed : WordCtrl =
  106.                       (x : 3; y : 17;
  107.                        color : WHITE;
  108.                        dirty : TRUE;
  109.                        title : 'R)otational speed: ';
  110.                        next : NIL;
  111.                        value : 3600;
  112.                        fieldSize : 4;
  113.                        max : 8000;
  114.                        min : 1200;
  115.                        increment : 100;
  116.                        bigIncrement : 1000;
  117.                        unitStr : 'RPM');
  118.  
  119.    numHeads : WordCtrl =
  120.                       (x : 3; y : 18;
  121.                        color : WHITE;
  122.                        dirty : TRUE;
  123.                        title : 'D)isk heads: ';
  124.                        next : NIL;
  125.                        value : 4;
  126.                        fieldSize : 2;
  127.                        max : 32;
  128.                        min : 1;
  129.                        increment : 1;
  130.                        bigIncrement : 10;
  131.                        unitStr : '');
  132.  
  133.    secsPerTrack : WordCtrl =
  134.                       (x : 3; y : 19;
  135.                        color : WHITE;
  136.                        dirty : TRUE;
  137.                        title : 'S)ectors per track: ';
  138.                        next : NIL;
  139.                        value : 17;
  140.                        fieldSize : 2;
  141.                        max : 99;
  142.                        min : 1;
  143.                        increment : 1;
  144.                        bigIncrement : 10;
  145.                        unitStr : '');
  146.  
  147.    interleave : WordCtrl =
  148.                       (x : 3; y : 20;
  149.                        color : WHITE;
  150.                        dirty : TRUE;
  151.                        title : 'I)nterleave factor: ';
  152.                        next : NIL;
  153.                        value : 1;
  154.                        fieldSize : 2;
  155.                        max : 99;
  156.                        min : 1;
  157.                        increment : 1;
  158.                        bigIncrement : 10;
  159.                        unitStr : ': 1');
  160.  
  161.    cacheSize : WordCtrl =
  162.                       (x : 45; y : 14;
  163.                        color : WHITE;
  164.                        dirty : TRUE;
  165.                        title : 'C)ache size : ';
  166.                        next : NIL;
  167.                        value : 1024;
  168.                        fieldSize : 4;
  169.                        max : 4096;
  170.                        min : 64;
  171.                        increment : 64;
  172.                        bigIncrement : 256;
  173.                        unitStr : 'Kbytes');
  174.  
  175.    help : ButtonCtrl =
  176.                       (x : 45; y : 18;
  177.                        color : WHITE;
  178.                        dirty : TRUE;
  179.                        title : 'P)rogram information';
  180.                        next : NIL);
  181.  
  182.    go : ButtonCtrl =
  183.                       (x : 45; y : 19;
  184.                        color : WHITE;
  185.                        dirty : TRUE;
  186.                        title : 'G)o (recalculate estimates)';
  187.                        next : NIL);
  188.  
  189.    quit : ButtonCtrl =
  190.                       (x : 45; y : 20;
  191.                        color : WHITE;
  192.                        dirty : TRUE;
  193.                        title : 'Q)uit program';
  194.                        next : NIL);
  195.  
  196.  
  197. { Variables used in the estimation process. Some of these are Real
  198.   copies of Word variables that eliminate the need for repeated
  199.   conversions. }
  200.  
  201. var
  202.   elapsedTime : Real;
  203.   currCyl : Word;
  204.   avgLatency, maxLatency, seekConst, realNumCyl : Real;
  205.   realAdjSeek, realInterleaveDelay, realSecsPerTrack : Real;
  206.   maxTime : Real;
  207.  
  208. procedure PrepareFactors;
  209.   { This routine prepares factors for use in the estimation process.
  210.     In some cases, this amounts to floating-point conversion; in
  211.     others, there's a small amount of arithmetic to be done.}
  212.   begin {PrepareFactors}
  213.   avgLatency := 30000.0/rotSpeed.value;
  214.   maxLatency := 60000.0/rotSpeed.value;
  215.   seekConst := avgSeek.value - adjSeek.value;
  216.   realNumCyl := numCyl.value;
  217.   realAdjSeek := adjSeek.value;
  218.   realInterleaveDelay := Pred(interleave.value);
  219.   realSecsPerTrack := secsPerTrack.value;
  220.   end;  {PrepareFactors}
  221.  
  222. procedure SimulateSeek(endCyl : Word);
  223.   { Simulate a seek from "currCyl" to "endCyl". "currCyl" is a global
  224.     that the simulator uses to keep track of the current head position. }
  225.   var
  226.     distance : LongInt;
  227.   begin {SimulateSeek}
  228.   {To estimate seek time, start with adjacent track time. Then add
  229.    a fraction of the average time such that a seek crossing 1/3 of
  230.    the tracks takes the average time.}
  231.   distance := Abs(LongInt(endCyl) - currCyl);
  232.   if distance > 0 then
  233.     elapsedTime := elapsedTime + realAdjSeek +
  234.                    (distance * 3.0 * seekConst) / realNumCyl;
  235.   currCyl := endCyl
  236.   end;  {SimulateSeek}
  237.  
  238. procedure SimulateReadWrite(cyl,head,sec,numSec : Word);
  239.   { Simulate a read or a write operation. A large portion of the overhead
  240.     of such operations is caused by rotational latency. }
  241.   var
  242.     currSec, currHead, i : Word;
  243.   begin {SimulateReadWrite}
  244.   {To estimate read/write time, assume a latency of 1/2 rotation. Then
  245.    add the time to write numSec sectors. If a seek is required for a
  246.    multiple-sector operation, add in the time for that as well.}
  247.   if currCyl <> cyl then {Get to cylinder}
  248.     SimulateSeek(cyl);
  249.   elapsedTime := elapsedTime + avgLatency; {Latency}
  250.   currSec := sec;
  251.   currHead := head;
  252.   i := 1;
  253.   repeat
  254.     elapsedTime := elapsedTime + maxLatency/realsecsPerTrack *
  255.                                                          realInterleaveDelay;
  256.     if i = numSec then
  257.       Exit;
  258.     Inc(i);
  259.     Inc(currSec);
  260.     if currSec > secsPerTrack.value then {Move from head to head, cyl to cyl}
  261.       begin
  262.       currSec := 1;
  263.       Inc(currHead);
  264.       if currHead > numHeads.value then
  265.         begin
  266.         currHead := 1;
  267.         SimulateSeek(Succ(currCyl));
  268.         end
  269.       end
  270.     until FALSE;
  271.   end;  {SimulateReadWrite}
  272.  
  273.  
  274.  
  275. { The object type CacheStrategy is defined here. Each strategy can use
  276.   the snooper's data tables to estimate total elapsed time for disk
  277.   operations, and can prepare to display a titled bar graph showing
  278.   the result. An object of the type CacheStrategy uses a "null" strategy
  279.   -- that is, no caching actually occurs. Other caching strategies should
  280.   be defined as decendents of this type. }
  281.  
  282. type
  283.   CacheStrategyPtr = ^CacheStrategy;
  284.   CacheStrategy = object (HBar)
  285.     time : Real;
  286.     next : CacheStrategyPtr;
  287.     procedure Estimate(var s : SnoopObject); virtual;
  288.     procedure PrepareBarDisplay;
  289.     end;
  290.  
  291. procedure CacheStrategy.PrepareBarDisplay;
  292.   var
  293.     sec : String[12];
  294.   begin {CacheStrategy.PrepareBarDisplay}
  295.   size := Round((time/maxTime)* MAXBARWIDTH * 2);
  296.   Delete(title,Pos('=',title) + 2, 255);
  297.   Str(time/1000.0:0:2,sec);
  298.   title := title + sec + ' sec';
  299.   end;  {CacheStrategy.PrepareBarDisplay}
  300.  
  301. procedure CacheStrategy.Estimate(var s : SnoopObject);
  302.   var
  303.     t : Transaction;
  304.   begin {CacheStrategy.Estimate}
  305.   PrepareFactors;
  306.   elapsedTime := 0.0;
  307.   currCyl := 0;
  308.   if s.GetFirstTransaction(t) then
  309.     repeat
  310.       with t do
  311.         SimulateReadWrite(cylNum,headNum,secNum,sectors);
  312.       until not s.GetNextTransaction(t);
  313.   time := elapsedTime;
  314.   end;  {CacheStrategy.Estimate}
  315.  
  316. { An object of type WriteThroughCache implements a simple write-through
  317.   cache. The caching strategy used in this implementation is designed
  318.   to be straightforward, not optimal. A multi-sector read that includes
  319.   even one uncached sector is treated as a complete miss (as it is,
  320.   ironically, in some commercial disk cache programs. }
  321.  
  322. type
  323.   WriteThroughCache = object (CacheStrategy)
  324.     procedure Estimate(var s : SnoopObject); virtual;
  325.     end;
  326.   LRUCacheRecPtr = ^LRUCacheRec;
  327.   LRUCacheRec = record
  328.     older,younger : LRUCacheRecPtr;
  329.     nextHash,prevHash : LRUCacheRecPtr;
  330.     head : Byte;
  331.     sector : Byte;
  332.     cylinder : Byte;
  333.     end;
  334.  
  335. { The write-through cache is fully associative and uses a true LRU
  336.   algorithm. Records representing cache data are maintained as a linked
  337.   list from "youngest" to "oldest" -- each sector is also indexed (via
  338.   a simple hashing scheme) to make it easy to determine that it's in the
  339.   cache. }
  340.  
  341.  
  342. const
  343.   youngestCacheRec : LRUCacheRecPtr = NIL;
  344.   oldestCacheRec : LRUCacheRecPtr = NIL;
  345.   numCacheRecs : Word = 0;
  346.   maxCacheRecs : Word = 256;
  347.  
  348. var
  349.   lruHashTable : array [0..2047] of LRUCacheRecPtr;
  350.  
  351. function HashFunction(hd,cyl,sec : Word) : Word;
  352.   begin {HashFunction}
  353.   HashFunction := (sec + hd shl 4 + cyl shl 6) and 2047
  354.   end;  {HashFunction}
  355.  
  356. function FindCacheRecord(hd,cyl,sec : Word) : LRUCacheRecPtr;
  357.   var
  358.     rec : LRUCacheRecPtr;
  359.     hash : Word;
  360.   begin {FindCacheRecord}
  361.   {First search hash table for existing data}
  362.   hash := HashFunction(hd,cyl,sec);
  363.   rec := lruHashTable[hash];
  364.   {Statement below uses short-circuit Booleans} {$B-}
  365.   while (rec <> NIL) and ((rec^.head <> hd) or
  366.                           (rec^.cylinder <> cyl) or
  367.                           (rec^.sector <> sec)) do
  368.     rec := rec^.nextHash;
  369.   FindCacheRecord := rec;
  370.   end;  {FindCacheRecord}
  371.  
  372. procedure DumpCache; {For debugging; not used otherwise}
  373.   var
  374.     rec : LRUCacheRecPtr;
  375.     hash : Word;
  376.     bin : Word;
  377.   begin {DumpCache}
  378.   Writeln;
  379.   Writeln('Cache dump:');
  380.   for bin := 0 to 2047 do
  381.     begin
  382.     rec := lruHashTable[bin];
  383.     Writeln('BIN #',bin,':');
  384.     while (rec <> NIL) do
  385.       begin
  386.       Writeln(' head: ',rec^.head,
  387.               ' cylinder: ',rec^.cylinder,
  388.               ' sector: ',rec^.sector);
  389.  
  390.       rec := rec^.nextHash
  391.       end
  392.     end
  393.   end;  {DumpCache}
  394.  
  395. procedure ClearCache;
  396.   var
  397.     rec1,rec2 : LRUCacheRecPtr;
  398.   begin {ClearCache}
  399.   {Dispose of all storage in the cache, if any}
  400.   rec1 := youngestCacheRec;
  401.   while rec1 <> NIL do
  402.     begin
  403.     rec2 := rec1^.older;
  404.     Dispose(rec1);
  405.     rec1 := rec2
  406.     end;
  407.   {Clear the hash table}
  408.   FillChar(lruHashTable,SizeOf(lruHashTable),0);
  409.   numCacheRecs := 0;
  410.   youngestCacheRec := NIL;
  411.   oldestCacheRec := NIL
  412.   end;  {ClearCache}
  413.  
  414. procedure RecordToFront(rec : LRUCacheRecPtr);
  415.   begin {RecordToFront}
  416.   with rec^ do
  417.     begin
  418.     if younger <> NIL then
  419.       begin
  420.       if rec = oldestCacheRec then
  421.         oldestCacheRec := younger;
  422.       younger^.older := older;
  423.       if older <> NIL then
  424.         older^.younger := younger;
  425.       younger := NIL;
  426.       older := youngestCacheRec;
  427.       youngestCacheRec := rec
  428.       end
  429.     end
  430.   end;  {RecordToFront}
  431.  
  432. function UnlinkOldest : LRUCacheRecPtr;
  433.   var
  434.     hash : Word;
  435.   begin {UnlinkOldest}
  436.   UnlinkOldest := oldestCacheRec;
  437.   with oldestCacheRec^ do
  438.     begin
  439.     {Remove from end of chronological chain}
  440.     younger^.older := NIL;
  441.     {Remove from hash "bucket"}
  442.     if prevHash <> NIL then
  443.       prevHash^.nextHash := nextHash
  444.     else
  445.       begin
  446.       hash := HashFunction(head,cylinder,sector);
  447.       lruHashTable[hash] := nextHash
  448.       end;
  449.     if nextHash <> NIL then
  450.       nextHash^.prevHash := prevHash
  451.     end;
  452.   oldestCacheRec := oldestCacheRec^.younger
  453.   end;  {UnlinkOldest}
  454.  
  455. procedure AddNewRecord(hd,cyl,sec : Word);
  456.   var
  457.     rec : LRUCacheRecPtr;
  458.     hash : Word;
  459.   begin {AddNewRecord}
  460.   if numCacheRecs < maxCacheRecs then
  461.     begin
  462.     New(rec);
  463.     Inc(numCacheRecs)
  464.     end
  465.   else
  466.     rec := UnlinkOldest;
  467.   {Add to the hash table}
  468.   hash := HashFunction(hd,cyl,sec);
  469.   with rec^ do
  470.     begin
  471.     head := hd;
  472.     sector := sec;
  473.     cylinder := cyl;
  474.     nextHash := lruHashTable[hash];
  475.     lruHashTable[hash] := rec;
  476.     prevHash := NIL;
  477.     if nextHash <> NIL then
  478.       nextHash^.prevHash := rec;
  479.     {Also chain onto front of list}
  480.     older := youngestCacheRec;
  481.     if youngestCacheRec <> NIL then
  482.       youngestCacheRec^.younger := rec
  483.     else
  484.       oldestCacheRec := rec;
  485.     youngestCacheRec := rec;
  486.     younger := NIL
  487.     end;
  488.   end;  {AddNewRecord}
  489.  
  490.  
  491. procedure WriteThroughCache.Estimate(var s : SnoopObject);
  492.   var
  493.     max, count, hash : Word;
  494.     rec : LRUCacheRecPtr;
  495.     sec, hd, cyl : Word;
  496.     t : Transaction;
  497.     i : Word;
  498.     missFlag : Boolean;
  499.   begin {WriteThroughCache.Estimate}
  500.   PrepareFactors;
  501.   maxCacheRecs := cacheSize.value * 2;
  502.   ClearCache;
  503.   elapsedTime := 0.0;
  504.   currCyl := 0;
  505.   if s.GetFirstTransaction(t) then
  506.     repeat
  507.       with t do
  508.         begin
  509.         missFlag := FALSE;
  510.         hd := headNum;
  511.         cyl := cylNum;
  512.         sec := secNum;
  513.         for i := 1 to sectors do
  514.           begin
  515.           rec := FindCacheRecord(hd,cyl,sec);
  516.           if rec <> NIL then {Found the record. Move it to front of list.}
  517.             RecordToFront(rec)
  518.           else
  519.             begin
  520.             AddNewRecord(hd,cyl,sec);
  521.             missFlag := TRUE;
  522.             end;
  523.           Inc(sec);
  524.           if sec > secsPerTrack.value then
  525.             begin
  526.             sec := 1;
  527.             Inc(hd);
  528.             if hd > numHeads.value then
  529.               begin
  530.               hd := 1;
  531.               Inc(cyl)
  532.               end
  533.             end
  534.           end;
  535.         {Access disk if any sector in a group missed, or on write}
  536.         if missFlag or (requestType = 3) then
  537.           SimulateReadWrite(cylNum,headNum,secNum,sectors);
  538.         end
  539.       until not s.GetNextTransaction(t);
  540.   time := elapsedTime;
  541.   end;  {WriteThroughCache.Estimate}
  542.  
  543. { Here are the objects that are used to represent the cache strategies.
  544.   They contain graphic and textual information that tells them how to
  545.   display their own results when "asked." }
  546.  
  547. const
  548.   none : CacheStrategy =
  549.     (x : 3; y : 3;
  550.      color : WHITE;
  551.      dirty : TRUE;
  552.      title : 'Estimated time without cache = ';
  553.      size : 15;
  554.      maxWidth : MAXBARWIDTH;
  555.      time : 0.0);
  556.  
  557.   writeThrough : WriteThroughCache =
  558.     (x : 3; y : 8;
  559.      color : WHITE;
  560.      dirty : TRUE;
  561.      title : 'Estimated time with simple write-through cache = ';
  562.      size : 15;
  563.      maxWidth : MAXBARWIDTH;
  564.      time : 0.0);
  565.  
  566. var
  567.   strategy : CacheStrategyPtr; { This pointer is used to scan the list of
  568.                                  caching strategies and use each in turn. }
  569.  
  570. { Display, help, and exit routines follow }
  571.  
  572. procedure Display(calc : Boolean);
  573.   { Display the bounding box, bar graphs, etc. }
  574.   begin {Display}
  575.   {Prepare the screen}
  576.  
  577.   ClrScr;
  578.   SetCursorMode(CURSOROFF);
  579.   LowVideo;
  580.   bigBox.color := TextAttr;
  581.   bigBox.Draw;
  582.  
  583.   GoToXY(20,12);
  584.   HighVideo;
  585.   Write('-- Disk Drive and Cache Parameters --');
  586.  
  587.   DrawChain(@adjSeek);
  588.  
  589.   if calc then
  590.     begin
  591.     HighVideo;
  592.     Inc(TextAttr,BLINK);
  593.     GoToXY(35,8);
  594.     Write('Working....');
  595.     Dec(TextAttr,BLINK);
  596.  
  597.     {Do the calculations}
  598.  
  599.     strategy := @none;
  600.     maxTime := 0;
  601.     repeat
  602.       with strategy^ do
  603.         begin
  604.         Estimate(snooper);
  605.         if time > maxTime then
  606.           maxTime := time
  607.         end;
  608.       strategy := strategy^.next
  609.       until strategy = NIL;
  610.  
  611.     FillBox(35,8,11,1,' '); {Clear away text}
  612.     end;
  613.  
  614.   {Scale and show the bar graphs}
  615.  
  616.   strategy := @none;
  617.   repeat
  618.     with strategy^ do
  619.       begin
  620.       PrepareBarDisplay;
  621.       Draw
  622.       end;
  623.     strategy := strategy^.next
  624.     until strategy = NIL;
  625.   end;  {Display}
  626.  
  627. {$F+}
  628. procedure Helper;
  629.   { Display the help file upon request }
  630.   var
  631.     f : Text;
  632.     s : String;
  633.   begin {Helper}
  634.   {$I-}
  635.   LowVideo;
  636.   ClrScr;
  637.   Assign(f,'EVAL.HLP');
  638.   Reset(f);
  639.   if IOResult <> 0 then
  640.     Writeln('Help file EVAL.HLP not found.')
  641.   else
  642.     while (not Eof(f)) and (IOResult = 0) do
  643.       begin
  644.       Readln(f,s);
  645.       Writeln(s)
  646.       end;
  647.   Close(f);
  648.   if IOResult <> 0 then; {Ignore errors}
  649.   Writeln;
  650.   HighVideo;
  651.   Writeln('Press a key to return to the main program.');
  652.   if ReadKey = #0 then {Wait for a key. Absorb second code if special.}
  653.     if ReadKey = #0 then;
  654.   {$I+}
  655.   Display(FALSE)
  656.   end;  {Helper}
  657.  
  658. procedure Recalc;
  659.   { Recalculate estimates and display them. Triggered by G)o command. }
  660.   begin {Recalc}
  661.   Display(TRUE)
  662.   end;  {Recalc}
  663.  
  664. procedure Finis;
  665.   { End the program and leave the screen in a known state. }
  666.   begin {Finis}
  667.   ClrScr;
  668.   SetCursorMode(cursorOn);
  669.   Halt;
  670.   end;  {Finis}
  671.  
  672. {$F-}
  673.  
  674. begin
  675.  
  676. {Link together the text controls}
  677.  
  678. adjSeek.next := @avgSeek;
  679. avgSeek.next := @numCyl;
  680. numCyl.next := @rotSpeed;
  681. rotSpeed.next := @secsPerTrack;
  682. secsPerTrack.next := @numHeads;
  683. numHeads.next := @interleave;
  684. interleave.next := @cacheSize;
  685. cacheSize.next := @help;
  686. help.next := @go;
  687. go.next := @quit;
  688.  
  689. {Bind procedures to the button controls}
  690.  
  691. help.proc := Helper;
  692. go.proc := Recalc;
  693. quit.proc := Finis;
  694.  
  695. {Set up help lines to appear on lines 23, 24}
  696.  
  697. firstHelpLine := 22;
  698.  
  699. {Link the strategies we'll try into a list}
  700.  
  701. none.next := @writeThrough;
  702. writeThrough.next := NIL;
  703.  
  704. {Do the initial display}
  705.  
  706. Display(TRUE);
  707.  
  708. {Check each character as it comes in to see if it's a command.
  709.  Each control object is responsible for recognizing the keys
  710.  it responds to, and forwarding the ones it doesn't use on to
  711.  the remaining ones.}
  712.  
  713.  
  714. repeat
  715.   adjSeek.CheckChar(UpCase(readKey))
  716.   until FALSE;
  717.  
  718. end.
  719.