This is an old revision of the document!
Pentru desfășurarea laboratorului pornim de la arhiva de sarcini a laboratorului. Descărcăm și decomprimăm arhiva în directorul so2/
din directorul home al utilizatorului student
de pe sistemul de bază (stația mjolnir
):
student@mjolnir:~$ cd so2/ student@mjolnir:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab08-tasks.zip student@mjolnir:~/so2$ unzip lab08-tasks.zip student@mjolnir:~/so2$ tree lab08-tasks
În cadrul directorului lab08-tasks/
se găsesc resursele necesare pentru dezvoltarea exercițiilor de mai jos: fișiere schelet de cod sursă, fișiere Makefile și Kbuild, scripturi și programe de test.
Vom dezvolta exercițiile pe sistemul de bază (stația mjolnir
) și apoi le vom testa pe mașina virtuală QEMU. După editarea și compilarea unui modul de kernel îl vom copia în directorul dedicat pentru mașina virtuală QEMU folosind o comandă de forma
student@mjolnir:~/so2$ cp /path/to/module.ko ~/so2/qemu-so2/fsimg/root/modules/
unde /path/to/module.ko
este calea către fișierul obiect aferent modulului de kernel. Apoi vom porni, din directorul ~/so2/qemu-so2/
mașina virtuală QEMU folosind comanda
student@mjolnir:~/so2/qemu-so2$ make
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU pentru a încărca și descărca modulul de kernel:
# insmod modules/module-name.ko # rmmod module/module-name
unde module-name
este numele modulului de kernel.
Ctrl+Alt+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/qemu-so2/
.student@mjolnir:~$ netcat -lup 6666
Pentru început ne propunem să ne acomodăm cu interfața expusă de nucleul Linux și de componenta VFS (Virtual File System). De aceea, pentru început vom lucra cu un sistem de fișiere simplu, virtual (adică fără suport fizic pe disc). Sistemul de fișiere poartă denumirea myfs
.
Pentru aceasta vom accesa subdirectorul myfs/
din arhiva de sarcini a laboratorului. Vom implementa în cadrul acestui laborator operațiile pe superbloc, iar laboratorul viitor vom continua cu operațiile pe inode.
Primul pas în lucrul cu sistemul de fișiere este înregistrea și deînregistrarea acestuia. Dorim să facem acest lucru pentru sistemul de fișiere descris în fișierul myfs.c
. Parcurgeți conținutul fișierului și urmăriți indicațiile marcate cu TODO 1
.
Pașii pe care trebuie să-i realizați sunt descriși în secțiunea Înregistrarea și deînregistrarea sistemelor de fișiere. Folosiți șirul "myfs"
pentru numele sistemului de fișiere.
myfs_mount
, prezentă în scheletul de cod. În cadrul funcției myfs_mount
apelați funcția specifică unui sistem de fișiere fără suport pe disc (vedeți secțiunea Funcțiile mount, kill_sb; este vorba de funcția mount_nodev). Ca argument pentru funcția de mount specifică folosiți funcția de tipul funcția fill_super definită în scheletul de cod.
Pentru distrugerea superblocului (efectuată la demontare) folosiți funcția kill_litter_super, funcție specifică, de asemenea, unui sistem de fișiere fără suport pe disc.
După completarea secțiunilor marcate cu TODO 1
, compilați modulul, copiați-l în directorul dedicat pentru mașina virtuală QEMU și porniți mașina virtuală. Încărcați modulul în kernel și apoi verificați prezența sistemului de fișiere myfs
în cadrul fișierului /proc/filesystems
.
Pe moment sistemul de fișiere este doar înregistrat, nu expune operații de utilizare a acestuia. Dacă încercăm să-l montăm, operația va eșua. Pentru a încerca montarea, creăm punctul de montare /mnt/myfs/
# mkdir -p /mnt/myfs
și apoi folosim comanda mount
# mount -t myfs none /mnt/myfs
Mesajul de eroare obținut ne arată că nu avem implementate operațiile de lucru pe superbloc. Va trebui să implementăm operațiile de lucru pe superbloc și să inițializăm inode-ul rădăcină. Vom face aceste lucru în continuare.
none
trimis comenzii mount
indică faptul că nu avem un dispozitiv de pe care montăm, sistemul de fișiere fiind unul virtual. În mod similar sunt montate sistemele de fișiere procfs
sau sysfs
pe sistemele Linux.
Pentru a putea monta sistemul de fișiere, trebuie să completăm superblocul acestuia, adică o structură generică din VFS de tipul struct super_block
. Completarea structurii o vom face în cadrul funcției myfs_fill_super
; este vorba de variabila sb
transmisă ca argument funcției. Urmăriți indicațiile marcate cu TODO 2
.
myfs_fill_super
puteți porni de la exemplul din secțiunea funcția fill_super.
Pentru câmpurile structurii de superbloc, acolo unde se poate, folosiți macro-urile definite în cadrul scheletului de cod.
Câmpul s_op
al superblocului trebuie inițializat la structura de operații a superblocului de tipul struct super_operations
. Trebuie să definiți o astfel de structură.
Informații despre definirea structurii struct super_operations
și despre completarea superblocului se găsesc în secțiunea Operațiile pe superbloc.
drop_inode
și statfs
ale structurii struct super_operations
.
Deși în acest moment superblocul va fi inițializat corespunzător, operația de mount va eșua în continuare. Pentru ca operația să fie definitivată cu succes, va trebui inițializat inode-ul rădăcină, lucru pe care îl vom facem la exercițiul următor.
Inode-ul rădăcină este inode-ul aferent directorului rădăcină al sistemului de fișiere (adică /
). Inițializarea sa se face la montare. Funcția myfs_fill_super
, apelată la montare, este cea care apelează funcția myfs_get_inode
care creează și inițializează un inode. În mod obișnuit, funcția este folosită pentru crearea și inițializarea tuturor inode-urilor; în acest exercițiu, însă, vom crea doar inode-ul rădăcină.
Inode-ul este alocat în cadrul funcției myfs_get_inode
. Este vorba de variabila locală inode
, alocată cu ajutorul apelului new_inode.
Pentru a definitiva cu succes montarea sistemului de fișiere, va trebui să completați funcția myfs_get_inode
. Urmăriți indicațiile marcate cu TODO 3
. Un punct de plecare este funcția ramfs_get_inode.
uid
, gid
, mode
, puteți folosi funcția inode_init_owner așa cum este folosită și în funcția ramfs_get_inode. Când apelați funcția inode_init_owner folosiți NULL
ca al doilea parametru, întrucât nu există director părinte pentru inode-ul creat.
Inițializați câmpurile i_atime
, i_ctime
și i_mtime
ale inode-ului VFS la valoarea CURRENT_TIME.
Va trebui să inițializați operațiile pentru inode-ul de tip director. Pentru aceasta urmați pașii:
i_op
și i_fop
, folosiți funcţii din kernel deja implementate:i_op
: simple_dir_inode_operations.i_fop
: simple_dir_operations
Acum putem să montăm sistemul de fișiere. Urmați pașii indicați mai sus pentru a compila modulul de kernel, copia pe mașina virtuală și porni mașina virtuală și apoi inserați modulul de kernel, creați punctul de montare /mnt/myfs/
și montați sistemul de fișiere. Verificăm că sistemul de fișiere a fost montat investigând fișierul /proc/mounts
.
Ce număr de inode are directorul /mnt/myfs
? De ce?
ls -di /path/to/directory
/path/to/directory/
este calea către directorul al cărui număr de inode vrem să-l afișăm.
Verificăm statisticile sistemului de fișiere myfs
cu ajutorul comenzii:
stat -f /mnt/myfs
Vrem să vedem ce conține punctul de montare /mnt/myfs
și dacă putem crea fișiere. Pentru aceasta rulăm comenzile:
# ls -la /mnt/myfs # touch /mnt/myfs/a.txt
Observăm că nu putem crea fișierul a.txt
în sistemul de fișiere. Acest lucru se întâmplă pentru că nu avem implementate operațiile de lucru cu inode-uri în structura struct super_operations
. Vom implementa aceste operații laboratorul următor.
Demontăm sistemul de fișiere folosind comanda
umount /mnt/myfs
Descărcați și modulul de kernel aferent sistemul de fișiere.
test-myfs.sh
:
./test-myfs.sh
Statisticile afişate pentru sistemul de fişiere sunt minimale, întrucât informaţiile sunt furnizate de către funcţia simple_statfs.
În continuare vom implementa bazele unui sistem de fișiere foarte simplu, denumit minfs
, cu suport pe disc. Vom folosi un disc din cadrul mașinii virtuale pe care îl vom formata și monta cu sistemul de fișiere minfs
.
Pentru aceasta vom accesa directorul minfs/
din archiva de sarcini a laboratorului și vom folosi scheletul de cod minfs.c
. La fel ca la myfs
nu vom implementa operații de lucru cu inode-urile, limitându-ne doar la operațiile de lucru cu superblocul și, așadar, la montare. Restul operațiilor le vom implementa în laboratorul următor.
Urmăriți diagrama de mai jos pentru a clarifica rolul structurilor din cadrul sistemului de fișiere minfs
.
Pentru înregistrarea și deînregistrarea sistemului de fișiere va trebui să completați, în fișierul minfs.c
, structura minfs_fs_type
și funcția minfs_mount
. Urmăriți indicațiile marcate cu TODO 1
.
minfs_mount
prezentă în scheletul de cod. În cadrul acestei funcții apelați funcția aferentă pentru montarea unui sistem de fișiere cu suport de disc (vedeți secțiunea Funcțiile mount, kill_sb; este vorba de funcția mount_bdev).
Alegeți funcția cea mai potrivită pentru distrugerea superblocului (efectuată la demontare); țineți cont de faptul că este un sistem de fișiere cu suport pe disc. Este vorba de funcția kill_block_super.
Inițializați câmpul fs_flags
al structurii minfs_fs_type
cu valoarea corespunzătoare pentru un sistem de fișiere cu suport pe disc. Vedeți secțiunea Înregistrarea și deînregistrarea sistemelor de fișiere.
Funcția de completare a superblocului este minfs_fill_super
.
După completarea secțiunilor marcate cu TODO 1
, compilați modulul, copiați-l în directorul dedicat pentru mașina virtuală QEMU și porniți mașina virtuală. Încărcați modulul în kernel și apoi verificați prezența sistemului de fișiere minfs
în cadrul fișierului /proc/filesystems
.
Pentru a putea testa montarea sistemului de fișiere va trebui să formatăm discul cu structura acestuia. Formatarea necesită utilitarul de formatare mkfs.minfs
compilabil din fișierul mk_minfs.c
folosind comanda
student@mjolnir:~so2/lab08-tasks/minfs$ make -f Makefile.format
În urma compilării rezultă executabilul mkfs.minfs
care trebuie copiat, împreună cu modulul de kernel, în mașina virtuală.
După compilare, copiere și pornirea mașinii virtuale, formatăm discul /dev/vdb
folosind utilitarul de formatare:
# ./mkfs.minfs /dev/vdb
Încărcăm modulul de kernel
# insmod minfs.ko
creăm punctul de montare /mnt/minfs/
# mkdir -p /mnt/minfs/
și montăm sistemul de fișiere
# mount -t minfs /dev/vdb /mnt/minfs/
Operaţia eşuează din cauză că inode-ul rădăcină nu este iniţializat.
Pentru a putea monta sistemul de fișiere va trebui să completați superblocul (adică o structură de tip struct super_block
) în cadrul funcției minfs_fill_super
; este vorba de argumentul s
al funcției. Structura de operații pe superbloc este definită: minfs_ops
. Urmăriți indicațiile marcate cu TODO 2
. Puteți urmări implementarea funcției minix_fill_super.
minfs.h
.
Pentu informații legate de lucrul cu buffere, parcurgeți secțiunea Buffer cache-ul.
Citiți primul bloc de pe disc (blocul cu indexul 0). Pentru a citi blocul, folosiți funcția sb_bread. Convertiți datele citite (câmpul b_data
din struct buffer_head
) la structura de tip informație de superbloc de pe disc: struct minfs_super_block
, definită în fișierul cod sursă.
Structura struct minfs_super_block
deține informații specifice sistemului de fișiere, care nu se regăsesc în structura generică struct super_block
(în cazul de față doar versiunea). Acele informații suplimentare (care se găsesc în struct minfs_super_block
(disc) dar nu în struct super_block
(VFS)) vor fi stocate în structura struct minfs_sb_info
.
Pentru verificarea funcționalității avem nevoie de o funcţie pentru citirea inode-ului rădăcină. Pe moment folosiți funcția myfs_get_inode
de la exercițiile cu sistemul de fișiere myfs
. Copiați funcția în codul sursă şi apelaţi-o la fel ca în cazul myfs
. Al doilea argument în cazul apelării funcției myfs_get_inode
îl reprezintă permisiunile de creare ale inode-ului, similar exercițiului cu sistem de fișiere virtual (myfs
).
Validaţi implementarea executând comenzile de la exerciţiul anterior.
Pentru montare avem nevoie de inițializarea inode-ului rădăcină, iar pentru inițializarea inode-ului rădăcină avem nevoie de implementarea funcțiilor de lucru cu inode-uri. Adică trebuie să implementați funcțiile minfs_alloc_inode
și minfs_destroy_inode
. Urmăriții indicațiile marcate cu TODO 3
. Puteți folosi ca model funcțiile minix_alloc_inode și minix_destroy_inode.
În implementare urmăriți macro-urile și structurile din fișierul minfs.h
.
minfs_alloc_inode
și minfs_destroy_inode
recomandăm folosirea kzalloc și kfree.
În funcția minfs_alloc_inode
alocați inode-uri de tip minfs_inode_info
, dar returnați doar structuri de tip struct inode
, adică cele date de câmpul vfs_inode
.
În funcția de minfs_alloc_inode
apelați funcția inode_init_once pentru inițializarea inode-ului.
În funcția destroy_inode
. să puteți accesa structura de tip struct minfs_inode_info
folosiți macro-ul container_of.
minfs_alloc_inode
şi minfs_destroy_inode
, dar ele nu sunt încă apelate. Corectitudinea implementării o veţi verifica la sfârşitul exerciţiului următor.
Inițializarea inode-ului rădăcină este necesară pentru montarea sistemului de fișiere. Pentru aceasta va trebui să completați structura minfs_ops
cu funcțiile minfs_alloc_inode
și minfs_destroy_inode
și să completați funcția minfs_iget
. Funcția minfs_iget
este funcția apelată pentru alocarea unui inode de tip VFS (adică struct inode
) și completarea acestuia cu informații specifice inode-ului minfs
de pe disc (adică struct minfs_inode
). Urmăriții indicațiile marcate cu TODO 4
.
Completați câmpurile alloc_inode
și destroy_inode
ale structurii struct super_operations cu funcțiile implementate la pasul anterior.
Informațiile despre inode-ul rădăcină se găsesc în al doilea bloc de pe disc (inode-ul cu indexul 1). Realizați, în cadrul funcției minfs_iget
citirea inode-ului rădăcină de tip minfs
de pe disc (struct minfs_inode
) și completarea inode-ului VFS (struct inode
).
În cadrul funcției minfs_fill_super
înlocuiți apelul funcției myfs_get_inode
cu apelul funcției minfs_iget
.
b_data
al structurii struct buffer_head
) la inode-ul de tip minfs
de pe disc (struct minfs_inode
).
Câmpurile i_uid
, i_gid
, i_mode
, i_size
le completați în cadrul inode-ului VFS cu valorile din inode-ul de pe disc. Pentru initializarea câmpurilor i_uid
și i_gid
, folosiți funcțiile i_uid_write
, și, respectiv, i_gid_write
.
Inițializați câmpurile atime
, ctime
și mtime
ale inode-ului VFS la valoarea CURRENT_TIME.
Va trebui să inițializați operațiile pentru inode-ul de tip director. Pentru aceasta urmați pașii:
i_op
și i_fop
, folosiți funcții din kernel deja implementate:i_op
: simple_dir_inode_operations.i_fop
: simple_dir_operations
Acum putem să montăm sistemul de fișiere. Urmați pașii indicați mai sus pentru a compila modulul de kernel, copia pe mașina virtuală și porni mașina virtuală și apoi inserați modulul de kernel, creați punctul de montare /mnt/minfs/
și montați sistemul de fișiere. Verificăm că sistemul de fișiere a fost montat investigând fișierul /proc/mounts
.
Verificăm că totul este în regulă prin listarea conținutului punctului de montare /mnt/minfs/
:
# ls /mnt/minfs/
După montare și verificare, demontăm sistemul de fișiere și descărcăm modulul din kernel.
test-minfs.sh
:
# ./test-minfs.sh