Laborator 8: Exerciții

Pregătirea laboratorului

Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (~/so2/linux/tools/labs).

Pașii de rezolvare sunt următorii:

  • pregătirea scheletului de laborator
  • compilarea modulelor de Kernel
  • copierea modulelor pe mașina virtuală
  • pornirea mașinii virtuale și testarea modulelor

Pregătirea scheletului de laborator

Scheletul de laborator este generat din sursele din directorul tools/labs/templates. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda:

tools/labs $ make skels

Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu LABS:

tools/labs $ make clean
tools/labs $ LABS=<lab name> make skels

Numele laboratorului curent este filesystems.

Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name> variabilei LABS.

Scheletul este generat în directorul tools/labs/skels.

Compilarea modulelor

Comanda make build compilează toate modulele din directorul skels.

student@eg106:~/so2/linux/tools/labs$ make build
echo "# autogenerated, do not edit " > skels/Kbuild
echo "ccflags-y += -Wno-unused-function -Wno-unused-label -Wno-unused-variable " >> skels/Kbuild
for i in ./filesystems/minfs/kernel ./filesystems/myfs; do echo "obj-m += $i/" >> skels/Kbuild; done

Copierea modulelor pe mașina virtuală

Putem copia modulele generate pe mașina virtuală folosind target-ul copy al comenzii make, atunci când mașina virtuală este oprită.

student@eg106:~/so2/linux/tools/labs$ make copy
student@eg106:~/so2/linux/tools/labs$ make boot

Alternativ, putem copia fișierele prin scp, pentru e evita repornirea mașinii virtuale. Pentru detalii despre folosirea interacțiunea prin rețea cu mașina virtuală citiți Interacțiunea cu mașina virtuală.

Testarea modulelor

Modulele generate sunt copiate pe mașina virtuală în directorul /home/root/skels/<lab_name>.

root@qemux86:~/skels/filesystems# ls
minfs  myfs
root@qemux86:~/skels/filesystems# ls myfs/
myfs.ko       test-myfs.sh

După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU (sau în minicom) pentru a încărca și descărca modulul de kernel:

root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko 
root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko 

Pentru dezvoltarea laboratorului, este recomandat să folosim trei terminale sau, mai bine, trei tab-uri de terminal. Pentru a deschide un nou tab de terminal folosim combinația de taste Ctrl+Shift+t. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:

  1. În primul tab de terminal dezvoltăm modulul de kernel: editare, compilare, copiere în directorul dedicat pentru mașina virtuală QEMU. Lucrăm în directorul aferent rezultat în urma decomprimării arhivei de sarcini a laboratorului.
  2. În al doilea tab de terminal pornim mașina virtuală QEMU și apoi testăm modulul de kernel: încărcare/descărcare modul, rulare teste. Lucrăm în directorul aferent mașinii virtuale: ~/so2/linux/tools/labs.
  3. În al treilea tab de terminal accesăm directorul ~/so2/linux/ cu sursele nucleului unde putem folosi Vim și cscope pentru parcurgerea codului sursă.

student@eg106-pc:~$ netcat -lup 6666
</code>

Pentru buna desfășurare a laboratorului recomandăm să aveți deschise trei tab-uri de browser:
  - În primul tab să fie deschis [[:so2:laboratoare:lab08|breviarul laboratorului]].
  - În al doilea tab să fie deschisă [[:so2:laboratoare:lab08:exercitii|pagina curentă]].
  - În al treilea tab să fie deschisă [[https://elixir.bootlin.com/linux/v4.15/source|pagina LXR]] pentru parcurgerea codului sursă din nucleu.

</note>

<note warning>
Citiți cu atenție toate precizările unui exercițiu înainte de a începe rezolvarea acestuia.
</note>

<note important>
Înainte de începerea rezolvării laboratorului, rulați comanda ''%%git pull --rebase%%'' in directorul ''~/so2/linux'', pentru a obține ultima versiune a scheletului de laborator.
</note>

===== [4.5p] myfs =====

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 scheletul de laborator. Vom implementa în cadrul acestui laborator operațiile pe superbloc, iar [[:so2:laboratoare:lab09|laboratorul viitor]] vom continua cu operațiile pe inode.

==== 1. [1.5p] Înregistrare și deînregistrare sistem de fișiere myfs ====

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 [[:so2:laboratoare:lab08#inregistrarea_si_deinregistrarea_sistemelor_de_fisiere|Înregistrarea și deînregistrarea sistemelor de fișiere]]. Folosiți șirul ''<nowiki>"myfs"</nowiki>'' pentru numele sistemului de fișiere.

<note tip>
În cadrul structurii de sistem de fișiere, pentru completarea superbloculului (efectuată la montare) folosiți funcția ''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 [[:so2:laboratoare:lab08#functiile_mount_kill_sb|Funcțiile mount, kill_sb]]; este vorba de funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/super.c#L1155|mount_nodev]]). Ca argument pentru funcția de mount specifică folosiți funcția de tipul [[:so2:laboratoare:lab08#functia_fill_super|funcția fill_super]] definită în scheletul de cod.

Pentru distrugerea superblocului (efectuată la demontare) folosiți funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/super.c#L997|kill_litter_super]], funcție specifică, de asemenea, unui sistem de fișiere fără suport pe disc.
</note>

După completarea secțiunilor marcate cu ''TODO 1'', compilați modulul, copiați-l pe 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/''<code>
# mkdir -p /mnt/myfs
</code> și apoi folosim comanda ''mount''<code>
# mount -t myfs none /mnt/myfs
</code>

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.

<note>
Argumentul ''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.
</note>

==== 2. [1p] Completare superbloc myfs ====

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''.

<note tip>
Pentru completarea funcției ''myfs_fill_super'' puteți porni de la exemplul din secțiunea [[:so2:laboratoare:lab08#functia_fill_super|funcția fill_super]].

Pentru câmpurile structurii de superbloc, acolo unde se poate, folosiți macro-urile definite în cadrul scheletului de cod.
</note>

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 [[:so2:laboratoare:lab08#operatiile_pe_superbloc|Operațiile pe superbloc]].

<note tip>
Inițializați câmpurile ''drop_inode'' și ''statfs'' ale structurii ''struct super_operations''.
</note>

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.

==== 3. [1.5p] Inițializare inode rădăcină myfs ====

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ă.

[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/fs.h#L570 | Inode-ul]] este alocat în cadrul funcției ''myfs_get_inode''. Este vorba de variabila locală ''inode'', alocată cu ajutorul apelului [[https://elixir.bootlin.com/linux/v4.15/source/fs/inode.c#L901|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 [[https://elixir.bootlin.com/linux/v4.15/source/fs/ramfs/inode.c#L61|ramfs_get_inode]].

<note tip>
Pentru inițializarea ''uid'', ''gid'', ''mode'', puteți folosi funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/inode.c#L1997|inode_init_owner]] așa cum este folosită și în funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/ramfs/inode.c#L61|ramfs_get_inode]]. Când apelați funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/inode.c#L1997|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 întoarsă de funcția [[https://elixir.bootlin.com/linux/v4.9/source/fs/inode.c#L2104|current_time]].

Va trebui să inițializați operațiile pentru inode-ul de tip director. Pentru aceasta urmați pașii:
  - Verificați dacă este vorba de inode de tip director folosind macro-ul [[https://elixir.bootlin.com/linux/v4.15/source/include/uapi/linux/stat.h#L23|S_ISDIR]].
  - Pentru câmpurile, ''i_op'' și ''i_fop'', folosiți funcţii din kernel deja implementate:
    * pentru ''i_op'': [[https://elixir.bootlin.com/linux/v4.15/source/fs/libfs.c#L227| simple_dir_inode_operations]].
    * pentru ''i_fop'': [[https://elixir.bootlin.com/linux/v4.15/source/fs/libfs.c#L217|simple_dir_operations]]
  - Incrementați numărul de link-uri pentru director folosind funcția [[https://elixir.bootlin.com/linux/v4.15/source/fs/inode.c#L330|inc_nlink]].
</note>

==== 4. [0.5p] Testare montare și demontare myfs ====

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?

<note tip>
Pentru a afișa numărul de inode al unui director folosiți comanda:<code bash>
ls -di /path/to/directory
</code>
</note>
unde ''/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:<code bash>
stat -f /mnt/myfs
</code>

Vrem să vedem ce conține punctul de montare ''/mnt/myfs'' și dacă putem crea fișiere. Pentru aceasta rulăm comenzile:<code>
# ls -la /mnt/myfs
# touch /mnt/myfs/a.txt
</code>
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 [[:so2:laboratoare:lab09|laboratorul următor]].

Demontăm sistemul de fișiere folosind comanda<code bash>
umount /mnt/myfs
</code>
Descărcați și modulul de kernel aferent sistemul de fișiere.

<note tip>
Pentru testarea întregii funcționalități puteți folosi scriptul ''test-myfs.sh'':<code bash>
./test-myfs.sh
</code>
Scriptul este copiat pe mașina virtuală folosind comanda ''make copy'' doar dacă este executabil:
<code bash>
student@workstation:~/so2/linux/tools/labs$ chmod +x skels/filesystems/myfs/test-myfs.sh </code> Statisticile afișate pentru sistemul de fișiere sunt minimale, întrucât informațiile sunt furnizate de către funcția simple_statfs.

[6.5p] minfs

Î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/kernel din scheletul 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.

5. [0.5p] Înregistrare și deînregistrare sistem de fișiere minfs

Înainte de rezolvarea exercițiului, este necesar să adaugăm un disc la mașina virtuală. Pentru aceasta, generați un fișier pe care îl vom folosi ca imaginea discului folosind comanda

dd if=/dev/zero of=qemu/mydisk.img bs=1M count=100

și adăugați argumentul -drive file=qemu/mydisk.img,if=virtio,format=raw comenzii qemu, în fișierul qemu/Makefile (în variabila QEMU_OPTS). Noul argument pentru comanda qemu trebuie sa fie adăugat după cel corespunzător discului existent (YOCTO_IMAGE).

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.

În cadrul structurii de sistem de fișiere, pentru montare folosiți funcția 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 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, din directorul minfs/user. Utilitarul se compilează automat la comanda

make build

și se copiază pe mașina virtuală la

make copy

.

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.

6. [2.5] Completare superbloc minfs

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.

Unele structuri se găsesc definite în fișierul header 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.

7. [1.5p] Creare și distrugere inode-uri minfs

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.

Pentru alocarea/dezalocarea de memorie în cadrul funcțiilor 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.

În acest exerciţiu aţi implementat funcţiile 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.

8. [1.5p] Inițializare inode rădăcină minfs

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.

Pentru implementarea funcției minfs_iget urmăriți implementarea funcției V1_minix_iget.

Pentru a citi un bloc folosiți funcția sb_bread. Convertiți datele citite (câmpul 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 i_atime, i_ctime și i_mtime ale inode-ului VFS la valoarea întoarsă de funcția current_time.

Va trebui să inițializați operațiile pentru inode-ul de tip director. Pentru aceasta urmați pașii:

  1. Verificați dacă este vorba de inode de tip director folosind macro-ul S_ISDIR.
  2. Pentru câmpurile, i_op și i_fop, folosiți funcții din kernel deja implementate:
  3. Incrementați numărul de link-uri pentru director folosind funcția inc_nlink.

9. [0.5p] Testare montare și demontare minfs

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.

Alternativ, pentru testarea întregii funcționalității puteți folosi scriptul test-minfs.sh:

# ./test-minfs.sh

Scriptul este copiat pe mașina virtuală la rularea comenzii make copy doar dacă este executabil.

student@workstation:~/so2/linux/tools/labs$ chmod +x skels/filesystems/minfs/user/test-minfs.sh 

Soluții

so2/laboratoare/lab08/exercitii.txt · Last modified: 2018/04/18 09:36 by ionel.ghita
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0