Curs 02 - Interfața sistemului de fișiere

  • Suport curs
    • Operating System Concepts Essentials
      • Capitolul 9 - File-System Interface
      • Capitolul 10, Secțiunea 10.1 - File-System Structure
    • Linux System Programming
      • Capitolul 2 (programatic)
    • Windows System Programming
      • Capitolul 2 (programatic)

Demo-uri

Pentru parcurgerea demo-urilor, folosim arhiva aferentă. Demo-urile rulează pe Linux. Descărcăm arhiva folosind comanda

wget http://elf.cs.pub.ro/so/res/cursuri/curs-02-demo.zip

și apoi decomprimăm arhiva

unzip curs-02-demo.zip

și accesăm directorul rezultat în urma decomprimării

cd curs-02-demo/

Acum putem parcurge secțiunile cu demo-uri de mai jos.

Tabela de descriptori de fișier a unui proces

Pentru a afișa tabela de descriptor de fișier a procesului shell current (PID-ul său este reținut în construcția $$), folosim comanda

lsof -a -d 0-1023 -p $$

Ce referă descriptorii 0, 1, 2?

Răspuns

Răspuns

Descriptorii 0, 1 și 2 reprezintă, respectiv, intrarea standard (standard input, stdin), ieșirea standard (standard output, stdout) și ieșirea de eroare standard (standard error, stderr). Toți cei trei descriptori referă, de obicei, un dispozitiv de tip terminal (de forma /dev/pts/0); adică atunci când se citesc sau se scriu informații de la/către descriptori, acestea sunt preluate de la terminal

De ce (pe unele sisteme) descriptorul 0 este marcat 0r (adică read-only)?

Răspuns

Răspuns

Descriptorul 0 referă intrarea standard (standard input). Întrucât de la intrarea standard doar se citesc informații, are sens să fie deschis doar pentru citire (adică read-only).

Pe anumite sisteme descriptorul este marcat cu 0u adică este read-write; dar, în mod practic, este folosit doar pentru citire.

Descriptori de fișier pentru procesele daemon

Vrem să investigăm descriptorii de fișiere pentru procesele daemon din sistem. Pentru început vrem să aflăm procesele daemon din sistem; procesele daemon au ca proces părinte procesul init (procesul cu PID-ul 1) și vom folosi comanda de mai jos pentru a le afla:

ps --ppid 1

Pentru unul dintre procesele daemon descoperite prin rularea comenzii anterioare, afișăm tabela de descriptori (e nevoie de drept de root) folosind comanda lsof:

sudo lsof -a -d 0-1023 -p $PID

Mai sus, construcția $PID referă PID-ul procesului daemon inspectat.

Ce referă descriptorii 0, 1, 2? De ce?

Răspuns

Răspuns

Descriptorii 0, 1, 2, aferenți intrării, ieșirii și ieșirii de eroare standar, referă /dev/null.

Un proces daemon nu este atașat nici unui terminal. Nu există mod prin care utilizatorul poate comunica direct cu acesta prin intermediul intrării sau ieșirii standard, motiv pentru care acestea referă /dev/null, “gaura neagră” a sistemului.

Ce alți descriptori sunt folosiți? Ce referă acești descriptori?

Raspuns

Raspuns

Sunt folosiți în continuare alți descriptori: 4, 5, 6 etc. Descriptorii proceselor daemon vor referi fișiere de jurnalizare (log files), sockeți Unix sau sockeți de rețea sau fișiere deschise pentru a fi prelucrate. Utilizatorul va comunica cu daemonii prin semnale, fișiere de configurare, sockeți și fișiere de jurnalizare.

Descriptorii de fișier după redirectare

Vrem să vedem cum se modifică descriptorii de fișier în cazul redirectării. Pentru a putea vedea acest lucru vom rula o comandă de durată (sleep) și vom redirecta intrarea și ieșirea standard:

sleep 100 < /etc/passwd > f.txt

Pentru a investiga procesul sleep proaspăt pornit, trebuie să știm PID-ul său. Deschidem o altă consolă și aflăm PID-ul procesului sleep creat folosind comanda:

pidof sleep

Vom folosi construcția $PID referă PID-ul procesului sleep pe care-l investigăm. Ca și până acum, afișam tabela de descriptori de fișier a procesului folosind comanda:

lsof -a -o -d 0-1023 -p $PID

Ce referă acum descriptorul 0, respectiv 1?

Răspuns

Răspuns

În urma redirectării intrării standard cu operatorul <, descriptorul 0 referă acum fișierul /etc/passwd. La fel, în urma redirectării ieșirii standard cu operatorul >, descriptorul 1 referă acum fișierul f.txt.

Modificarea cursorului de fișier

Vrem să urmărim modificarea cursorului de fișier (numit și file pointer sau file offset). Pentru aceasta vom folosi un program C în care, la cererea utilizatorului folosim apeluri care alterează cursorul de fișier: write și lseek,

Pentru început intrăm în subdirectorul c-file-ops/ din directorul cu demo-uri și urmărim fișierul c-file-ops.c. Observăm că în program se deschide fișierul f.txt și apoi se scrie (folosind write) și se parcurge fișierul (folosind lseek). Fiecare operație este precedată de apăsarea tastei ENTER din parte utilizatorului. Compilăm programul folosind comanda

user@host:~$ make

și obținem executabilul c-file-ops. Rulăm executabilul c-file-ops:

./c-file-ops

Pentru a urmări evoluția tabelei de descriptori și a cursorului de fișier, vom folosi comanda lsof. Într-o altă consolă rulăm comanda

lsof -a -o -d 0-1023 -p $(pidof c-file-ops)

Pentru început sunt afișați doar descriptorii standard.

Pentru a urmări evoluția tabelei de descriptori de fișier și a cursorului de fișier, vom folosi ENTER în consola în care am rulat executabilul c-file-ops. Vom urmări evoluția programului în consola în care am rulat lsof.

Coloana OFFSET indică poziția cursorului de fișier.

Inițial cursorul de fișier are valoarea 0 întrucât a fost proaspăt deschis și trunchiat. La apăsarea tastei ENTER în prima consolă, care indică programul să facă o nouă acțiune, observăm modificarea cursorului de fișier în a doua consolă.

Parcurgem întreg programul pentru a urmări evoluția completă a cursorului de fișier.

La finalul rulării programului urmărim dimensiunea fișierului f.txt:

stat -c "%s" f.txt
256

Observăm că fișierul are dimensiunea de 256 octeți, atât cât a primit ca argument apelul ftruncate.

Ce efect are apelul ftruncate asupra cursorului de fișier?

Răspuns

Răspuns

Apelul ftruncate modifică dimensiunea fișierului. Un fișier are un câmp de dimensiune alterat de comanda ftruncate. Acest câmp este diferit de cursorul de fișier. Teoretic cursorul de fișier poate fi plasat dincolo de sfârșitul fișierului. Din acest motiv, apelul ftruncate nu are nici un efect asupra cursorului de fișier.

Acest lucru este precizat și în secțiunea DESCRIPTION a paginii de manual a apelului ftrunctate.

Modificarea cursorului de fișier (Python)

Vrem să vedem cum este alterat cursorul de fișier în cazul unui program Python. Pentru aceasta, accesăm subdirectorul py-file-ops/ din directorul cu demo-uri al cursului și parcurgem fișierul py-file-ops.py. Observăm că structura este similară celei de la demo-ul anterior: se scriu date în fișier, se parcurge fișierul, se așteaptă apăsarea tastei ENTER din partea utilizatorului.

Rulăm programul folosind comanda:

python py-file-ops.py

Pentru a urmări evoluția tabelei de descriptori și a cursorului de fișier, deschidem o altă consolă în care rulăm comanda

lsof -a -o -d 0-1023 -p $(pgrep -f py-file-ops)

Sunt afișați doar descriptorii standard pentru că încă nu a fost deschis fișierul.

Pentru a deschide fișierul și a efectua oprații asupra sa, vom apăsa ENTER în consola în care am rulat programul py-file-ops. Urmărim evoluția programului în consola în care ați rulat lsof.

Coloana OFFSET indică poziția cursorului de fișier.

Folosim de mai multe ori ENTER pentru a parcurge toți pașii din program și pentru a investiga evoluția cursorului de fișier. La fel ca la demo-ul anterior, fișierul va avea în final dimensiunea de 256 octeți:

stat -c "%s" f.txt 
256

De ce nu există tot timpul o corespondență între operațiile Python și modificarea cursorului de fișier? De exemplu, în cazul f.read(), deși trebuia să se incrementeze contorul cu 256 octeți, a fost incrementat cu 512.

Răspuns

Răspuns

Python nu folosește direct apelurile oferite de sistemul de operare. Biblioteca standard Python oferă wrappere peste apelurile expuse de sistemul de operare și biblioteca standard C. Astfel, în cazul unui apel f.read() se realizează, în fundal, un transfer de 512 octeți și se bufferează pentru viitoare operații.

Ce se întâmplă dacă nu se apelează f.flush() (după ciclul de scriere cu f.write())?

Răspuns

Răspuns

Dacă nu se apelează f.flush() datele rămân în bufferele interne ale bibliotecii standard Python. Aceste date vor fi sincronizate (flushed) la un moment de timp ulterior: fie când se apelează f.flush(), fie când se închide fișierul, fie când se face o operație care impune sincronizarea datelor.

Buffered I/O vs. System-Level I/O

Ne propunem să investigăm diferențele dintre apelurile de tip buffered I/O și cele de tip system-level I/O. Intrăm în subdirectorul buffered-system-io/ din directorul cu demo-uri ale cursului și urmărim fișierele buffered.c și system.c. Observăm că operațiile efectuate sunt similare; diferă doar funcțiile folosite, unele de tip buffered I/O, altele de tip system-level I/O.

Compilăm cele două programe folosind comanda:

make

Pentru a investiga apelurile de bibliotecă ale programului buffered executăm rapid pașii:

  1. Într-o consolă rulăm programul buffered:
    ./buffered
  2. Rapid, într-o altă consolă, urmărim apelurile de bibliotecă realizate de programul buffered folosind comanda:
    ltrace -e putchar,fputc -p $(pidof buffered)

Pentru a investiga apelurile de sistem ale programului buffered executăm rapid pașii:

  1. Într-o consolă rulăm programul buffered:
    ./buffered
  2. Rapid, într-o altă consolă, urmărim apelurile de sistem realizate de programul buffered folosind comanda:
    strace -e write -p $(pidof buffered)

Care este numărul de apeluri de bibliotecă și numărul de apeluri de sistem realizate de programul buffered în cadrul buclelor for?

Răspuns

Răspuns

Programul buffered realizează 10 apeluri de bibliotecă putchar și 20 apeluri de bibliotecă putc, conform celor două bucle for. Întrucât folosește buffered I/O, se face flush/sincronizare, adică se face apel de sistem doar dacă se ajunge la un caracter newline (\n) sau dacă se apelează fflush(). Deci se vor face doar două apeluri de sistem: unul la apelul printf(“\n”); și altul la apelul fflush(f);.

Realizați pașii de mai sus și pentru programul system. Doar că pentru a detecta apelurile de bibliotecă realizate folosiți comanda

ltrace -e write -p $(pidof system)

Care este numărul de apeluri de bibliotecă și numărul de apeluri de sistem realizate de programul system?

Răspuns

Răspuns

La fel ca programul buffered, programul system realizează tot 10 + 20 = 30 apeluri de bibliotecă, dar write în acest caz. Întrucât folosește system-level I/O fiecărui apel de bibliotecă îi corespunde un apel de sistem, deci vor fi tot 30 de apeluri de sistem.

Întrucât apelurile de sistem sunt costisitoare, este recomandată folosirea buffered I/O. Dar, dacă este nevoie ca datele să fie sincronizate/flush imediat ce au fost scrise, va trebui folosit system-level I/O.

Apelul dup și cursorul de fișier

Ne propunem să urmărim efectul apelului dup() asupra cursorului de fișier. Pentru aceasta accesăm subdirectorul open-dup/ din directorul cu demo-uri a cursului și parcurgem fișierele open.c și dup.c. În ambele fișiere se deschid doi descriptori de fișier (fd1 și fd2): în cazul fișierului open.c descriptorul fd2 este creat folosind apelul open(), iar în cazul fișierului dup.c descriptorul fd2 este creat folosind apelul dup().

Compilăm programele folosind comanda

make

și obținem executabilele open și dup.

Rulăm executabilul open:

./open

Pentru a urmări tabela de descriptori de fișier și cursorul de fișier pentru procesul creat, deschidem o altă consolă și rulăm comanda:

watch -d lsof -a -o -d 0-1023 -p $(pidof open)

În consola în care am rulat executabilul open folosim comanda ENTER pentru a parcurge pașii din program. Urmărim în a doua consolă evoluția tabelei de descriptori și a cursorului de fișier.

Coloana OFFSET indică poziția cursorului de fișier.

Realizați aceeași pași pentru executabilul dup.

Ce diferențe apar la nivelul tabelei de descriptori de fișier și cursor de fișier între programul open și programul dup?

Răspuns

Răspuns

Ambele apeluri creează o intrare nouă în tabela de descriptori de fișier. În cazul apelului dup intrarea nou creată este o clonă a celei vechi. Alterarea cursorului de fișier pentru descriptorul fd1 este vizibilă pentru descriptorul fd2. Cei doi descriptori referă aceeași structură, care conține același cursor de fișier. În cazul apelului open intrările sunt distincte, chiar dacă în final referă același fișier. Modificarea cursorului de fișier pentru descriptorul fd1 nu afectează cursorul de fișier pentru descriptorul fd2.

so/cursuri/curs-02.txt · Last modified: 2019/04/14 09:08 by dragos_florin.costea
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