Laborator 12 - Implementarea sistemelor de fișiere

Nice to read

  • TLPI - Chapter 14, File Systems
  • TLPI - Chapter 15, File Attributes
  • TLPI - Chapter 18, Directories and Links

Resurse utile

Linux

Înainte de laborator: Feedback

Pentru a îmbunătăți cursul de SO, componentele sale și modul de desfășurare, ne sunt foarte utile opiniile voastre. Pentru aceasta, vă rugăm să accesați și completați formularul de feedback de pe site-ul curs.upb.ro. Trebuie să fiți autentificați și înrolați în cadrul cursului.

Formularul este anonim și este activ în perioada 23 mai 2022 - 3 iunie 2022. Rezultatele vor fi vizibile în cadrul echipei cursului doar după încheierea sesiunii. Este accesibil la link-ul “Formular feedback” a paginii principale a cursului de SO al seriei voastre pe curs.upb.ro. Nu este în meta-cursul disponibil tuturor seriilor.

Vă invităm să evaluați activitatea echipei de SO și să precizați punctele tari și punctele slabe și sugestiile voastre de îmbunătățire a disciplinei. Feedback-ul vostru ne ajută să creștem calitatea materiei în anii următori și să îmbunătățim disciplinele pe care le veți face în continuare.

Vom publica la începutul semestrului viitor analiza feedback-ului vostru.

Ne interesează în special:

  • Ce nu v-a plăcut și ce credeți că nu a mers bine?
  • De ce nu v-a plăcut și de ce credeți că nu a mers bine?
  • Ce ar trebuie să facem ca lucrurile să fie plăcute și să meargă bine?

Device Nodes

Nucleul gestionează fiecare device hardware sau virtual prin intermediul unui device driver. Un device driver este o porțiune de cod din nucleu care implementează o serie de operații corespunzătoare acțiunilor de I/E (intrare/ieşire) asociate cu un device hardware. Procesele din userspace (spațiul utilizator) interacționează cu device driver-ul prin intermediul unor fișiere speciale denumite device nodes. API-ul (interfața de programare) oferit de device drivere include, în general, următoarele operații:

  • open,
  • close,
  • read,
  • write,
  • ioctl.

Operaţiile de mai sus sunt aplicate asupra device node-ului sau descriptorului de fişier asociat acelui device node.

Unele device-uri sunt reale (mouse, tastatură, disc), altele sunt virtuale în sensul că nu au un device hardware asociat (e.g /dev/zero, /dev/null). După modul în care se accesează datele, device-urile sunt împărțite în două categorii:

  • device de tip caracter, datele sunt procesate octet cu octet. În această categorie se înscriu: tastatura, linia serială, mouse-ul.
  • device de tip bloc, datele pot fi procesate la nivel de bloc, e.g. hard disk.

Fișierele device node se găsesc în /dev și au asociat un identificator format din major ID și minor ID.

Majorul și minorul sunt dați de coloanele 5 și 6 din output-ul ls -l, separate prin virgulă.

Puteți vizualiza majorii folosiți în sistem din fișierul /proc/devices.

Crearea unui device node se face folosind funcția mknod

int mknod(const char *pathname, mode_t mode, dev_t dev);

În general, informații despre fișiere și în particular despre device node-uri se pot afla cu funcțiile din familia stat.

int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);

Toate aceste funcții completează informațiile despre un fișier în structura struct stat, care conține următoarele câmpuri:

struct stat {
    dev_t     st_dev;     /* ID of device containing file */
    ino_t     st_ino;     /* inode number */
    mode_t    st_mode;    /* protection */
    nlink_t   st_nlink;   /* number of hard links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    dev_t     st_rdev;    /* device ID (if special file) */
    off_t     st_size;    /* total size, in bytes */
    blksize_t st_blksize; /* blocksize for file system I/O */
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last status change */
};

Sisteme de fișiere

Un sistem de fișiere este o colecție organizată de fișiere și directoare. Un sistem de fișiere este creat folosind comanda mkfs. Din punct de vedere funcțional sistemele de fișiere se pot împărți în:

  • sisteme de fișiere pentru disc (ext2, ext3, reiserfs, fat, ntfs etc.)
  • sisteme de fișiere pentru rețea (nfs, smbfs, ncp etc.)
  • sisteme de fișiere virtuale (procfs, sysfs, sockfs, pipefs etc.)

Tipurile de sisteme de fișiere suportate de nucleu pot fi observate în fișierul /proc/filesystems.

daniel@debian$ cat /proc/filesystems 
nodev	sysfs
nodev	proc
nodev	ramfs
	ext4
	fuseblk

Pentru a putea fi folosit un sistem de fișiere trebuie atașat (montat) în ierarhia de directoare din sistem. Acest lucru se realizează cu comanda mount(8):

mount -t type device dir

sau apelul mount(2):

int mount(const char *source, const char *target,
          const char *filesystemtype, unsigned long mountflags,
          const void *data);

Operația inversă, demontarea sistemului de fișiere din ierarhia de directoare se face cu comanda umount(8):

umount {dir|device}...

sau apelul: umount(2)

int umount(const char *target);

Fiecare proces are două atribute legate de directoare:

  • directorul rădăcina, determină punctul de unde căile absolute sunt interpretate.
  • directorul curent, determină punctul de unde căile relative sunt interpretate.

Un director este stocat în sistemul de fișiere într-un mod similar cu un fișier obișnuit. Există două lucruri diferite:

  • tipul din structura inode este diferit.
  • conținutul este diferit: un director conține un vector de nume de fișiere și inode-uri.

Un link simbolic (sau soft link), este un tip special de fișier al cărui conținut reprezintă numele altui fișier. Link-urile simbolice sunt create cu comanda ln -s sau cu apelul symlink(2)

int symlink(const char *oldpath, const char *newpath); 

Ștergerea unui link simbolic se face cu comanda unlink sau cu apelul unlink(2)

int unlink(const char *pathname); 

Crearea și ștergerea directoarelor

Un director poate fi creat folosind comanda mkdir sau apelul mkdir(2))

int mkdir(const char *pathname, mode_t mode);

Apelul rmdir(2) șterge directorul specificat în argumentul pathname:

int rmdir(const char *pathname);

De asemenea, pentru a șterge un fișier sau un director gol se poate folosi funcția remove(3)

int remove(const char *pathname); 

Citirea directoarelor

După cum am precizat mai sus, un director conține nume de directoare sau fișiere.

Apelul opendir(3) deschide un director și întoarce un handle ce poate fi folosit mai târziu pentru a referi directorul.

DIR *opendir(const char *name);
DIR *fdopendir(int fd);

Apelul readdir(3) citește intrări succesive dintr-un stream de directoare (DIR).

struct dirent *readdir(DIR *dirp);

Apelul readdir întoarce un pointer la următoarea structură struct dirent din streamul referit de dir:

struct dirent {
    ino_t          d_ino;       /* inode number */
    off_t          d_off;       /* offset to the next dirent */
    unsigned short d_reclen;    /* length of this record */
    unsigned char  d_type;      /* type of file; not supported
                                   by all file system types */
    char           d_name[256]; /* filename */
};

Directorul curent al unui proces

Directorul curent al unui proces definește punctul de start pentru formarea căilor relative referite de procesul respectiv. Un proces nou creat moștenește directorul curent de la procesul părinte.

Directorul curent al unui proces poate fi determinat folosind apelul getcwd(3):

char *getcwd(char *cwdbuf, size_t size);
  • cwdbuf, trebuie alocat înainte de apel astfel încât să poată stoca cel puțin size octeți.
  • după apel cwdbuf va conține calea absolută a directorului curent.

Schimbarea directorului curent

Apelul chdir(2) schimbă directorul curent al procesului apelant către numele absolut sau relativ primit ca argument.

int chdir(const char *path);

Schimbarea directorului rădăcina al unui proces

Fiecare proces are un director rădăcină reprezentând punctul de unde căile absolute sunt interpretate. În mod implicit, acesta este directorul rădăcina real al sistemului de fișiere. Un proces nou moștenește directorul rădăcină de la părintele său. Există situații (e.g pentru a ascunde o parte din sistemul de fișiere) în care este util pentru un proces să-și schimbe directorul rădăcină. Acest lucru se realizează folosind apelul chroot(2)

int chroot(const char *path);

Rezolvarea unei căi

Apelul realpath(3) dereferențiază link-ul simbolic primit ca argument în path și elimină subşirurile /./, /../ precum şi apariţii în plus ale caracterului / pentru a genera o cale absolută plasată în parametrul de ieşire realpath.

Exemplu: Dacă parametrul de intrare path ar fi /home/madalina/////so/tema4/../../seriale/thewire/ , în urma apelului realpath(path, resolved_path), parametrul de ieşire resolved_path devine /home/madalina/seriale/thewire/. Presupunem că ierarhia de directoare prezentată în exemplu există deja.

char *realpath(const char *path, char *resolved_path);

dirname și basename

Apelurile dirname(3) și basename(3) împart un șir de caractere reprezentând o cale în partea de director și partea de fișier.

char *dirname(char *path);
char *basename(char *path);

De exemplu:

path         dirname    basename
"/usr/lib"    "/usr"    "lib"
"/usr/"       "/"       "usr"
"usr"         "."       "usr"
"/"           "/"       "/"
"."           "."       "."
".."          "."       ".." 

Exerciții

În cadrul laboratoarelor vom folosi repository-ul de git al materiei SO - https://github.com/systems-cs-pub-ro/so. Va trebui sa clonați repository-ul pe masinile virtuale folosind comanda: git clone https://github.com/systems-cs-pub-ro/so. Dacă doriți să descărcați repositoryul în altă locație, folosiți comanda git clone https://github.com/systems-cs-pub-ro/so ${target}.

Pentru a actualiza repository-ul, folosiți comanda git pull origin master din interiorul directorului în care se află repository-ul. Recomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentă. În cazul în care gitul detectează conflicte la nivelul vreunui fişier, folosiți următoarele comenzi pentru a vă păstra modificările:

git stash
git pull origin master
git stash pop

Pentru mai multe informații despre folosirea utilitarului git, urmați ghidul de la https://gitimmersion.com.

Completare feedback

Vă invităm să evaluați activitatea echipei de SO și să precizați punctele tari și punctele slabe și sugestiile voastre de îmbunătățire a materiei. Feedback-ul vostru este foarte important pentru noi să creștem calitatea materiei în anii următori și să îmbunătățim materiile pe care le veți face în continuare.

Găsiți formularul de feedback în partea dreaptă a paginii principale de SO de pe curs.upb.ro într-un frame numit “FEEDBACK”. Trebuie să fiți înrolați la cursul de SO și să intrati pe pagina asociată seriei voastre (nu pe metaserie), altfel veți primi o eroare de acces.

Vă mulțumim!

Linux

Codul va fi scris în fișierul mini.c din directorul 1-mini/. Pentru fiecare exercițiu decomentați linia TODO corespunzătoare.

Exercițiul 1

Folosiți comanda ls -l /dev și precizați două device node-uri de tip caracter și două device node-uri de tip bloc. Ce major și minor au?

Exercițiul 2

Implementați comanda list <device_node>, ce va primi ca argument un device node și va afișa pentru acesta tipul (c/b), identificatorii major, respectiv minor. Folosiți funcția stat(2) pentru a obține o structură de tipul struct stat din care veți extrage tipul device-ului (st_mode) (hint: S_ISCHR, S_ISBLK) apoi din câmpul st_rdev extrageți major și minor. Nu uitați să decomentați linia marcată cu #define TODO2

Exercițiul 3

Creați punctul de montare /mnt/my. Ca root, în terminalul bash, rulați comanda:

$ mkdir /mnt/my

Parcurgeți paginile de manual ale funcțiilor mount și umount.

Folosiți comenzile mount și umount din executabilul mini pentru a monta fişierul my_fs în punctul de montare /mnt/my. Citiți secțiunea marcată cu TODO din fișierul mini.c. Pentru argumentul 4 și argumentul 5 al funcției mount folosiți, respectiv, valorile 0 și NULL.

Testare: Rulați, ca root, comanda:

./mini

și apoi rulați comanda de montare în cadrul acestui shell:

mount /dev/mapper/vgvagrant-root /mnt/my ext4

În cazul în care nu lucraţi în maşina virtuală de SO, folosiţi /dev/sda1 în loc de /dev/mapper/vgvagrant-root.

Într-o altă consolă, într-un shell obișnuit, verificați rezultatele folosind comanda:

cat /proc/mounts

Pentru demontare rulați comanda:

umount /mnt/my

Exercițiul 4

Adăugați suport pentru comenzile symlink și unlink în programul mini. Urmăriți TODO4 .

Pentru testare folosiți, în shell-ul aferent comenzii ./mini, comanda:

symlink /proc/filesystems local-filesystems

Ca să verificați, într-o altă consolă, în același director cu cel în care ați rulat comanda ./mini, folosiți

ls -l

Pentru a șterge symlink-ul folosiți comanda

unlink local-filesystems

Pentru validare rulați din nou comanda

ls -l

Exercițiul 5

Adăugați suport pentru comenzile mkdir și rmdir în programul mini. Urmăriți TODO5 .

Ca al doilea argument pentru funcția mkdir folosiți (mode_t) 0755.

Exercițiul 6

Adăugați suport pentru comanda ls dirname/ în programul mini. Aceasta va trebui să afișeze recursiv toate directoarele și fișierele începând cu directorul dat ca parametru (puteți parcurge recursiv în adâncime arborele de fișiere). Urmăriți TODO6 și demo-ul 5 de la curs

Exercițiul 7

Adăugați suport pentru comenzile pwd și chdir în programul mini. Urmăriți TODO7 .

so/laboratoare/laborator-12.txt · Last modified: 2022/05/29 20:59 by maria.mihailescu
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