Előző számunkban két különböző szálon gyarapítottuk ismereteinket, mint a DOS (INT 21h) megszakítás rutinjaival, és a fizikai lemezkezeléssel. Ma is hasonlóan gondoltam felépíteni a cikket. A fizikai lemezműveletek részében a főkönyvtár (drive:\) kilistázását tárgyalnánk hisz ez az egyik legegyszerűbb művelet. A DOS file-kezelő megszakításaiból pedig a handle-s file-kezelést kezdjük el tárgyalni.

A főkönyvtár kilistázása DOS nélkül !

Minden szükséges ismeretanyagot már az előző számban megtárgyaltunk. Vázoljuk mi szükséges a kilistázáshoz: Van pár apróság amit a DOS DIR parancs is kiír: ezek a drive-név, ill a széria-szám (serial number). Ezek is a boot-szektorban vannak tárolva.

A fizikai lemezműveletek track ill. szektor megadása

Egy fincsi kis részhez értünk ahol muszály lesz picit elidőznünk. Vegyük sorra az INT 13h első pár rutinját: Első nekifutásból erre lesz csak szükségünk, de lássunk egy ábrát a szektor olvasásánál (és késöbbiekben minden írás | ellenőrzés, stb. műveletnél is így lesz.) a track ill. szektor megadására a CX (CH - CL) ben.

Lássuk a megvalósítást arra, hogy hogyan kerül CH-ba és CL-be a megfelelő érték. A forráskód assembly-ben:

Mivel logikai szektorokról beszéltünk lássuk, hogyan kezelhetjük fizikailag (nem INT 25h-val !) de logikai szektor szinten a lemezt.

Mindenek előtt le kell lomboznom benneteket, de az INT 13h-nak nincs ilyen rutinja mely logikai szinten kezelné a lemezt. Csak meghajtó-fej, track (avagy HDD-nél ezt cylinder-nek hívják, egyébként magyarul ez a sáv), ill. szektor szinten kezelhető a lemez. Viszont mi logikai szinten kívánjuk kezelni, hiszen a a FAT, a főkönyvtár kezdő szektora is logikai szektorral van megadva.

Ki kell találnunk, hogyan lehet a logikai szektor számozást fej-sáv-szektorra visszaalakítani.

Mindenek előtt két részre kell bontanunk ezen eset vizsgálatát:

A floppy egység melynek drive számozása 128 alatt van (avagy a 7. bit nem bekapcsolt), ill. a harddisk egység melynek meghajtó száma 128 vagy annál nagyobb (a 7. bit bekapcsolt).

Az elkülönítésre azért van szükség, mert a harddisk-eknél lehetőség van egy lemezen több partíció (cca: egy lemezen több elkülönített rész melyeket külön betűvel jelölünk.) kialakítására.

A több partíció ill. a harddisk szerepe miatt létezik egy particiós tábla mely a winchester fizikailag 0 fej, 0 sáv, 1 szektorán található. Ez a táblázat mondja meg, hogy hol kezdődik az első, pl. a C: partíció. Általában OP-rendszer az 1 fej, 0 sáv, 1 szektorára teszi az első partíció boot-szektorát mely magát a boot-olást végzi.

A floppy-knál ezzel ellentétben - mivel nincs partíciós tábla - a 0. fej, 0 sáv, 1 szektorán van a boot-szektor.

Ezekre azért van szükség, mert egy logikai szektor-számból fizikai jellemzőket (fej-sáv-szektor) kívánunk előállítani, hogy INT 13h-val kezelhessük.

Lesz egy - Pascal - rutin melybe a logikai szektor-szám mely be, és cím szerint paraméterként adja vissza a beolvasott szektorokat (és egyéb változókat, mely pl. az eredményességet tárolja):

function ReadLogicalSectorIntoMemory(var Buf; ADrive: Byte; StartSec: Longint; SecCount: Byte; BootInf: PBootSecInforms; var Status, ReadedSecCount: Byte): Boolean;
Lássuk, hogyan lehet a logikai szektor-számból visszaállítani a fizikait:
Sector:=Byte(((StartSec) mod BootInf^.Sector_Track)+1);
Track:=Word(((StartSec) div BootInf^.Sector_Track) div BootInf^.Head_Number);
if ADrive > $7F
then Head:=Byte(((((StartSec) div BootInf^.Sector_Track) mod BootInf^.Head_Number))+1)
else Head:=Byte((((StartSec) div BootInf^.Sector_Track) mod BootInf^.Head_Number));
Mindenhol láthatjuk a BootInf rekordot fehasználtuk. Ez nem más, mint a boot-szektor beolvasásakor feltöltött TBootsecInfos típusú rekord, amit pointer-ként adunk át a rutinnak. (Mint ahogy fent a paraméter-listán is láthatjuk.) Erre szükség van, hisz ebből nyerjük a szektor/sáv, ill. maximális fej-számot.

Miután kiszámítottuk a fej (head), sáv (track - cylinder), szektor (sector) értékeket, már használhatjuk az eredeti INT 13h megszakítás szektor beolvasó rutinját, hisz minden szükséges értéket kiszámoltunk hozzá.

Láthatjuk a rutin vissza is ad értéket, név szerint ezek: a Status, ill. ReadedSecCount. Ez nem más amit az INT 13h szektor olvasó rutinja ad vissza rendre: AH ill. AL regiszterekben. (a művelet sikeressége, ill. a beolvasott szektorok száma.)

A főkönyvtár szektorainak olvasása, és visszaalakítása

Most már tudjuk és ki is számítottuk, hogy a fökönyvtár a RootStart szektortól kezdődik és a RootSize hosszú szektorban mértve. Így a RootStart + RootSize szektoron fejeződik be.

Nincs más dolgunk mint egy ciklusban RootStart-tól RootStart + RootSize-ig a szektorokat beolvasni, az alábbiak szerint átalakítani és kiírni.

Tudjuk, hogy:

Nos két ciklusból áll a kilistázás. Egy a fent megnevezett RootStart-tól RootStart + RootSize-ig. Ill. egy ebben lévő 0 - 15-ig tartó belső ciklus mely az egy szektorban lévő könyvtárbejegyzéseket írja ki darabonként. Az összes ciklusból akkor lehet kilépni amikor a következő könyvtárbejegyzés első karaktere #0 lenne.

A ciklus felépítéséhez szükséges még pár apróság:

procedure CopyBufToDirEntry(var Buf; Index: Byte; var _Aentry: TDirectoryEntry); Assembler;
const SizeOfAEntry: Word = SizeOf(TDirectoryEntry); {megadja egy bejegyzés hosszát}
asm
  push es
  push ds
  push di
  push si
  push bp
  les  di, [Buf]
  lds  si, [_AEntry]
  mov  cx, SizeOfAEntry
  xor  ah, ah
  mov  al, Index
  mul  cl
  mov  bx, ax
  xor  bp, bp
@Loop:
  mov  al, Byte Ptr es:[di+bx]
  mov  Byte Ptr ds:[si+bp], al
  inc  bx
  inc  bp
  cmp  bp, cx
  jb   @Loop
  pop  bp
  pop  si
  pop  di
  pop  ds
  pop  es
end;
MUL-lal összeszorozza az indexet és a rekord hosszát, ez lesz az offszet melytől 32 byte-on (a rekord hosszán át) az _AEntry-be másolja a byte-okat.

Lássuk a két ciklust mely megvalosítja a fenti rutinok segítségével a főkönyvtár kilistázását. Sematikus ábra:

AllFileSize:=0; AllFileNumber:=0; AllDIRNumber:=0;
for i:=RootStart to RootStart+RootSize do
begin
   if BreakAll then Break; {kiugrás a belső után a külső ciklusból is !}

   ReadLogicalSectorIntoMemory(Sector, Drv, i, 1, @BootInf, Status, Readed);
   {Ez olvassa be a főkönyvtár szektorait ...}

   if Status <> 0 then {ha nem sikerült beolvasni a szektort akkor kilépés !!!}
   begin
    System.WriteLn('Error: ', ConvertStatusByteToString(GetStatusOfLastDriveOperation(Drv)));
    System.WriteLn;
    System.WriteLn('Exitting ...');
    Halt($FF);
  end;

  for OneEntry:=0 to 15 do {ez a belső ciklus mely egy szektor bejegyzéseit dolgozza fel}
  begin
    CopyBufToDirEntry(Sector, OneEntry, AEntry); {Ez másolja a szektorból az AEntry-be az
     OneEntry-edik indexű bejegyzést}

    if AEntry.FileName[1] = #0 then begin BreakAll:=True; Break; end; 
    {vége, nincs több file kilépés a belső ciklusból ! Ugrás a külsőre !}

    with AEntry do
      if Not ( ((Attribute and VolumeID) = VolumeID) or
               ((Attribute and Hidden) = Hidden) or
               ((Attribute and SysFile) = SysFile) )
      then if Not (FileName[1] in[#229, #0..#32]) then {ha nem hidden vagy systemfile}
           begin

            TTUtil.WriteLn(FileName+ ' '+Extension+' '+
            ConvertAttrByteToAttrStr(Attribute)+' '+FillToSizeA(MSeeable(FileSize), 11)+
            ' '+ConvertTimeAndDateToString(Time, Date));

            {rendre kiírjuk a file nevet és kiterjesztést, attribútumot, file-hoszt,
             dátumot, időt}

            AllFileSize:=AllFileSize+FileSize;
            if (Attribute and Directory) = Directory
            then Inc(AllDIRNumber)
            else Inc(AllFileNumber);
            {ezen utolsó 4 sor pedig a file-ok számát, az összes file
             hosszát ill. a könyvtárak számát adja meg.}
          end;
  end;
end;
Nos a fökönyvtár kilistázása kész is lenne. Ezzel cikkünknek a lemez fizikai kezelésével foglalkozó része végetért, ezek után DOS file-kezelő rutinjait tárgyaljuk ahogy előző számunk végén megígértem.

A DOS file-kezelő rutinjai II

Előző számunkban nagyjából végigvettük az FCB-s file-kezelést. Ma a handle-lel történő file-kezelést feszegetnénk.
De mielőtt fejest ugranánk a mélybe, hogy mindenki számára érthető legyen mondjuk el mi az a file-handle.
Nos ez típusát tekintve egy Word szám. A file műveletre való megnyitásakor meg kell adni a nevet egy ASCIIZ karakter-láncban, esetleg az attribútumot, meg még ki tudja ... Nos ugye az FCB-nél az egész FCB rekordot el kellett tárolni ha hivatkozni akartunk a már megnyitott file-ra. És mi tagadás kissé körülményes volt az FCB-s file kezelés a mai ellustult puhány programozóknak. Így a DOS a UNIX-tól átvette a hande orientált file-kezelést mely egyszerűbb, és már könyvtárstruktúrát is kezel. Szóval file megnyitásakor vagy első létrehozásakor kapunk ha az sikeres volt (ha sikerült megynitni, ill. létrehozni) akkor kapunk egy word számot mellyel a későbbi műveletek során (írás | olvasás) hivatkozhatunk a file-ra. Nem kell ezek után se nevet, se path-t megadni csak a handle számot és már tudja is a DOS melyik file-ra gondoltunk. (Ezt ő belsőleg tárolja, hogy melyik handle-höz melyik file tartozik.)
 

Azonban vannak előre lekötött handle számok amiket már a DOS hozzárendelt bizonyos egységekhez a könnyebb kezelés végett. Ezek:
 
0 Standard Input Device (alapértelmezett bemeneti egység, általában a billentyűzet) 
1 Standard Output Device (alapértelmezett kimeneti egység, általában a képernyő) 
2 Standard Error Device (alapértelmezett hiba-kiíró egység, CON - => képernyő) 
3 Standard AUX Device (alapértelmezett AUX egység, az első COM port = COM1) 
4 Standard Printer (alapértelmezett printer port, LPT1) 
 
Ezeket az egységeket nem kell megnyitni, ezekre rögtön közvetlenül írhatunk - vagy olvashatunk róla. (Amelyikről amit lehet. Nyilván a printer-port-ról nem fogunk betűket beolvasni. (Nem a bi-dirrekciós printer-ekről van szó.))

Volt még egy dolog amit lehet, hogy érdemes tisztázni. Igaz régen már beszéltünk róla.

A DOS a karakter-láncot (String) nem úgy tárolja ahogy a Pascal. Nem fixen 256 byte hosszon ahol az első byte jelenti a szöveg hosszát. (Ord(S[0]) = a szöveg hossza, S[1], S[2], ... maga a szöveg). Szóval nem a DOS nem így tárolja, hanem van maga a szöveg, pl: 'itt ez egy karakterlánc'#0 . És amint láthatjuk a végén egy ASCII nulla karakter. Nincsen sehol letárolva, hogy milyen hosszú a szöveg, hanem a szöveg végét egy ASCII 0 - vagy Pascal-osan egy #0 - karakter jelzi.

Most pedig kezdjük el a handle tipusú file-kezelést:

Lássunk egy példát a könyvtár létrehozására. A Pascal megvalósítás:
var
  S, O   : Word;
  DirPath: String;
  Error  : Word;
BEGIN
  Error:=0;
  DirPath:='C:\!TOROLD.LE!'#0;        {Töröld le ez csak egy próba volt !}
  S:=Seg(DirPath); O:=Ofs(DirPath)+1; {Növeljük eggyel mert nincs szükség}
  asm                                 { a hosszra az ASCIIZ-hez.         }
    push ds
    mov  dx, S
    mov  ds, dx
    mov  dx, O
    mov  ax, 3900h
    int  21h
    jnc  @NoHiba        {Ha CF felkapcsolt, akkor Error változóba a hiba}
    mov  Error, ax
  @NoHiba:
    pop  ds
  end;
  if Error <> 0 then WriteLn('Nem sikerült a könyvtárat létrehozni !');
END.
Most kicsit rövidebb lett a DOS file-kezelő részének leírása, mert a fizikai lemezkezelésnél most nem csak elméletet, hanem egy programot is tárgyalnunk kellett. Következő számunkban folytatjuk a DOS-os file-kezelést, és a fizikai részben valószínüleg már a FAT felépítését, és a linkeléseket vesszük.

Amennyiben a mai cikk kissé tömör lenne - bár szerintem érthető - (és második elolvasás után is homályos) eMail-ezz.
 

 Bérczi László
eMail:PC-XUser@IDG.HU, Subject: "Tippek-Trukkok rovat"
BELA@MI.STUD.PMMFK.JPTE.HU