home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / btree / tree / page.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-07-13  |  41.2 KB  |  1,071 lines

  1. (* TBTree15             Copyright (c)  1988,1989       Dean H. Farwell II    *)
  2.  
  3. unit Page;
  4.  
  5. {$I-}                                         (* turn off I/O error checking *)
  6.  
  7. (*****************************************************************************)
  8. (*                                                                           *)
  9. (*          P A G E  B U F F E R  H A N D L I N G  R O U T I N E S           *)
  10. (*                                                                           *)
  11. (*****************************************************************************)
  12.  
  13. (*  This unit handles the page buffer.  This buffer is used for keeping
  14.     disk pages in memory.  The pages can be for data files or index files.
  15.     The buffer uses a demand paging scheme in which the least recently used
  16.     page is swapped out when a page is needed and the buffer is full.        *)
  17.  
  18. (* Version Information
  19.  
  20.    Version 1.1 - No Changes
  21.  
  22.    Version 1.2 - No Changes
  23.  
  24.    Version 1.3 - No Changes
  25.  
  26.    Version 1.4 - Performed some internal optimizations
  27.                  Specifically, I am passing more variables as VAR parameters
  28.                  for those routines which are internal to the unit.
  29.  
  30.                - Made internal modifications necessary to use the redesigned
  31.                  TIME unit.
  32.  
  33.                - Fixed error in SetMaxBufferPages routine.  Routine did not
  34.                  previously work correctly
  35.  
  36.    Version 1.5 - Error handling added.  Unit now uses the ERROR unit to handle
  37.                  I/O errors which occur in the internal ReadFromDisk and
  38.                  WriteToDisk routines.
  39.  
  40.                - In previous versions, there was a danger of running out of
  41.                  heap space and being unable to allocate enough space on the
  42.                  heap to allocate room for at least one page.  This is now
  43.                  handled properly by initially reserving enough space on the
  44.                  heap for one entry. In this way, you will always be able to
  45.                  have at least one page in the buffer.  It reserves the space
  46.                  as part of the initialization sequence when the code in the
  47.                  initialization section is called.  If there is not enough
  48.                  heap space available, a runtime error occurs.  If an error
  49.                  does not occur during the initialization, a problem will
  50.                  never occur later.  However, if there is a very limited
  51.                  amount of heap space available, the unit will not allow very
  52.                  many pages reside in the buffer at one time.  This will be
  53.                  transparent to you except that performance will suffer
  54.                  somewhat.
  55.  
  56.                - Changed code internally to use Inc and Dec where practical
  57.  
  58.    Version 1.6 - Fixed error which caused a catestrophic error when the heap
  59.                  was nearly full and a page was allocated.                   *)
  60.  
  61.  
  62. (*\*)
  63. (*////////////////////////// I N T E R F A C E //////////////////////////////*)
  64.  
  65. interface
  66.  
  67. uses
  68.     Compare,
  69.     Error,
  70.     FastMove,
  71.     FileBuff,
  72.     FileDecs,
  73.     Hex,
  74.     MyPrint,
  75.     Numbers,
  76.     Printer,
  77.     Strings,
  78.     Time;
  79.  
  80. const
  81.     PAGESIZE = 512;                  (* Number of bytes in a Physical Record *)
  82.  
  83. type
  84.     BufferSizeType = 0 .. MAXWORD; (* used for number of pages in the buffer *)
  85.  
  86.     PageRange  = 1 .. PAGESIZE;    (* type used primarily for indexing a page
  87.                                       byte by byte.                          *)
  88.  
  89.     SinglePage = Array [PageRange] of Byte;    (* type used to hold one page *)
  90.  
  91. (*\*)
  92. (* This routine will check to see if a given physical record for a given file
  93.    actually exists either on disk or in the buffer.  It first checks the
  94.    buffer.  If its not in the buffer, it checks to see if it is past the
  95.    end of the file.  It essentially replaces EOF.  EOF will not work properly
  96.    if the pages reside in the buffer but have not been written to disk yet.
  97.  
  98.    Note - This routine is quite different than routines found in the LOGICAL
  99.    unit and the BTREE unit.  Those units use bitmaps to to see if a record is
  100.    actively being used as opposed to existing and containing garbage.
  101.    PageExists only checks the physical existence of a physical record.  It
  102.    does not check bitmaps like the others do.  It first checks the page buffer
  103.    to see if the page exists there.  If it is not found there, then the file
  104.    itself is checked.                                                        *)
  105.  
  106. function PageExists(fName : FnString;
  107.                     prNum : PrNumber) : Boolean;
  108.  
  109.  
  110. (* This function will fetch a page and return a copy of the page to the caller.
  111.    It accomplishes this by first looking in the buffer itself.  If it can't
  112.    locate it in the buffer, it checks to see if there is room in the buffer.
  113.    If there is no available room, the least recently used page is written to
  114.    disk.  That frees up that page for use.                                   *)
  115.  
  116. procedure FetchPage(fName : FnString;
  117.                     prNum : PrNumber;
  118.                     var pg : SinglePage);
  119.  
  120.  
  121. (* This routine will store a page in the buffer.  It accomplishes this by
  122.    seeing if an old version is in the buffer.  If it is not it creates a new
  123.    page.  The page is stored, the dirty flag is set, and the timeUsed is
  124.    set.
  125.  
  126.    This can be used to store a page even if the corresponding page does not
  127.    yet exist.  It this case, the record will be created and stored in the
  128.    buffer. It will be physically created in the file when the page is written
  129.    to disk.
  130.  
  131.    note - This routine will immediately write this page to disk if the user
  132.    has called SetImmediateDiskWrite with a value of TRUE.  Using this feature
  133.    will ensure that current info is always on the disk but will greatly reduce
  134.    efficiency.                                                               *)
  135.  
  136. procedure StorePage(fName : FnString;
  137.                     prNum : PrNumber;
  138.                     pg : SinglePage);
  139.  
  140. (*\*)
  141. (* Routine to write all dirty pages in buffer to disk.                       *)
  142.  
  143. procedure WriteEntireBufferToDisk;
  144.  
  145.  
  146. (* This routine will all pages corresponding to the given file out to disk.
  147.    The routine does not release the pages from the buffer.                   *)
  148.  
  149. procedure WriteBufferToDisk(fName : FnString);
  150.  
  151.  
  152. (* This routine will release the page in the buffer for a given physical
  153.    record in a given file.  Of course, the routine first checks to see
  154.    if the record is in fact in the buffer.                                   *)
  155.  
  156.  
  157. procedure ReleasePage(fName : FnString;
  158.                       prNum : PrNumber);
  159.  
  160.  
  161. (* This routine will release all pages in the buffer for the given file (fName)
  162.    It is extremely important to realize that this DOES NOT write the buffer
  163.    pages to disk prior to releasing them.  This must be done explicitly or they
  164.    will be lost.  This routine is handy for deleting a file to ensure that no
  165.    pages are left roaming around in the buffer.                              *)
  166.  
  167. procedure ReleaseAllPages(fName : FnString);
  168.  
  169.  
  170. (* This routine will return the number of buffer pages currently in use.     *)
  171.  
  172. function CheckPagesInUse : BufferSizeType;
  173.  
  174.  
  175. (* This routine will return the number of maximum buffer pages allowed       *)
  176.  
  177. function CheckMaxBufferPages : BufferSizeType;
  178.  
  179. (*\*)
  180. (* This routine will allow the user to set the maximum number of buffer pages
  181.    to be in use at one time.  This routine allows the user to change this
  182.    at ANY time while the program is running.  The program will check to
  183.    ensure that the user is not setting the maximum number of pages in use
  184.    to an illegal value.  An illegal value is zero or less.  The buffer must
  185.    contain at least one page to function properly.  If the caller has
  186.    specified a new setting which is below the number of pages in use, the
  187.    routine will release pages randomly until the count of pages in use is
  188.    reduced to n.  There is nothing fancy about the algorithm to chose pages
  189.    to release.  The user can alleviate having the wrong pages swapped out
  190.    by specifying certain pages to be swapped out prior to calling this.
  191.    For example, the user could save and release all pages for a file which
  192.    won't be used for awhile.  Remember, swapping out the wrong pages will
  193.    not cause errors, but it may temporarily affect performance as the pages
  194.    will have to be read back in upon their next use.  As an aside, I did
  195.    not swap out least recently used pages since a large number might be
  196.    swapped out.  Each swap would entail going through the entire buffer to
  197.    find the least recently used page.  This would cause too much overhead.
  198.  
  199.    note - notice use of Exit for exiting the routine.  The routine will not
  200.    normally fall out the bottom.                                             *)
  201.  
  202. procedure SetMaxBufferPages(n : BufferSizeType);
  203.  
  204.  
  205. (* This routine will allow the user to set whether stored pages will be
  206.    immediately  written to disk during StorePage operations.  It will
  207.    set an internal variable which keeps track of whether pages should
  208.    be immediately written to disk or only stored in the buffer.  The
  209.    variable cannot be accessed by the user.  The only way is to set
  210.    it is to call this routine.  The routine is called at initialization
  211.    time with x = FALSE which means that pages will not be written immediately
  212.    to disk.  The user can change this at any time.  Once this is set to
  213.    TRUE all dirty pages in the buffer (ones that have not yet been written
  214.    to disk) will still be dirty (will not be automatically written).  All
  215.    newly stored pages will be immediately written.  To ensure that all
  216.    pages have been written to disk, use WriteEntireBufferToDisk.  Regardless
  217.    of whether this is set to TRUE or FALSE, the page will still be in the
  218.    buffer and will be available for FetchPage operations without going to
  219.    disk.
  220.  
  221.    note - As stated above, this can be called anytime.  However, if this is
  222.    set to TRUE after pages have been written to the buffer, make sure you
  223.    still call WriteEntireBufferToDisk.  Calling this routine will not cause
  224.    pages which are already dirty to be immediately written to disk           *)
  225.  
  226. procedure SetImmediateDiskWrite(x : Boolean);
  227.  
  228. (*\*)
  229. (* This routine will return TRUE if the parameter has been set forcing pages
  230.    to be written to disk immediately on calling StorePage.  This is the only
  231.    to check the variable since it is hidden in the implementation.           *)
  232.  
  233. function CheckImmediateDiskWrite : Boolean;
  234.  
  235. (* This routine will print the entire page buffer to the printer *)
  236.  
  237. procedure PrintPageBuffer;
  238.  
  239.  
  240. (* This routine will print the buffer statistics                             *)
  241.  
  242. procedure PrintBufferStats;
  243.  
  244. (*!*)
  245. (*\*)
  246. (*///////////////////// I M P L E M E N T A T I O N /////////////////////////*)
  247.  
  248. (* the following declarations are for defining and storing the buffer *)
  249.  
  250. implementation
  251.  
  252. const
  253.     POINTERARRAYSIZE = 199;          (* used to set up array of linked lists
  254.                                          this number needs to be prime       *)
  255.  
  256. type
  257.     PagePtr = ^PageEntry;
  258.  
  259.     PageEntry  = record
  260.                  fName     : FnString;
  261.                  prNum     : PrNumber;
  262.                  dirty     : boolean;
  263.                  timeUsed  : TimeArr;
  264.                  page      : SinglePage;
  265.                  nextPage  : PagePtr;
  266.                  end;
  267.  
  268.     PointerArrayRange = 0 .. POINTERARRAYSIZE;
  269.  
  270.  
  271. var
  272.     pagesInUse : BufferSizeType;     (* value should never exceed the current
  273.                                                      value of maxBufferPages *)
  274.  
  275.     pointerArray : Array [PointerArrayRange] of PagePtr;  (* Type of Array
  276.                                                          holding the pointers
  277.                                                          to the linked list of
  278.                                                          pages in the
  279.                                                          page buffer *)
  280.  
  281.     immediateDiskWrite : Boolean;         (* used to keep track of whether to
  282.                                              store pages immediately to disk
  283.                                              on all StorePage calls          *)
  284.  
  285.     reservedPgPtr : PagePtr;              (* used to reserve enough room on
  286.                                              the heap for at least one page  *)
  287.  
  288. (*\*)
  289. (* the following declarations are for keeping and printing statistics on
  290.    buffer usage                                                              *)
  291.  
  292. type
  293.     StatsRange = 0 .. MAXLONGINT;  (* used as type for many buffer stat vars *)
  294.  
  295.     BufferStats = record                   (* used to hold buffer statistics *)
  296.                   pagesInUse : StatsRange;
  297.                   maxPages : StatsRange;
  298.                   attempts : StatsRange;
  299.                   hits : StatsRange;
  300.                   end;
  301.  
  302.  
  303. var
  304.     maxBufferPages : BufferSizeType;  (* Number of buffer pages in buffer.
  305.                                          This can be set by the user to
  306.                                          allow a flexible buffer size        *)
  307.  
  308.  
  309.     bufferAttempts: StatsRange;     (* total attempts to fetch a page from the
  310.                                        buffer                                *)
  311.  
  312.     bufferHits : StatsRange;        (* used for to keep track of attempts to
  313.                                        fetch a physical record from the buffer
  314.                                        in which the record was there         *)
  315.  
  316.  
  317. (*\*)
  318. (* This routine will initialize the pointer array to all NILS and will set
  319.    the pages in the pagesInUse counter to zero.  This last item will reflect
  320.    the fact that there are no pages active in the buffer.                    *)
  321.  
  322. procedure InitializePointerArray;
  323.  
  324. var
  325.     cnt : PointerArrayRange;
  326.  
  327.     begin
  328.     for cnt := 0 to POINTERARRAYSIZE do
  329.         begin
  330.         pointerArray[cnt] := NIL;
  331.         end;
  332.     pagesInUse := 0;
  333.     end;                            (* end of InitializePointerArray routine *)
  334.  
  335.  
  336. (* This routine will write a specified page to disk.  It will also change the
  337.    Dirty flag to FALSE showing that the page is not dirty.                   *)
  338.  
  339. procedure WriteToDisk(pgPtr : PagePtr);
  340.  
  341. var
  342.     tempFile : File;
  343.     ioRes : Word;
  344.     ioErrRec : IOErrorRec;
  345.  
  346.     begin
  347.     OpenUntypedFile(pgPtr^.fName,tempFile,PAGESIZE);
  348.     repeat                                   (* I/O loop with error checking *)
  349.         Seek(tempFile,pgPtr^.prNum);
  350.         BlockWrite(tempFile,pgPtr^.page,1);
  351.         ioRes := IOResult;
  352.         if ioRes <> 0 then
  353.             begin
  354.             ioErrRec.routineName := 'CloseFile';
  355.             ioErrRec.tBTreeIOResult := ioRes;
  356.             UserIOError(ioErrRec);
  357.             end;
  358.     until ioRes = 0;
  359.     pgPtr^.dirty := FALSE;
  360.     end;                                     (* end of WriteToDisk procedure *)
  361.  
  362. (*\*)
  363. (* This routine will read in a specified page from disk.  It will change the
  364.    Dirty flag to false showing that the page is not dirty.  It will also
  365.    set the file name and set the physical record number.  It does not set the
  366.    the time.  This will be done by the procedure which actually decides to
  367.    fetch this record.                                                        *)
  368.  
  369. procedure ReadFromDisk(var fName : FnString;           (* var for speed only *)
  370.                        prNum : PrNumber;
  371.                        pgPtr : PagePtr);
  372.  
  373. var
  374.     tempFile : file;
  375.     ioRes : Word;
  376.     ioErrRec : IOErrorRec;
  377.  
  378.     begin
  379.     OpenUntypedFile(fName,tempFile,PAGESIZE);
  380.     repeat                                   (* I/O loop with error checking *)
  381.         ioRes := IOResult;
  382.         Seek(tempFile,prNum);
  383.         BlockRead(tempFile,pgPtr^.page,1);
  384.         if ioRes <> 0 then
  385.             begin
  386.             ioErrRec.routineName := 'CloseFile';
  387.             ioErrRec.tBTreeIOResult := ioRes;
  388.             UserIOError(ioErrRec);
  389.             end;
  390.     until ioRes = 0;
  391.     pgPtr^.fName := fName;
  392.     pgPtr^.prNum := prNum;
  393.     pgPtr^.dirty := FALSE;
  394.     end;                                    (* end of ReadFromDisk procedure *)
  395.  
  396.  
  397. (* This routine will return the index to the pointerArray corresponding to the
  398.    given file and physical record.                                           *)
  399.  
  400. function Hash(var fName : FnString;                    (* var for speed only *)
  401.               prNum : PrNumber) : PointerArrayRange;
  402.  
  403. {$V-}
  404.     begin
  405.     Hash := (prNum + TotalString(fName)) Mod POINTERARRAYSIZE;
  406.     end;                                              (* end of Hash routine *)
  407. {$V+}
  408.  
  409. (*\*)
  410. (* This routine will return a pointer pointing to the page corresponding to a
  411.    given file and physical record number.  It will return NIL if the page is
  412.    not in the buffer.                                                        *)
  413.  
  414. function GetPagePtr(var fName : FnString;              (* var for speed only *)
  415.                     prNum : PrNumber) : PagePtr;
  416.  
  417. var
  418.     tempPtr : PagePtr;
  419.     found : boolean;
  420.  
  421.     begin
  422.     tempPtr := pointerArray[Hash(fName,prNum)];
  423.     found := FALSE;
  424.     while (not found) and (tempPtr <> NIL) do
  425.         begin
  426.         if (tempPtr^.fName = fName) and (tempPtr^.prNum = prNum) then
  427.            begin
  428.            found := TRUE;
  429.            end
  430.        else
  431.            begin
  432.            tempPtr := tempPtr^.nextPage;
  433.            end;
  434.        end;
  435.    GetPagePtr := tempPtr;
  436.    end;                                           (* end of FindPage routine *)
  437.  
  438.  
  439. (* This routine will pull a page out of a page list.  It does not Dispose of
  440.    the page.  This allows the page to be immediately reused.  The calling
  441.    routine should either reuse it or Dispose it.                             *)
  442.  
  443. procedure DeletePgFromList(pgPtr : PagePtr);
  444.  
  445. var
  446.     tempPtr : PagePtr;
  447.  
  448.     begin
  449.     tempPtr := pointerArray[Hash(pgPtr^.fName,pgPtr^.prNum)];
  450.     if tempPtr = pgPtr then
  451.         begin                             (* page to delete is first in list *)
  452.         pointerArray[Hash(pgPtr^.fName,pgPtr^.prNum)] := pgPtr^.nextPage;
  453.         end
  454.     else
  455.         begin
  456.         while tempPtr^.nextPage <> pgPtr do
  457.             begin
  458.             tempPtr := tempPtr^.nextPage;
  459.             end;
  460.         tempPtr^.nextPage := pgPtr^.nextPage;
  461.         end;
  462.     end;                                  (* end of DeletePgFromList routine *)
  463.  
  464. (*\*)
  465. (* This routine will take a page and insert it into the proper place in the
  466.    buffer.                                                                   *)
  467.  
  468. procedure InsertPgInList(var fName : FnString;         (* var for speed only *)
  469.                          prNum : PrNumber;
  470.                          pgPtr : PagePtr);
  471.  
  472. var
  473.     arrayIndex : PointerArrayRange;
  474.  
  475.     begin
  476.     arrayIndex := Hash(fName,prNum);
  477.     pgPtr^.nextPage := pointerArray[arrayIndex];  (* insert page as first    *)
  478.     pointerArray[arrayIndex] := pgPtr;            (* page in page list       *)
  479.     end;                                    (* end of InsertPgInList routine *)
  480.  
  481.  
  482. (* This routine creates a new page and sets the file name and record number
  483.    fields.  It then inserts the new page in the front of the appropriate
  484.    page list.  It does not set the time nor dirty fields.  This routine does
  485.    not check to see if there is a page available.   This is the responsibility
  486.    of the caller.                                                            *)
  487.  
  488. procedure CreateNewPage(var fName : FnString;          (* var for speed only *)
  489.                         prNum : PrNumber;
  490.                         var pgPtr : PagePtr);
  491.  
  492.     begin
  493.     New(pgPtr);
  494.     Inc(pagesInUse);                                (* one more page used up *)
  495.     InsertPgInList(fName,prNum,pgPtr);               (* put page into proper
  496.                                                              place in buffer *)
  497.     end;                                     (* end of CreateNewPage routine *)
  498.  
  499. (*\*)
  500. (* This routine will find the least recently used page, delete it from the
  501.    page list and write it to disk.  The pointer to the page is then returned *)
  502.  
  503. function LRUPage : PagePtr;
  504.  
  505. var
  506.     cnt : PointerArrayRange;
  507.     tempPgPtr,
  508.     leastPgPtr : PagePtr;
  509.     minTime : TimeArr;
  510.  
  511.     begin
  512.     SetMaxTime(minTime);
  513.     for cnt := 0 to POINTERARRAYSIZE do
  514.         begin
  515.         tempPgPtr := pointerArray[cnt];
  516.         while tempPgPtr <> NIL do
  517.             begin
  518.             if CompareTime(tempPgPtr^.timeUsed,mintime) = LESSTHAN then
  519.                 begin
  520.                 minTime := tempPgPtr^.timeUsed;
  521.                 leastPgPtr := tempPgPtr;
  522.                 end;
  523.             tempPgPtr := tempPgPtr^.nextPage;
  524.             end;
  525.         end;
  526.     WriteToDisk(leastPgPtr);                       (* write page out to disk *)
  527.     DeletePgFromList(leastPgPtr);              (* pull page out of page list *)
  528.     LRUPage := leastPgPtr;               (* return pointer to page to caller *)
  529.     end;                                           (* end of LRUPage routine *)
  530.  
  531. (*\*)
  532. (* This routine will check to see if a given physical record for a given file
  533.    actually exists either on disk or in the buffer.  It first checks the
  534.    buffer.  If its not in the buffer, it checks to see if it is past the
  535.    end of the file.  It essentially replaces EOF.  EOF will not work properly
  536.    if the pages reside in the buffer but have not been written to disk yet.
  537.  
  538.    Note - This routine is quite different than routines found in the LOGICAL
  539.    unit and the BTREE unit.  Those units use bitmaps to to see if a record is
  540.    actively being used as opposed to existing and containing garbage.
  541.    PageExists only checks the physical existence of a physical record.  It
  542.    does not check bitmaps like the others do.  It first checks the page buffer
  543.    to see if the page exists there.  If it is not found there, then the file
  544.    itself is checked.                                                        *)
  545.  
  546. function PageExists(fName : FnString;
  547.                     prNum : PrNumber) : Boolean;
  548.  
  549. var
  550.     tempFile : File;
  551.  
  552.     begin
  553.     if GetPagePtr(fName,prNum) = NIL then            (* check to see if record
  554.                                                                 is in buffer *)
  555.         begin
  556.         OpenUntypedFile(fName,tempFile,PAGESIZE);
  557.         if prNum <= FileSize(tempFile) - 1 then
  558.             begin                             (* record not past end of file *)
  559.             PageExists := TRUE;
  560.             end
  561.         else
  562.             begin               (* record not in buffer and past end of file *)
  563.             PageExists := FALSE;
  564.             end;
  565.         end
  566.     else
  567.         begin                    (* page is in buffer .. therefore it exists *)
  568.         PageExists := TRUE;
  569.         end;
  570.     end;                                        (* end of PageExists routine *)
  571.  
  572. (*\*)
  573. (* This function will fetch a page and return a copy of the page to the caller.
  574.    It accomplishes this by first looking in the buffer itself.  If it can't
  575.    locate it in the buffer, it checks to see if there is room in the buffer.
  576.    If there is no available room, the least recently used page is written to
  577.    disk.  That frees up that page for use.  If there are currently no pages in
  578.    the heap and there is no heap space to create the first page, the ERROR
  579.    unit comes into play.  UserHeapError is called.  You                                    *)
  580.  
  581. procedure FetchPage(fName : FnString;
  582.                     prNum : PrNumber;
  583.                     var pg : SinglePage);
  584.  
  585. var
  586.     pgPtr : PagePtr;
  587.  
  588.     begin
  589.     pgPtr := GetPagePtr(fName,prNum);          (* try to find page in buffer *)
  590.     if pgPtr = NIL then                    (* check to see if page was found *)
  591.         begin                                    (* page not found in buffer *)
  592.         if (pagesInUse <> maxBufferPages) and      (* check for unused pages *)
  593.            (MaxAvail >= SizeOf(PageEntry)) then      (* check for heap space *)
  594.             begin                             (* there is room in the buffer *)
  595.             CreateNewPage(fName,prNum,pgPtr);    (* make new page and use it *)
  596.             end
  597.         else
  598.             begin                                         (* no unused pages *)
  599.             if pagesInUse = 0 then
  600.                 begin
  601.                 pgPtr := reservedPgPtr;          (* used reserved heap space *)
  602.                 InsertPgInList(fName,prNum,pgPtr);   (* put page into proper
  603.                                                              place in buffer *)
  604.                 end
  605.             else
  606.                 begin
  607.                 pgPtr := LRUPage;            (* get least recently used page *)
  608.                                                      (* and write it to disk *)
  609.                 InsertPgInList(fName,prNum,pgPtr);  (* reuse page and put in
  610.                                                       proper place in buffer *)
  611.                 end;
  612.             end;
  613.         ReadFromDisk(fName,prNum,pgPtr);             (* read in desired page *)
  614.         end
  615.     else
  616.         begin                                           (* page is in buffer *)
  617.         Inc(bufferHits);                              (* update hits counter *)
  618.         end;
  619.     GetTime(pgPtr^.timeUsed);                 (* set time page was requested *)
  620.     FastMover(pgPtr^.page,pg,SizeOf(pg));       (* return copy of the actual
  621.                                                           page to the caller *)
  622.     Inc(bufferAttempts);
  623.     end;                                         (* end of FetchPage routine *)
  624.  
  625. (*\*)
  626. (* This routine will store a page in the buffer.  It accomplishes this by
  627.    seeing if an old version is in the buffer.  If it is not it creates a new
  628.    page.  The page is stored, the dirty flag is set, and the timeUsed is
  629.    set.
  630.  
  631.    This can be used to store a page even if the corresponding page does not yet
  632.    exist.  It this case, the record will be created and stored in the buffer.
  633.    It will be physically created in the file when the page is written to
  634.    disk.
  635.  
  636.    note - This routine will immediately write this page to disk if the user
  637.    has called SetImmediateDiskWrite with a value of true.  Using this feature
  638.    ensure that current info is always on the disk but will greatly reduce
  639.    efficiency                                                                *)
  640.  
  641. procedure StorePage(fName : FnString;
  642.                     prNum : PrNumber;
  643.                     pg : SinglePage);
  644.  
  645. var
  646.     pgPtr : PagePtr;
  647.  
  648.     begin
  649.     pgPtr := GetPagePtr(fName,prNum);
  650.     if pgPtr = NIL then
  651.         begin
  652.         if (pagesInUse <> maxBufferPages) and      (* check for unused pages *)
  653.            (MaxAvail >= SizeOf(PageEntry)) then      (* check for heap space *)
  654.             begin
  655.             CreateNewPage(fName,prNum,pgPtr);
  656.             end
  657.         else
  658.             begin
  659.             pgPtr := LRUPage;
  660.             InsertPgInList(fName,prNum,pgPtr);
  661.             end;
  662.         pgPtr^.fName := fName;
  663.         pgPtr^.prNum := prNum;
  664.         end;
  665.     FastMover(pg,pgPtr^.page,SizeOf(pg));  (* move page to store into buffer *)
  666.     pgPtr^.dirty := TRUE;
  667.     GetTime(pgPtr^.timeUsed);
  668.     if immediateDiskWrite then
  669.         begin
  670.         WriteToDisk(pgPtr);
  671.         end;
  672.     end;                                         (* end of StorePage routine *)
  673.  
  674. (*\*)
  675. (* Routine to write all dirty pages in buffer to disk.                       *)
  676.  
  677. procedure WriteEntireBufferToDisk;
  678.  
  679. var
  680.     pgPtr : PagePtr;
  681.     cnt : PointerArrayRange;
  682.  
  683.     begin
  684.     for cnt:= 0 to POINTERARRAYSIZE do
  685.         begin
  686.         pgPtr := PointerArray[cnt];
  687.         while pgPtr <> NIL do
  688.             begin
  689.             if pgPtr^.dirty then
  690.                 begin
  691.                 WriteBufferToDisk(pgPtr^.fName);
  692.                 end;
  693.             pgPtr := pgPtr^.nextPage;
  694.             end;
  695.         end;
  696.     end;                           (* end of WriteEntireBufferToDisk routine *)
  697.  
  698.  
  699. (* This routine will all pages corresponding to the given file out to disk.
  700.    The routine does not release the pages from the buffer.                   *)
  701.  
  702. procedure WriteBufferToDisk(fName : FnString);
  703.  
  704. var
  705.     pgPtr : PagePtr;
  706.     cnt : PointerArrayRange;
  707.  
  708.     begin
  709.     for cnt := 0 to POINTERARRAYSIZE do
  710.         begin
  711.         pgPtr := PointerArray[cnt];
  712.         while pgPtr <> NIL do
  713.             begin
  714.             if pgPtr^.fName = fName then
  715.                 begin
  716.                 WriteTodisk(pgPtr);
  717.                 end;
  718.             pgPtr := pgPtr^.nextPage;
  719.             end;
  720.         end;;
  721.     end;                                 (* end of WriteBufferToDisk routine *)
  722.  
  723. (*\*)
  724. (* This routine will release the page in the buffer for a given physical
  725.    record in a given file.  Of course, the routine first checks to see
  726.    if the record is in fact in the buffer.  It is important to realize that
  727.    this page will not be written to disk, but will be lost.                  *)
  728.  
  729. procedure ReleasePage(fName : FnString;
  730.                       prNum : PrNumber);
  731.  
  732. var
  733.     pgPtr : PagePtr;
  734.  
  735.     begin
  736.     pgPtr := GetPagePtr(fName,prNum);
  737.     if pgPtr <> NIL then
  738.         begin
  739.         DeletePgFromList(pgPtr);
  740.         if pgPtr <> reservedPgPtr then
  741.             begin                (* dispose of the heap space unless it is
  742.                                     the reserved space                       *)
  743.             Dispose(pgPtr);
  744.             end;
  745.         Dec(pagesInUse);
  746.         end;
  747.     end;                                       (* end of ReleasePage routine *)
  748.  
  749. (*\*)
  750. (* This routine will release all pages in the buffer for the given file (fName)
  751.    It is extremely important to realize that this DOES NOT write the buffer
  752.    pages to disk prior to releasing them.  This must be done explicitly or they
  753.    will be lost.  This routine is handy for deleting a file to ensure that no
  754.    pages are left roaming around in the buffer.                              *)
  755.  
  756. procedure ReleaseAllPages(fName : FnString);
  757.  
  758. var
  759.     pgPtr : PagePtr;
  760.     cnt : PointerArrayRange;
  761.  
  762.     begin
  763.     for cnt := 0 to POINTERARRAYSIZE do
  764.         begin
  765.         pgPtr := pointerArray[cnt];
  766.         while pgPtr <> NIL do
  767.             begin
  768.             if pgPtr^.fName = fName then
  769.                 begin
  770.                 ReleasePage(fName,pgPtr^.prNum);
  771.                 pgPtr := PointerArray[cnt];     (* reset to a valid location *)
  772.                 end
  773.             else
  774.                 begin
  775.                 pgPtr := pgPtr^.nextPage;
  776.                 end;
  777.             end;
  778.         end;
  779.     end;                                   (* end of ReleaseAllPages routine *)
  780.  
  781.  
  782. (* This routine will return the number of buffer pages currently in use.     *)
  783.  
  784. function CheckPagesInUse : BufferSizeType;
  785.  
  786.     begin
  787.     CheckPagesInUse := pagesInUse;
  788.     end;                                   (* end of CheckPagesInUse routine *)
  789.  
  790. (* This routine will return the number of maximum buffer pages allowed       *)
  791.  
  792. function CheckMaxBufferPages : BufferSizeType;
  793.  
  794.     begin
  795.     CheckMaxBufferPages := maxBufferPages;
  796.     end;                               (* end of CheckMaxBufferPages routine *)
  797.  
  798. (*\*)
  799. (* This routine will allow the user to set the maximum number of buffer pages
  800.    to be in use at one time.  This routine allows the user to change this
  801.    at ANY time while the program is running.  The program will check to
  802.    ensure that the user is not setting the maximum number of pages in use
  803.    to an illegal value.  An illegal value is zero or less.  The buffer must
  804.    contain at least one page to function properly.  If the caller has
  805.    specified a new setting which is below the number of pages in use, the
  806.    routine will release pages randomly until the count of pages in use is
  807.    reduced to n.  There is nothing fancy about the algorithm to chose pages
  808.    to release.  The user can alleviate having the wrong pages swapped out
  809.    by specifying certain pages to be swapped out prior to calling this.
  810.    For example, the user could save and release all pages for a file which
  811.    won't be used for awhile.  Remember, swapping out the wrong pages will
  812.    not cause errors, but it may temporarily affect performance as the pages
  813.    will have to be read back in upon their next use.  As an aside, I did
  814.    not swap out least recently used pages since a large number might be
  815.    swapped out.  Each swap would entail going through the entire buffer to
  816.    find the least recently used page.  This would cause too much overhead.
  817.  
  818.    note - notice use of Exit for exiting the routine.  The routine will not
  819.    normally fall out the bottom.                                             *)
  820.  
  821. procedure SetMaxBufferPages(n : BufferSizeType);
  822.  
  823. var
  824.     pgPtr : PagePtr;
  825.     cnt : PointerArrayRange;
  826.  
  827.     begin
  828.     if n > 0 then      (* make sure that value is not 0! if it is do nothing *)
  829.         begin
  830.         if pagesInUse <= n then
  831.             begin
  832.             maxBufferPages := n;
  833.             end
  834.         else
  835.             begin
  836.             cnt := 0;
  837.             while pagesInUse > n do
  838.                 begin
  839.                 pgPtr := pointerArray[cnt];                (* reset pgPtr to
  840.                                                             a valid location *)
  841.                 if pgPtr <> NIL then
  842.                     begin
  843.                     if pgPtr^.dirty then
  844.                         begin
  845.                         WriteToDisk(pgPtr);
  846.                         end;
  847.                     ReleasePage(pgPtr^.fName,pgPtr^.prNum);
  848.                     end
  849.                 else
  850.                     begin
  851.                     Inc(cnt);
  852.                     end;
  853.                 end;
  854.             end;
  855.         end;
  856.     end;                                 (* end of SetMaxBufferPages routine *)
  857.  
  858. (*\*)
  859. (* This routine will allow the user to set whether stored pages will be
  860.    immediately  written to disk during StorePage operations.  It will
  861.    set an internal variable which keeps track of whether pages should
  862.    be immediately written to disk or only stored in the buffer.  The
  863.    variable cannot be accessed by the user.  The only way is to set
  864.    it is to call this routine.  The routine is called at initialization
  865.    time with x = FALSE which means that pages will not be written immediately
  866.    to disk.  The user can change this at any time.  Once this is set to
  867.    TRUE all dirty pages in the buffer (ones that have not yet been written
  868.    to disk) will still be dirty (will not be automatically written).  All
  869.    newly stored pages will be immediately written.  To ensure that all
  870.    pages have been written to disk, use WriteEntireBufferToDisk.  Regardless
  871.    of whether this is set to TRUE or FALSE, the page will still be in the
  872.    buffer and will be available for FetchPage operations without going to
  873.    disk.
  874.  
  875.    note - As stated above, this can be called anytime.  However, if this is
  876.    set to TRUE after pages have been written to the buffer, make sure you
  877.    still call WriteEntireBufferToDisk.  Calling this routine will not cause
  878.    pages which are already dirty to be immediately written to disk           *)
  879.  
  880. procedure SetImmediateDiskWrite(x : Boolean);
  881.  
  882.     begin
  883.     ImmediateDiskWrite := x;
  884.     end;                             (* end of SetImmediateDiskWrite routine *)
  885.  
  886.  
  887. (* This routine will return TRUE if the parameter has been set forcing pages
  888.    to be written to disk immediately on calling StorePage.  This is the only
  889.    to check the variable since it is hidden in the implementation.           *)
  890.  
  891. function CheckImmediateDiskWrite : Boolean;
  892.  
  893.     begin
  894.     CheckImmediateDiskWrite := immediateDiskWrite;
  895.     end;                           (* end of CheckImmediateDiskWrite routine *)
  896.  
  897. (*\*)
  898. (* These routines support debugging of the page buffer routines              *)
  899.  
  900. procedure PrintPageInfo(pgPtr : PagePtr);
  901.  
  902.     (* Prints out string equivalent of boolean value *)
  903.     procedure PrintBoolean(x : boolean);
  904.  
  905.     begin
  906.     case x of
  907.         FALSE : Write(lst,'FALSE');
  908.         TRUE  : Write(lst,'TRUE');
  909.         end;                                        (* end of case statement *)
  910.     end;                                   (* end of PrintPageBuffer routine *)
  911.  
  912.     (* determines if x is a screen printable non control character *)
  913.     function PrintableChar(x : Char) : boolean;
  914.  
  915.     begin
  916.     PrintableChar := Integer(x) in [32 .. 127];
  917.     end;                                     (* end of PrintableChar routine *)
  918.  
  919. const
  920.     LINESIZE = 32;          (* number of bytes output on one line of printer *)
  921.  
  922. var
  923.     loopByteCnt,            (* used in inner loop to point to bytes *)
  924.     maxLoopByteCnt,         (* used in inner loop to keep from going past
  925.                                end of buffer page  *)
  926.     byteCnt : PageRange;    (* current byte in buffer page *)
  927.     done : boolean;         (* used for inner loop termination *)
  928.  
  929.     begin
  930.     Writeln(lst,'     fName = ',pgPtr^.fName);
  931.     Writeln(lst,'     prNum = ',pgPtr^.prNum);
  932.     Write(lst,'     dirty = ');
  933.     PrintBoolean(pgPtr^.dirty);
  934.     Writeln(lst);
  935.     Write(lst,'     timeUsed = ');
  936.     Write(lst,pgPtr^.timeUsed.msLongInt,'     ');
  937.     Write(lst,pgPtr^.timeUsed.lsLongInt);
  938.     Writeln(lst); Writeln(lst);
  939.     byteCnt := 1;
  940.     done := FALSE;
  941.     repeat
  942.         begin
  943.         if ((byteCnt + LINESIZE) - 1) <= PAGESIZE then
  944.             begin
  945.             maxLoopByteCnt := byteCnt + LINESIZE - 1;
  946.             end
  947.         else
  948.             begin
  949.             maxLoopByteCnt := PAGESIZE;
  950.             end;
  951.         (* print column position *)
  952.         for loopByteCnt := byteCnt to maxLoopByteCnt do
  953.             begin
  954.             Write(lst,loopByteCnt : 3,' ');
  955.             end;
  956.         Writeln(lst);
  957.         (* Print HEX value *)
  958.         for loopByteCnt := byteCnt to maxLoopByteCnt do
  959.             begin
  960.             Write(lst,'$',ByteToHex(pgPtr^.page[loopByteCnt]),' ');
  961.             end;
  962.         Writeln(lst);
  963.         (* print integer equivalent *)
  964.         for loopByteCnt := byteCnt to maxLoopByteCnt do
  965.             begin
  966.             Write(lst,pgPtr^.page[loopByteCnt] :3,' ');
  967.             end;
  968.         Writeln(lst);
  969.         (* character equivalent or print '*' if char not printable *)
  970.         for loopByteCnt := byteCnt to maxLoopByteCnt do
  971.             begin
  972.             if PrintableChar(Chr(pgPtr^.page[loopByteCnt])) then
  973.                 begin
  974.                 Write(lst,' ',Chr(pgPtr^.page[loopByteCnt]),'  ');
  975.                 end
  976.             else
  977.                 begin
  978.                 Write(lst,' *  ');
  979.                 end;
  980.             end;
  981.         Writeln(lst); Writeln(lst);
  982.         if byteCnt + LINESIZE > PAGESIZE then
  983.             begin
  984.             done := TRUE;
  985.             end
  986.         else
  987.             begin
  988.             Inc(byteCnt,LINESIZE);
  989.             end;
  990.         end;
  991.     until done;
  992.     Writeln(lst); Writeln(lst);
  993.     end;                                     (* end of PrintPageInfo routine *)
  994.  
  995. (*\*)
  996. (* This routine will print the entire page buffer to the printer *)
  997.  
  998. procedure PrintPageBuffer;
  999.  
  1000. var
  1001.     pgPtr : PagePtr;
  1002.     cnt : PointerArrayRange;
  1003.  
  1004.     begin
  1005.     SetCompressedMode;                 (* sets printer to 132 character mode *)
  1006.     for cnt := 0 to POINTERARRAYSIZE do
  1007.         begin
  1008.         pgPtr := PointerArray[cnt];
  1009.         while pgPtr <> NIL do
  1010.             begin
  1011.             PrintPageInfo(pgPtr);
  1012.             pgPtr := pgPtr^.nextPage;
  1013.             end;
  1014.         end;
  1015.     CancelCompressedMode;
  1016.     end;                                   (* end of PrintPageBuffer routine *)
  1017.  
  1018.  
  1019. (* This routine will initialize the variables used to keep track of buffer
  1020.    use statistics.                                                           *)
  1021.  
  1022. procedure InitializeBufferStats;
  1023.  
  1024.     begin
  1025.     bufferAttempts := 0;
  1026.     bufferHits := 0;
  1027.     end;                             (* end of InitializeBufferStats routine *)
  1028.  
  1029.  
  1030. (* This routine will return buffer statistics.  The statistic will be returned
  1031.    in a a record of type BufferStats.                                        *)
  1032.  
  1033. procedure CreateBufferStats(var stats : BufferStats);
  1034.  
  1035.     begin
  1036.     stats.pagesInUse := pagesInUse;
  1037.     stats.maxPages := maxBufferPages;
  1038.     stats.attempts := bufferAttempts;
  1039.     stats.hits := bufferHits;
  1040.     end;                                 (* end of CreateBufferStats routine *)
  1041.  
  1042. (*\*)
  1043. (* This routine will print the buffer statistics                             *)
  1044.  
  1045. procedure PrintBufferStats;
  1046.  
  1047. var
  1048.     stats : BufferStats;
  1049.  
  1050.     begin
  1051.     CreateBufferStats(stats);
  1052.     Writeln(lst);
  1053.     Writeln(lst,'** Buffer Statistics Follow: **');
  1054.     Writeln(lst);
  1055.     Writeln(lst,'Buffer Pages In Use = ',stats.pagesInUse);
  1056.     Writeln(lst,'Maximum buffer pages available =  ',stats.maxPages);
  1057.     Writeln(lst,'Attempts to Fetch Data = ',stats.attempts);
  1058.     Writeln(lst,'Number of Hits = ',stats.hits);
  1059.     Writeln(lst,'Hit percentage = ',Trunc((stats.hits/stats.attempts)*100),'%');
  1060.     end;                                       (* end of PrintBuffer routine *)
  1061.  
  1062.  
  1063.  
  1064. begin
  1065. New(reservedPgPtr);              (* reserve space for one page in the buffer *)
  1066. InitializePointerArray;
  1067. InitializeBufferStats;
  1068. SetMaxBufferPages(256);                           (* initially a 128K buffer *)
  1069. SetImmediateDiskWrite(FALSE);
  1070. end.                                                     (* end of Page unit *)
  1071.