Saját file-kezelésünk alapjai: file megnyitása, olvasása

Előző számunkban az objektumokat építettük fel melyek megvalósítják a file-kezelést. Létrehoztuk a lemezt ill. az Image-file-t kezelő objektumokat TPhisicalDrive / TImageDrive. Ezek valósítják meg a szektorok beolvasását, és egy logikai szektorkezelési felületet nyújtanak a file-kezelő objektumnak esetünkben a TR4sFileDOS objektumnak.

Továbbá előző számunkban arról is szót ejtettünk, hogy hogyan kell Image-file-t készíteni. Így elmondtam, hogy szektorról-szektorra pontosabban track-ről – track-re (track = sáv) kell lementeni a lemez tartalmát. Most szeretnék egy profibb megoldást kínálni azok számára akik nincsenek megelégedve az image-file készítésének sebességével.

Majd ezek után az FCB-s file-kezelő utasításokat kezdjük el.
 
Hogyan gyorsítsuk az Image-file elkészítését?

Előre ki kell ábrándítanom benneteket, de csak azt a rendszert lehet gyorsítani, amin van mit gyorsítani. Így nem minden lemezen fog gyorsulást eredményezni az alábbi rutin. Lássuk az elvét, hogy hogyan működik:

Ugye egy egyszerű lemezmásoló program ugyan olyan fapados módszert használ, mint mi az előző számban. De most megmutatjuk, hogy valójában hogy is kell ezt csinálni. [csak szerényen... a szerk.]

A gyorsítás lényege, hogy ha nincs tele a lemez, akkor NEM kell mindent beolvasni.

Bevallom töredelmesen én még nem találkoztam olyan DOS-os lemezmásoló programmal ami ne olvasta volna be az egész lemezt ha nem kell. Olyat viszont már láttam, ha a lemez végén összefüggő üres hely van, akkor azt nem olvassa be. Nos hát ez egy eléggé rút megoldás annál is inkább, hogy nem feltétlenül biztos, hogy a lemez végén van az összefüggő üres hely. Pl. mi van, ha a lemez közepén van 500 KB üres hely ?

Egy olyan megvalósítást írok le mely a 3.5" 1.44 MB kapacitású floppy-lemezeknél gyorsítást eredményez. Teljes forráskódot nem közlök, csak a unit (MAKEIMG.INT) Interface részét adom oda. (Akinek nagyon szüksége van a forráskódra, és meg tud győzni, írjon: PC-XUSER@FREEMAIL.C3.HU)

Felhívnám a T. figyelmet, hogy ez egy több mint két éves unit-om, így nem foglalkoztam vele mostanában, s csak a 1 sector / cluster –re formázott kislemezeket kezeli megbízhatóan !!!

Miből áll a gyorsítás ?

Nos ezen pár sort elolvasása után a Tisztelt Olvasó csak legyint, hát ez ilyen egyszerű lenne ?

Persze sok más is áll a háttérben !

A figyelmesebbek elkezdenek gondolkozni, hogy álljunk csak meg egy szóra !

Ugyanis tudnunk kell, hogy melyik sávon van információ s melyiken nincs. Mert a gyorsaság megőrzése végett még mindig sávokat olvasunk be egyszerre. Tehát amelyik sávon legalább egy szektornyi információ van (egy szektor a 18-ból) azt ugyanúgy be kell olvasnunk akár ha tele lenne a sáv mind a 18 szektorral. Nem látom értelmét csak pl. 10 szektor beolvasását, mert akkor oda is kell a fejet a kontrollernek pozícionálni stb. s ez időt vesz el. Nem vagyok arról meggyőződve, hogy amennyi időt nyerhetnénk a pl. 8 szektor be nem olvasásával az nem menne –e el a plusz pozícionálással. (Itt persze nem csak a fejet kell mozgatni, hanem meg kell várni amíg a lemeznek azon pontja forog be a fej alá amire szükség van. Persze még vannak egyéb járulékos nem túl dokumentált dolgok, amik időt vehetnek el így szumma szummárum beolvassuk amit be kell olvasni !)

Tehát dolgunk "csekély" a FAT-ból ki kell találni mely sávokon nincs egyáltalán használt szektor – cluster, s ezen üres sávok kivételével kell minden mást beolvani.

Lássuk az elvi magvalósítás lépcsőfokait:

  1. Hogy egyáltalán legyen miből megállapítani, hogy mit kell s mit nem kell beolvasni, olvassuk be a lemezterületet a bootsector-tól egészen a datastart-ig. (Bootsector, 1st FAT, 2nd FAT, Rootentrys)
  2. Ezekre úgy is szükség lesz az image-file-hoz, így mentsük is le az image-file-ba ezt az első 33 szektort.
  3. Ezek után egy második ciklusban mely a rootentry (a főkönyvtár szektorai) utáni sávtól a maximum sávig megy történik meg a beolvasás.

  4. Ennek lépései:
  5. Mielőtt eme ciklusba belépnénk tároljuk el egy boolean tömbbe, hogy melyik cluster használt, s melyik nem. Erre azért van szükség, mert FAT12-t használ az FDD és nem akarjuk a program futása alatt a memóriában tartani az egész FAT-ot, hanem csak egy tömböt ami azt tárolja, hogy van –e az x. cluster használva, vagy sem.
  6. Nos ezek után beléphetünk második főbb ciklusunkba.
  7. Ez a ciklus mielőtt meghívná a fizikai olvasást megnézi, hogy a beolvasandó sávon lévő összes cluster üres –e. Sok aprólékos konverziós dolog után nem kell mást tennie, mint azt a boolean tömböt megvizsgálnia amit előbb feltöltöttünk a FAT-nak megfelelően. A vizsgálat tárgya, hogy a sávon található mind a 18 szektor – cluster (most egyenlőek méretüket tekintve az 1 sec / cluster miatt) üres –e. Ezt úgy készítsük el, hogy mind a 18 cluster-nek megfelelő boolean tömb megfelelő elemeit össze vagyoljuk (OR). Így ha egy igaz akkor már az egész igaz, s be kell olvasni. Ez így leírva egész egyszerű de a megvalósítása – melyet nem kívánok közölni – jelentősen rágósabb.
  8. Persze ne felejtkezzünk el arról se ha nem sikerült beolvasni előszörre egy sávot akkor azt próbáljuk meg még párszor.
  9. Ha meg a fentiek szerint kiderült, hogy nem kell beolvasni egy adott sávot, mert teljesen üres, akkor egy olyan tömböt írjunk ki az imge-file-ba mely üres, s nem tartalmaz semmit, pontosabban a DOS által szabványosnak tekinthető $F6 hexa értéket.

  10. FillChar(Buf^, SizeOf(Buf^), $F6);
  11. Ha igazán igényesek vagyunk persze nem közvetlenül az Image-file-ba dolgozunk, hanem egy cache-be. Persze 1.44 MB-os MEMÓRIA cache-t nehéz valós módban készíteni DE használjunk XMS-t ! S ha vége az egész image-file készítésnek ezután írjuk ki az XMSStream tartalmát file.
  12. Továbbá ajánlott az image-file kezelő rutinnak paraméterbe megadni olyan far rutinokat melyek pl. a 100% kiírását végzik, vagy hibalekezelést valósítanak meg.
Mindezek után lássuk az általam megvalósított rutin kezelését:

function SaveDisk(PhisicalDrive: Byte; ToFile: String; RefreshProc: PercentRefresher; ErrorProc: ErrorMessage; Use_XMS: XMSInfo): Boolean;

  1. Első paraméternek a fizikai drive-számot adjuk meg: 0 = A, 1 = B.

  2. Figyelem: még egyszer elmondom, hogy csak a 3.5” 1.44 MB és 1 sec / cluster lemezeket szereti ! Továbbá szíveskedjünk NEM winchesterre használni, mert nem egészséges !
  3. Második paraméter az image-file neve, amibe a floppy lemez image-t akarjuk menteni.
  4. Harmadik paraméter egy:

  5. PercentRefresher = procedure(Percent: Byte);
    Ilyen formán felépített rutin melyet far-nak definiálva hozunk létre, s ez a rutin fogja paraméterként megkapni az aktuális %-ot, hogy hol tart a rutin a másolásban.
  6. ErrorMessage = procedure(Error : Byte);

  7. Ez a rutin akkor hívódik meg ha hiba történt, s a hiba kódja az INT 13h-által generált hibakód.
  8. Az utolsó paraméter meg csak arra szolgál, hogy visszaadja a főprogramnak, hogy a SaveDisk() rutin használ –e XMS-t. (Nem rossz mi ? Egy kis XMS kezelés, sux.)

  9. XMSInfo = procedure(Use : Boolean);
  10. A SaveDisk() függvény visszatérési értéke egy Boolean típus mely jelzi, hogy sikerült –e az image-file-t elkészíteni.
Lássunk erre egy példaprogramot:

uses MakeIMG, Crt, DOS;

procedure WritePercent(Percent: Byte); far;
begin
  GotoXY(1, 1);
  Writeln(Percent, ' % readed');
end;

procedure Error(Err: Byte); far;
begin
  GotoXY(1, 2);
  if Err = $15 then Writeln('Drive Not Ready')
               else begin
                      Writeln('Error: ', Err);
                    end;
  WriteLn('Press any key to continue . . .');
  ReadKey;
end;

procedure IsXMS(UseXMS: Boolean); far;
begin
  GotoXY(1, 3);
  Writeln('Use XMS: ', UseXMS);
end;

BEGIN
  SaveDisk(0, ParamStr(1), WritePercent, Error, IsXMS);
  { Ez a 0 adja meg a fizikai drive-számot}
END.

Persze, ha ezt egy objektum-orientált felhasználó felületbe akarjuk beilleszteni – mint ahogy én is tettem az R4s Quick Floppy programomban – akkor sajnos át kell hágnunk az OOP elméletét, mert a Pascal nem engedi meg a far objektum metódusok használatát, s így végképp nem tudjuk őket meghivatni a SaveDisk() külső rutinunkkal.

Simán az objektumból kilógó far rutint kell deklarálni, amit az objektum egyik metódusában található SaveDisk()-nek adunk oda. Persze ezek a rutinok állítanak egy view-ot (Pl. StaticText.SetText() ami a %-ot írja ki.) ami újabb megfontolásokat igényel, hogy helyesen illesszük ezeket össze, s így újra az OOP elméletének szükséges áthágását követjük el.
 
FCB-s file-kezelés dióhéjban

Hogy miért dióhéjban ?
Mert már a MicroSoft szerint is ezer éve elavult, s józan programozók nem igen használják. Persze nem azért mondom el, mert teljesen ütődött vagyok, hanem ez az egyszerűbb kezelési mód s ezen keresztül könnyebb lesz megérteni a handle-es file-kezelést. Na és az is lehet, hogy valaki arra vetemedne, hogy spec. okok miatt ezt szeretné használni, hát tegye, egészségére. (Pedig amikor még réges-rég írtam eme saját file-kezelést, arra gondoltam az FCB-s file-kezelésnél, hogy belűl a handle-s rutinokkal lesz megoldva, de azt’ mégsem.)

Csak pár FCB-s utasítást – metódust valósítunk meg ezek:

Még rég tisztáztuk, hogy mit értünk FCB-n, azért most feleleveníteném:

FCB – File Control Block. Ezt még a DOS 1.0 használták, s a CP/M-ből vették át. Ez egy információs rekord mely a megnyitandó – megnyitott file jellemzőit hordozza.

PFCB = ^TFCB;
TFCB = record {for DOS 5.0+}
         DriveNumber : Byte;
         FileName : TChar8
         FileExt : TChar3
         CurrBlockNum : Word;
         LogicalRecSize: Word;
         FileSize : Longint;
         FileDate,
         FileTime : Word;
         SysFileEntry : Byte;
         Attribute : Byte;
         ExtraInfo : Byte;
         StartCluster : Word;
         SectorNumber : Word;
         DirEntrypSec : Byte;
         RecordInCurBlk: Byte;
         RndAccRecNum : Longint;
         {R4sFCBsPos : Byte;}
end;

Lépkedjünk végig gyorsan mezőnként az FCB rekordon:

  1. A drive, amelyiken a file megtalálható. (Ugyanis DOS 1.0-ban noha könyvtárak még nem voltak, de már több meghajtó létezett.
  2. A File-név megadása,
  3. s a file-kiterjesztésének megadása. FIGYELEM: a nem használt karaktereket #32-vel (space-szel) kell feltölteni !
  4. Az éppen aktuális blokk száma, ugyanis FCB-s file-kezelésben a file-t 128 byte-os blokkokra osztjuk. Így a legnagyobb kezelhető file kb. 8 MB. Ez persze érthető, hisz amikor a DOS 1.0-t használták akkor még 10 MB winchester-ek voltak.
  5. A logikai rekord hossza, mert blokkokban logikai rekordokkal dolgozunk. (Persze a rekord mérete lehet = a blokk méretével.)
  6. A file hossza, ezt először a dir-entry-ből veszi
  7. A file létrehozásának vagy utolsó módosításának dátuma
  8. A file létrehozásának vagy utolsó módosításának ideje
  9. Itt kezdődnek a kiterjesztett funkciók:

  10. Az elfoglalt rendszer-file-tábla bejegyzések száma
  11. A file attribútuma
  12. ExtraInfo:

  13. bit 7: Read-only attribútum a System File Table-ből (SFT)
    bit 6: Archive attribútum az SFT-ből
    bit: 5-0: A szektor számának felső 6 bit-je. Lásd: 13 !
  14. Az első cluster ahol a file kezdődik
  15. A szektor száma (az alsó 16 bit – lásd 11. Bit 5-0) ahol a file könyvtárbejegyzése található
  16. Megadja, hogy hányadik bejegyzés tartozik a file-hoz a könyvtárban, adott 13. meghatározott szektorban. (Ezzel vége is a kiterjesztett funkcióknak.)
  17. Az aktuális blokk, melyik rekordjával kívánunk műveletet végezni. 0-127-ig, hisz a blokkot kezelhetjük úgy is, hogy a rekord mérete egy byte.
  18. A véletlen hozzáférésű művelet végzéskor a rekord száma Longint-ben.
File megnyitása FCB-vel: A saját file-kezelő objektumunk, a TR4sFileDOS, FCB-s file-megnyitásának megvalósítása:

function TR4sFileDOS.OpenFileByFCB(var FCB: TFCB): Boolean;
var
  TempB : Byte;
  TempW, Sector : Word;
  Found, End_It : Boolean;
  TempSec, TempEntry: Word;
begin
  Found:=False; End_It:=False;
  for Sector:=CurDIR.Sector to BootInf.Total_Sector do
  begin
    ReadLogicalSector(Longint(Sector));
    for TempW:=0 to MaxEntryPerSec-1 do
    begin
      CopyBufToDirEntry(ASector, TempW, AEntry); {copy the TempW-th entry }
      if IsNULLEntry(AEntry) then {from ASector to AEntry var.}
      begin {exit if no more}
        End_It:=True;
        Break;
      end;
      if IsEntryASimpleFile(AEntry) and (AEntry.FileName <> '')
         and (AEntry.FileName[1] <> #0) then
         if (AEntry.FileName = FCB.FileName) and (AEntry.Extension = FCB.FileExt)
           then begin Found:=True; TempSec:=Sector; TempEntry:=TempW; Break; end;
    end; {Next TempW}
    if Found or End_It then Break;
  end; {Next Sector}
  if Found then
  begin
    with FCB do
    begin
      FileSize:=AEntry.FileSize;
      FileTime:=AEntry.Time;
      FileDate:=AEntry.Date;
      StartCluster:=AEntry.ClusterNumber;
      Attribute:=AEntry.Attribute;
      DirEntrypSec:=TempW;
      SectorNumber:=TempSec;
    end;
    TempB:=GetFirstFreeFCBsPos;
    if TempB <> 255 then begin FCB.R4sFCBsPos:=TempB; FCBs[TempB]:=Addr(FCB) end
                    else Error:=R4sFDOS_TooManyOpenFiles;
  end else OpenFileByFCB:=False;
end;
 
Hogyan is működik eme rutin:

Ezzel megvolnánk, lássuk az FCB-vel megnyitott file bezárását:

function TR4sFileDOS.CloseFileByFCB(var FCB: TFCB): Boolean;
begin
  FCBs[FCB.R4sFCBsPos]:=nil;
  FillChar(FCB, SizeOf(FCB), 0);
  CloseFileByFCB:=True;
end;

 

Végezetül lássuk az FCB-vel történő szekvenciális file-olvasást:

function TR4sFileDOS.SeqReadByFCB(var FCB: TFCB): Boolean;
var
  Cluster, ReadCluster, TempW, ToCopy, i, ToX: Word;
  W, W0, W1, W2, W3 : Word;
begin
  if (FCB.CurrBlockNum * 128) + (FCB.LogicalRecSize * FCB.RecordInCurBlk) < FCB.FileSize then
  begin
    ReadCluster:=FCB.StartCluster;
    W0:=FCB.LogicalRecSize;
    W1:=Word(FCB.RecordInCurBlk);
    W2:=Word(BootInf.Byte_Sector);
    W3:=Word(FCB.CurrBlockNum * 128);
    ToX:=Word((Word((W0 * W1) div W2)) + W3);
    for Cluster:=1 to ToX do ReadCluster:=Convert12BitFATToWord(FAT, ReadCluster);
    TempW:=ConvertClusterNumberToLogicalSector(ReadCluster);
    if ReadLogicalSector(TempW) then
    begin
      if (FCB.LogicalRecSize mod BootInf.Byte_Sector) <> 0 then
      begin
        W:=(FCB.LogicalRecSize * FCB.RecordInCurBlk) mod BootInf.Byte_Sector;
        MoveMemXByteToBuffer(DTA^, Seg(ASector), Ofs(ASector)+W, FCB.LogicalRecSize);
      end else
        CopyASectorToDTA(ASector, FCB.LogicalRecSize);
      if FCB.RecordInCurBlk = 127 then begin
        Inc(FCB.CurrBlockNum);
        FCB.RecordInCurBlk:=0;
      end
      else Inc(FCB.RecordInCurBlk);
    end else begin SeqReadByFCB:=False; Error:=R4sFDOS_DriveNotReady end;
  end else begin SeqReadByFCB:=False; Error:=R4sFDOS_EOF; end;
end;

 Nos kutyafuttában egye fene, elismételhetem:
Nos ezzel ki is végeztük volna az FCB-s file-kezelést, csak az alapkonvenciókat kívántam vázolni, s egy jobb alapot adni a file-hendle-ös file-kezeléshez amit következő számunkban kezdek el. Ha valaki valahol elakadt sikítson egy eMail-t a címemre!
 
Bérczi László
eMail: PC-XUSER@FREEMAIL.C3.HU, Subject: "R4s File DOS"