Un proiect în Sisteme de Operare

Mihai Budiu -- mihaib+ at cs.cmu.edu, Raluca Budiu -- ralucav+ at cs.cmu.edu
http://www.cs.cmu.edu/~mihaib/

ianuarie 1998

Subiect:
cum se argumentează în sprijinul unei idei într-un articol
Cunoștințe necesare:
cunoștințe destul de serioase despre funcționarea sistemul de operare Unix
Cuvinte cheie:
apel de sistem, intercepție, performanță, model teoretic
Articole
anterioare din PC Report utile (disponibile din pagina de web a autorului):


Contents



Informatica

Cea mai grosolană diviziune a ``științei calculatoarelor'' (cum o numesc anglo-saxonii), sau, dacă preferați, a ``informaticii'' (în accepțiunea francofonă a termenului) va segmenta domeniul în două zone relativ distincte prin natura cercetării și metoda aplicată. Cele două zone sunt ``Teoria'' și ``Sistemele''.

Teoria

Despre prima este destul de limpede cum stau lucrurile: este o ramură a matematicii, care uzînd de arsenalul acesteia (definiții, leme, teoreme, demonstrații, etc.), manipulează o serie de abstracții ale fenomenelor petrecute în calculatoare. Multe părți din informatică aparțin majoritar acestui domeniu; cele mai evidente sunt: teoria algoritmilor, teoria complexității, metodele numerice (numite și ``scientific computing''), etc.

Trebuie spus că aproape întotdeauna dezvoltarea teoretică a unei ramuri a calculatoarelor o precede pe cea practică; calculatoarele însele există pe hîrtie dinainte de anii 1930, deși realizarea lor concretă început să prindă contur abia în timpul celui de-al doilea război mondial.

De fapt rolul teoriei în informatică este enorm; foarte puțini dintre ``butonari'' realizează în ce măsură sculele pe care le folosesc există numai pentru că au fost inițial concepute în termeni matematici. Un exemplu frapant este teoria compilării, care a fost dezvoltată la începutul anilor '70, și care a deschis era unor limbaje mult mai clare și mai coerente, care realmente au mărit enorm sfera de accesibilitate a calculatoarelor pînă la ``marele public''.

Scopul acestui articol nu este însă să vorbească despre această ramură, așa că mă voi opri aici cu considerațiunile care o privesc.

Sistemele

A doua parte a informaticii este un teren de activitate ``inginerească''; denumirea sa dominantă pare să fie ``sisteme''. Trăsătura definitorie este că manipulează ``sisteme'' foarte complexe (din foarte multe părți) a căror totalitate sau comportare nu se pretează foarte bine la o manipulare analitică (adică folosind uneltele teoretice). Munca în acest domeniu se bazează pe o mare cantitate de fapte empirice (observate experimental) și pe capacitatea de a agrega mental părțile componente într-un tot.

Totul

Dichotomia aceasta (teorie-sisteme) este exprimarea dichotomiei știință-tehnică. Știința încearcă să descrie fenomene idealizate, simplificate, tehnica încearcă să aplice aceste descrieri obiectelor reale, care sunt o întrețesere încîlcită de fenomene simple, din care foarte adesea unele încă ``necunoscute'' din punct de vedere științific. Distanța este cea de la ecuațiile cîmpului magnetic ale lui Maxwell și fizica cuantică la construcția unui televizor1.

Din observațiile mele personale aceste două arii de activitate cer însușiri deosebite. Există astfel teoreticieni renumiți care nu știu mai mult decît cîteva comenzi ale unui calculator, pentru a manipula poșta electronică. Există de asemenea ``legende vii'' în sisteme care nu știu prea multe metode de sortare. Firește, există și celelalte două extreme: matematicieni de forță care totodată scriu și manipulează sisteme extrem de complexe (două nume faimoase ar fi John von Neumann, care este unul dintre cei mai mari matematicieni ai secolului și care este considerat părintele arhitecturii contemporane a calculatoarelor, și Donald Knuth, care, pe lîngă cele 3 faimoase volume de teoria algoritmilor a scris programele TeX și Metafont pentru tehnoredactare computerizată), și există (marea masă a utilizatorilor de calculatoare) inși care nu știu nici teorie și nici sisteme.

Un raport tehnic în sisteme

Acest articol își propune să arate ceva din natura cercetării științifice și a argumentării în domeniul ``sistemelor''. Eu însumi sunt un proaspăt învățăcel, așa că sunt mult mai multe fapte pe care nu le știu decît lucruri pe care le știu. Cu toate acestea aș vrea să spun cîteva lucruri neevidente (cel puțin eu nu le-am ghicit de unul singur) care ar putea fi de folos celor care activează în acest domeniu.

Prezentarea se bazează pe un proiect de curs pe care l-am realizat în toamna lui 1997. Proiectul și raportul însoțitor au fost realizate de Raluca și Mihai Budiu. Textul care urmează este destul de ``greu''; mi-ar fi plăcut să mă pot apleca mai mult asupra unora dintre detalii, dar și așa textul a ieșit cam mare. Voi folosi tacit informații dintr-o mulțime de articole pe care le-am publicat anterior în PC Report.

Textul care urmează va încerca să evidențieze următoarele două aspecte care trebuie puse în valoare într-un raport tehnic:

O să vedeți că articolul poate părea plictisitor prin amănunte, mai ales în secțiunile de performanță. Specifică tot felul de detalii, parametrii cu care se fac măsurătorile, tipul mașinilor, încărcătura rețelei, ce se măsoară, cînd începe și cînd se termină măsurătoarea, etc. Cu toate acestea, este relativ tipic (nu ca nivel științific, ci ca alcătuire) pentru un articol de cercetare contemporan în ``sisteme''.

În absența acestor detalii nu există nimic; acestea trebuie să fie suficiente ca oricine să poată duplica experimentele întocmai. Valoarea unui singur parametru poate schimba complet rezultatele, deci trebuie încercată menținerea sub control a tuturor parametrilor semnificativi.

O propunere: ``cîini de pază''

Dorința noastră inițială a fost nu prea ambițioasă: să implementăm în cadrul sistemului de operare Linux un mecanism descris în urmă cu 10 ani într-un articol apărut în ``Computing Systems''. Titlul articolului era ``Watchdogs -- Extending the UNIX File System'', de Brian Bershad si Brian Pinkerton, de la universitatea Washington din Seattle2. Traducerea titlului ar fi ``Cîini de pază: o extensie a sistemelor de fișiere din Unix''.

Articolul propunea implementarea unui mecanism în interiorul nucleului prin care anumite fișiere pot fi supravegheate de către procese. Cînd un alt proces încearcă să acceseze un fișier supravegheat, supervizorul (cel care este de fapt numit ``cîine de pază'') este executat și i se pasează informații despre tipul accesului încercat. Supervizorul decide care trebuie să fie rezultatul accesului.

Aplicații

Vom face mai tîrziu un desen care arată cum funcționează un astfel de sistem. Pentru început să observăm că numărul de aplicații al unui astfel de mecanism este uriaș. Iată cîteva posibilități:

ACL
(access control lists: liste de control al accesului): Unix oferă un sistem de protecție al fișierelor relativ rudimentar, bazat pe diviziunea utilizatorilor în 3 categorii: utilizatorul (unic) care posedă un fișier, grupul utilizatorilor care posedă fișierul și restul lumii. Se pot imagina scheme mai flexibile, în care un fișier are drepturi pentru mai mulți utilizatori, de genul: X are voie să scrie dar Y nu are voie să citească. Un ``watchdog'' ar putea implementa un astfel de control;

fișiere active:
în Windows NT fișierele sunt obiecte ``active'' în sensul că procesele pot să primească automat evenimente (events) atunci cînd alte procese acționează asupra fișierelor. În acest fel un manager de fișiere (File Manager) poate afișa permanent situația cea mai recentă a fișierelor. În Unix așa ceva nu este cu putință. Un watchdog ar putea însă intercepta acțiunile și trimite semnale proceselor interesate de schimbările pe fișiere;

conținut generat dinamic:
un watchdog poate genera dinamic conținutul fișierelor în funcție de anume parametri, cum ar fi identitatea procesului care accesează fișierul (cu alte cuvinte fiecare proces ar putea vedea altceva în același fișier). Acest mecanism ar putea fi folosit pentru a implementa un server de web simplu emulînd mecanismele cgi-bin, prin care serverul execută alte procese cînd i se cer anume fișiere. Watchdog-ul poate subsuma aceste alte procese;

managere pentru ``obiecte de memorie'':
sistemul de operare Mach permite utilizatorilor să construiască propriile lor sisteme de memorie virtuală; fiecare zonă de memorie are un proces responsabil care paginează zona. În Unix de acest lucru se ocupă nucleul. Un watchdog, împreună cu apelul de sistem mmap(), ar putea permite implementarea unor mecanisme ca cele din Mach: mmap() transformă un fișier într-o zonă de memorie, iar managementul fișierului este făcut de un watchdog. Schema asta este foarte flexibilă; folosind-o se poate implementa de pildă un sistem de memorie partajată distribuită (DSM: distributed shared memory), în care mai multe procese de pe calculatoare diferite scriu în ``aceeași'' zonă de memorie;

colectare de statistici:
putem scrie un watchdog care numără accesele la fișiere pentru a permite eventual optimizarea plasării fișierelor des accesate sau studii despre obiceiurile programatorilor;

mecanismul de portal din BSD Unix:
BSD 4.4 conține un mecanism numit ``portal'' care folosește nume de fișiere pentru ``obiecte'' din rețea; de pildă o legătură telnet TCP/IP la calculatorul foo.bar.org are un fișier numit /portal/tcpip/foo.bar.org/telnet. Un proces care deschide acest fișier stabilește de fapt o legătură telnet cu acel calculator. (Un portal este un fel de URL).

controlul versiunii:
sistemul de fișiere Translucent de la Sun crează pentru fiecare fișier cîte o ``versiune'' nouă atunci cînd este modificat, permițînd astfel recuperarea versiunilor mai vechi. Watchdog-ul poate face ceva asemănător;

fișiere la distanță:
vom vedea această aplicație în articolul de față;

etc.
mai putem imagina și alte aplicații ale mecanismului de watchdog, dar ne oprim aici.

Arhitectura sistemului

Terminologie

Să stabilim niște convenții terminologice ca să simplificăm discuția. Voi numi ``watchdog'' atît procesul care supervizează accesul la un fișier cît și structurile de date din nucleu prin care watchdog-ul este implementat.

Procesele care acționează asupra fișierului supervizat vor fi numite ``clienți'' ai watchdog-ului.

Arhitectura

Un singur lucru mai avem de lămurit: cum comunică procesul watchdog cu nucleul. În Unix nu există nici un mecanism prin care nucleul să ceară servicii unui proces. Am ales următoarea variantă, inspirată de sistemul de fișiere /proc: un watchdog comunică cu nucleul folosind apelurile de sistem pentru a scrie/citi fișiere. (Această soluție este diferită de cea oferită de articolul lui Bershad.)

Introducem un nou apel de sistem prin care nucleul crează un ``fișier'' special, de tip ``watchdog''. Să ne reamintim că în interiorul nucleului fișierele se reprezintă printr-o structură de date specială numită vnod3. Din multe puncte de vedere funcționarea unui watchdog seamănă cu cea a unui pipe între două procese; în interiorul nucleului se vor implementa la fel, sub forma unor ``fișiere'' (sau mai exact vnod-uri).

Figura 1 ne arată cum lucrează un watchdog.

Figure 1: Arhitectura unui watchdog
\begin{figure}\centerline{\epsfxsize=14cm\epsffile{arhitectura.eps}}\end{figure}

Pașii execuției sunt următorii:

Întîi procesul supervizor execută apelul de sistem watchdog(fisier), prin care anunță nucleul că vrea să devină un watchdog pentru fișierul indicat. Nucleul construiește în interior un fișier (vnod) care este asociat procesului watchdog și returnează acest fișier special (să-l numim în cele ce urmează wd).

  1. Procesul watchdog încearcă să citească ceva din acest fișier special (cu read(wd, request, sizeof(wd_request)); nucleul blochează watchdog-ul în operația de citire pînă la apariția unui client;

  2. Un proces (client) încearcă să acceseze fișierul supervizat;

  3. Nivelul VFSSW (Virtual File System Switch; descris în articolul citat mai sus din PC Report din februarie 1998) interceptează apelul de sistem;

  4. Nucleul construiește o structură de date (de tipul wd_request) care descrie apelul de sistem al clientului; pasează această structură în vnod-ul watchdog-ului.

  5. Procesul client este suspendat și watchdog-ul este trezit;

  6. Procesul watchdog termină apelul de sistem început la pasul 0; rezultatul acelei citiri este structura wd_request;

  7. Watchdog-ul descifrează structura și acționează în consecință;

  8. Watchdog-ul ar putea decide să acceseze el însuși fișierul supervizat;

  9. Accesul watchdog-ului la acel fișier nu este interceptat ci merge direct la fișier;

  10. Watchdog-ul pune rezultatul deciziei sale într-o structură de tipul wd_reply și face un write(wd, reply, sizeof(wd_reply)), trimițînd structura nucleului;

  11. Nucleul decodifică răspunsul watchdog-ului;

  12. Nucleul re-pornește clientul blocat;

  13. Nivelul VFSSW execută acțiunea indicată de watchdog; dacă watchdog-ul a trimis date acestea sunt pasate procesului;

  14. Dacă watchdog-ul a spus că putem lăsa clientul să facă cu fișierul ce a cerut, atunci clientul accesează direct fișierul;

  15. Apelul de sistem al clientului se termină.

Cu toții sunt gata pentru un nou ciclu de operații de la pasul 0.

Observați că toată procesarea watchdog-ului este absolut independentă de tipul fișierului accesat (pentru că se petrece în nivelul VFSSW); fie că fișierul este DOS, Unix sau NFS, watchdog-ul face același lucru. Din cauza asta un watchdog poate superviza orice fel de fișier sau obiect care se reprezintă în nucleu ca un fișier, cum ar fi un periferic, un pipe sau o conexiune de rețea (socket).

Un exemplu de watchdog

Iată o implementare a unui proces watchdog foarte simplu, care permite deschiderea unui fișier, dar nu scrierea în acel fișier. Cînd un proces citește din fișier, rezultatul va fi întotdeauna șirul ``Paseaza datele astea clientului cind citeste'' (sau un prefix, dacă clientul a citit mai puțin decît lungimea șirului).

#include "watchdog.h"

int main()
{
   int fd, /* Fisier de supervizat. */ 
       wd; /* Interfata watchdog-ului cu nucleul. */
   char buf[] = "Paseaza datele astea clientului cind citeste";

   fd = open("nume_fisier", O_RDWR); /* e nevoie de permisiuni de scriere/citire. */
   wd = watchdog(fd);   /* apel de sistem: ne inregistram ca watchdog. wd=descriptor de fisier */

   while (1) {
      struct wd_request wq;   /* nucleul va descrie apelul de sistem aici */
      struct wd_reply   wr;   /* watchdog-ul raspunde nucleului aici */

      read(wd, (char*)&wq, sizeof(wq));     /* Asta blocheaza watchdog-ul pina
                                               cind nucleul are ceva de zis */
      switch (wq.operation) {
        case WD_OPEN:
                printf("Procesul %d deschide `nume_fisier', mod %d\n", wq.mode);
                wr.action = WD_ALLOW;  /* lasam procesul sa faca ce a cerut */
                break;
        case WD_READ:
                printf("Procesul %d citeste din `nume_fisier', offset %ld,"
                        "marime %ld\n", wq.pid, wq.offset, wq.size);
                wr.action = WD_FAKE;  /* Watchdog-ul o sa dea datele procesului */
                if (wq.size < sizeof(buf)) wr.size = wq.size;
                else wr.size = sizeof(buf);  /* Ii dam atitea date pentru read() */
                wr.buffer = buf;      /* Aici sunt datele de pasat procesului */
                break;
        case WD_WRITE:
                printf("Procesul %d vrea sa scrie in `nume_fisier', offset %ld,"
                        "marime %ld\n", wq.pid, wq.offset, wq.size);
                wr.action = WD_DENY;  /* Nu permitem scrierea. */
                break;
      }
      write(wd, (char *)&wr, sizeof(wr));    /* Trimite raspunsul nucleului. */
      if (wq.operation == WD_CLOSE) break;   /* Daca clientul a inchis fisierul, terminam*/
   }
   close(fd);  /* Gata cu supervizarea fisierului. */
   close(wd);  /* Inchidem fisierul ``watchdog''. */
}

Iată cum arată în implementarea noastră informațiile de la nucleu și răspunsurile:

enum operation {                /* apeluri de sistem interceptate */
        WD_READ, WD_WRITE, WD_OPEN, WD_CLOSE, WD_LSEEK
};

enum action {                   /* ce spune watchdog-ul nucleului */
        WD_ALLOW,               /* :da-i voie sa faca ce a cerut */
        WD_DENY,                /* :clientul nu are voie sa faca asta */
        WD_FAKE                 /* :OK, paseaza datele la/de la
                                        watchdog (nu din fisier) */
};

struct wd_request {     /* Descrierea operatiei, primita de watchdog de la nucleu */
        enum operation operation; /* operatia */
        int fd;                 /* care fisier */
        int pid;                /* care proces */
        int uid;                /* posesorul procesului */
        int gid;                /* grupul procesului */
        long offset;            /* unde in fisier se opereaza */
        int size;               /* citi octeti se transfera */
        int mode;               /* drepturile cu care se deschide fisierul */
        int whence;             /* pentru lseek: de unde se face lseek */
        int inode_count;        /* cite procese au deschis acest fisier */
};

struct wd_reply {               /* Raspunsuri de la watchdog la nucleu: */
        enum action action;     /* ce actiune sa faca nucleul */
        int fd;                 /* pe care fisier (pot fi mai multe simultan) */
        char * buf;             /* daca e FAKE aici sunt datele pentru read/write */
        int count;              /* cit de multe date pentru read/write */
};

Singura operație care merită explicații este FAKE (``simulează''). Cînd un client face o citire și watchdog-ul răspunde cu FAKE, nucleul trebuie să preia datele de citit nu din fișier ci din buffer-ul indicat de watchdog.

Cînd un client face o scriere și watchdog-ul zice FAKE, nucleul nu trebuie să scrie datele în fișier ci în buffer-ul indicat de watchdog.

O întrebare: eficacitatea

Iată deci principalul avantaj al unui astfel de mecanism: flexibilitatea. Un watchdog ne permite să implementăm o sumedenie de servicii noi dar fără a modifica cu nimic interfețele existente, păstrînd în întregime compatibilitatea. Procesele care lucrează cu fișiere nu vor afla niciodată că de fapt vorbesc cu un watchdog, pentru ca folosesc exact aceleași apeluri de sistem ca mai-nainte.

Nimic nu e pe gratis, însă. Ce avem de plătit pentru această flexibilitate?

Răspunsul este clar: performanță. De fiecare dată cînd un proces accesează un fișier cu watchdog avem de făcut multe alte lucruri în plus față de cazul ``normal''.

Aceasta este una din tezele acestui articol:

Înainte de a ne lansa în implementarea unui proiect trebuie să estimăm dacă merită. Pentru asta trebuie să avem o idee despre performanța sistemului.

Mai ales dacă avem de construit un sistem foarte mare, trebuie să ne facem o idee despre performanță înainte de a construi sistemul. Dacă rezultatul o sa fie de 10 ori mai lent decît orice există atunci nu merită; trebuie reconsiderat întreg proiectul.

Hai deci să estimăm ce avem de pierdut. Care este deci cărarea critică: traseul execuției care costă cel mai mult.

Componentele costului

Din păcate cam toți pașii din figura de mai sus 1 sunt pe cărarea critică, mai puțin pasul 0, care este executat în paralel de watchdog și de client.

Costul fără watchdog

Dacă nu avem watchdog, o operație pe un fișier constă doar din pașii 1, 2, 13 și 14. Toți ceilalți pași sunt suplimentari (overhead), iar costul lor relativ trebuie să fie scăzut ca operația watchdog-ului să nu fie disruptivă pentru performanță.

Costul suplimentar cu watchdog

Ia să vedem ce fel de costuri plătim pentru watchdog:

Ceilalți pași sunt relativ neglijabili în cost4.

Rezultatele sunt prezentate într-un tabel:

Operație suplimentară Cantitate
Apel de sistem 2
Comutare de proces 2 -- zeci
Copiere a datelor 2

Copierea datelor între procese.

Întrebarea este ``cum putem copia date între două procese''? Problema constă în faptul că atunci cînd un proces se execută, folosește propriul său spațiu de memorie virtuală; fiecare proces are propria lui adresă 5, care nu coincide cu a nici unui alt proces. Atunci cum pot muta date de la o adresa 7 dintr-un proces la adresa 12 a altui proces? Nicicum direct, pentru că atunci cînd se execută primul, adresa celui de-al doilea nu are sens și invers.

Soluția este ca datele să fie copiate în interiorul nucleului, care aparține ambelor spații de adrese. (Acesta este și mecanismul folosit de pipe în Unix pentru a transfera datele între procese.) Soluția este deci următoarea:

Iată de ce transferul de date implică mai multe comutări de procese; numărul lor depinde de raportul dintre cantitatea de date transferate și mărimea buffer-ului intern. În implementarea noastră am folosit un buffer intern de 8Kb (două pagini). Asta înseamnă că 1Mb de transferat implică 1000/8 = 125 de comutări de proces înainte și înapoi.

Costul componentelor

Acum hai să vedem cît costă concret fiecare componentă. Asta o să ne dea o idee despre fezabilitate. Niște măsurători simple5 pentru sistemul pe care am implementat watchdog-ul (PII 266MHz cu 64M RAM, rulînd Linux 2.0.30) au dat următoarele valori:

Operație Cost în $\mu$s
Apel de sistem 2
Comutare de proces 7.5
Copiere de date 20 000-40 000/Mb

(Durata copierii depinde de mai mulți factori; dimensiunea influențează rata de succes în cache, care influențează viteza.)

Se vede că dominant este costul copierii, chiar pentru cantități mici de date (1Kb ar lua peste 10$\mu$s) pentru o copiere, iar noi avem de făcut două, una ``în sus'' și una ``în jos''.

Performanța relativă

Toate aceste evaluări nu s-au preocupat de loc de costul accesului la disc. Ori discul este un periferic extrem de lent; numai mutarea brațului durează de ordinul a 10ms (10 000 de $\mu$s!). Costul suplimentar (overhead) al watchdog-ului pentru astfel de circumstanțe devine oarecum neglijabil.

Citirea dintr-un fișier de pe disc (deschis dinainte) a 1Mb de date durează cam 150ms. Costul operațiilor watchdog-ului pentru o operație care implică 1Mb este cam de 40ms + 0.0075ms * 125 + 0.004 = 41ms, deci cam de 25%.

Pe scurt, iată importanța costului suplimentar al unui watchdog, în funcție de tipul operației efectuate:

Discul accesat Cantitate de date Overhead
nu puține 1000%
nu multe 200%
da oricîte 25-50%

Utilitatea proiectului revizuită

Iată cum o simplă evaluare teoretică ne spune deja foarte multe lucruri despre mecanismul de watchdog: ne spune de pildă ce fel de servicii merită să fie oferite de watchdog și care nu. Dacă un serviciu costă mult mai puțin decît overhead-ul, atunci watchdog-ul este prea costisitor și trebuie să căutăm altă soluție.

De exemplu nu rentează să implementăm ACL (controlul accesului) cu watchdog, pentru că astfel de operații nu transferă date și nu accesează discul (decît prima oară cînd un fișier este deschis; următoarele accese vor merge în cache). Pentru astfel de operații citim din tabelul de mai sus că watchdog-ul încetinește operația de 10 ori! Locul unui astfel de serviciu este în nucleul sistemului de operare.

Un watchdog nu va fi prea costisitor în următoarele cazuri:

O măsurătoare a performanței

Acum că am văzut că un watchdog nu e complet inutil ne-am apucat să implementăm mecanismele necesare. Am construit în nucleu intercepția apelurilor de sistem și am scris apoi un watchdog foarte foarte simplu (care seamănă cu cel din exemplul de cod anterior), cu care am măsurat performanța reală. Acum vom vedea dacă estimarea noastră teoretică de mai sus a fost realistă.

Am făcut un set de 5 experimente, după cum urmează:

  1. Am executat un apel de sistem lseek() (nu atinge discul);
  2. Am executat un lseek() după care am citit din fișier un octet;
  3. Am executat lseek() și am citit o pagină (4K);
  4. lseek() urmat de citirea a 40K;
  5. lseek() urmat de citirea a 1M.

Am măsurat costul acestor operații atunci cînd sunt efectuate de cinci entități diferite:

  1. Costul atunci cînd folosim pentru același scop două procese care comunică printr-un named pipe (am remarcat mai sus asemănarea dintre un watchdog și un pipe); (operația lseek() nu are sens pe un pipe; se va termina mereu cu o eroare; am pus-o ca să măsurăm același lucru în toate cazurile);
  2. Costul operației executate de sistemul de operare (SO);
  3. Costul operației atunci cînd watchdog-ul interzice operația;
  4. Costul atunci cînd watchdog-ul zice ``da'' (ALLOWED) iar nucleul execută operația;
  5. Costul atunci cînd watchdog-ul execută el însuși operația și pasează datele clientului (FAKE).

Toate operațiile se fac cu datele în cache; discul nu este folosit niciodată. Iată rezultatele, în microsecunde:

Operație seek read(1) read(4096) read(40K) read(1M)
SO 2.1 5.1 8.6 220 21406
pipe 2.2 11 66 720 57812
interzis 17 33 33 33 33
permis 17 35 42 270 22968
watchdog 20 44 88 634 50300

Rezultatele par să confirme estimările noastre: interzicerea costă o valoare constantă: 17$\mu$s pentru fiecare serviciu (2 comutări de procese plus 1 apel de sistem). Darea permisiunii adaugă un cost constant la operația cu SO: cel al comutării la watchdog și înapoi. Cînd watchdog-ul face operația el însuși costul copierii datelor între watchdog și client apare în ecuație și domină la cantități mari de date.

Demn de remarcat este că datorită unor optimizări simple, watchdog-ul este mai rapid ca un pipe. Codul nostru folosește buffere în nucleu de două ori mai mari decît un pipe, (deci face de două ori mai puține comutări), și apoi permite unui apel de sistem read() să returneze mai mult decît o pagină de date (pipe nu permite așa ceva), deci reduce numărul de apeluri de sistem executate de client.

O aplicație a mecanismului: un sistem de fișiere la distanță

Un watchdog este un mecanism. Avem nevoie de o aplicație pentru a demonstra utilitatea lui. Am ales o aplicație al cărei cost intrinsec promitea să facă overhead-ul unui watchdog neglijabil: accesul la fișiere la distanță, prin rețea. Timpul mare de transfer al unei rețele este factorul pe care ne bazăm. Să observăm că latența (timpul de transfer) rețelelor este relativ mare, fiind limitată de viteza de transmisiune, care nu poate fi mai mare ca viteza luminii. Cantitatea de date transmise (bandwidth sau throughput) este oarecum nelimitată; vezi rețelele giga-bit/secundă care devin populare.

Alți candidați și slăbiciunile lor

Sisteme de fișiere la distanță există de cînd lumea; NFS (Network File System) de la firma SUN este extrem de popular. Ne-am propus să scriem serviciul nostru în așa fel încît să batem NFS-ul. Pentru asta trebuie să profităm de lucrurile la care NFS este slab. NFS este (în implementarea pe Linux) foarte slab la scrieri: deși ține într-un cache la client datele citite, scrierile le trimite direct la server, ca nu cumva alți clienți care folosesc același fișier să nu le vadă. Mărimea blocului de cache pentru NFS este de 8K.

Un al doilea candidat faimos este sistemul de fișiere distribuit dezvoltat la Universitatea Carnegie Mellon numit AFS (Andrew File System). Andrew este mult mai inteligent și folosește mult mai bine cache-ul de la client6. Mărimea blocului pentru cache-ul Andrew este de 64K.

Un serviciu de ``caching'' inteligent

Am discutat pe larg despre performanța în sistemele de fișiere într-un articol publicat în PC Report în noiembrie 1997. Voi recapitula aici faptele care ne interesează:

Să observăm că pentru o aplicație scrierile și citirile se comportă în mod deosebit:

Toate sistemele de fișiere de rețea încearcă să amortizeze latența rețelei făcînd transferuri în blocuri mari de date. Cam tot atîta costă să aduci un octet cît costă 10K, așa că de ce să nu aduci 10K; poate mai ai nevoie de ei. Această caracteristică a cache-urilor, de a manipula blocuri mari, este uneori și slăbiciunea lor.

Iată de ce: problema constă în scrierile mici. Cînd un proces scrie mai puțin de un bloc, pentru că cache-ul manipulează numai blocuri întregi, trebuie să aducă de la server blocul întreg și să aplice scrierea peste bloc. Din cauza asta scrierile mici sunt sincrone, ca și citirile.

Acest lucru este adevărat atît în NFS cît și în AFS.

Am scris deci propriul nostru serviciu de cache. Am încercat să exploatăm slăbiciunile sistemelor de fișiere de rețea în două feluri:

Deși este foarte interesant în sine, nu vom explora aici arhitectura cache-ului cu intervale. Doar intern cache-ul manipulează datele tot în blocuri, iar mărimea blocului din cache are un oarecare impact asupra performanței.

Să vedem cum este el folosit împreună cu un watchdog pentru a oferi servicii îmbunătățite clienților.

Serviciul de fișiere la distanță

Am implementat deci un watchdog care este și un cache manager. Am implementat apoi un server care va comunica cu watchdog-ul prin TCP-IP. Cache-ul este împărțit în două thread-uri care funcționează asincron:

Pachetul de thread-uri folosit a fost scris tot de autorul acestui articol, și a fost prezentat în PC Report din ianuarie 1997.

Figura 2 prezintă arhitectura sistemului de fișiere.

Figure 2: Arhitectura Sistemului de fișiere la distanță.
\begin{figure}\centerline{\epsfxsize=14cm\epsffile{poornfs.eps}}\end{figure}

Măsurători de performanță

Iată secțiunea crucială, cea a măsurătorilor de performanță comparative. Am făcut experimente numai pentru cazurile care ne interesează (unde anticipăm că sistemul nostru o să fie ``mai tare''), și anume:

În testele noastre am avut sub control calculatoarele client (întotdeauna mașina Linux descrisă mai sus). Serverul pentru watchdog și NFS a fost pe o mașină Sun SPARC4. Mașinile nu erau încărcate la măsurători. Mașinile sunt pe același cablu Ethernet, separate doar de bridge-uri.

Nu am avut sub control:

Din cauza asta am repetat măsurătorile de mai multe ori și am considerat cele mai bune valori ale performanței.

Teste secvențiale

Iată un test secvențial care doar copiază un fișier de 2M între cele două mașini (cu comanda Unix cp). Cache-ul nostru avea un bloc intern de 64K. Cache-urile erau goale la începutul transferului.

Cache-ul folosit de AFS și NFS poate crește pînă la aprox. 60M, mărimea memoriei calculatorului. WD a avut un cache fixat la 640K capacitate totală.

Comparăm 4 sisteme:

UFS:
Sistemul de fișiere din Unix de pe discul local;
NFS:
Sun Network File System;
AFS:
Andrew File System;
WD:
sistemul nostru de fișiere bazat pe watchdog.

Ceea ce măsurăm este timpul total real al copierii (adică cît timp trece de cînd începe pînă se termină); asta nu e totuna cu timpul de execuție al proceselor, pentru că procesele vor dormi mult timp așteptînd datele, deci nu vor consuma timp de procesor. Copierea se termină cu un apel de sistem fsync() care ne asigură că toate datele au fost trimise la destinație, și că nu au rămas în cache-ul local!

Protocol Sursă Destinație Timp (s)
UFS disc local disc local 0.2
NFS la distanță disc local 8.4
WD la distanță disc local 6.9
AFS la distanță disc local 5.3
NFS disc local la distatță 61.0
WD disc local la distanță 11.3
AFS disc local la distanță 9.5

După cum se vede am reușit să-l batem pe NFS. NFS face citire anticipată (read-ahead), de aceea performanța la citire este rezonabilă. NFS trimite datele una cîte una în blocuri de 8K, deci pierde foarte mult timp la scrieri pentru a iniția pachetele.

AFS trimite blocuri de 64K, deci este foarte eficient. AFS face și el citire anticipată. AFS este implementat în nucleul sistemului de operare (ca și NFS), deci nu are overhead atît de mare ca WD pentru comutarea proceselor și copierea datelor (mai ales!). De aceea este mai performant.

Performanța secvențială variind bufferele

Datele trebuie să circule între client și server prin două canale:

Performanța primului transfer este influențată de mărimea blocului intern de cache folosit (deși noi transmitem mai multe blocuri pe rețea într-o singură operație, pentru că folosim apelul de sistem writev(), acesta impune un număr maxim de 16 blocuri scrise simultan). Performanța celei de-a doua este influențată de cîte date cere clientul într-un apel de sistem. Cît cere un client într-un apel de sistem constituie pentru cache un interval.

De exemplu, programul cp copiază datele în felii de cîte 4K, chiar dacă fișierele sunt uriașe.

De aceea am scris un alt client, care face ce face și cp, dar care face transferul datelor în felii mai mari.

Iată performanța măsurată, din nou în secunde:

Interval 1K 4K 16K 64K
bloc în cache Spre discul local
64K 8 7 7 8
32K 13 13 13 9
16K 27 27 28 8
8K 54 54 27 8
Spre discul distant
64K 10 10 10 4
32K 16 16 16 4
16K 30 23 4 4
8K 48 57 4 4

Performanța depinde mai puțin de mărimea internă a blocului (cum am spus, cache-ul poate trimite multe blocuri pe rețea dintr-un foc), cît de mărimea intervalului (cerută printr-un apel de sistem). Cu cît intervalele sunt mai mari, cu atît performanța este mai bună.

Performanța scrierilor este excelentă pentru că scrierile sunt asincrone tot timpul (indiferent de raportul interval/bloc).

O anomalie avem în tabel pe coloana a treia, unde scăderea blocului crește performanța, un rezultat complet ne-intuitiv. Acesta este un artifact al modului de implementare: inspectarea codului cache-ului a revelat faptul că în cazul în care intervalul este mai mare ca blocul, totul e în regulă, dar atunci cînd blocul este mai mare decît intervalul, cache-ul inițiază operații care puteau fi evitate. (Iată deci cum măsurătorile descoperă deficiențe de implementare și sugerează îmbunătățiri!) Asta se întîmplă pe coloana a treia: cînd blocul devine mai mic decît intervalul performanța crește brusc.

Teste de acces aleator

Programele de calcul intensiv (scientific computing) manipulează adesea cantități enorme de date într-un mod cvasi-aleatoriu și în bucăți mici (au deci localitate proastă în cache și poate scrieri mici). Am scris două aplicații (oarecum artificiale, dar reprezentative la o scară redusă, sperăm) care încearcă să simuleze astfel de accese:

Am plasat fișierul la distanță și am pus blocul în WD la 4K. Iată timpii măsurați:

Test Protocol Durată (s)
Matrici UFS 1.6
NFS 301.0
WD 22.5
AFS 4.6
Sortare UFS 0.2
NFS 13.6
WD 1.7
AFS 0.4

Performanța lui NFS este îngrozitor de slabă (de 12 ori mai lent decît metoda noastră!) Măsurători care variau dimensiunea n a matricii au arătat că pentru valori mici ale lui n timpul lui NFS crește ca n2. Pentru că înmulțirea face n3 citiri și n2 scrieri deducem că problema lui NFS sunt scrierile mici (exact cum am anticipat).

AFS este excelent pentru că toate datele încap într-un singur bloc din cache (64K) care este adus imediat la început, deci face un singur transfer pe rețea.

WD este mai slab din cauza overhead-ului comutării proceselor.

Un test greu pentru AFS: ``strides''

Am vrut să găsim și un test dificil pentru AFS. Pentru că AFS aduce datele în blocuri de 64K soluția era să facem un test care exploatează foarte puțin din fiecare felie de 64K, deci care-l pune pe AFS la muncă degeaba.

Testul este simplu: scrie într-un fișier pre-existent de 2M petice de cîte 20 de octeți (scrieri mici!) la distanțe variabile (distanța dintre două scrieri o vom numi ``stride''). Am făcut două serii de teste (blocul în cache-ul WD de 64K):

Iată rezultatele:

Protocol Cache Stride Intervale scrise Timp (s)
AFS rece 1K 64*25 9.8
AFS cald 1K 64*25 5.5
AFS rece 64K 25 10.0
AFS cald 64K 25 5.6
WD rece 1K 64*25 6.3
WD cald 1K 64*25 6.4
WD rece 64K 25 6.4
WD cald 64K 25 6.4

WD este remarcabil de constant, pentru că manipulează intervalele scurte (20 de octeți) în exact același fel indiferent unde se află (practic datele sunt permanent în cache). AFS este mai slab decît WD cînd are cache-ul gol, dar nu cu mult mai slab!

Numerele arată că performanța AFS nu depinde de cîte date se scriu, ci de cîte intervale de 64K se traversează; chiar dacă scriu 64*25 intervale sau doar 25, dacă ating același număr de blocuri, tot atîta contează.

Concluzii

Acest proiect demonstrează un lucru foarte interesant:

Prețul flexibilității obținute prin implementarea serviciilor înafara nucleului (un watchdog, de pildă) poate fi cîteodată răscumpărat dacă serviciile oferite se potrivesc foarte bine cu nevoile clientului. Nucleul oferă un singur serviciu, care este eficace pentru o clasă largă de aplicații (de exemplu cele care au localitate bună în cache). Dar folosind schema noastră putem implementa simultan diferite scheme de cache pentru aplicații diferite, potrivind comportarea cache-ului pentru fiecare aplicație, și optimizînd independent.

Bibliografie

Deși nu am citat aceste lucrări, raportul de față se bazează pe ele. Rolul unei bibliografii într-un articol este enorm pentru cititori, dar despre asta altă dată7.

Bibliography

Bersh88
B. B. Bershad, C. B. Pinkerton. Watchdogs -- Extending the UNIX File System. Computing Systems, 1, 1988, pp. 169.

Saltz84
J. Saltzer, D. Reed, D Clark -- The End-to-End Argument in System Design. ACM Transactions on Computer Systems, vol 2, nr. 4, 1984, pp 277-288.

Faul91
R. Faulkner, R. Gomes. The Process File System and Process Model in UNIX System V. Proceedings of the 1991 Winter USENIX Conference, Jan. 1991, pp.243-252.

Klei86
S. R. Kleiman, 1986. Vnodes: An Architecture for Multiple File System Types in Sun Unix. USENIX Summer Conference Proceedings 1986, pp. 238-247.

Vaha96
Uresh Vahalia. Chapters 9, 10, 11 in UNIX Internals., Prentice Hall, 1996.

Traseul parcurs

Mai interesante decît concluziile (care științific vorbind nu sunt extraordinare) este drumul parcurs. Să-l revedem:

  1. Un proiect este propus;

  2. Este comparat cu alte proiecte similare sau disimilare (această secțiune este mai puțin reprezentată în acest articol);

  3. O evaluare teoretică a performanței este făcută; acesta este și un studiu de fezabilitate; eventual proiectul este simulat sau evaluat din părțile componente;

  4. Designul este adaptat pentru a ataca părțile slabe indicate de evaluare;

  5. O serie de ipoteze asupra comportării sistemului sunt enunțate explicit;

  6. Proiectul este implementat;

  7. Proiectul este măsurat cu mare grijă, într-o varietate de circumstanțe, controlînd (pe cît posibil) toți parametri influenți;

  8. Eventualele slăbiciuni sunt remediate;

  9. Concluziile verifică în ce măsură ipotezele s-au dovedit corecte și explică rezultatele obținute;

  10. Noi direcții de cercetare și întrebări sunt formulate (lipsesc din acest text).

Iată de ce spun că în sisteme totul stă în detalii: dacă pentru watchdog raportul valorilor duratelor diferitelor componente ar fi fost altul (de exemplu dacă un apel de sistem ar fi fost de 10 ori mai costisitor decît copierea datelor), atunci relevanța și aplicabilitatea watchdog-ului ar fi fost cu totul altele. Cum spunea cineva în fizică:

Dacă ai de-a face cu valori [pentru un parametru] care diferă cu un ordin de mărime, de fapt ai de-a face cu două fenomene diferite.

Asta am avut de zis; probabil am fost cam ocolit...



Footnotes

... televizor1
Paragraful acesta trebuie luat ca o opinie strict personală.
... Seattle2
Departamentul de calculatoare al acestei universități este clasat între primele 10 din Statele Unite; Brian Bershad însuși este o somitate în lumea sistemelor de operare.
...vnod3
Vedeți și articolul meu din PC Report din februarie 1998, a cărui copie este disponibilă din pagina mea de web.
... cost4
Mai există un cost ascuns al serviciilor oferite de un watchdog: și anume memoria watchdog-ului este paginabilă, spre deosebire de a nucleului. Dacă implementăm un serviciu în nucleu ar putea fi mai rapid decît dacă îl punem în watchdog pentru că memoria nucleului rămîne tot timpul în memoria fizică. Vom ignora acest lucru în acest articol.
... simple5
Sper să pot discuta într-un articol viitor despre metoda prin care am făcut aceste măsurători.
... client6
AFS este un sistem de fișiere ``mondial''; sper să pot reveni într-un articol asupra lui. Practic este un singur arbore de directoare pentru cîteva sute de instituții din cîteva zeci de țări; cu un simplu cd eu pot citi directoare aflate pe servere din Elveția. Toate calculatoarele din universitatea Carnegie Mellon sunt clienți ai acestui sistem de fișiere.
... a7
E adevărat că în textele trimise la PC Report am omis în mod sistematic bibliografiile. Mea culpa.