Virtual Filesystem (cunoscut și sub prescurtarea VFS) este o componentă a nucleului care se ocupă de tratarea tuturor apelurilor de sistem legate de fișiere și sisteme de fișiere. VFS este o interfață generică între utilizator și un sistem de fișiere particular. Acest lucru simplifică implementarea sistemelor de fișiere și oferă o integrare facilă a mai multor sisteme de fișiere. În acest fel, implementarea unui sistem de fișiere este realizată prin folosirea API-ului pus la dispoziție de VFS iar părțile generice de comunicație cu dispozitivul hardware și subsistemul de I/O sunt rezolvate de VFS.
Din punct de vedere funcțional sistemele de fișiere pot fi grupate în:
O instanță de nucleu Linux va folosi VFS pentru ierarhia (de tip arbore) de directoare și fișiere. Un nou sistem de fișiere va fi adăugat ca un subarbore a VFS prin operațiunea de montare. Fiecare sistem de fișiere este de obicei montat de pe mediul pentru care a fost construit (de pe un dispozitiv de tip bloc, de pe rețea etc.). În particular însă, VFS-ul poate folosi drept dispozitiv de tip bloc virtual un fișier normal, deci se pot monta sisteme de fișiere pentru disc peste fișiere normale. Astfel, se pot crea stive de sisteme de fișiere.
Ideea de bază a VFS-ului este de a oferi un singur model de fișier, care să poată reprezenta fișierele din orice sistem de fișiere. Driver-ul de sistem de fișiere este responsabil pentru aducerea la numitorul comun. Astfel se poate crea o singură structură de directoare care conține întreg sistemul. Va exista un sistem de fișiere care va fi rădăcina, restul fiind montate în diverse directoare ale acestuia.
Modelul general al sistemului de fișiere, la care trebuie să se reducă orice sistem de fișiere implementat, este format din mai multe entități cu rol bine definit: superbloc, inode, file și dentry. Aceste entități sunt metadatele sistemului de fișiere (conțin informații despre date sau despre alte metadate).
Entitățile modelului interacționează cu ajutorul unor subsisteme ale VFS sau ale nucleului: cache-ul de dentry-uri, cache-ul de inode-uri, buffer cache-ul. Fiecare entitate este tratată ca un obiect: are o structură de date asociată și un pointer la o tabelă de metode. Inducerea unui comportament particular al fiecărei componente este făcut prin înlocuirea metodelor asociate.
Superblocul stochează informațiile necesare unui sistem de fișiere montat:
Localizare:
Inode-ul (index node) menține informații despre un fișier în sensul general (abstractizare): fișier obișnuit (regular file), director, fișier special (pipe, fifo), dispozitiv de tip bloc, dispozitiv de tip caracter, link, sau orice poate fi abstractizat ca fișier.
Un inode menține informații precum:
Localizare:
Fiecare inode este în general identificat de un număr. Pe Linux, argumentul -i
la comanda ls
precizează numărului inode-ului asociat fișierului:
razvan@valhalla:~/school/2008-2009/so2/wiki$ ls -i 1277956 lab10.wiki 1277962 lab9.wikibak 1277964 replace_lxr.sh 1277954 lab9.wiki 1277958 link.txt 1277955 tema4.wiki
File este componenta din modelul general al sistemului de fișiere care se apropie cel mai mult de utilizator. Structura există doar ca entitate VFS în memorie și nu are corespondent fizic pe disc.
În vreme ce inode-ul abstractizează un fișier situat pe disc, file-ul abtractizează un fișier deschis. Din punctul de vedere al procesului, entitatea file abstractizează fișierul. Din punctul de vedere al implementării sistemului de fișiere, însă, inode-ul este entitatea care abstractizează fișierul.
Structura file menține informații precum:
Localizare:
Dentry (directory entry) realizează asocierea între un inode și numele fișierului.
În general o structură dentry conține două câmpuri:
dentry reprezintă o componentă specifică dintr-o cale, care poate fi un director sau un fișier. Spre exemplu, pentru calea /bin/vi, vor fi create obiecte dentry pentru /, bin și vi (un total de 3 obiecte dentry).
În versiunea actuală, kernel-ul Linux are suport pentru un număr în jur de 50 de sisteme de fișiere, dintre care:
Pe un singur sistem, însă, este puțin probabil să existe mai mult de 5-6 sisteme de fișiere. Din acest motiv, sistemele de fișiere (sau, mai corect, tipurile de sisteme de fișiere) sunt implementate ca module și pot fi încărcate sau descărcate oricând.
Pentru a putea încărca / descărca în mod dinamic un modul de sistem de fișiere este necesar un API de înregistrare / deînregistrare a tipului sistemului de fișiere în / din sistem. Structura care descrie un anumit sistem de fișiere este struct file_system_type:
#include <linux/fs.h> struct file_system_type { const char *name; int fs_flags; struct dentry *(*mount) (struct file_system_type *, int, const char *, void *); void (*kill_sb) (struct super_block *); struct module *owner; struct file_system_type * next; struct hlist_head fs_supers; struct lock_class_key s_lock_key; struct lock_class_key s_umount_key; //... };
name
este șirul de caractere prin care numele va identifica un sistem de fișiere (argumentul dat la mount -t
).owner
este THIS_MODULE
pentru sisteme de fișiere implementate în module, și NULL
dacă sunt scrise direct în kernel.mount
citește superblocul de pe disc în memorie la încărcarea sistemului de fișiere. Funcția este proprie fiecărui sistem de fișiere. Pentru mai multe detalii, citiți Secțiunea Funcțiile mount kill_sb .kill_sb
eliberează superblocul din memorie, citiți Secțiunea Funcțiile mount kill_sb .fs_flags
precizează flag-urile cu care trebuie montat sistemul de fișiere. Un exemplu de flag este FS_REQUIRES_DEV care precizează VFS-ului că sistemul de fișiere are nevoie de un disc (nu este un sistem de fișiere virtual).fs_supers
este o listă ce conține toate superblocurile asociate acestui sistem de fișiere. Dat fiind că același tip de sistem de fișiere poate fi montat de mai multe ori, pentru fiecare mount va exista un superbloc separat.Înregistrarea unui sistem de fișiere în sistem se realizează, în general, în funcția de inițializare a modulului. Pentru înregistrare, programatorul va trebui să
La descărcarea modulului trebuie să se deînregistreze sistemul de fișiere prin apelarea funcției unregister_filesystem.
Un exemplu de înregistrare a unui sistem de fișiere virtual se găsește în codul pentru ramfs:
static struct file_system_type ramfs_fs_type = { .name = "ramfs", .mount = ramfs_mount, .kill_sb = ramfs_kill_sb, .fs_flags = FS_USERNS_MOUNT, }; static int __init init_ramfs_fs(void) { if (test_and_set_bit(0, &once)) return 0; return register_filesystem(&ramfs_fs_type); }
La montarea sistemului de fișiere, nucleul apelează funcția mount definită în cadrul structurii struct file_system_type. Funcția face un set de inițializări și returnează un dentry (structura struct dentry) ce reprezinta directorul punctului de mount. De obicei, mount
este o funcție simplă care apelează una din funcțiile:
sockfs
, pipefs
, în general sisteme de fișiere care nu pot fi montate)Aceste funcții primesc ca parametru un pointer spre o funcție fill_super care va fi apelată după inițializarea superblocului pentru terminarea inițializării acestuia de către driver. Un exemplu de o astfel de funcție găsiți în secțiunea fill_super.
La demontare nucleul apelează kill_sb, care face operații de tip cleanup și apelează una din funcțiile:
Un exemplu pentru un sistem de fișiere fără suport pe disc este funcția ramfs_mount din sistemul de fișiere ramfs:
struct dentry *ramfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return mount_nodev(fs_type, flags, data, ramfs_fill_super); }
Un exemplu pentru un sistem de fișiere pentru disc este funcția minix_mount din sistemul de fișiere minix:
struct dentry *minix_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) { return mount_bdev(fs_type, flags, dev_name, data, minix_fill_super); }
Superblocul există atât ca entitate fizică (entitate pe disc) cât și ca entitate VFS (în cadrul structurii struct super_block). Superblocul conține numai metainformație și este folosit pentru scrierea și citirea de metainformații de pe disc (inode-uri, directory entries). Un superbloc (și implicit structura struct super_block) va conține informații despre dispozitivul utilizat, lista de inode-uri, pointer-ul la inode-ul rădăcină al sistemului de fișiere și un pointer la operațiile de superbloc.
O parte din definiția structurii struct super_block este prezentată mai jos:
struct super_block { //... dev_t s_dev; /* identifier */ unsigned char s_blocksize_bits; /* block size in bits */ unsigned long s_blocksize; /* block size in bytes */ unsigned char s_dirt; /* dirty flag */ loff_t s_maxbytes; /* max file size */ struct file_system_type *s_type; /* filesystem type */ struct super_operations *s_op; /* superblock methods */ //... unsigned long s_flags; /* mount flags */ unsigned long s_magic; /* filesystem’s magic number */ struct dentry *s_root; /* directory mount point */ //... char s_id[32]; /* informational name */ void *s_fs_info; /* filesystem private info */ };
Superblocul memorează informația globală pentru o instanță a unui sistem de fișiere:
dentry
-ul directorului rădăcină
În plus, un pointer generic (void *
) memorează date private sistemului de fișiere. Superblocul poate fi privit ca un obiect abstract căruia îi sunt adăugate date proprii în momentul în care există o implementare concretă.
Operațiile pe superbloc sunt descrise de structura struct super_operations:
struct super_operations { //... int (*write_inode) (struct inode *, struct writeback_control *wbc); struct inode *(*alloc_inode)(struct super_block *sb); void (*destroy_inode)(struct inode *); void (*put_super) (struct super_block *); int (*statfs) (struct dentry *, struct kstatfs *); int (*remount_fs) (struct super_block *, int *, char *); //... };
Câmpurile structurii sunt pointeri de funcții cu următoarele semnificații:
write_inode, alloc_inode, destroy_inode
scrie, alocă, respectiv eliberează resurse asociate unui inode și sunt descrise în Laboratorul 09;put_super
este apelată în momentul eliberării superblocului, la umount
; în cadrul acestei funcții trebuie eliberate orice resurse (în general memorie) din datele private ale sistemului de fișiere, dacă există memorie alocată;remount_fs
este apelată atunci când nucleul a detectat că se încearcă o remontare (flag-ul de montare MS_REMOUNTM
); cel mai adesea aici trebuie să se detecteze dacă se încearcă o trecere read-only → read-write sau viceversa; acest lucru se poate face simplu pentru că se pot accesa flag-urile vechi (în sb→s_flags
) cât și cele noi (în flags
); data
e un pointer către datele trimise de mount
ce reprezintă opțiuni specifice sistemului de fișiere;statfs
se apelează atunci când se face un apel de sistem statfs
(încercați stat –f
sau df
); în cazul acestui apel trebuie completate câmpurile structurii struct kstatfs, aşa cum se face, de exemplu, în funcţia ext4_statfs.
După cum s-a specificat, funcția fill_super
este apelată pentru terminarea inițializării superblocului. Această inițializare presupune completarea câmpurilor structurii struct super_block și inițializarea inode-ului rădăcină.
Un exemplu de implementare este funcția ramfs_fill_super apelată pentru inițializarea câmpurile din superbloc care au mai rămas de inițializat:
#include <linux/pagemap.h> #define RAMFS_MAGIC 0x858458f6 static const struct super_operations ramfs_ops = { .statfs = simple_statfs, .drop_inode = generic_delete_inode, .show_options = ramfs_show_options, }; static int ramfs_fill_super(struct super_block *sb, void *data, int silent) { struct ramfs_fs_info *fsi; struct inode *inode; int err; save_mount_options(sb, data); fsi = kzalloc(sizeof(struct ramfs_fs_info), GFP_KERNEL); sb->s_fs_info = fsi; if (!fsi) return -ENOMEM; err = ramfs_parse_options(data, &fsi->mount_opts); if (err) return err; sb->s_maxbytes = MAX_LFS_FILESIZE; sb->s_blocksize = PAGE_SIZE; sb->s_blocksize_bits = PAGE_SHIFT; sb->s_magic = RAMFS_MAGIC; sb->s_op = &ramfs_ops; sb->s_time_gran = 1; inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0); sb->s_root = d_make_root(inode); if (!sb->s_root) return -ENOMEM; return 0; }
În kernel sunt disponibile funcții generice pentru implementarea operațiilor cu structurile sistemelor de fișiere. Funcțiile generic_drop_inode și simple_statfs, folosite în codul de mai sus, sunt astfel de funcții și pot fi folosite în implementarea driverelor, daca funcționalitatea acestora este suficientă.
În mare, funcția ramfs_fill_super din codul de mai sus setează câteva câmpuri din superbloc, apoi citește inode-ul rădăcină și alocă dentry-ul rădăcină. Citirea inode-ului radăcină se face în funcția ramfs_get_inode, și constă din alocarea unui nou inode folosind new_inode și inițializarea acestuia. Pentru eliberarea inode-ului se folosește iput, iar pentru alocarea dentry-ului rădăcină d_make_root.
Un exemplu de implementare pentru un sistem de fișiere pentru disc este funcția minix_fill_super din sistemul de fișiere minix.
Funcționalitatea pentru sistemul de fișiere pentru disc este similară cu cea a sistemului de fișiere virtual, cu deosebirea folosirii buffer cache-ului. De asemenea, sistemul de fișiere minix
păstrează date private de forma struct minix_sb_info. Mare parte a acestei funcții se ocupa cu inițializarea acestor date private (care nu sunt incluse în extrasul de cod de mai sus, pentru claritate). Datele private sunt alocate folosind funcția kzalloc și sunt păstrate în câmpul s_fs_info
al superblocului.
Funcțiile VFS-ului primesc de obicei ca parametru superblocul, un inode sau/și un dentry, care conțin un pointer către superbloc, astfel încât aceste date private pot fi accesate ușor.
Buffer cache-ul este un subsistem în kernel care se ocupă caching-ul (atât la citire cât și la scriere) blocurilor de pe dispozitivele de tip bloc. Entitatea de bază cu care lucrează buffer cache-ul este struct buffer_head. Câmpurile mai importante din această structură sunt:
b_data
, pointer către o zonă din memorie de unde au fost citite sau unde trebuie scrise dateb_size
, dimensiunea buffer-uluib_bdev
, device-ul cu care se lucreazăb_blocknr
, numărul blocului de pe device care a fost încărcat sau trebuie să fie salvat pe discb_state
, starea buffer-uluiExistă câteva funcții importante ce lucrează cu astfel de structuri:
buffer_head
; în caz de succes întoarce un pointer către buffer_head
, altfel întoarce NULL
;BH_Dirty
); buffer-ul va fi scris pe disc la un moment ulterior de timp (din când în când kernel thread-ul bdflush
se trezește și scrie buffere pe disc);Superblocul conține de obicei o hartă a blocurilor ocupate (de inode-uri, dentry, date) sub forma unui bitmap (vector de biți). Pentru lucrul cu aceste hărți se recomandă folosirea următoarelor funcții:
Pentru verificarea tipului unui inode se pot folosi următoarele macrodefiniții: