Reliable File System


Spodní vrstva


Dokumentace: Martin Dvořák



Co by nemělo ujít Vaší pozornosti:

Srdce spodní vrstvy CacheMan - pro pochopení toho jak spodní vrstva funguje je nutné si pročíst kapitolu o něm. CacheMan je nositelem mazanosti cache. CacheMan byl navržen tak, aby horní vrstva NEMUSELA vůbec LOGOVAT a také se nemusela v ůbec starat o KONZISTENCI - vše je v něm zabudováno! Cacheman je interfacem mezi spodní a horní vrstou - je poskytovatelem služeb. Zásadní význam pro práci s ním má pojem package.

Další superdůležitou kapitolou je kapitola o TRANSAKCÍCH a LOGOVÁNÍ. Když už nic jiného tak tato kapitola ozřejmí proč nazývat náš file system RELIABLE.

Examply

Examply, které předvádějí spodní vrstvu jsou tyto projekty:
example1.prj, ( main v EXAMPLE1.LOW ) - vytvoření disků, oblastí, formátování
example2.prj, ( main v EXAMPLE2.LOW ) - dva examply na fungování CacheMana
example3.prj, ( main v EXAMPLE3.LOW ) - transakce, logy a recovery spodní vrstvy
example4.prj, ( main v EXAMPLE4.LOW ) - striping a záchrana disku

Všechny informace k tomu kterému examplu jsou v jeho main.cpp. Tam najdete co example provádí, co máte udělat...

Obsah
Schema spodní vrstvy file systému
Jak již bylo zmíněno interfacem mezi horní a spodní vrstvou je Cacheman. Horní vrstva volá při obvyklé práce vlastně pouze jeho funkce. Ty jsou dekladrovány v CACHEMAN.H


Struktura dat na disku


Nyní podrobněji k jednotlivým strukturám...

Master Boot Record
Master boot record má klasický formát. Jeho velikost je 1 sektor == 512B. Vyhrazena je pro něj ale celá první stopa. Pro větší bezpečnost při každé změně MBR duplikujeme jeho obsah do všech sektorů první stopy. V případě havárie tedy může záchranná utili ta s velkou pravděpodobností MBR restaurovat.
Formát Master boot recordu je deklarován v FSTYPES.H

Boot Record
Zabírá jeden sektor. Obsahuje klasické informace. Struktura ne nepodobná MBR. Oproti MBR obsahuje informace důležité pro oblast:
Formát Boot recordu je deklarován v FSTYPES.H

The Second Boot Record
Je rozšířením + částečnou zálohou Boot Recordu. Obsahuje kopii BIOS parametr bloku boot recordu -> ten obsahuje nejdůležitější informace důležité pro orientaci v oblasti ( bez nich se to dá v podstatě zabalit ). Dále obsahuje statistické informace jako: počet volných sektorů v oblasti, počet sektorů se statistikou grup ( viz níže ), duplikát počtu grup v oblasti, label oblasti. The Second Boot Record není bezpodmíněčně nutný pro běh RFS. Informace z něj využívají funkce pro alokace/dealokace sektorů. Jeho výpadek není fatální.
Formát The Second Boot recordu je deklarován v FSTYPES.H

Statistika grup
Je to oblast několika sektorů, která udržuje informaci o tom kolik je v každé grupě v oblasti volných sektorů ( Word ). Každý sektor tedy obsahuje informace o 512/2 grupách, počet statistických sektorů se řídí počtem grup. Tyto statistiky jsou spravován y a využívány funkcemi CacheManAllocateSector a CacheManFreeSector z CACHEMAN.*. Funkce statistiku používají např. pro rychlé vytypování grupy pro alokaci.

Oblast ( myšlena partition )
Oblasti jsou číslovány od 1 do 4. Vytváření se provádí pomocí funkcí z FDISK.H

Limity uvnitř jedné oblasti:
Datová kapacita grupy v sektorech: 12b sektorů ... 4k * 512B = 2MB
Počet grup v oblasti: 20b ... 1M grup
Velikost grupy: 4k datových sektorů + 1 sektor bitmapa
Maximální adresovatelná velikost FS: 32b sektorů ... 4,3M * 512B = 2,2TB neboli 1M grup * 2MB velikost grupy = 2,2TB

Grupy
Data v oblastech jsou organizována v grupách. Funkce pro práci s grupami jsou deklarovány v GROUP.H, deklarece fcí provádějících alokace a dealokoce sektorů v CACHEMAN.H implementace je v ALLOCFRE.CPP.


Fyzické chyby media a jak jsme se s nimi vyrovnali
Velikosti postižené oblasti: chyby bitové, bytové jsme se rozhodli na úrovni filesystemu neošetřovat. Využívat budeme hardwarového zabezpečení. Každý disk má za sektorem 32 bitový ECC částečně samoopravný kód. Je tedy zbytečné zajišťovat data lepším kóde m apod. Menší chyby bitového rázu ( tuším do 4B ) by tedy měl opravit hardware.

Striping
My jsme se rozhodli pro ochranu na úrovni sektorů a to metodou STRIPINGU. Data jsou na discích rozložena takto:

Jak je vidět tak díky rozložení dat můžou disky fungovat absolutně nezávisle na sobě.

Data NEjsou jako u ostaních systémů RAID rozložena tak, že následující blok je na následujícím disku. Takový systém je sice tolikrát rychlejší než 1 disk kolik je v něm disků ( 5 disků - 5*rychlejší operace (čistě teoreticky)), ale má to bezpečno stní nevýhody. Disk nemůže fungovat samostatně protože data jsou rozložena + pokud dojde k výpadku více než dvou disků je konec protože data všech ostatních disků nedávají smysl - jsou kompletně ztracena.

Naše uložení dat je MÉNĚ výkonné, ale je mnohem bezpečnější. Každý disk je autonomní, má svoje data na ostatních nezávislá. Může fungovat jak sám tak ve strivacím systému - striping se může zapínat/vypínat po přebootování. Je to vlastně tak, že k dyž máte v počítači dva disky, můžete tam přidat paritní, zapnout striping file sytemu a od té chvíli jedete bezpečně. Pak paritní disk vyndáte a systém funguje dál. Největší přínos stripingu v tomto modelu je ten, že pokud se vyskytne na některém z dato vých disků badsektor o data nepřijdete, protože ty se pomocí parity dopočtou.

Vlastnosti:

Režijní struktury
Struktury, které drží seznam stripovaných oblastí, stav stripingu a vůbec vše důležité co se ho týká jsou deklarovány v CACHESUP.H.
 //---------------------------------------------------------------------------------
 //- STRIPING STRUCTURES - STRIPING STRUCTURES - STRIPING STRUCTURES -
 //---------------------------------------------------------------------------------

 struct _StripItem : PriQueueItem
 {
  byte  Device;
  byte  Party;
  dword Begin;  // logical HD sector 0..
  dword End;    // logical HD sector 0..
  dword Number; // End-Begin+1 ( data devices )
  byte  Type;   // PARITY_STRIP || DATA_STRIP - used for table on disk
 };

 typedef struct far _StripItem StripItem;



 class far DataStripsQueue : public PriQueue
 {
  public:

   DataStripsQueue( void ):PriQueue() { return; };
    void      Insert( StripItem far *Item  );

    StripItem far *FindFirstParty( void );
    StripItem far *FindNextParty( StripItem far *Actuall );
    bool      IsInStripingArea( byte Device, dword Logical );
    void      Show( void );
    word      DeleteParty( byte Device, byte Party );

    word      WriteSingleTable( byte Device, byte Party );
    word      WriteTablesToPartys( void );
              // these two functions would be methods of StripSession
              // but here is easier implementation

    bool      IsStriped( byte Device, byte Party ); // checks ParityHD too
    word      WriteSingleOffTable( byte Device, byte Party );
    word      WriteOffTablesToPartys( DataPartysQueue far * DirtyDataPartys );

   ~DataStripsQueue();
 };



 struct far _StripSession
 {

  byte StripingIs;              // ON / OFF

  DataStripsQueue DataHDs;      // data HDs queue

  StripItem       ParityHD;     // parity HD

 };

 typedef struct far _StripSession StripSession; // static structure in stripe.cpp

Struktura StripSession obsahuje flag zapnutí / vypnutí stipingu a frontu deskriptorů stripovaných oblastí a deskriptor paritní oblasti. Jak si můžete všimnout v deskriptorech, striping se orientuje podle logických sektorů na device ( nikoli na partyšně ) . Deskriptory jsou také používány jako položky stripovacích tabulek na disku.

Inicialize stripingu
Následuje popis toho co se děje při startu systému se stripingem. Implementace je v STRIPING.CPP, co která funkce dělá poznáte podle názvu nebo podle komentáře pod hlavičkou v místě implementace funkce.

Algoritmus:
1) Předchozími operacemi jsou v paměti připraveny fronty CLEAN a DIRTY oblastí ( ve funkci FSOpenFileSystem() z INIT.CPP ) . Nyní se nacházím ve funci InitializeStriping() STRIPING.CPP.
2) Beru oblast po oblasti a hledám na nich konfigurační tabulky stripingu.
3) Vezmu tedy z fronty první dosud nezkontrolovanou oblast. Pokud jsou všechny oblasti zkontrolovány GOTO 8)
4) Pokud na ní tabulka neexistuje beru z fronty další oblast GOTO 3). Pokud tabulka existuje GOTO 5)
5) Provede se analýza tabulky. Pokud je v ní "striping on this device OFF" GOTO 3) jinak GOTO 6)
6) Je to první nalezená tabulka? ANO - uložím si ji do speciálního bufferu. NE - porovnám ji s první nalezenou tabulkou a nastavím si podle toho flag FOUNDED_TABLES_ARE_SAME ( TRUE/FALSE ).
7) Všechny tabulky byly načteny. Nyní se podle první načtené tabulky testuje, které oblasti z ní skutečně existují a které v systému nejsou. Pokud oblast je uvedena v tabulce a v systému není zvýši se počítadlo mrtvých oblastí ( Dead++ a zapamatuje se ty p oblasti )
8) Dead==0 a tabulky stejné - striping se nahodí v normálním módu - KONEC
9) Dead==1 a typ mrvé oblasti je RFSData - nahodí se rescue utilita, která provede záchranu ztracené oblasti do oblasti jiné. Viz. níže. Po této akci se ještě nahodí rekonfigurační utilita, kde má uživatel možnost ještě session doladit. Po rekonfiguraci - KONEC.
10) Dead>=2 nic nelze zachránit. Dvě a více mrtvých oblastí to je moc. Jediné co zbýva je rekonfigurace sešny. Po rekonfiguraci - KONEC.

Setup utility
Konfigurační mód se nabídne uživateli při startu systému. V něm má možnost přidávat, ubírat a měnit seznam oblastí na kterých bude striping probíhat. Striping zde lze zapnout/vypnout. Lze také vypsat informace o jednotlivých oblastech. Po aktualizaci sez namu se provede přepočet parity na paritním disku ( pokud je striping zapnut ).

Rescue utility
Implementace je v STRIPING.CPP. Ve funkci StripingRescue() se provádí nastavení a záchranu provádí funkce STRIPRescueParty().
Pokud jsme přišli o jednu datovou oblast nic se neděje. Jsme totiž schopni ji z parity a zbylých datových oblastí zrekonstruovat.

Záchrana jednotlivého sektoru se provádí takto.
Načtu si z paritního disku sektor s paritou. Potom jednotlivě načítám sektory z datových oblastí a vždy provádím XOR se sektorem parity. Nakonec mi zbyde v bufferu kde jsem měl uloženu paritu obsah ztraceného sektoru.

Záchrana oblasti.
Provede se záchrana Boot Recordu. Načte se Boot Record oblasti kam zachraňuji. V tomto boot recordu se změní informace jako adresa FNODU root direktoráře, počet grup v oblasti, počet rezervovaných sektorů na informace zachráněného Boot Recordu. Zbytek se ponechá tak jak je a Boot Record se zapíše. Obdobná operace se provede záchrana The Second Boot Recordu. Zbytek oblasti ze zrekonsruuje jak bylo uvedeno v odstavci o záchraně jednoho sektoru.

Čtení a zápis sektorů ve stripovacím módu
Jak již bylo výše uvedeno, striping se orientuje na úrovni logických sektorů na device. Proto výběr toho, která funkce se použije ( klasická nebo stripovací ) se provádí ve funkci pracující na této úrovni. Všechny výše postavené moduly volají tuto funkci a ta už provede výběr za ně. Zde je kód čtecí funkce ( zapisovací je analogická ) z STRIPING.CPP, který myslím vysvětlí více než dlouhý popis.
word ReadLogicalSector( byte Device, dword Logical, void far *Buffer, byte Number )
// this function is used by upper level
{
 #ifdef DEBUG
  printf("\n READing sector %lu... ", Logical );
 #endif

 if(
     StripingSession.StripingIs==ACTIVE
     &&
     StripingSession.DataHDs.IsInStripingArea( Device, Logical )
   )
 {
  #ifdef DEBUG
   printf(" STRIPing version...", Logical );
  #endif

  return STRIPReadLogicalSector( Device, Logical, Buffer, Number );
 }
 else
 {
  return CLASSICReadLogicalSector( Device, Logical, Buffer, Number );
 }
}

STRIPReadLogicalSector() je tedy funkce, která provádí čtení ve stripovacím módu. Čtení probíhá takto. Načítají se odpovídající sektory z jednotlivých datových oblastí a počítá se parita levelů. Nakonec se parita zkontroluje a případně opraví. Načtená da ta se nakopírují do bufferu volající funkce.
STRIPWriteLogicalSector() provádí zápis. Zápis probíhá takto. Načtou se data z jednotlivých datových oblastí a spočítá se jejich parita KROMĚ OBLASTI DO KTERÉ BUDU ZAPISOVAT. K paritě se připočtou zapisovaná data. Následuje zápis dat a hned po něm zápis parity.

Pro podrobnosti nahlédněte prosím do zdrojového kódu.

Pár slov k implementaci jako takové
Striping je místo, kde se nejvíce projevilo, že provádíme implementaci našeho file systemu pod DOSem. Při implementaci jsem musel velmi šetřit pamětí a tak jsou přepočty dělány po sektorech ( a tedy velmi hloupě ). Z toho plyne, že záchrany, přepočty par it i běh je velmi pomalý. Do bufferů se data z jednotlivých disků čtou po sobě. Pokud by jsme systém implementovali pod UNIXem forknul by jsem si na každé čtení jedno vlákno a čtení by probíhalo paralelně ( tedy tolikrát rychleji než při normálním čtení kolik je datových disků ( v naší implementaci díky sekvenčnosti je ale tolikrát pomalejší )). Na implementaci stripingu se tedy prosím dívejte spíš jako na špatnou implementaci snad dobrých nápadů, které by jinde mohly být implementovány mnohem lépe. Díky za pochopení.

CacheMan = Cache + Swap, package, konzistentní zápisy

Naším cílem je zachování konzistence souborů. Proto data se kterými se bude pracovat zorganizujeme do logických celků - packageů. Zhruba řečeno, při práci s daty souboru RD/WR vyšší vrstva na vlastní žádost získá nějaké PackageID. Při všech dalších opera cích nad tímto souborem bude používat tuto identifikaci. Spodní vrsta tedy bude vědět, která data k sobě logicky patří. Na konci práce se souborem nebo ve chvíli, kdy bude horní vrstva vědět, že byla všechna potřebná data "zapsána", zavolá horní vrstva C ommitPackage( PackageID ). Na to provede spodní vrstva "double write" což je zápis, který zaručuje konzistenci. Ten se provede tak, že se obsah cache zapíše do odkládací oblasti, tato skutečnost se zapíše do logu a následně se kompletní package nakopíruj e do cílového souboru. V připadě havárie tedy bude možné operaci dokončit.

Konzistentním zápisům bylo nutné podřídit především cacheovací mechanismus. Museli jsme navrhnout cache tak, aby ji vyšší vrstva měla přehled o tom CO JE SKUTEČNĚ ZAPSÁNO NA DISKU A CO JE POUZE V CACHE. CacheMan se skládá ze dvou částí:
(i) paměťová část: klasická paměťová cache
(ii) odkládací část: pokud dojde paměť v paměťové části cache, musí se dirty data odložit na swapovací oblast konzistentních zápisů. Nelze je zapsat přímo do souboru - nevím totiž jestli uživatel zapsal do vše co chtěl.
Schema CacheMana:




Pro cacheování jsme si zavedli package. Je to způsob jak předává horní vrstva cache informaci o tom, že určitá data k sobě logicky patří. Díky tomu cache ví, jak má data do souboru zapisovat, aby zůstal v konzistentním stavu.

Typicky při otevření souboru zažádá vyšší vrstva o vytvoření package ( package můžou být jemnější, třeba několik na jeden soubor). Vytvoří se struktury v cache a cache vrátí horní vrstvě číslo vytvořeného package. Potom kdykoli ze souboru vyšší v rstva data čte/zapisuje odkazuje se na ně tímto číslem. Tato data, buď zústávají v cache nebo se odkládají na swap. Do souboru se uloží až po zavolání CommitPackage( PackageID ). Tak má vyšší vrstva kontrolu nad tím co je v cache a co je ve skutečnosti na disku.

Pracuje se vždy s intervaly sektorů. Operace jsou navrženy tak, že package nikdy neobsahuje sektor dvakrát. DOKONCE PLATÍ ŽE V CELÉM CACHEOVACÍM SYSTÉMU MUŽE BÝT SEKTOR NEJVÝŠE JEDNOU!. Buď sektor v CacheManovi není, nebo je pouze v cache, nebo j e pouze na swapu. Nyní popíši jak se postupuje při jednotlivých operacích:

Čtení
Dostanu číslo package, ke kterému se čtení vztahuje - PackageID. Vytvořím si bitmapu pro interval sektorů, který čtu ( čtu je do userbufferu ). Bit v bitmapě odpovídá sektoru v userbufferu. Kdykoli načtu sektory, nastavím odpovídající bity na 1. Končím k dyž jsou všechny bity nastaveny na 1 - všechna data byla načtena.

Postup:
a) prohledám swap. Procházím swap descriptory package PackageID a čtu data do userbufferu a značím v bitmapě ( interval sektorů který mám načíst se částečně nebo úplně překrývá s intervalem na swapu ). Poznámka: když se data dostala na swap, znamená to ž e jsou dirty - proto s nimi začínám. Projdu celou frontu swap deskriptorů package PackageID.

b) procházím cache. Procházím cache descriptory package PackageID. Pokud se interval sektorů který mám načíst částečně nebo úplně překrývá s intervalem na v cache kopíruji data do userbufferu a značím v bitmapě. Projdu celou frontu cache deskriptorů.

c) pokud ještě chybí nějaké sektory, natáhnu je přímo ze souboru. Pokud je dost paměti umístím natažená data do cache mojeho package.

Zápis
Dostanu číslo package, ke kterému se zápis vztahuje - PackageID. Vytvořím si bitmapu pro interval sektorů, který budu zapisovat. Bit v bitmapě odpovídá sektoru v userbufferu ze kterého zapisuji. Kdykoli zapíši sektory, nastavím odpovídající bity na 1. Ko nčím když jsou všechny bity nastaveny na 1 - všechna data byla zapsáno. ZDE SE DATA ZAPISUJÍ POUZE DO CACHEMANA NE DO SOUBOURU ( TO PROVÁDÍ AŽ COMMIT PACKAGE! )

Postup:
a) prohledám swap package PackageID. Na swapu jsou pouze dirty data, proto je musím přepsat jako první. Procházím swap descriptory a zapisuji data při částečném či úplném překrytí intervalů na swap a značím v bitmapě. Projdu celou frontu swap deskriptor ů.

b) procházím cache package PackageID. Procházím cache descriptory. Pokud se interval sektorů který mám zapsat částečně nebo úplně překrývá s intervalem na v cache, kopíruji data do cache + v markuji jako dirty + značím v bitmapě. Projdu celý frontu cach e deskriptorů.

c) pokud ještě chybí nějaké sektory zkusim: (i) pokud je místo v cache - vytvořím desckriptor, data nakopíruji tam + omarkovaji descr dirty + poznačím v bitmapě. (ii) pokud není místo v cachu zkusím swap. Analogicky. (iii) pokud není místo na swapu následuji zápis přímo do souboru - PORUŠUJE KONZISTENCI.

Velikost cache, swapovací oblasti
Zde je myslím už celkem vidět jak systém funguje. Záleží tedy na uživateli jak velkou si zvolí cache a jak velkou odkládací oblast. Čím větší budou tyto oblasti tím lépe budou konzistentní zápisy fungovat ( větší objem dat, větší soubory ). Pokud bude ca che dostatečně velká, uštří se čtení ze swapu - systém bude rychlejší.
Vše je tedy na uživateli. Čím bude velkorysejší, tím rychleji a lépe bude vše fungovat.

Commit package
Provede konzistentní zápis do souboru.
a) prochází se cache descriptory package PackageID. Pokud je swapování vypnuto nic se na swap samozřejmě nekopíruje a data se "nekonzistně zapisují přímo do souboru".

b) prochází se swap descriptory package PackageID. Seznam intervalů se připíše do logu.

c) prochází se podruhé swap descriptory a kopíruje se ze swapu do filu, descriptory se následně dealokují.
Pokud dojde v tomto místě k výpadku proudu nic se neděje. Po restartu systému se udělá REDO: podle logu se ze swapu data znovu zkopírují do souboru - KONZISTENCE JE ZARUČENA ( na swapu jsou nyní již všechna data - i z cache! ).

d) prochází se podruhé cache descriptory a data se z cache kopírují do souboru, descriptory se následně dealokují ( je to korektní a rychlejší - viz. výše ).

e) do logu se zapíše COMMIT a logovací file se smaže.

Byly tu i jiné možnosti...
Místo double writes přicházela ještě druhá možnost jak provést konzistentní zápis: zapsat data z cache někam do datové oblasti, zapsat to do logu a pak začít přepojovat odkazy na sektory v FNODEu ze starých na nové aktuální sektory. Pro tuto možnost jsem se nerozhodl protože:
  1. Docházelo by k velké fragmentaci dat. Se zaplňováním file systemu by už data nebyla u sebe ( připomínám, že horní vrstva si může říci za kterým sektorem chce nové sektory naalokovat, spodní vrstva se potom snaží alokovat co nejblíže tomuto sektoru ), natož v té samé grupě. Ve výsledku by to, podle mého názoru, bylo méně výkonné než poměrně rychlé souvislé zápisy ( méně seekování ).
  2. Swapovalo by se mezi data
  3. Problém by šel mnohem hůře rozdělit na horní a spodní vrstvu tak jak je tomu nyní. Spodní vrstva by totiž musela hrabat ve strukturách vrstvy horní a vice versa.


Funkce CacheMana jsou deklarovány v CACHEMAN.H, dále jsou využívány funkce podpory cache CACHESUP.H, kde jsou deklarace package a režijních struktur...


TRANSAKCE a LOGY : *.ALF, *.NOT, *.OKA, *.HLP, *.CML
Logy jsou páteří transakčního systému RFS ( jestli mu tak lze říkat ). Nejdříve po jednom popíši jednotlivé logy. Pokud chcete jejich strukturu do podrobna zkoumat nebo Vás zajímá implementace => nahlédněte prosím do zdrojáků. Později v kapitole o tom co a jak se pomocí logů opravuje vyjasním jejich konkrétní význam a v ěci jako jak se logy využívají navzájem a jaké jsou jejich vztahy.

*.ALF - ALLOC/FREE log
Je základním typem logu. Většina ostatních logů na něm stojí. Jeho vyjímečností je to, že se v jedné oblasti může vyskytovat nejvýše jeden. Je to z důvodu serializace transakcí. Existence nejvýše jednoho ALF logu mi zaručuje, že transakční operace budou prováděny pořadě.
Tento log je používán funkcemi CacheManAllocateSector() a CacheManFreeSector(). Tyto funkce volají fci AllocFreeUsingLog() ( RECOVERY.H ). Tato fce má zásadní význam. Provádí totiž konzistentní zápis bitmapy. To znamená, že tr ansakčně pomocí logu ALF provede změny ve statistikách a zápis bitmapy tak, aby vše odpovídalo a úspěšnou alokoci rovněž transakčně zaznamená do OKA ( OK Allocated log - viz. níže ) logu package. ALF record se při crash vždy ROLLBACKUJEuje ( Undo AlfLog() z RECOVERY.H )=> nikdy se neprovádí redo ( nicméně jeho struktura je navržena tak, že by redo šlo z informací v něm obsažených provést ). Další důležitou vlastností ALF logu je, že operace jeho rollbacku je IDE MPOTENTNÍ tzn. dojde-li v době rollbacku ke crash může se celá operace rollback provést znovy přes již jednou provedený a vše bude OK.
Struktura ALF logu ( pro detaily nahlédněte do zdrojáků )
// Log structure:                                                 Size:  Off:
//
// LOG_BEG_TRANSACTION  ( 0 Alloc, 1 Free )                        8      0
//
//  LOG_BEG_WR_BMP                                                 8      8
//    write_to_log   GroupID                                       4      16
//    write_to_log   Logical                                       4      20
//    write_to_log   LogicalInBmp (0-4095)                         4      24
//    write_to_log   Number                                        2      28
//    write_to_log   Bitmap                                        512    30
//   LOG_CHECKPOINT                                                8      542
//    write_to_party Bitmap                                        ..
//  LOG_COM_WR_BMP                                                 8      550
//
//  LOG_BEG_WR_GRPSTAT                                             8      558
//    write_to_log   OldGrpFreeSpace                               2      566
//   LOG_CHECKPOINT                                                8      568
//    write_to_party NewGrpFreeSpace                               ..
//  LOG_COM_WR_GRPSTAT                                             8      576
//
//  LOG_BEG_WR_SCND_BOOT                                           8      584
//    write_to_log   OldPartyFreeSpace                             4      592
//   LOG_CHECKPOINT                                                8      596
//    write_to_party NewPartyFreeSpace                             ..
//  LOG_COM_WR_SCND_BOOT                                           8      604
//
//  LOG_BEG_WR_OKA                                                 8      612
//    ( call function ... which writes it transactionaly to OKA )  ..
//  LOG_COM_WR_OKA                                                 8      620
//
// LOG_COM_TRANSACTION                                             8      628
//                                                              ---------
//                                                        Total:   636
Vlastností kterou se liší od ostatních logů stejnou strukturou jako je NOT je to, že je vytvářen pouze pro alokace v nějakém Package ( připomínám, že při práci s cache se na package odkazuje při operacích loadtocache/load/save/alloc/free/commit ). Tedy package ID je pozpodmínečně nutné pro jeho vytvoření.

*.NOT - NOT PackageID Alloc/Free log
Tento log má absolutně stejnou strukturu jako ALF log. Liší se od ALF logu tím, že je vytvářen při alokacích mimo package. Příkladem použití takový alokoací je alokace místa pro logy. Logy jdou mimo cache -> zápisy i čtení z nich se provádějí přímo -& gt; nevyrábí se pro ně package protože nemá smysl provádět commit když cache nic z nich nesmí obsahovat. Na NOT logu se Vždy provádí operace ROLLBACK, která je IDEMPOTENTNÍ. Rollback se provádí funkcí UndoAlfLog() z RECOVERY.H ).

*.OKA - OK Allocated/Released log
Log OKA slouží pro evidenci naalokovaných/uvolněných sektorů toho kterého package. Každý package má vlastní OKA log. V průběhu práce se allokace/dealoakace v package provádějí přímo ( neodkládají se ). Pokud dojde ke crash před commitem musí být takové m ísto uvedeno do původního stavu. Jak už bylo výše uvedeno, tyto operace se provádějí pomocí ALF logu, který připisuje do OKA logu ty úspěšné. Při crash se tedy provede rollback OKA zpětnou analýzou ( podrobněji níže ) a vše se uvede do původního stavu. V rámci commitu se provádí mazání OKA logů - to je ta lepší možnost. Uvnitř commitu se v určité době už najisto ví ( viz níže ), že místo bude použito -> OKA log se může smazat. Do OKA logu se připisuje nakonec. OKA log se skládá z recordů této struktury:
 typedef struct
 {
  dword  Record_Beg;    // ALLOC_RECORD || FREE+RECORD
   dword  State;        // NORMAL_STATE || ROLLBACKED_STATE
   dword  Logical;
   word   Number;
   dword  GroupID;
   dword  LogicalInBmp;
  dword  Record_End;    // OKA_RECORD_END
 }
 OkaRecord;                     // sizeof()=26

Podrobnosti RECOVERY.CPP.

*.HLP - HeLP Alloc/Free undo log
Tento log má absolutně stejnou strukturu jako ALF log. Je používán při rollbacku OKA logu. Jak se provádí rollback OKA logu je popsáno níže.
Podrobnosti RECOVERY.CPP.

*.CML - Commit undo/redo log
CML je log, který se používá pro zajíštění konzistence commitu určitého package( připomeňte si jak se provádí commit ). Během commitu se do CML uloží, kde je obsah cache na swapu ( určeno pro double write ), kde je odložen zbytek na swapu ( obsah swap de scriptorů ). Jakmile se do commitu zapíšou všechny tyto informace, uloží se tam checkpoint, který říká "to byl poslední záznam, teď začínám kopírovat ze swapové partyšny na datovou". Ano zde je ta chvíle pro smazání OKA logu. Jakmile se uloží tento check point, už se bude dělat po crash vždy REDO -> místo bude každoupádně použito a tak tedy každopádně nebudu rollback OKA potřebovat. Pokud dojde ke crash před zapsáním magiky provádí se vždy UNDO - nestihl se dostat obsah package/cache do odkládací obla sti celý - takový zápis do datové oblasti by nebyl konzistentní.
Nyní se vždy podle CML logu načtou data ze swapu, uloží se do datové partyšny a místo na swapu se odalokuje ( ano pomocí ALF logu ). Mám tedy jistoto, že bude vše konzistentní. Provedení jak REDU tak UNDO podle CML logu je IDEMPOTENTNÍ ( Zarazila vás víc enásobná dealokace na v připadě crash při opravách - po recovery se provádí přeformátování swapovacích oblastí. Testy jsem zjistil, že od zaplnění oblasti kolem půlky kapacity je to rychlejší než zvláštní log a dealokace dle něj ).
Struktura CML logu ( RECOVERY.H):
// Structure of CML record:
//
// TypeOfRecord  // CACHE_INTERVAL || NOT_LAST_RECORD ( SWAP ) || BEGIN COPY
//  Device;             // 0, ..
//  Party;            // 1, ..
//  SwapDevice;
//  SwapParty;
//  Logical;
//  LogicalSwap;
//  Number
//
//
// Structure of CML log file:
//  Type
//   CACHE_INTERVAL     // intervals moved in pack commit from cache to swap
//   ..
//   CACHE_INTERVAL     // intervals moved in pack commit from cache to swap
//   NOT_LAST_RECORD    // intervals in pack commit time on swap
//   ..
//   NOT_LAST_RECORD    // intervals in pack commit time on swap
//   BEGIN_COPY         // checkpoint: all data on swap, begin of copy
//                      // from swap party to data party



Co se děje při inicializaci RFS
Pro otevření RFS slouží funkce FSOpenFileSystem() z INIT.CPP.

Co se děje při startu systému:

Testuje se zda jsou v systému přítomné fyzické disky. Fyziké disky se berou popořadě, na každém disku se načte MBR, podle něj se čtou všechny BR, které jsou použity. V těchto boot recordech se hledá v BR.OSID.ID magic "RFSData" nebo "RFSSwap". Pokud tam tento magic je identifikuje se oblast jako oblast našeho file systemu. Oblasti se třídí do čtyř spojových seznamů.

V prvním jsou RFSData - tj. datové oblasti, které měly dirty flag nastaven na CLEAN ( ty jsou OK a s těmi už se nic dělat nebude)

Ve druhém jsou RFSData, které měly dirty flag nastaven na DIRTY - bude se muset provést jejich oprava.

Ve třetím jsou RFSSwap, které měly dirty flag nastaven na CLEAN. Ty jsou v pořádku.

Ve čtvrtém jsou RFSSwap, které měly dirty flag nastaven na DIRTY - bude se muset provést jejich oprava.

Takto se projdou všechna zařízení přítomná v systému a naplní se spojové seznamy.

Nyní se začne procházet fronta dirty RFSData na každou takovou oblast se zavolá funkce RecoverParty() z RECOVERY.CPP ( viz. níže ), která provede opravu oblasti. Po opravě se datová oblast přepojí do spojáku CLEAN dat ových oblastí.

Takto se zpracuje celý DIRTY spoják datových oblastí.

Nyní se projde fronta dirty RFSSwap a každá oblast z něj se zformátuje. Data, která na nich byla už z nich dostala funkce RecoverParty(). Nyní se tedy pouze musejí pročisti a rychlý formát je tou nejefektivnější metodou. Po přeformátování se odklá dací oblast přepojí do spojáku CLEAN odkládacích oblastí.

Takto se zpracuje celý DIRTY spoják odkládacích oblastí.

V dalším kroku se provede nastavení dirty flagu všem oblastem ( které jsou nyní už pouze ve dvou CLEAN spojácích ).

Provede se nahození CacheMana


RECOVERY aneb jak se opravuje pomocí logů po pádu systému
Implementace všeho o čem zde budu psát je v RECOVERY.CPP.

V této kapitole dokumentace popíši co se začne dít, když se po restartu systému zjistí, že oblast je dirty a je ji tedy třeba uvést do konzistentního stavu užitím logů. Tento úkol plní funkce RecoverParty().

Co recovery v jedné oblasti dělá: Recovery práce jsou hotovy => oblast je opravena...


Co se děje při zavírání RFS
Pro uzavření RFS slouží funkce FSShutdownFileSystem() z INIT.CPP. Tato funkce nejdříve provede kompletní commit cache a tím dokončí všechny transakce. Potom projde jak spojík datových oblastí tak spoják odkládacích oblastí a vš em oblastem nastaví dirty flag na CLEAN, který znamená, že oblasti byly odpojeny korektním způsobem.