Virtual File System (VFS)
Vytvoreno 9.1.1999 pro seminar Linux.
Obsah
Virtual File System navzdory nazvu neni filesystem. Je to rozhrani, pres ktere
Linux pristupuje k filesystemum. Je to soubor funkci, ktere musime napsat (napr.
vytvoreni souboru, smazani souboru) a ktere muzeme pouzit (treba cteni bloku z
disku), abychom meli ovladac filesystemu. Pisu o tom proto, ze jsem napsal
vlastni ovladac filesystemu HPFS, co umi i zapisovat (najit to muzete
tady, do
kernlu se to asi v nejblizsi dobe nedostane). Psal jsem to proto, ze mam OS/2 a
HPFS je jedina moznost (FAT - zoufale neefektivni na vetsim disku,
FAT32, NTFS - vyplody firmy Micro$hit(r), EXT2 - umi to
jen Linux, existuje sice i driver EXT2 pro OS/2, ale ten je tak bugovity, ze je
nepouzitelny - urcite je snazsi psat ovladac do Linuxu nez opravovat ovladac pro
OS/2).
Pokud si myslite, ze po precteni tohoto textu budete umet delat ovladace
filesystemu, tak si to myslite spatne. Tento text muze slouzit jen jako
doplnkova informace, nejlepsi dokumentace se vzdy da najit v
/usr/src/linux/fs/*.c,
/usr/src/linux/fs/*/*.c a
/usr/src/linux/include/linux/*.h.
Pokud najdete, ze v tomto dokumentu pisi bludy, tak se radsi podivejte do
zdrojaku, jak je to spravne :-)
Pokud budeme psat nejaky ovladac, je vyhodne ho psat jako modul jadra, ponevadz
pak nemusime rebootovat pocitac po kazde zmene. Moduly se pisou tak, ze v
programu napiseme dve funkce: int init_module() a void cleanup_module(). Modul
zkompilujeme, ale nelinkujeme. Pri nahrati modulu jadro zavola funkci
init_module, ta vrati 0 - uspech, jinak neuspech. Pred odstranenim modulu se
zavola funkce cleanup_module(). K nahrati a odstraneni modulu muzeme pouzit
prikazy insmod a rmmod.
V jadre samozrejme nemuzeme pouzivat funkce standartni libc.
Zde uvadim nektere obecne funkce, ktere se nam budou hodit:
-
int printk(char *fmt, ...)
-
Jako printf. Na zacatku textu muzeme uvest prioritu <0> az <7> (viz
linux/include/linux/kernel.h). 0 znamena nejvyssi prioritu (oops, panic), <7>
nejnizsi. Vetsinou se nastaveni priority nepouziva a vse se pise s defaultni
prioritou <4>. Normalne se zpravy z printk pisou na konsoli, jinak si to muzeme
menit daemony klogd a syslogd.
-
void *kmalloc(size_t size, int pri)
-
Alokace pameti. Prvni parametr je zrejmy, druhy urcuje prioritu danou konstantou
GFP_XXX. Vetsinou se tam dava GFP_KERNEL, GFP_ATOMIC kuprikladu znamena, ze se
nebude swapovat - pouziva se v obsluze preruseni nebo jinde, kdyz se proces
nesmi blocknout. Pamet alokovana pomoci GFP_KERNEL se neodswapovava (ale swapuje
se uzivatelska pamet, kdyz neni misto).
-
void kfree(void *ptr)
-
Uvolni pamet. Pokud na to zapomeneme, dojde k takzvanemu memory leaku. Pamet se
porad alokuje a alokuje, swapuje to cim dal vic a vic ... a pak uz clovek
nevydrzi to cekani a resetuje to.
-
void schedule()
-
Prepne proces. V jadre je kooperativni multitasking (t.j. proces nemuze byt z
vyssi moci prepnut, leda ze sam odevzda rizeni pomoci teto funkce). Neni moc
duvodu tuto funkci volat. Kdyz clovek zkouma, jak funguji jine funkce tak narazi
prave na to, ze se pomoci teto funkce preda rizeni jinemu procesu treba kdyz
proces ceka na data z disku.
-
void sleep_on(struct wait_queue **p)
-
Zpusobi, ze proces ceka na fronte p dokud ho nekdo neprobudi, a pripadne prepne
na jiny proces, ktery chce bezet.
-
void wake_up(struct wait_queue **p)
-
Probudi procesy cekajici na dane fronte.
-
void lock_kernel(), void unlock_kernel()
-
Na 2.1 jadrech zamykaji jadro pri SMP. Ve 2.0 se mohl do jadra dostat pouze
jeden procesor, v 2.1 jich tam muze byt vic a tak pouzivaji tyto funkce na
zamykani. Podivame-li se vsak na vetsinu funkci sys_open, sys_read..., uvidime
na zacatku lock_kernel() a na konci unlock_kernel(), takze si 2.1 jadro moc
nepomohlo... Do kodu na ovladani filesystemu se tedy v zadnem pripade dva
procesory dostat nemohou.
Samozrejme, ze toto nejsou vsechny funkce jadra, uplny seznam ziskame prikazem:
grep '^[a-z_].*(.*);' `find /usr/src/linux -name "*.h"` | less
Sprava bufferu byla v Linuxu delana s cilem minimalizovat kopirovani dat.
Treba v msdosu je bezne zavolat funkci na cteni dat a jako parametr ji predat
adresu, kam ma data ulozit. V Linuxu to funguje jinak: zavola se funkce a ta
vrati adresu, kde jsou data ulozena. Vyhoda je zrejma - data se nekopiruji. Ma
to ovsem i nevyhodu - kdyz chceme mit nekolik bloku po sobe v pameti, stejne je
musime kopirovat :-( Obejit to jde snad nejak pres page cache, hrozne se mi
do toho nechce.
Tak, zde jsou funkce:
-
struct buffer_head *bread(kdev_t dev, int block, int size)
-
Nacte blok do bufferu nebo vrati pointer na buffer, co je uz v cachi. Adresu dat
urcuje polozka bh_data struktury buffer_head. dev je cislo
zarizeni, block cislo bloku, size je velikost bloku. Radsi tim nezkousejte cist
z jednoho zarizeni bloky ruzne velikosti - se zlou se potazete.
-
void brelse(struct buffer_head *bh)
-
Uvolni buffer (t.j. snizi jeho pocitadlo; pokud je pocitadlo na nule, buffer
muze byt odstranen z pameti podle algoritmu LRU). Buffery ziskane pomoci bread
by se _mely_ uvolnovat, jinak se zaflakava pamet.
-
void mark_buffer_dirty(struct buffer_head *bh, int pri)
-
Pokud jsme do bufferu zapisovali, je treba pouzit tuto funkci, aby o tom Linux
vedel. Casem se buffer prepise na disk. Parametr pri je 0 pro datove bloky a 1
pro systemove bloky (bitmapy, inody, adresare). Systemove bloky jsou zapisovany
driv. Cas je mozno nastavit v daemonovi bdflush. Pokud tento daemon nebezi,
bloky se nezapisuji (leda pri nedostatku pameti).
-
struct buffer_head *getblk(kdev_t dev, int block, int size)
-
Podobne jako bread, ale necte blok. Oblibena je konstrukce
bh=getblk(...);mark_buffer_uptodate(bh);
, coz se pouziva treba pri
vytvareni
souboru (byla by blbost data cist, kdyz je pak kompletne prepiseme).
-
struct buffer_head *get_hash_table(kdev_t dev, int block, int size)
-
Jako getblk, ale pokud blok neni v cachi, tak vrati NULL.
-
void bforget(struct buffer_head *bh)
-
Jako brelse, ale smaze buffer z cache. Pri delete souboru je napriklad vhodne
smazat vsechny jeho buffery.
bh=get_hash_table(...);bforget(bh);
-
void ll_rw_block(int rw, int nr, struct buffer_head * bh[])
-
Zpusobi fyzicke clteni nebo zapis bloku. Nepouzivat, sic bude zbytecne hrkat
disk.
Takze pokud napiseme filesystem, musime jadru nejakym zpusobem sdelit, ze jsme
filesystem napsali. To ucinime funkci register_filesystem, jako parametr se ji
predava struktura file_system_type, ktera ma polozky: nazev, priznaky (dava se
tam vetsinou FS_REQUIRES_DEV jako ze to potrebuje zarizeni), funkce read_super a
pointer na dalsi filesystem, ktery nastavime NULL. Kdyz si pak filesystem nekdo
namountuje, tak se zavola funkce read_super, ktera jako parametr dostane
castecne vyplneny superblok, volby filesystemu (-o v prikazu mount) a posledni
parametr silent, ktery znamena, ze se nema rvat, kdyz se filesystem nepodari
namountovat. Funkce read_super ma za cil vyplnit superblok a nacist root inodu.
V superbloku se musi nastavit poloka s_op, aby ukazovala na struktutu
super_operations.
Tato struktura obsahuje seznam dalsich funkci pro spravu
filesystemu:
-
void (*read_inode) (struct inode *)
-
Nacte inodu. Inoda ma vyplneno jen cislo a zarizeni a cilem teto funkce je
nacist ostatni polozky. U neinodoveho filesystemu si musime rozmyslet, co bude
inoda. Inoda by mela obsahovat informace jako velikost souboru, datum, prava
pristupu. Inoda neobsahuje jmeno souboru. Inoda by nemela menit cislo (i kdyz i
to je mozne, kdyz uz to jinak nejde). Treba na FAT je vhodne za cislo inody
povazovat fyzicke umisteni adresarove polozky na disku.
-
void (*write_inode) (struct inode *)
-
Zapise inodu. Na 2.0 kernelch je volana kdyz posledni proces uvolni inodu, na
2.1 je volana asynchronne nekdy pozdeji. Na 2.1 jsou s touto funkci celkem
problemy, protoze se z ni nesmi zamykat dalsi struktury. Ja jsem to vyresil tak,
ze ji nepouzivam.
-
void (*put_inode) (struct inode *)
-
Provede se pri iput (viz nize).
-
void (*delete_inode) (struct inode *)
-
Provede se pri smazani souboru nebo adresare.
-
int (*notify_change) (struct dentry *, struct iattr *)
-
Zavola se pri zmene velikosti, vlastnika nebo prav pristupu inody. Muze vratit
chybu,
pokud si filesystem mysli, ze by takova zmena nemela nastat. Treba na MSDOS FS
je vhodne pomoci teto funkce zmenu vlastnika zakazat. Jinak se misto pointeru
na tuto funkci muze dat NULL,
pak Linux sam kontroluje, zda je zmena legalni, a ovladac filesystemu se o
to nemusi starat. K fyzickemu zapsani zmen na disk se tato funkce nepouziva
(vyjimku tvori moje HPFS, ktere ma extremne hnusnou strukturu a kde to jinak
nejde), zapis inody je treba napsat do write_inode.
-
void (*put_super) (struct super_block *)
-
Odmountovani filesystemu.
-
void (*write_super) (struct super_block *)
-
Syncnuti filesystemu.
-
int (*statfs) (struct super_block *, struct statfs *, int)
-
Informace o filesystemu (volne misto, volne inody apod...).
-
int (*remount_fs) (struct super_block *, int *, char *)
-
Remountovani (mount -o remount).
-
void (*clear_inode) (struct inode *)
-
Vola se pri uvolnovani inody z cache.
-
void (*umount_begin) (struct super_block *)
-
Vola se pri umount.
Bufferova cache neni jedina. Existuje jeste druha (a v 2.1.1xx jadrech i treti)
cache inodova. Funkce struct inode *iget(struct super_block *sb, int
ino)
vrati
inodu z cache (pokud tam uz je) nebo ji do cache prida a nacte ji pomoci
read_inode. Funkce void iput(struct inode *)
dekrementuje pocitadlo
a pokud je
nulove, inoda muze byt uvolnena. Pokud je inoda dirty (flag
inode->i_dirt)
,
zavola se write_inode. Na 2.0 jadrech se write_inode vola rovnou pri iput, na
2.1 se vola asynchrone nekdy pozdeji (pozor na to: zrovna vcera jsem opravil
chybu, ze to zarvalo, pokud se write_inode zavolalo pri mazani souboru).
Dale zde mame funkce clear_inode
- vymaze inodu,
mark_inode_dirty
- oznaci inodu
dirty (pouze v 2.1.1xx), na 2.0 se musi napsat inode->i_dirt = 1
.
Jadra 2.1.1xx disponuji dalsi cachi - je to cache na adresarove polozky
(dentry). Ke kazde adresarove inode je pridelena tato cache. Pri vyhledani
souboru se jadro nejdrive podiva do teto cache, pokud tam jmeno najde, vrati
rovnou inodu asociovanou s timto jmenem; pokud ho tam nenajde, tak zavola funkci
lookup (viz nize). Pokud ma nas filesystem nejake podivnosti pri praci se jmeny
souboru (treba je case-insensitive), meli bychom si napsat aspon dve funkce:
compare_dentry
a hash_dentry
- porovnani dvou jmen a zahashovani jmena. Tyto
funkce ulozime do struktury dentry_operations a na kazde dentry nastavime
pointer na tuto strukturu (viz filesystem AFS nebo moje HPFS).
Aby nas filesystem fungoval, musime napsat nejake funkce na bezne operace se
soubory a adresari. Pointery na tyto funkce dame do struktur inode_operations a
file_operations. Tyto funkce zde tedy popisu. Popis je vzhledem k 2.1 kernlu:
-
int (*create) (struct inode *,struct dentry *,int)
-
Vytvoru soubor. Inoda adresar, ve kterem se ma vytvorit, v dentry je
nazev polozky. Poledni parametr jsou prava pristupu. Vlastnika a grupu noveho
souboru je treba zjistit z current->fsuid a current->fsgid. (current je promenna
ukazujici na aktualni proces).
-
int (*lookup) (struct inode *,struct dentry *)
-
Vyhleda polozku dentry v adresari inode.
-
int (*link) (struct dentry *,struct inode *,struct dentry *)
-
Udela hard link. Nic vic o tom nevim, protoze na hpfs hardlinky nejsou :-)
-
int (*unlink) (struct inode *,struct dentry *)
-
Smaze soubor.
-
int (*symlink) (struct inode *,struct dentry *,const char *)
-
Udela symlink. Posledni parametr urcuje, kam ma symlink ukazovat, prostredni
parametr je nazev symlinku.
-
int (*mkdir) (struct inode *,struct dentry *,int)
-
Vytvori adresar.
-
int (*rmdir) (struct inode *,struct dentry *)
-
Smaze adresar. Pokud je adresar neprazdny, tak musi vratit chybu.
-
int (*mknod) (struct inode *,struct dentry *,int,int)
-
Jako create, az na to, ze nevytvari soubor, ale device, pipu nebo socket.
-
int (*rename) (struct inode *, struct dentry *, struct inode *, struct
dentry *)
-
Prejmenovani nebo presunuti souboru nebo adresare. Pozor! Tato funkce musi
kontrolovat, zda nepresouvame adresar do sebe sama.
-
int (*readlink) (struct dentry *, char *,int)
-
Nacte obsah symlinky.
-
struct dentry * (*follow_link) (struct dentry *, struct dentry *, unsigned
int)
-
Otevre soubor tam, kam symlika ukazuje.
-
int (*readpage) (struct file *, struct page *)
-
Provadi asynchroni cteni stranky souboru. Asynchroni znamena, ze funkce posle
pozadavek na i/o, ale hned se vrati a _neceka_, az pozadavek dobehne a stranka
je nactena. Pomoci teto funkce delaji jine funkce readahead a kdybyste ji
naprogramovali synchronne, tak by to vyrazne zpomalilo.
-
int (*writepage) (struct file *, struct page *)
-
Zapis stranky.
-
int (*bmap) (struct inode *,int)
-
Tato funkce je dost dulezita. Vraci, z jakych bloku se soubor sklada. Prvni
parametr je inoda souboru, druhy je cislo bloku v souboru. Funkce ma vratit
cislo fyzickeho bloku na disku, kde se blok souboru nachazi. Pokud napiseme tuto
funkci, muzeme se pak uz vyhnout psani nekterych jinych a pouzit misto toho
generic funkce jadra. Napriklad je pak mozne uvest funkci generic_readpage,
ktera je zase pouzivana funkcemi generic_file_read a generic_file_mmap. Pouziti
techto generic funkci je vyhodne, protoze jsou dobre optimalizovany a maji
readahead. Kdybychom si napsali vlastni read a mmap, jiste by nedosahly takovych
kvalit, jako generic funkce.
-
void (*truncate) (struct inode *)
-
Useknuti souboru. V inode->i_size je uz nova delka, nase funkce soubor na tuto
delku zkrati. Neni jasne, proc ma typ void. Co kdyby nastala chyba?
-
int (*permission) (struct inode *, int)
int (*smap) (struct inode *,int)
int (*updatepage) (struct file *, struct page *, const char *, unsigned long,
unsigned int, int)
int (*revalidate) (struct dentry *)
-
Tohle nevim. Ja tam mam NULL a funguje mi to :-)
A ted operace na souborech. Kazda inoda ma pointer na struct file_operations,
coz
je tabulka nasledujicich funkci. (otazkou je, jaka je vyhoda, ze jsou v linuxu
operace
rozdeleny na operace na souboru a na inode...)
-
loff_t (*llseek) (struct file *, loff_t, int)
-
Seek ... vetsinou NULL, coz znamena defaultni seek. Muzeme si tam dat treba
neco, co zacne readaheadit na zvolenem offsetu, ale neverim, ze to zpusobi
vyrazne zrychleni.
-
ssize_t (*read) (struct file *, char *, size_t, loff_t *)
-
Cteni souboru. Je dobre delat cteni pomoci funkce generic_file_read, protoze ta
ma readahead.
-
ssize_t (*write) (struct file *, const char *, size_t, loff_t *)
-
Zapis do souboru. Pri zapisu na ci za konec se soubor prodlouzi.
-
int (*readdir) (struct file *, void *, filldir_t)
-
Cteni adresare. Cte pouze nazvy adresarovych polozek, nic vic. Pozice odkud mame
cist je v file->f_pos. Poprve je to nula, pak si tuto polozku sami menime jak
chceme. filldir_t je typ
funkce, ktera se zavola pro kazdou adresarovou polozku. Tato funkce navratovou
hodnotou rekne, zda mame cist dalsi polozku nebo skoncit. Po precteni vsech
polozek poprve vratime hned 0 (bez chyby) a po dalsim zavolani teprv chybu. V
2.0 kernlech je bug, ze nektere filesystemy (treba msdos) to tak nedelaji.
Zpusobi to treba nefunkcnost prikazu diff -r.
Velmi neprijemna vlastnost teto funkce je, ze musi byt schopna pokracovat cteni
i kdyz mezi jednotlivymi volanimi doslo k vytvareni nebo mazani polozek v
adresari :-( Pritom musi pokracovat presne od toho mista, kde se pri minulem
volani skoncilo (jinak nefunguje treba mazani adresaru v Midnight Commanderu).
Na MSDOS FS nebo EXT2 to necini problemy, na HPFS to cini problemy znacne.
Jediny
zpusob, jak to vyresit je vest si z inody seznam ukazatelu na jeji soubory a pri
zmene adresare vsechny tyto pozice updatovat (coz je v pripade stromove
struktury adresare, jakou ma HPFS celkem vzrusujici).
-
unsigned int (*poll) (struct file *, struct poll_table_struct *)
-
Funkce poll. Vetsinou se zde dava NULL.
-
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long)
-
Taky NULL.
-
int (*mmap) (struct file *, struct vm_area_struct *)
-
Mmapovani souboru. Muzeme sem dat generic_file_mmap. generic_file_mmap ma jednu
znacnou nevyhodu, ze cte primo sektory do pameti a nenuluje prostor v poslednim
sektoru za koncem souboru. Na filesystemech, ktere tam drzi nuly (treba ext2) to
nevadi, ale na msdosu to uz vadi. Nektere programy (treba mkdep.c z kernelu)
vesele segfaultuji, pokud za koncem mmapovaneho souboru nejsou nuly. Jinak je to
taky trochu bezpecnostni dira; ja myslim, ze uzivatelsky proces by _nemel_ mit
pristup k nahodnym neinicializovanym datum.
-
int (*open) (struct inode *, struct file *)
-
Chceme delat nejakou cinnost pri otvirani souboru? Pokud ne, tak sem dame NULL.
-
int (*flush) (struct file *)
-
Flushnuti bufferu. Vetsinou NULL.
-
int (*release) (struct inode *, struct file *)
-
Zavreni souboru. Pokud pri nem nechceme nic delat, tak NULL.
-
int (*fsync) (struct file *, struct dentry *)
-
Fsync. Dame sem bud file_fsync, nebo jinou vlastorucne napsanou funkci.
Pozor, pokud sem dame NULL, tak bude volani sys_fsync vracet chybu - to pusobi
problemy treba v Netscapu (nejdou ukladat bookmarky).
-
int (*fasync) (int, struct file *, int)
-
Zase nejaky sync. NULL tam davat.
-
int (*check_media_change) (kdev_t dev)
-
Kontrola zmeny media v ovladaci filesystemu? No nevim...
-
int (*revalidate) (kdev_t dev)
-
Ani nevim, kdy je to volano - najdete si to sami pomoci grepu.
-
int (*lock) (struct file *, int, struct file_lock *)
-
Pri zamykani vetsinou taky nemusime delat nejake specialni akce.
Linux ma, jak znamo, multitasking. Takze se nam muze do naseho driveru dostat
vic procesu. Pokud jeden proces treba chce cist z disku, tak se rizeni preda
jinemu procesu, a ten muze klidne taky vlezt do nejake funkce VFS. Aby se dva
procesy nesnazili soucasne modifikovat nejakou strukturu, je treba ji zamknout.
Globalni veci (superblok, bitmapy) se zamykaji pomoci uz napsanych funkci
lock_super a unlock_super. Pokud chceme zamykat i inody, musime si vetsinou
vytvorit zamky vlastni (ty, co tam jsou jsou casto nepouzitelne - treba lookup
se nekdy vola s otevrenym i->i_sem a nekdy se zavrenym - plati pro kernly 2.0,
jak je to v 2.1 nevim). Ty zamky, co uz v kernlu jsou, nedovoli, aby vic procesu
soucasne modifikovalo adresar nebo zapisovalo do souboru, ale bohuzel uz dovoli,
aby se adresar nebo soubor soucasne modifikoval i cetl, coz je casto nezadouci,
protoze se mohou nacist nesmyslne hodnoty. Zamek vytvorime bud tak, ze ke
strukture, kterou chceme zamknout pridame dve polozky
int lock;
struct wait_queue queue;
Zamkneme pak pomoci while (lock) sleep_on(&queue); lock=1;
a odemkneme lock=0; wake_up(&queue);
Jak jste jiste postrehli, tento zamek selze na
viceprocesorovych systemech. To ale nevadi, protoze do VFS se muze vzdy dostat
jen jeden procesor. I funkce lock_super a unlock_super jsou napsany timto
zpusobem. Podivame-li se na funkce sys_open, sys_read a dalsi,
zjistime, ze na jejich zacatku je vzdy lock_kernel a na konci unlock_kernel.
Dalsi zpusob je pouziti semaforu (ty jsou SMP-safe). Do struktury pridame
polozku struct semaphore sem = MUTEX;
Zamkneme pomoci
down(&sem)
a odemkneme up(&sem)
.
Kdyz neco dvakrat zamkneme (treba down(&sem);down(&sem);
),
nastane takzvany
deadlock - proces se navzdy zablokuje, nejde killnout (ani -9) a musime
rebootovat. Stejne tak to muze spadnout, kdyz se dva procesy pokousi zamcit dve
struktury v opacnem poradi - tj. proces 1 vykona
down(&inode1->sem);down(&inode2->sem);
a proces 2
down(&inode2->sem);down(&inode1->sem);
Jiste si domyslite, co se stane. Ja jsem
to obesel tak, ze inody zamykam podle cisla vzestupne.
Na zaver zde uvedu nekolik chyb, co jsem v Linuxu nasel (ve verzi 2.0.33).
Nemusime vsak kvuli nim podlehat hysterii, kuprikladu OS/2 ma bugy vetsi.
- Fat filesystem: kdyz se narazi v adresari na nulu, tak se pouze preskoci
prislusna polozka a cte se dal, zatimco msdos za nulou uz nic necte. Diky teto
chybe Linux spatne cte zavirovane diskety.
- Read-only Hpfs: dost chyb...
- Isofs: na konci read_super se tam vola check_disk_change() a kdyz neuspeje,
tak se neprovede spravny navrat - root inoda zustane naveky viset a filesystem
nejde ani namountovat ani odmountovat.
- Fat a vetsina dalsich (ne ext2): generic_file_mmap pri nacitani souboru
nenuluje oblast za koncem souboru v poslednim sektoru. Kdyz si tedy nammapuju
soubor, za jeho koncem uvidim i nahodny obsah disku. Ja myslim, ze uzivatelsky
program by k nahodnym datum nemel mit pristup. Navic to nekterym programum
(mkdep.c z jadra) vadi.
- Fat: readdir po precteni celeho adresare vrati v dalsim volani chybu, ale
spravne ma v dalsim volani nevratit nic a az potom chybu. Pusobi to problem s
prikazem diff -r. V 2.1 tato chyba neni.
mkswap;swapon;swapoff;mkfs.msdos
na nejake partion vede ke
kernel panic. V
2.1.123 jeste tato chyba je, v 2.1.129 uz ne (ze by ty bug reporty prece jen
nekdo cetl?)
Ponevadz EXT2 ci FAT filesystemy jiste kazdy zna, uvedu zde strukturu mene
znameho filesystemu HPFS, na kterem bezi OS/2. Detailnejsi informace - viz
hpfs.h. Bloky maji velikost
512b, ale nektere (bitmapy a dnody) maji 2048b. V sektoru 16 je superblok a v
sektoru 17 spareblok. V nich jsou obecne informace, jake v superblocich
zpravidla byvaji. Na disku jsou rozmisteny bitmapy - vetsinou tak, aby byly
blizko datum, ale neni to nutne. Kazdy soubor nebo adresar ma fnodu (neco jako
inoda na EXT2), ale na rozdil od inod muzou byt fnody kdekoliv na disku. Fnoda
obsahuje v pripade souboru alokacni informace nebo (pokud ma soubor vic jak 8
fragmentu) pointer na alokacni b-strom. Na rozdil od EXT2 zde neni seznam vsech
bloku souboru nalezicich, ale seznam ve tvaru zacatek-delka. V pripade adresare
fnoda ukazuje na korenovou dnodu adresare, ktera obsahuje direnty (polozky
adresare), kdyz zacne byt dirent moc, tak se seskupi nekolik dnod v b-strom
setrideny podle jmen souboru. Vyhledani souboru v
adresari ma tedy logaritmickou slozitost vzhledem k poctu polozek adresare.
Direnty navic obsahuji i takove informace, jako delku, datum
apod, takze kdyz dame v OS/2 'ls -la', staci pouze precist adresar a nemusi se
tolik seekovat jako na EXT2. HPFS ma uprostred disku vyhrazeno nekolik MB na
dnody - to snizuje (lepe receno ma snizovat, ale diky dementnimu umistovani fnod
se tak nedeje) seekovani. Kdyz je tento prostor plny tak se dnody umistuji
kdekoli.
Kazda fnoda navic muze obsahovat extended attributy
- dvojice (jmeno, hodnota). Kdokoli si tam muze zapsat, co chce. Vesinou jsou EA
v fnode, kdyz jsou velke, tak se presunou do samostatneho sektoru, nebo jejich
umisteni urcuje strom anod.
+------------+
| superblock |
+------------+
|
|
|
Korenovy adresar filesystemu: V
+------------+ +---------------------+
| root fnode |---->| extended attributes |
+------------+ +---------------------+
|
|
V
+------------+ Polozky v jednom adresari jsou ve
| root dnode | stromu, takze vyhledani souboru se
+------------+ deje v logaritmickem case
/ | \
/ | \
/ | \
V V V
+-----+ +-----+ +-----+
|dnode| |dnode| |dnode|
+-----+ +-----+ +-----+
/ | | | | | | | |
/ |
/ ... |
Podadresar: V V Soubor:
+-----------------+ +--+ +------------+ +---------------------+
| directory fnode |->|ea| | file fnode |---->| extended attributes |
+-----------------+ +--+ +------------+ +---------------------+
| | ... |
| V V
V +-------+ +-------+
+------------+ | anode | | anode |
| root dnode | +-------+ +-------+
+------------+ | | | | | | | |
a zase dalsi strom V V V V V V V V
strom anod, v jehoz listech je informace o
fyzickem umisteni bloku na disku; listy jsou
trojice (offset v souboru, sektor na disku, pocet),
tedy ne primy seznam bloku, jako na EXT2
Kdyz se nad tim zamyslime, zjistime, ze HPFS ma oproti EXT2 nektere dobre
vlastnosti, ktere zpusobuji urychleni:
-
Alokacni strom - je podle meho nazoru lepsi. Je otazkou, zda by byl rychlejsi
alokacni strom, nebo linearni seznam ve tvaru (sektor na disku, pocet
sektoru), jaky ma asi NTFS, ale to, co ma EXT2, se mi jevi jako naprosta
zvrhlost.
-
Umistovani fnod na libovolna mista - je nejspis lepsi, nez predalokovane misto
na inody na EXT2 - inody bud zabiraji zbytecne mnoho mista a nebo dochazeji.
Bylo by rozumne v pripade jejich nedostatku je alokovat i jinde.
-
Predalokovany prostor na adresare uprostred disku - to je dobra myslenka, ale v
OS/2 blbe implementovana. OS/2 sice umistuje adresare doprostred, ale fnody (a
to i fnody adresaru!) se snazi umistit na zacatek disku :-(. Muj driver to
nedela, fnody se snazi alokovat blizko stredu disku. Kdyby se vsechny systemove
informace narvaly doprostred, meli bychom velmi rychle prohledavani adresaroveho
stromu a kontrolu disku, ale zase vetsi seekovaci cas od systemovych dat k
vlasnimu obsahu souboru.
-
Adresar je strom - to podle meho nazoru moc dobre neni - je otazka, zda je
rychlejsi linearne cist dlouhy adresar nebo seekovat ve stromu. Navic to
priserne zneprijemnuje updatovani informaci v direntach.
-
Vice informaci v direntach. Kdyz napiseme v OS/2 'ls -la', nemusi se seekovat po
jednotlivych inodach jako v EXT2 a tak je to rychlejsi. Mozna by bylo rozumne do
dirent dat i alokacni informaci a tak by se ani pri otevirani souboru nemuselo
seekovat.
-
V OS/2 se muze pri vytvareni souboru specifikovat jeho velikost (pokud je znama)
a filesystem se na nej pokusi vyhradit misto presne na miru.
Ma smysl se pokouset EXT2 o tyto featury vylepsit (nebo napsat nove HP-EXT3-FS)?
Obavam se, ze asi ne. :-(
Sotvaktery uzivatel bude riskovat ztratu dat na disku za drobne urychleni.