su questo sito
Transcript
su questo sito
Principali Revisioni • v. 1.0, novembre 2000 • v. 1.1, febbraio 2002 • v. 1.2, aprile-giugno 2002 • v. 1.3, dicembre 2002 • v. 1.4, aprile 2003 Il presente testo contiene materiale dell’autore di proprietà della Axis&Agix Sarl (Boulogne, Francia) e della AX Digital Systems Srl (Pomezia, Italia), il cui uso è stato consentito esclusivamente per le attività didattiche dell’autore stesso. Pertanto l’uso e la riproduzione dell’opera, in qualsiasi modalità, sono consentiti a titolo gratuito per i soli fini personali degli studenti dei corsi tenuti dall’autore. Ogni altra forma di utilizzazione potrà avvenire soltanto previa richiesta esplicita e successiva autorizzazione per iscritto dall’autore, sentito il parere dei coproprietari dell’opera. !#"%$'& )(*%+ ! ",-! ! $',%.! 0/ ,'&1-2$!43'! * "%!456,7)" *98: !#" * 5 3 ;=<> ? @A BDCEGF BDH 1. Introduzione Il presente testo è destinato agli studenti dei corsi di laurea in Ingegneria Informatica e in Ingegneria delle Telecomunicazioni dell’Università Roma 1 "La Sapienza", sede distaccata di Latina, e agli studenti dei corsi di laurea e di laurea specialistica in Ingegneria Gestionale, Ingegneria delle Telecomunicazioni e Ingegneria Elettronica dell’Università Roma 2 "Tor Vergata", come parte dei sussidi didattici per i corsi di "Sistemi Operativi" tenuti dall’autore; il testo è completato, per ciò che attiene alla parte pratica di programmazione di script shell, da una dispensa gemella, "Esercitazioni di programmazione in linguaggio shell". Allo stato attuale, la dispensa è in una forma leggermente incompleta; alcune parti necessitano di una revisione e/o riorganizzazione del materiale. Lo studente è invitato, per queste parti, a far riferimento a quanto detto nelle lezioni. 1.1 Storia Il Sistema Operativo UNIX1 è stato originalmente concepito e realizzato nei primi anni anni ’70, da due ricercatori dei Bell Laboratories2 (Ken Thompson e Dennis Ritchie), e si è diffuso commercialmente a partire dagli anni ’80. Le prime versioni giravano su sistemi della Digital Equipment Corporation3 (DEC): dapprima su un PDP-7, poi (dal 1971) su quelli della famiglia PDP-11, quindi (1979) sui VAX-11. Nel 1981, con la versione PC/IX, UNIX fa la sua prima apparizione nella gamma dei Personal Computer IBM e compatibili. La storia della nascita e dell’evoluzione di questo sistema è reperibile in molti testi, tra cui [Bach 1986], [Silberschatz, Galvin e Gagne 2003], [Asta 1984] e altri; esistono poi due numeri speciali del The Bell System Technical Journal, dedicati esclusivamente a UNIX, che illustrano molti aspetti interessanti del sistema, della sua storia, delle sue motivazioni originali e delle sue caratteristiche ([BSTJ 1978], [BSTJ 1984]). Qui ci si limiterà ad esporre succintamente la cronologia essenziale degli sviluppi e delle versioni più importanti. Una cronologia più dettagliata è riportata in Appendice 1. - 1969: Thompson e Ritchie, reduci dal progetto MULTICS4, iniziano a lavorare su un sistema di filesystem e di gestione dei processi, scritto parte in assembler e parte in Fortran; sperimentazioni su un PDP-7 1. La proprietà del nome UNIX ha una lunga storia alle spalle; in origine, UNIX era un marchio registrato dei Bell Laboratories, poi della AT&T, e successivamente di varie altre organizzazioni; oggi UNIX è un marchio registrato di The Open Group (http://www.opengroup.org), un consorzio industriale senza fini di lucro. 2. I Bell Laboratories erano allora una filiale della Western Electric Company, casa madre della AT&T (American Telephone and Telegraph Company); le prime versioni di UNIX si acquistavano dalla Western Electric, e solo successivamente dalla AT&T. 3. La Digital Equipment Corporation è stata successivamente acquisita dalla Compaq. 4. Progetto per lo sviluppo di un Sistema Operativo di tipo interattivo e multiutente a cui parteciparono l’MIT (Massachusetts Institute of Technology), General Electric, e Bell Laboratories; il risultato fu un sistema concettualmente interessante ma elefantiaco nella sua implementazione. Ciò deluse i ricercatori dei Bell Laboratories, che finirono per ritirarsi dal progetto. V. Asta - Introduzione a Unix e GNU/Linux 3 - 1971: viene pubblicata, internamente ai Bell Laboratories, la prima edizione del manuale di UNIX; le prime versioni, fino al 1979, saranno identificate col numero di edizione del manuale. Viene effettuato il porting su PDP-11 - 1973: riscrittura del sistema in linguaggio C, sviluppato da Ritchie - 1973: UNIX Versione 3 (terza edizione del manuale), distribuita alle Università di Berkeley e di Columbia - 1975: V.6, prima versione commercializzata; primi esperimenti di porting su calcolatori non DEC - 1979: V.7, prima versione riscritta senza alcuna dipendenza concettuale dall’architettura dei PDP-11; prime licenze con diritto di ridistribuzione binaria (fino ad allora, l’unico tipo di distribuzione era tramite i sorgenti); prima versione UNIX per minicalcolatori VAX (UNIX 32/V) - 1980: Microsoft introduce sul mercato Xenix, una delle più famose versioni commerciali (ridistribuzione binaria) di UNIX - 1981: AT&T subentra a Western Electric (che finora era stato l’organismo di vendita delle licenze del sistema) per la commercializzazione di UNIX; nuova versione, denominata System III - 1983: versione System V release 15; Berkeley pubblica la versione 4.2 per VAX, una delle più importanti, che vede tra l’altro la prima implementazione dei protocolli di rete TCP/IP - 1984: System V rel. 2 - 1986: System V rel. 3; molti sistemi UNIX commerciali, tra cui SCO UNIX per architettura IBM-PC, si basano su questa versione - 1988: System V rel. 3.2 - 1989: System V rel. 4, versione che riunifica tutte le caratteristiche di System V e quelle di Berkeley Unix. - 1991: Linus Torvalds, all’epoca studente di informatica all’università di Helsinki, inizia a sviluppare LINUX, una riscrittura indipendente di UNIX, con l’intento di renderne liberi i sorgenti; è rapidamente seguito da moltre altre persone che collaborano al progetto. - 1994: Versione 1.0 di LINUX - 2001: Versione 2.4 di LINUX Nell’arco della sua storia, il sistema ha visto la nascita e l’introduzione sul mercato di molte implementazioni, più o meno fedeli allo standard AT&T, tutte legate all’evoluzione sia dei microprocessori Intel (8088/86, 80286, 80386 e seguenti) e di altri costruttori (inizialmente soprattutto Motorola 68000/88000, Zilog Z-8000, National Semiconductors 32000, poi SUN Sparc e altri), sia delle varie release di System V. Oggi, il sistema GNU/LINUX è sicuramente l’implementazione su IBM-PC più nota e diffusa, e una tra le più valide in generale. Lo scopo di questo testo è di fornire delle nozioni di base su UNIX (di norma con riferimento al contesto IBM-PC e GNU/LINUX, anche se non necessariamente limitate ad esso), con particolare attenzione agli aspetti dell’utilizzazione a livello utente e con alcuni accenni alle problematiche di amministrazione di un sistema. 1.2 Caratteristiche generali del kernel Nell’uso corrente, il termine UNIX si riferisce genericamente ad una famiglia di Sistemi Operativi, tutti derivati in vario modo dal lavoro originale dei Bell Laboratories; si tratta di un moderno sistema timesharing, multi-utente e multi-task. Una distribuzione UNIX comprende il kernel del sistema più numerosi applicativi e programmi di utilità. Il kernel si occupa di diversi compiti indispensabili al funzionamento del sistema, tra cui: 5. La versione System IV non ha mai visto la luce al difuori dei Bell Laboratories. V. Asta - Introduzione a Unix e GNU/Linux 4 — inizializzazione — gestione dei processi (priorità, cambiamenti di stato etc.) — gestione delle risorse (CPU, memoria primaria e secondaria, periferiche etc.) — gestione dei filesystems — gestione della comunicazione tra processi e tra sistemi (protocolli di rete) È importante sottolineare che il kernel gestisce solo le funzioni essenziali dei processi: dati, interruzioni, ingressi ed uscite. Tendenzialmente, ogni altro compito è separato e realizzato da programmi indipendenti, a livello utente, come ad esempio la shell che è l’interprete dei comandi UNIX. Questo tipo di approccio è solo uno tra i numerosi elementi di novità introdotti dal sistema UNIX, che storicamente è stato un sistema "rivoluzionario" da molti punti di vista; tra le molte verità universalmente accettate fino allora, e che sono state ribaltate da UNIX, suscitando inizialmente non poche perplessità tra gli addetti ai lavori, si possono ricordare le seguenti: — Non è vero che l’interprete di linguaggio di comandi deve essere implementato nel kernel del Sistema Operativo In UNIX, la shell è un programma qualsiasi come gli altri, che per di più gira senza nessun particolare privilegio; ne esistono anzi numerose varianti, e ciascun utente può scegliere il programma che più gli è congeniale — Non è vero, analogamente, che i principali sottosistemi devono essere almeno parzialmente implementati nel kernel In UNIX, lo spooler di stampa, il sottosistema di accoglienza e autenticazione degli utenti, tutti i server Internet, quelli di basi di dati e così via, sono implementati esclusivamente a livello utente; il kernel ne ignora praticamente l’esistenza — Non è vero che il kernel di un Sistema Operativo deve essere scritto tutto in linguaggio assembler, pena un grave abbassamento delle prestazioni ottenibili In UNIX, tutto il kernel è scritto in linguaggio C6, compresi i device drivers, salvo una minima parte in assembler; nella Versione 7 di UNIX, la prima ad essere diffusa in modo significativo al difuori dei Bell Laboratories, il kernel si componeva in tutto di circa 10.000 righe di codice C e 1.000 righe di assembler (cioè il 9,1% del totale); di queste, 200 lo erano per ragioni di efficienza ed 800 effettuavano funzioni hardware impossibili in linguaggio C, come gestione del Memory Management o altro — Corollario: Non è vero che sono necessarie squadre di decine e decine di programmatori per realizzare il kernel di un Sistema Operativo Se si pensa di realizzarlo in assembler, la mole di lavoro è tale da richiedere molte persone; ma grazie all’uso di un linguaggio evoluto come C, UNIX è stato interamente progettato e (nelle sue prime versioni) realizzato da due persone (come già detto, in tutto sono bastate 11.000 righe di codice7); ciò ha avuto forti conseguenze positive per ciò che riguarda la compattezza e la coerenza della logica di tutto il sistema, che è stato concepito e realizzato soltanto da due menti8 6. Il linguaggio C fu progettato e realizzato da Dennis Ritchie esattamente per il proposito di scrivere UNIX in linguaggio evoluto; ciò indica chiaramente l’orientamento originale di questo linguaggio, concepito come uno strumento di implementazione di sistemi, potente ma al tempo stesso capace di permettere operazioni di basso livello che un tempo venivano spesso effettuate solo in assembler. 7. Le cose sono evolute non poco negli anni; oggi il kernel di LINUX (versione 2.4.18) consta di oltre 3.800.000 righe di codice (comprendendo il codice per tutte le piattaforme supportate); ma la proporzione tra codice in assembler e codice in C è ancora nettamente diminuita, e oscilla oggi tra l’uno e il cinque percento (per la piattaforma Intel IBM-PC, senza considerare i device drivers, il conto è approssimativamente di 9.000 righe in assembler contro 670.000 righe in C, cioè un rapporto dell’1.3%). 8. Questa strategia di sviluppo è stata poi rivoluzionata ulteriormente dal modello di sviluppo di LINUX e di tutto il software libero − vedi oltre, nel paragrafo "Il fenomeno del Software Libero" (paragrafo 1.5 pag. 10). V. Asta - Introduzione a Unix e GNU/Linux 5 — Non è vero che un Sistema Operativo debba essere concepito e realizzato per uno specifico tipo di hardware, quindi per una sola macchina o al massimo per una famiglia di macchine simili di uno stesso costruttore UNIX è stato scritto in linguaggio C proprio perchè, fin dall’inizio, lo si voleva portabile ad altre architetture hardware; il primo porting ad un’architettura hardware diversa dai PDP-119 è stato realizzato già prima del rilascio della Versione 7, ed oggi il sistema è disponibile praticamente su tutti i tipi di macchine esistenti sul mercato, dagli IBM-PC agli Apple McIntosh o ai PowerPC, passando attraverso i mini-computer di tutti i fabbricanti (HP, IBM, Bull, DEC/Compaq, Data General e così via) fino ai mainframe della IBM e ai supercomputers della Cray Inc. — Non è vero che il Sistema Operativo debba prevedere un certo numero di tipi di files, gestiti a livello del kernel UNIX rifiuta di occuparsi di questo aspetto, e rimane perfettamente neutrale; un file, per il kernel, è proprio quello che è, cioè semplicemente un array ordinato di bytes, numerati da 0 in su. Qualunque altra struttura di dati (file di testo o binari, records, lunghezza fissa o variabile e così via) è interamente lasciata ai livelli applicativi utente — Non è vero nemmeno che un Sistema Operativo debba imporre una struttura per i nomi dei files, come ad esempio del tipo <nome>.<estensione> Il kernel di UNIX ignora cosa sia un’estensione del nome di un file; per UNIX, un tale nome è semplicemente una stringa di caratteri assolutamente arbitrari: il carattere ‘.’ è un carattere come un altro, ed un nome come ".a?...!" è perfettamente legale. Un compilatore, quanto a lui, distingue tra nomi di file che finiscono per .c o per .s o .o, e li interpreta come indicatori di formato dei file; ma questo avviene appunto a livello di programmi utente. In sintesi, i principali elementi di interesse di un sistema UNIX possono essere così riassunti ([Bach 1986]): — Kernel multi-utente e multi-task; ciascun utente può eseguire più processi simultaneamente. — Capacità di gestione asincrona dei processi. — Organizzazione gerarchica (ad arborescenza) dei filesystems, caratterizzata da un’implementazione efficiente e semplicità di manutenzione. — Supporto per files non strutturati, basato su una rappresentazione semplice e coerente del formato dei files, come array di bytes, ciò che rende i programmi più semplici da scrivere. — Banalizzazione dei volumi di memoria secondaria (dischi), visti come un’unica arborescenza di files dall’utente e dal programmatore grazie a un meccanismo di montaggio logico di filesystems. — Banalizzazione dei meccanismi di I/O, gestiti in modo univoco (dal punto di vista dell’utente e del programmatore) per - files - periferiche (dispositivi) - comunicazione tra processi. — Interfaccia tra kernel e processi utente chiaramente definita (chiamate di sistema, il cui codice nel kernel è isolato in appositi file sorgente). — Interfaccia tra kernel e periferiche esterne chiaramente definita (device drivers, il cui codice nel kernel è isolato in appositi file sorgente). 9. Si trattava di una macchina Interdata 8/32, che era stata scelta proprio perchè aveva caratteristiche hardware notevolmente diverse da quelle dei PDP-11, così da testare adeguatamente l’effettiva portabilità del sistema (vedi [Johnson e Ritchie 1978]). V. Asta - Introduzione a Unix e GNU/Linux 6 — Kernel rigenerabile e configurabile. — Sistema (kernel e utilities) scritto in linguaggio evoluto (linguaggio C), e quindi facile da leggere, capire, cambiare, e portare a nuove macchine. — Sorgenti disponibili, o commercializzati10. — Interfaccia utente semplice e potente. — Interfaccia di programmazione semplice e potente, con un numero relativamente ristretto di chiamate di sistema. — Insieme di primitive che permettono e incoraggiano la costruzione di applicazioni complesse a partire da programmi più semplici. — Programmazione indipendente dall’hardware: il sistema nasconde totalmente all’utente i dettagli dell’architettura della macchina, permettendo così di scrivere programmi che girano inalterati su differenti implementazioni hardware. — Sistema non legato commercialmente a nessun hardware vendor, e pertanto intrinsecamente portato a favorire meccanismi e politiche di interoperabilità tra macchine diverse. Come già evidenziato, il kernel di un sistema UNIX o derivato si può considerare diviso in almeno in tre parti, mostrate nella Figura 1: — kernel interno — strato delle chiamate di sistema — strato dei device drivers. Le ultime due costituiscono l’interfaccia software tra il kernel interno e, rispettivamente, i processi utente e le periferiche fisiche. Il kernel è la sola parte del codice che in genere l’utente non può modificare, ed è per questo che nella filosofia di UNIX deve operare il minimo di decisioni possibili, in modo da rimanere più neutro possibile rispetto a futuri miglioramenti ed estensioni; viceversa, tutto ciò che è implementato a livello di processo utente è relativamente semplice da sostituire, e pertanto, ovunque la scelta si sia stata possibile, si è sempre teso ad usare il modo utente per l’implementazione di funzionalità, salvo quando ciò implicherebbe pesanti conseguenze sulle prestazioni. Pertanto il kernel interno (o core kernel) si occupa concettualmente di pochissime cose; essenzialmente — inizializzazione del sistema — gestione dei processi — gestione delle risorse (multiplexaggio della CPU, allocazione e protezione della memoria, etc.) — gestione dei filesystems — gestione dei meccanismi di protezione — gestione "astratta" (secondo una visione canonica, indipendente dall’hardware reale) delle periferiche. 10. Come già detto, fin dalle prime versioni dei Bell Laboratories i sorgenti di UNIX sono sempre stati disponibili, benchè rigorosamente protetti da meccanismi di licensing molto stringenti; l’acquisto di una licenza sorgente del Sistema Operativo era anzi inizialmente l’unico modo di ottenere UNIX. Tale licenza, comprensiva di tutto il kernel e di tutte le utilities applicative, era molto costosa per società con fini di lucro, ma era venduta ad un prezzo simbolico (500 dollari US) per le università ed altre istituzioni di tipo educativo o di ricerca. In ogni caso, la distribuzione consisteva in un nastro magnetico bootstrappabile, e due fogli di carta stampati con stringatissime istruzioni di installazione, che terminavano con la frase ormai celeberrima "Good luck." ("Buona fortuna."). Tutta la manualistica era on-line, e andava stampata a cura dell’acquirente. Il contratto di licenza specificava chiaramente che nessuna forma di assistenza era prevista. Erano tempi eroici. V. Asta - Introduzione a Unix e GNU/Linux 7 Kernel User Proc.s System Calls Kernel interno Device Drivers Devices Figura 1. - Struttura del kernel di UNIX. Esistono d’altra parte numerose utilities per lo sviluppo di software e praticamente per qualunque altra attività di interesse: — interpreti di linguaggio di comandi, in varie forme e varianti, — editor, compilatori, interpreti, debuggers, sistemi integrati di sviluppo software (IDE), — analizzatori lessicali e sintattici, — suites di programmi integrati per l’automazione d’ufficio, — utilities per la gestione e il trattamento di documenti grafici, sonori, video, e in genere per applicazioni multimediali — utilities per la gestione di progetti software, inclusa la loro documentazione, — ambienti desktop grafici, sia a livello utente che di sviluppo di applicazioni — sistemi di gestione di basi di dati, — programmi e utilities per applicazioni scientifiche, — servers e clients per applicazioni distribuite e per servizi interattivi su rete Internet (TCP/IP) ed altre reti: posta elettronica, news, groupware, World-wide Web, DNS (servizio di nomi a dominio), FTP (trasferimento di files), sistemi di filesystems distribuiti, login remoto, e così via. Com’è noto, il kernel costituisce una parte tra le più importanti della distribuzione del Sistema Operativo, ma relativamente piccola, in termini di righe di codice, rispetto al totale (meno del 5%; spesso solo il 2 o 3%). È opportuno precisare fin d’ora che, nel caso di LINUX, questo termine si riferisce normalmente al solo kernel, opera del suo ideatore e iniziatore, Linus Torvalds, e dei vari collaboratori che si sono via via aggiunti; mentre l’insieme del kernel e dei vari programmi applicativi e sottosistemi, cioè la distribuzione V. Asta - Introduzione a Unix e GNU/Linux 8 completa del Sistema Operativo, viene appropriatamente denominato GNU/LINUX, poichè si basa in larghissima parte su tutto il lavoro (il cosiddetto GNU Toolkit) derivato dal progetto GNU della Free Software Foundation (http://www.fsf.org, http://www.gnu.org)11, di cui si dirà meglio in seguito. 1.3 Tipi di UNIX È importante distinguere almeno tra le seguenti tipologie di kernel e relative distribuzioni, che si differenziano per numero e tipo di chiamate di sistema, programmi e applicazioni disponibili, e altri aspetti: • • Tipi fondamentali: UNIX AT&T La linea originale, partita dagli sviluppi iniziali presso i Bell Laboratories, attraverso le release principali: Version 6, Version 7, System III, System V, System V release 3, System V release 4. Unix BSD "Berkeley Software Distribution", derivata inizialmente dalla Version 7; distribuita dalla UCB (University of California at Berkeley), solo per licenziatari di UNIX AT&T. Tipi derivati (distribuzioni commerciali): Unix-based Derivati in genere da una licenza sorgente AT&T con diritto di ridistribuzione esclusivamente binaria; spesso queste distribuzioni riuniscono le caratteristiche di UNIX AT&T e quelle di UNIX BSD; molte tra di esse sono state realizzate dai costruttori di macchine hardware (Hewlett-Packard, Digital/Compaq, IBM etc.). tra queste si citano: Xenix, SCO Unix, Unixware (tutte e tre per generiche macchine IBM/PC), Ultrix (per macchine Digital Equipment Corporation), HP/UX (per macchine Hewlett-Packard), DG/UX (Data General), AIX (macchine RS-6000 IBM e Bull), SunOS e SOLARIS (Sparc di SUN), IRIX (Silicon Graphics Inc.), ... (la lista è lungi dall’essere esaustiva). Unix-like Si tratta di riscritture indipendenti, non vincolate al meccanismo di licensing della AT&T; si citano: Idris, Coherent, Regulus, Unos, Tunis, Xinu, Minix, FreeBSD, OpenBSD, BSDI, LINUX, etc. 1.4 Utilità dello studio di LINUX Lo studio e la pratica del kernel LINUX riveste un ruolo centrale nei corsi di Sistemi Operativi di molte Università nel mondo intero12, per diversi motivi: — È un Sistema Operativo compatibile con UNIX, da cui eredita tutta la potenza, l’efficienza, la modularità e la robustezza dell’ambiente di lavoro [Kernighan e Pike 1984] e il pieno supporto multitask e multi-utente. — È ampiamente diffuso sia in ambienti accademici che industriali. — Il codice del kernel è aperto e liberamente utilizzabile da chiunque, in particolare per scopi di studio; LINUX rappresenta quindi un’occasione ideale per toccare con mano il codice di un vero Sistema Operativo, apprezzando tutte le scelte fatte, osservando come le teorie e gli algoritmi studiati nella parte di base del corso si siano concretizzati nella realizzazione pratica di un sistema reale, e considerando quali compromessi siano stati adottati, nel classico processo di intermediazione tra teoria, pratica ed esperienza pregressa che è tipico di tutte le attività ingegneristiche. Un tale approccio è ovviamente impossibile con sistemi proprietari come quelli della Microsoft, con i quali 11. Le due URL http://www.fsf.org e http://www.gnu.org riportano al medesimo sito WEB. 12. Un’inchiesta della Addison-Wesley, citata in [Nutt 2001], indica che già nel 1998, su un campione di 78 Università, 43 insegnavano aspetti di implementazione interna (OSinternals) nel corso introduttivo di Sistemi Operativi; di queste, 26 usavano una variante di UNIX come sistema di studio, 13 usavano LINUX, 10 una versione non specificata di UNIX, e 3 usavano Minix; 8 Università hanno dichiarato di utilizzare un altro tipo di Sistema Operativo, e le rimanenti 9 non hanno specificato il sistema utilizzato. V. Asta - Introduzione a Unix e GNU/Linux 9 lo studio si ferma inevitabilmente alle descrizioni generiche degli algoritmi, per ciò che è dato sapere all’esterno dalle pubblicazioni esistenti. — È un sistema moderno ed altamente competitivo in termini di prestazioni, con caratteristiche importanti quali - vera multiutenza e vero multitasking su un’unica macchina - supporto per processi e threads a livello kernel - multitasking avanzato, con scheduling della CPU con prelazione (preemptive scheduling) e supporto per task "soft real-time" - meccanismi di protezione della memoria - memoria virtuale - supporto per multiprocessing simmetrico (macchine multi-CPU) - compatibilità con lo standard POSIX13 - supporto per TCP/IP e numerosi altri protocolli di rete - interfaccia utente grafica (con possibilità di scelta tra più ambienti desktop) - alta scalabilità in termini di carico di lavoro e di modelli e capacità delle macchine hardware (il sistema è utilizzato con successo su macchine che vanno dalla semplice workstation IBM-PC al mainframe IBM 390) - alta affidabilità e prestazioni, in termini di stabilità e di velocità. Ma il sistema GNU/LINUX riveste un’importanza tutta particolare soprattutto perchè è un sistema basato sul modello Software Libero, oltre ad essere stato il primo progetto di notevole successo in quest’ambito. Ciò, come sarà chiaro nel seguito, conferisce interesse all’argomento non solo per motivi tecnici o legati all’ampia diffusione, ma anche per motivi di tipo sociologico e di sviluppo dei futuri modelli di lavoro e di business nel software. 1.5 Il fenomeno del Software Libero 1.5.1 Introduzione A partire dall’inizio degli anni ’90 il mondo dell’informatica ha assistito al lento ma progressivo affermarsi di un fenomeno nuovo, che a giusto titolo può essere considerato ormai come una vera e propria rivoluzione in atto su scala mondiale: la nascita e la diffusione del Software Libero. Si tratta di un modo radicalmente nuovo di considerare l’organizzazione del lavoro di sviluppo software, basato su un modello di tipo aperto, cooperativo e volontario di partecipazione allo sviluppo e al miglioramento dei prodotti software; il fenomeno sta prendendo piede in modo inarrestabile, e un numero sempre crescente di osservatori sia tecnici che economici ritiene che possa rivoluzionare completamente il mercato. Il Software Libero è al tempo stesso un modello di metodologia di lavoro per la produzione del software, e un modello di strategia di business [Behlendorf 1999]14. Grazie a tale fenomeno, programmatori e sviluppatori che lavorano in varie parti del mondo riescono a condividere il loro lavoro e il codice risultante in modo più efficiente e produttivo di quanto non sia mai stato fatto in precedenza. GNU/LINUX, Apache e Netscape rappresentano tre casi notevoli di successo 13. POSIX è uno standard di interfaccia di Sistema Operativo, promosso dalla ISO/IEC, IEEE, e The Open Group, che mira a definire le caratteristiche di un Sistema Operativo aperto, in pratica di tipo UNIX. Per maggiori dettagli, vedi http://std.dkuug.dk/jtc1/sc22/wg15/ . Molti sistemi (di tipo UNIX e non) tendono ad essere compatibili con questo standard. 14. L’interesse per questo fenomeno dal punto di vista economico e di modello di business è in forte crescita negli ultimi anni, e il Software Libero è ormai oggetto di analisi sempre più approfondite da parte di ricercatori e studiosi di economia aziendale; oltre al lavoro di Behlendorf, si citano [Dalle e Jullien 2000], [Kuan 2001], [Garzarelli 2002]. V. Asta - Introduzione a Unix e GNU/Linux 10 nell’adozione del modello Software Libero. Per apprezzare appieno l’impatto di tale metodologia e le implicazioni da e verso il mondo di LINUX, è opportuno dettagliare alquanto la genesi, il tipo di approccio e le caratteristiche del Software Libero. Una buona esposizione informale dell’idea alla base del Software Libero è la seguente, derivata da http://www.opensource.org/advocacy/index.html: "Se i programmatori possono leggere, ridistribuire, e modificare il codice sorgente di un dato software, il software evolve. La gente lo migliora, lo adatta, ne corregge gli errori. E ciò può accadere ad una velocità che, per chi è abituato al lento ritmo di sviluppo convenzionale del software, risulta impressionante. Questo rapido processo evolutivo produce software migliore rispetto al tradizionale modello chiuso, nel quale solo pochi programmatori possono vedere il codice sorgente e tutti gli altri devono usare ciecamente un blocco opaco di bits. La Open Source Initiative esiste proprio per portare questo modello verso il mondo commerciale." È opportuno precisare subito che, per ciò che attiene alla terminologia, e al tipo di approccio al problema, esistono diverse scuole di pensiero, di cui le principali sono le seguenti: — Free Software - è il termine originale, coniato dal Richard M. Stallman, fondatore della Free Software Foundation (FSF - http://www.fsf.org); riflette un approccio di tipo più purista e integralista — Open Source - termine che fa capo a Bruce Perens ed Eric S. Raymond, della Open Source Initiative (OSI - http://www.opensource.org); riflette un approccio più orientato alle esigenze di mercato e di profitto delle aziende. Tali differenti denominazioni riflettono in ultima analisi diverse posizioni, di carattere tecnico, sociologico, politico e di visione del business, presenti tra gli operatori del settore. Un’analisi dettagliata delle differenze esistenti esula dallo scopo del testo; alcuni buoni punti iniziali per approfondire l’argomento sono le URL http://www.fsfeurope.org/documents/whyfs.en.html, http://www.opensource.org/docs/history.html e http://www.linux.it/GNU/softwarelibero.shtml, e l’introduzione a [DiBona et al. 1999]. Alcuni tendono oggi ad utilizzare il termine Libre Software, che riflette forse un approccio più neutro ed equidistante tra le parti; altri ancora si servono del termine FLOSS, che è l’acronimo di "Free/Libre and Open Source Software". In questo testo verrà consistentemente utilizzata la dizione "Software Libero" (con le iniziali in maiuscolo), come termine generico. Ad ogni modo, come fa giustamente notare F. Potortì [2002], "La voluta neutralità del movimento Open Source verso gli aspetti etici e politici del software libero è la caratteristica sostanziale che lo distingue dalla filosofia del Software Libero (Free Software), che al contrario pone l’accento sulle motivazioni ideali. Parlare di Software Libero piuttosto che di Open Source è una questione politica piuttosto che pratica; i due movimenti concordano infatti sulle licenze considerate accettabili, ed hanno obiettivi e mezzi comuni." 1.5.2 Licenze Software Libero Esistono diverse licenze di tipo Software Libero, ciascuna con le sue caratteristiche e le sue più o meno aperte concessioni d’uso del software; una lista delle principali è dettagliata in http://www.gnu.org/licenses/license-list.it.html. Senza entrare in un’analisi dettagliata dell’argomento, quella che è di gran lunga più utilizzata e conosciuta è comunque la licenza GNU GPL (General Public License - http://www.gnu.org/copyleft/gpl.html), messa a punto dalla FSF, e quelle da essa derivate: la GNU LGPL (Lesser General Public License - http://www.gnu.org/copyleft/lesser.html), adatta in particolare per le librerie di software, e la GNU GFDL (GNU Free Documentatione License http://www.gnu.org/copyleft/fdl.html), adatta per la pubblicazione di documentazione. Secondo i termini della GNU GPL, tutto il software generato da codice GPL è a sua volta necessariamente GPL. Preme comunque sottolineare che, contrariamente a quanto si possa ritenere a prima vista, il modello Software Libero, e tutte le licenze che ad esso si rifanno, pone la massima attenzione nel preservare V. Asta - Introduzione a Unix e GNU/Linux 11 integralmente i diritti di proprietà del software, e non è assolutamente incompatibile con una logica di profitto nel campo della commercializzazione del software, ma anzi facilita tale logica, aumentando l’affidabilità e la robustezza dei prodotti software, e quindi il grado di fiducia delle aziende clienti in ciò su cui investono. Si veda in proposito http://www.gnu.org/philosophy/selling.html, che dice tra l’altro: "Molta gente crede che lo spirito del progetto GNU sia che non si debba far pagare per distribuire copie del software, o che si debba far pagare il meno possibile − solo il minimo per coprire le spese. In realtà noi incoraggiamo chi ridistribuisce il software libero a far pagare quanto vuole o può." In sostanza, si può e anzi si deve fare business e generare profitto, senza di che non è ovviamente possibile nessuna strategia aziendale e quindi nessun futuro. Constatare che il modello Software Libero può portare benissimo al successo non solo tecnico ma anche commerciale è semplice, basta controllare le quotazioni in borsa di alcune società che hanno adottato tale modello di business; i dati sono eloquenti15: Società Red Hat Inc. VA Software Caldera International Fatturato 103 135 40.4 Valore di Mercato 781 62.5 15.2 Fatturato e Valore di Mercato sono espressi in milioni di dollari USA. 1.5.3 Definizioni Definizioni complete e formali del Software Libero, nelle sue varie accezioni, possono essere trovate in rete, in particolare sul sito della FSF (http://www.fsf.org) per l’approccio "Free Software", e su quello della OSI (http://www.opensource.org) per quello "Open Source"; la teoria sottostante alle licenze Software Libero (Copyleft) è spiegata alla URL http://www.gnu.org/licenses/licenses.html#WhatIsCopyleft. La definizione formale del Free Software, dovuta a Richard Stallman della FSF (http://www.gnu.org/philosophy/free-sw.html), parte da un’idea semplice e rivoluzionaria: usare le leggi sul copyright, così come definite dalla convenzione di Berna, per garantire e proteggere le libertà anzichè per limitarle; il documento, riportato integralmente in Appendice 2, dichiara tra l’altro: Il Free Software (software libero) è una questione di libertà, non di prezzo. L’espressione "Free Software" si riferisce alla libertà dell’utente di eseguire, copiare, distribuire, studiare, cambiare e migliorare il software. Più precisamente, esso si riferisce a quattro tipi di libertà per gli utenti del software: — La libertà di far girare il programma, per qualunque scopo (libertà 0) — La libertà di studiare come funziona il programma, e di adattarlo per le proprie necessità (libertà 1). L’accessibilità del codice sorgente è una necessaria precondizione per questo punto — La libertà di ridistribuire copie del programma, così da poter aiutare gli altri (libertà 2) — La libertà di migliorare il programma, e rilasciare al pubblico le proprie migliorìe, così che tutta la comunità ne possa beneficiare (libertà 3). Anche qui, l’accessibilità del codice sorgente è una precondizione necessaria. Un programma è software libero se l’utente ha tutte queste libertà. Queste libertà ne implicano poi molte altre, tra cui in particolare: libertà di imparare, libertà di insegnare, 15. Riferimento: sito WEB del NASDAQ (http://www.nasdaq.com, borsa statunitense dei titoli tecnologici) maggio 2002. V. Asta - Introduzione a Unix e GNU/Linux 12 libertà di concorrenza (libertà di competere liberamente sul mercato), libertà di parola, libertà di scelta. La definizione dell’Open Source, dovuta alla OSI (http://www.opensource.org/docs/osd-italian.html, riportata integralmente in Appendice 3), è più circostanziata, e sostanzialmente recita: Open source (sorgente aperta) non significa semplicemente accesso al codice sorgente. La distribuzione in termini di programmi open-source deve soddisfare i seguenti criteri: 1. Ridistribuzione Libera e Gratuita Le licenze non potranno limitare alcuno dal vendere o donare i programmi come componenti di una distribuzione aggregata di software contenenti programmi di varia origine. La licenza non potrà richiedere royalties o altri pagamenti per tali vendite. 2. Codice Sorgente Il programma deve includere il codice sorgente, e deve permetterne la distribuzione così come per la forma compilata. Dove alcune forme di un prodotto non sono distribuite con codice sorgente, ci deve essere un modo ben pubblicizzato di ottenerne il codice sorgente per niente più di una ragionevole riproduzione; preferibilmente, per via dei costi, scaricandolo da Internet gratis. Il codice sorgente deve essere la forma preferita in cui un programmatore modificherebbe il programma. Codice sorgente deliberatamente obnubilato non è permesso. Forme intermedie come l’output di un preprocessore o traduttore non sono permesse. 3. Prodotti Derivati La licenza deve permettere modifiche e prodotti derivati, e deve permettere loro di essere distribuiti sotto le stesse condizioni della licenza del software originale. 4. Integrità del Codice Sorgente dell’Autore La licenza potrà impedire il codice sorgente dall’essere redistribuito in forma modificata solo se la licenza consentirà la distribuzione di pezze ("patch files") con il codice sorgente al fine di modificare il programma all’installazione. La licenza deve esplicitamente permettere la distribuzione del software costruito da un diverso codice sorgente. La licenza può richiedere che i lavori derivati abbiano un nome diverso o versione diversa dal software originale. 5. Nessuna Discriminazione contro Persone o Gruppi La licenza non deve discriminare alcuna persona o gruppo di persone. 6. Nessuna Discriminazione contro Campi d’Applicazione La licenza non deve impedire ad alcuno da far uso del programma in un ambito specifico. Per esempio, non potrà impedire l’uso del programma nell’ambito di un’impresa, o nell’ambito della ricerca genetica. 7. Distribuzione della Licenza I diritti allegati a un programma devono valere a tutti coloro cui il programma è redistribuito senza necessità dell’emissione di una addizionale licenza da parte dei licenziatari. 8. La Licenza non deve essere Specifica a un Prodotto I diritti allegati al programma non devono dipendere dall’essere il programmma parte di una particolare distribuzione di software. Se il programma è estratto da quella V. Asta - Introduzione a Unix e GNU/Linux 13 distribuzione e usato o distribuito all’interno dei termini delle licenze del programma, tutte le parti cui il programma è ridistribuito dovranno avere gli stessi diritti che sono garantiti nel caso della distribuzione di software originale. 9. La Licenza non deve Porre Vincoli su Altro Software La licenza non deve porre restrizioni su altro software che è distribuito insieme al software licenziato. Per esempio, la licenza non dovrà insistere che tutti gli altri programmi distribuiti sugli stessi supporti siano software open-source. I testi integrali di entrambe le definizioni (nelle loro traduzioni ufficiali in italiano) sono riportati in due Appendici. 1.5.4 Benefici Oltre a rappresentare chiaramente un sistema nel quale è più facile l’apprendimento e la diffusione delle conoscenze e del progresso scientifico, e quindi un sistema di indubbia validità sociale, il modello del Software Libero presenta molti benefici anche dal punto di vista industriale [Bernardini 2001]: — Benefici per tutte le Piccole e Medie Imprese (PMI), che possono trarre profitto da tecnologie spesso altamente sofisticate ad una frazione del pricing usuale di mercato: il software diventa libero, e i prodotti finali, tipicamente comprensivi di software e servizi (customizzazione, installazione, aggiornamenti, assistenza tecnica) sono spesso proposti sul mercato a prezzi nettamente più abbordabili (non è infrequente un rapporto di 10:1 o più nelle riduzioni di costo). Va da sè che ciò aumenta notevolmente la "piramide di mercato", permettendo l’accesso a queste tecnologie ad un numero assai maggiore di aziende, realizzando così un effetto di raggiungimento di massa critica importante ad un tempo per gli utilizzatori e per chi propone la soluzione. Un numero via via maggiore di PMI può così trarre vantaggio da queste opportunità tecnologiche, di mercato e di business, a prezzi estremamente competitivi. — Benefici per molte società a vocazione tecnologica, e per i loro clienti: aziende con skills tecnici adeguati possono contribuire allo sviluppo dei prodotti, e fare business proponendo servizi di consulenza e di integrazione di sistemi. Queste aziende traggono vantaggio da tutti i classici punti di forza che derivano dal modello di business del Software Libero e dalla libertà d’azione che ne deriva, tra cui: - Maggior velocità di sviluppo: nuove ed efficienti pratiche di lavoro nel software, come la riusabilità e l’adattabilità, combinate con la conoscenza dei sorgenti, permettono di ottenere velocità di sviluppo di applicativi che non hanno precedenti nella storia del software - Efficienza estrema in termini di risorse umane: adottando le strategie del Software Libero, una piccola équipe di sviluppatori può produrre una notevole quantità di lavoro; oltre ad essere meno costosa, un’équipe limitata è notoriamente molto più efficiente di un grosso gruppo di lavoro - Disponibilità permanente di un gran numero di risorse: la comunità del Software Libero fornisce, attraverso Internet, libero accesso ad un immenso serbatoio di risorse e strumenti software; i progetti Software Libero hanno accesso istantaneo a tali risorse, pressochè illimitate e in continua crescita - Maggiori capacità di supporto ai clienti e di adattabilità alle loro necessità: oltre ad avere capacità di auto-assistenza, il Software Libero è fornito da una comunità che tipicamente consiste in un gran numero di persone sparse sul pianeta, in costante monitoraggio del proprio lavoro e di quello degli altri; quando si presenta un problema di sviluppo, ci sono ottime probabilità che, inviando un messaggio nella mailing list appropriata, la soluzione possa essere trovata nello spazio di poche ore. Da ciò consegue una capacità decisamente superiore di bug-tracking ed una maggior robustezza dei prodotti risultanti V. Asta - Introduzione a Unix e GNU/Linux 14 - Minori costi di produzione e budget di sviluppo: tutti i prodotti Software Libero sono normalmente disponibili e scaricabili via Internet, e hanno di norma, come già accennato, prezzi di licenza molto bassi; ciò porta a riduzioni di costi e agli ampliamenti di target di mercato già menzionati - Mercato più ampio e globalizzato: per sua stessa natura, il Software Libero fa sì che i prodotti pubblicati in questo contesto abbiano visibilità e clienti potenziali su scala mondiale. 1.5.5 Il caso di LINUX GNU/LINUX, con la sua progressiva e continua penetrazione sul mercato, è l’antesignano e principale esponente della nuova tendenza rappresentata dal Software Libero. Le distribuzioni di GNU/LINUX presentano grandi vantaggi su tutti i sistemi proprietari analoghi: esse sono totalmente configurabili e personalizzabili, forniscono una vastissima panoplia di programmi di utilità ed applicazioni in ogni possibile settore, e non da ultimo vantano caratteristiche di affidabilità e di sicurezza insorpassate. GNU/LINUX è accessibile agli utenti in diverse distribuzioni e configurazioni, a seconda delle preferenze e dei campi applicativi. L’impatto del modello di sviluppo del kernel LINUX, messo a punto dal suo iniziatore Linus Torvalds, è stato enorme in tutto il mondo del Software Libero, perchè ha dimostrato per primo che il modello poteva funzionare su larga scala e per grandi progetti, ed inoltre perchè ha sfatato molti miti sul "come" si sviluppa software; in questo senso, è paragonabile all’impatto dell’iniziativa di Ken Thompson e Dennis Ritchie alla fine degli anni ’60, quando decisero di farsi da soli un Sistema Operativo. Eric Raymond è probabilmente la persona che meglio di ogni altro ha saputo mettere in evidenza gli aspetti importanti di questa esperienza, e l’originalità e genialità di Linus Torvalds. Nel suo libro "The Cathedral and the Bazaar" [Raymond 2001], si evidenzia che il modello di lavoro dello sviluppo di LINUX è simile ad un bazar, dove si trova un po’ di tutto nel disordine; l’esatto contrario di quanto si faceva (e si fa ancora) dai più, che invece seguono un modello di sviluppo del software simile alla costruzione di una cattedrale, in modo monolitico, con pochi addetti ai lavori appartati nei laboratori. Ecco alcuni estratti interessanti: LINUX è sovversivo. Chi avrebbe potuto pensare appena cinque anni fa16 che un Sistema Operativo di livello mondiale sarebbe emerso come per magia dal lavoro part-time di diverse migliaia di hacker e sviluppatori sparsi sull’intero pianeta, collegati tra loro solo grazie ai tenui cavi di Internet? [...] LINUX stravolse gran parte di quel che credevo di sapere. Credevo che il software più importante andasse realizzato come le cattedrali, attentamente lavorato a mano da singoli geni o piccole bande di maghi che lavoravano in splendido isolamento, senza che alcuna versione beta vedesse la luce prima del momento giusto. Rimasi non poco sorpreso dallo stile di sviluppo proprio di Linus Torvalds − diffondere le release presto e spesso ("release early, release often"), delegare ad altri tutto il possibile, essere aperti fino alla promiscuità. Nessuna cattedrale da costruire in silenzio e reverenza. Piuttosto, la comunità LINUX assomigliava a un grande e confusionario bazar, pullulante di progetti e approcci tra loro diversi (efficacemente simbolizzati dai siti contenenti l’archivio di LINUX dove apparivano materiali prodotti da chiunque). Un bazar dal quale soltanto una serie di miracoli avrebbe potuto far emergere un sistema stabile e coerente. Il fatto che questo stile bazar sembrasse funzionare, e anche piuttosto bene, mi colpì come uno shock. Mentre imparavo a prenderne le misure, lavoravo sodo non soltanto sui singoli progetti, ma anche cercando di comprendere come mai il mondo LINUX non soltanto non cadesse preda della confusione più totale, ma al contrario andasse rafforzandosi sempre più a una velocità a malapena immaginabile per quanti costruivano cattedrali. [...] 16. La prima versione ufficiale di questo brano è del maggio 1997. La stesura originale è ancora anteriore. V. Asta - Introduzione a Unix e GNU/Linux 15 Nella parte restante di questo saggio, racconto la storia di quel progetto, usandola per proporre alcuni aforismi sull’efficacia dello sviluppo Open Source. Questi aforismi ci aiuteranno a comprendere con esattezza cos’è che rende la comunità LINUX una sorgente così copiosa di buon software − e aiuteranno tutti noi a divenire più produttivi. Vale la pena di citare alcuni tra questi aforismi, ed altre considerazioni dell’autore, perchè sono pregni di significato e possono indurre ad utili riflessioni; ognuno di essi illustra bene alcuni degli aspetti caratterizzanti il modello Software Libero, ognuno tende a sfatare dei miti, ognuno meriterebbe un commento specifico (ma per questo si rimanda il lettore interessato all’opera citata di Raymond). 1. Ogni buon lavoro software inizia dalla frenesia personale di uno sviluppatore (il che spiega l’alta qualità media del software originato dalla comunità LINUX) 2. I bravi programmatori sanno cosa scrivere. I migliori sanno cosa riscrivere (e riusare) 3. Quando hai perso interesse in un programma, l’ultimo tuo dovere è passarlo a un successore competente 4. Trattare gli utenti come co-sviluppatori è la strada migliore per ottenere rapidi miglioramenti del codice e debugging efficace 5. Ritengo che la mossa più scaltra e consequenziale di Linus non sia stata la costruzione del kernel di LINUX in sè, bens ì la sua invenzione del modello di sviluppo di LINUX. 6. Distribuisci presto. Distribuisci spesso. E presta ascolto agli utenti. Linus trattava gli utenti al pari di co-sviluppatori nella maniera più efficace possibile. 7. Stabilita una base di beta-tester e co-sviluppatori sufficientemente ampia, ogni problema verrà rapidamente definito e qualcuno troverà la soluzione adeguata. O, in modo meno formale, "Dato un numero sufficiente di occhi, tutti i bug vengono a galla". Io la chiamo la "Legge di Linus". Questa può essere definita anche così: "Il debugging è parallelizzabile". 8. Nella concezione a bazar si dà per scontato che generalmente i bug siano fenomeni marginali − o che almeno divengano rapidamente tali se esposti all’attenzione di migliaia di volenterosi cosviluppatori che soppesano ogni nuova release. Ne consegue la rapidità di diffusione per ottenere maggiori correzioni, e come positivo effetto collaterale, c’è meno da perdere se occasionalmente viene diffuso qualche pasticcio. 9. Se tratti i beta-tester come se fossero la risorsa più preziosa, replicheranno trasformandosi davvero nella risorsa più preziosa a disposizione. 10. La cosa migliore, dopo l’avere buone idee, è riconoscere quelle che arrivano dagli utenti. Qualche volta sono le migliori. 11. Stabilito che il coordinatore dello sviluppo abbia a disposizione un medium almeno altrettanto affidabile di Internet, e che sappia come svolgere il ruolo di leader senza costrizione, molte teste funzionano inevitabilmente meglio di una sola. 12. Forse alla fine la cultura dell’Open Source trionferà non perchè la cooperazione sia moralmente giusta o perché il software "chiuso" sia moralmente sbagliato, [...] ma semplicemente perché il mondo "closed-source" non è in grado di vincere la corsa agli armamenti dell’evoluzione contro quelle comunità Open Source capaci di affrontare un problema con tempi e capacità superiori di diversi ordini di grandezza. 1.5.6 Situazione e prospettive Oggi, molti prodotti Software Libero stanno iniziando ad essere sviluppati e supportati in un contesto orientato ormai direttamente al mercato industriale vero e proprio. Si possono citare molti "business case" di successo, tra cui importanti aziende come le citate Red Hat, VA Software e Caldera (che ha acquisito SCO - Santa Cruz Operation), e poi Cygnus, Mandrake, Vixie Enterprise ed altre; e l’enorme successo, legato ad un impatto sia tecnologico che commerciale, di progetti quali GNU/LINUX, Apache e Zope, solo V. Asta - Introduzione a Unix e GNU/Linux 16 per ricordare i più significativi. Non è facile quantificare ad oggi l’uso complessivo del Software Libero e il suo impatto, ma è fin troppo chiaro ormai che entrambi sono in grande e rapida espansione: il nuovo modello di lavoro e di business si è ormai stabilito, e sta prendendo sempre più piede, scalzando attivamente le posizioni di grosse società che finora avevano sempre basato tutte le loro strategie sul software proprietario. Molte di queste hanno modificato tali strategie, a dimostrazione della bontà ed efficacia del nuovo modello; prime fra tutte la IBM (http://www.investor.ibm.com/investor/events/linux080800/ ), che ha investito un miliardo di dollari sul Sistema Operativo GNU/LINUX, implementandolo addirittura su tutta la gamma delle loro macchine, dalle workstations su PC ai Mainframe; IBM è poi stata seguita da SGI, SUN, e altri. Già oggi il Software Libero ha raggiunto molti risultati significativi, tra cui in particolare un’ormai ben diffusa consapevolezza dell’importanza della libertà di scelta e di selezione del software, e non solo per fattori puramente economici (il Software Libero come detto non è necessariamente gratuito, ma è generalmente meno costoso del software proprietario). Alcuni fatti significativi possono dare un ulteriore contributo alla corretta valutazione della portata del fenomeno: — La Gartner Inc., azienda di consulting strategico di fama mondiale, ha dichiarato in un rapporto tecnico del settembre 2001 (http://www.gartner.com/DisplayDocument?doc_cd=101034), che il server WEB IIS (Internet Information Server) di Microsoft è troppo facilmente attaccabile da virus e altro, e ha esplicitamente raccomandato di migrare da IIS ad un server Apache; ciò è un chiaro segno di inversione di tendenza anche presso aziende che lavorano nel campo finanziario, notoriamente molto prudenti nell’accettare novità tecnologiche. — La Borsa di New York (NYSE - New York Stock Echange) ha cambiato il proprio sistema informativo centrale nel 2001: tutto è ora gestito da un mainframe IBM con Sistema Operativo GNU/LINUX. Ciò dimostra che il Software Libero ha raggiunto livelli di sicurezza e di affidabilità sufficienti anche per applicazioni estremamente mission-critical. — Il DoD (Department of Defense) degli Stati Uniti ha dichiarato da tempo l’intenzione di effettuare una migrazione di tutti i suoi server da Sistema Operativo Windows NT / 2000 a GNU/LINUX, per ragioni di convenienza economica, non reputando più giustificata la spesa continua di licenze e di upgrades imposta dalla Microsoft per ciascun server; il Ministero della Difesa francese ha successivamente dichiarato che farà altrettanto, ma non per ragioni economiche, bensì per ragioni di sicurezza: lo Stato Francese non ritiene sicuro, per applicazioni militari, l’utilizzo di un Sistema Operativo di cui non si possa controllare il codice sorgente, e quindi esattamente tutto ciò che fa e che non fa (quali garanzie si hanno, per un prodotto a modello chiuso, che non vi siano internamente parti di codice che vanno a trasferire informazioni altrove, oggi che tutte le macchine sono connesse in una rete mondiale?); GNU/LINUX risponde invece pienamente a tali esigenze. Ciò è un chiaro segno di come si stia evolvendo verso un mondo e un contesto di lavoro e sociologico che ammette sempre meno il modello "closed source", e men che meno il modello Microsoft, che rischia di finire un giorno fuori mercato. Esempi come quelli citati di adozione su scala nazionale di GNU/LINUX o altri prodotti Software Libero, si sono verificati anche in Cina (con la distribuzione "Red Flag LINUX" − http://www.linuxjournal.com/article.php?sid=5784), in Germania, dove il Ministero dell’Economia e Tecnologia finanzia lo sviluppo del progetto GnuPG (GNU Privacy Guard)17, ritenendolo importante per la sicurezza nazionale (http://www.gnupg.de/presse.en.html), e in altri paesi. — In Italia, nell’ottobre 2000 è stata redatta una lettera aperta, indirizzata al Dipartimento della Funzione Pubblica, all’Autorità per l’Informatica nella Pubblica Amministrazione, e al Ministero del Tesoro, intitolata "Soggezione informatica dello Stato italiano alla Microsoft" (http://www.interlex.it/pa/letterap.htm); la lettera ha registrato oltre duemila firme di adesione in 17. GnuPG (http://www.gnupg.org/ ) è un’implementazione Software Libero del noto sistema PGP (Pretty Good Privacy − vedi http://www.philzimmermann.com/ ), che riguarda applicazioni di sicurezza e privatezza dell’informzazione basata su algoritmi crittografici. V. Asta - Introduzione a Unix e GNU/Linux 17 pochi giorni. In essa si chiedono allo Stato le ragioni per cui si continua a spendere il denaro dei contribuenti per acquistare continue licenze Microsoft Office per più o meno tutti gli uffici pubblici, quando esistono prodotti funzionalmente equivalenti e con caratteristiche ergonomiche perfettamente simili, distribuiti gratuitamente (StarOffice, oggi OpenOffice, prodotto Software Libero). — Paesi quali l’Argentina, l’Inghilterra, la Finlandia e la Germania (ed altri) hanno ormai leggi che richiedono di adottare esclusivamente prodotti Software Libero per tutta la Pubblica Amministrazione. La Francia sta facendo altrettanto; anche in Italia è stato recententemente proposto (febbraio 2002) un disegno di legge analogo, per iniziativa del senatore Cortiana [2002], intitolato "Norme in materia di pluralismo informatico sull’adozione e la diffusione del software libero e sulla portabilità dei documenti informatici nella pubblica amministrazione". Nel settembre 2002, secondo il New York Times, si contavano 66 progetti di legge di questo tipo, in 26 diversi paesi [Lohr 2002]. Sono esempi come questi che portano un numero sempre crescente di osservatori e operatori del settore a ritenere che il Software Libero sia un processo irreversibile. Strategie legate al Software Libero sono state adottate ormai anche da diverse aziende leader di mercato nell’industria dei computer, come appunto la IBM, la Netscape Corp. (http://www.mozilla.org) e la SGI (http://www.sgi.com/linux), nonchè, pur dopo varie esitazioni, dalla SUN Microsystems (http://www.sun.com/software/linux/ ). Val la pena di rammentare che il successo del modello di business del Software Libero e l’alta produttività e qualità del software sviluppato secondo questo modello indussero i managers di Netscape Corp. a porre lo sviluppo dei propri browsers sotto una licenza Software Libero già nel gennaio 1998 (http://www.netscape.com/newsref/pr/newsrelease558.html), profondamente influenzati dagli scritti di Eric Raymond [1997], per loro esplicita ammissione. Anche se ci sono ancora moltissimi progressi da compiere in questo campo, e se la partita non è ancora vinta, il futuro del Software Libero sembra oggi decisamente promettente, e già oggi è una realtà significativa; solo pochissimi anni fa, anche i più accesi difensori del Software Libero non avrebbero osato sperare che: — un Sistema Operativo Software Libero (GNU/LINUX) avrebbe potuto preoccupare seriamente il dominio incontrastato della società che detiene il monopolio planetario del settore (Microsoft) — società importanti come IBM, Intel, Netscape, Creative, SGI, SUN ed altre avrebbero cominciato a guardare al Software Libero come una prospettiva di sviluppo percorribile — interi sistemi governativi, come i casi citati (Argentina, Inghilterra, Finlandia, Germania, Cina, Francia) avrebbero considerato l’ipotesi di adottare esclusivamente sistemi Software Libero. A proposito del primo punto ora esposto, val la pena citare il caso emblematico del famoso Halloween document [Valloppillil 1998]: un rapporto interno Microsoft, intitolato "Open Source Software: a (new?) development methodology", destinato a rimanere strettamente riservato, finì invece nelle mani di Eric Raymond, che lo rese pubblico in una versione da lui commentata passo per passo (il rapporto, i commenti, le reazioni della stampa e della Microsoft sono tutti disponibili a partire dalla URL http://www.opensource.org/halloween); questo documento, la cui inattesa pubblicazione ebbe una grande eco nella stampa di tutto il mondo, è da una parte la prova di quanto Microsoft sia seriamente preoccupata dall’ascesa del Software Libero, e dall’altra costituisce una vera e propria dichiarazione di guerra della Microsoft verso LINUX, e ha evidenziato agli occhi di molti che Microsoft era intenzionata a mettere in atto iniziative senza scrupoli e di dubbia correttezza pur di gettare discredito su LINUX e sul Software Libero in generale. Alla pubblicazione del documento, la stessa Microsoft ha dovuto ammettere la sua autenticità, difendendosi con la tesi che si trattava di "un semplice studio ingegneristico" che rifletteva le opinioni personali del suo autore, ma non le idee nè la politica dell’azienda. La reazione dell’opinione pubblica e degli addetti ai lavori non si fece attendere, e l’effetto risultante fu un’enorme crescita di interesse e popolarità per il Sistema Operativo GNU/LINUX: in sostanza, molti costruttori di hardware e produttori di software si dissero: "se LINUX preoccupa la Microsoft, allora è davvero una cosa seria"; risale a questo periodo la decisione di grandi multinazionali, come Eicon Technology, Dialogic e altri, di sviluppare per LINUX i device drivers e le librerie software di tutte le schede V. Asta - Introduzione a Unix e GNU/Linux 18 hardware da loro prodotte; similmente, software houses come Oracle, Informix, Computer Associates e altri hanno deciso di portare i loro prodotti software proprietari su una piattaforma libera, LINUX. L’industria dei computer ha già visto più volte, nella sua storia passata, sorprendenti ondate di crescita e di innovazione, come i primissimi microcomputer negli anni ’70, la coalizione spontanea di tutti i costruttori contro lo strapotere di IBM, grazie al potere aggregante di UNIX, i PC prodotti su larga scala dalla metà degli anni ’80 fino ad oggi, e l’irresistibile ascesa di Internet nello stesso periodo. Il software commerciale rilasciato sotto una licenza di sviluppo Software Libero può costituire il prossimo, importante passo. 1.5.7 Conclusione In conclusione, i punti salienti che caratterizzano il Software Libero possono essere così riassunti: — È un modello aperto e cooperativo di sviluppo software — È un modello di lavoro e modello di business a un tempo — È in forte espansione a livello mondiale — Offre i seguenti vantaggi: - maggior velocità di sviluppo - numero teoricamente illimitato di sviluppatori, a costo nullo o tendente a zero - costi di produzione più bassi - miglior posizionamento del fornitore, che si trova naturalmente più vicino ai bisogni del cliente - scala di mercato più ampia e globale — È basato sulla filosofia di sviluppo "Release fast, release often" — La proprietà del software è preservata — Sono possibili, in certe condizioni, strategie miste (che combinano software libero e software a licenza chiusa) 1.6 Tipi di distribuzioni di GNU/LINUX Esistono diverse distribuzioni del Sistema Operativo GNU/LINUX, che si differenziano, tra l’altro, per le procedure di installazione (più o meno facili e/o flessibili da usare), per i package di applicazioni inclusi, per gli ambienti grafici proposti; inoltre, alcune sono specializzate a certi tipi di ambienti di lavoro. Tra di esse: Slackware, Debian, Caldera, Red Hat, Mandrake, SuSe. V. Asta - Introduzione a Unix e GNU/Linux 19 2. Come iniziare 2.1 Login In questo paragrafo si introduce il sistema strettamente da un punto di vista di utente; si presuppone in particolare la disponibilità di un sistema UNIX regolarmente avviato, pronto all’interazione con gli utenti; ciò che accade prima, e che l’utente normale tipicamente non vede, viene spiegato in paragrafi successivi18. Dal punto di vista dell’utente, una sessione di lavoro con un sistema UNIX inizia invariabilmente col collegamento ad un terminale di lavoro; ciò in genere può corrispondere, in pratica, ad un terminale fisico reale (ad esempio una VT-100 connessa ad una linea seriale, o la console di un Personal Computer) ovvero ad un terminale virtuale (ad esempio una finestra xterm in una postazione grafica, o un utente connesso in rete ad un server TELNET). In ogni caso, la prima operazione da fare è l’identificazione dell’utente, o procedura di login, il che consiste nel fornire al sistema un nominativo utente (che dovrà essere già noto al sistema stesso) ed una password (o parola d’ordine) ad esso associata19. È da notare che in qualsiasi sistema UNIX si fa sempre distinzione tra caratteri minuscoli e caratteri maiuscoli, che sono considerate entità diverse. ...il sistema attende il nome dell’utente. ...nome dell’utente, in minuscolo. ...parola d’ordine dell’utente (che non comparirà sullo schermo), terminata da <CR> (Invio, o Carriage-Return) Login: Login: luca<CR> Password: Last login: Sat May 4 11:34:42 ...varie informazioni di servizio Il Sistema sarà... ...eventuale stampa dei messaggi del giorno You have mail. ...eventuale notifica di messaggi di E-mail $ ...prompt dell’interprete di comandi È bene, in generale, cambiare subito la password inizialmente assegnata dall’amministratore con un’altra nota solo all’utente. Il comando utilizzato per creare o modificare la password è passwd. Esempio: $ passwd Changing password for luca Old password: New password: Re-enter new password: password updated successfully $ Chiaramente nè la vecchia nè la nuova parola d’ordine appariranno sullo schermo. Per uscire, si digita ˆD, da inserire all’inizio della riga20; questo carattere segna la fine della sessione di login. Quando la shell incontra la fine della sessione di login, termina. 2.2 La manualistica on-line Una delle prime cose in assoluto da sapere su un sistema UNIX è che normalmente dispone di un’estesa manualistica on-line, in varie forme; la forma più immediata è quella che fa riferimento al comando man, il 18. La descrizione delle fasi di avvio del sistema è descritta nel paragrafo 11. pag. 78, "avvio del sistema", e nel paragrafo 12. pag. 81, "il programma init". 19. L’amministratore del sistema avrà assegnato un nome di login all’utente, ed una password iniziale, che permetta al sistema di identificare l’utente. V. Asta - Introduzione a Unix e GNU/Linux 20 quale permette di consultare da terminale il manuale di riferimento di tutto il sistema. Il manuale è diviso in un certo numero di sezioni, che riguardano i comandi, le chiamate di sistema, e altro; nella letteratura, ogni riferimento del tipo entry(n) indica un rimando alla pagina di manuale entry nella sezione numero n; ad esempio, per saperne di più sul manuale on-line, basta consultare man(1), cioè la voce man del manuale, in sezione 1. Anche in questo testo ci si atterrà a questa convenzione, dovunque si voglia evidenziare un rimando ad una pagina di manuale. Ciò detto, la lista delle sezioni è la seguente21: Sezione 1 2 3 4 5 6 7 8 Titolo Esempio comandi chiamate di sistema funzioni di libreria periferiche formato dei files giochi miscellanea comandi di amministrazione passwd(1) open(2) printf(3) console(4) inittab(5) chess(6) ascii(7) shutdown(8) 2.3 Utilizzazione del terminale L’interfaccia tra il sistema UNIX e tutte le periferiche (chiamate anche ’device’) è effettuata da parti di codice del kernel chiamati driver. Un driver gestisce uno o più device. I terminali o (tty) sono anch’essi gestiti da un driver, chiamato ’driver di tty’ o driver di linea seriale. Una tty è in pratica ciò che precedentemente è stato denominato, in termini generici, una "linea utente", e come detto può corrispondere ad un terminale reale o ad uno virtuale. Essendo il terminale software totalmente parametrabile, è utile conoscere alcune delle sue possibilità; queste sono tutte consultabili o posizionabili tramite il comando stty(1), la cui sintassi in generale è: stty [-a] stty param [value] [ param [value] ... ] Nella prima forma, vengono mostrati i parametri nella loro configurazione attuale22; nel secondo caso, uno o più parametri vengono posizionati a nuovi valori. I parametri possono avere un valore specifico ad essi relativo, come ad esempio stty intr ˆc oppure essere parametri di tipo booleano; in tal caso, specificando il nome del parametro questo viene asserito, mentre viene negato facendolo precedere da un segno ‘-’. speed Indica la velocità del terminale, in bps (bits per second); ad esempio, $ stty 19200 20. ˆD rappresenta il carattere ‘control-D’. In seguito i caratteri di controllo e gli altri caratteri speciali saranno indicati in questo modo: ˆcarattere o talvolta <carattere-speciale> Ad esempio: ˆD, <newline>, <CR>, <Invio>. 21. La numerazione delle sezioni è stata cambiata in System V, dove ad esempio le periferiche sono in sezione 7. Alcuni altri sistemi, come ad esempio SCO UNIX, utilizzano una propria divisione in sezioni, diversa da quella abituale. 22. Senza argomenti, vengono visualizzati solo i parametri principali; con l’opzione -a, vengono mostrati tutti. V. Asta - Introduzione a Unix e GNU/Linux 21 Naturalmente, specificare la velocità ha senso soltanto se si tratta di una vera linea seriale fisica, ad esempio connessa con un modem. erase Carattere che permette di sopprimere l’ultimo carattere digitato (di solito ˆH o <DEL>). Ad esempio, $ tty erase ’ˆH’ $ Dopo questo comando sarà il carattere ˆH che permetterà di annullare l’ultimo carattere digitato. kill Carattere che permette di sopprimere tutti i caratteri della riga (di solito ˆU). intr Carattere che permette di inviare il segnale di interrupt, SIGINT (vedere il paragrafo 5.13 pag. 53 sui segnali), che può essere considerato come un break (di solito ˆC). quit Carattere che permette di inviare il segnale di quit, SIGQUIT (vedere il paragrafo sui segnali) (per default ˆ\). stop, start Caratteri che permettono di sospendere e di riprendere una visualizzazione a terminale (in genere ˆS e ˆQ). eof Carattere che permette di simulare la fine di file a partire da un terminale (in genere ˆD) vedi oltre. susp Carattere che permette di sospendere il comando attualmente in esecuzione in foreground (di solito ˆZ). tabs Flag indicante che il terminale è in grado di visualizzare il carattere di tabulazione (<tab> o ˆI, codice ASCII 009), nel qual caso il driver invia direttamente tali caratteri inalterati, altrimenti invia, in loro luogo, il numero di spazi necessari. echo Abilita l’eco a terminale dell’input da tastiera; ad esempio, stty echo read stty -echo -n "parola segreta: " SECRET echo # disabilita l’eco # leggi la parola, senza eco # riabilita l’eco echoe Fissa l’eco del carattere erase alla stringa "<BS> <BS>"23. icanon Abilita il trattamento "canonico" dell’input; in questa modalità, normalmente attiva di default, tutto l’input da tastiera è "line-buffered", cioè bufferizzato fin quando la riga non è terminata da un <Invio>; solo allora il contenuto della riga è passato al programma che la deve leggere. La bufferizzazione di riga permette in particolare l’editing, cioè la possibilità di correggere la riga (con i caratteri erase e kill, che altrimenti non hanno senso), prima che questa sia mandata al programma. icrnl Valido solo se icanon è asserito. Abilita la traslazione \r => \n in input (cioè un carattere <CR> ovvero <Invio> è traslato, nel buffer di input, in un newline, o line-feed: <NL> - vedi oltre). 23. <BS> è il carattere "back-space"; la sequenza è dunque di tre caratteri: un back-space, uno spazio, un altro back-space. Ciò produce come effetto l’effettiva cancellazione dallo schermo del carattere che si vuole eliminare: il primo <BS> riporta il cursore indietro di una posizione (dunque sul carattere da eliminare), lo spazio cancella il carattere, l’ultimo <BS> riporta nuovamente il cursore sulla posizione giusta per il prossimo carattere. V. Asta - Introduzione a Unix e GNU/Linux 22 opost Abilita il post-trattamento dell’output; ciò è analogo al trattamento canonico dell’input. onlcr Valido solo se opost è asserito. Abilita la traslazione \n => \r\n in output, cioè un carattere <LF> (newline, o line-feed) è traslato, nel buffer di output, nella sequenza <CR><LF> vedi oltre. sane Questo non è un vero parametro, ma corrisponde ad un "menù" precostituito nel programma stty(1): imposta tutti i parametri a valori iniziali ragionevoli Per quanto riguarda i parametri icanon, icrnl, opost e onlcr, si precisa che in UNIX tutti i files di testo hanno le righe terminate da un solo carattere, il <newline> (`\n’); ciò è in contrasto con quanto avviene in molti altri sistemi, dove ciascuna riga di testo è terminata con due caratteri: il ritorno carrello, <CR>, e il line-feed o newline, <LF> (il che costituisce evidentemente uno spreco di dati da memorizzare). Per gestire con coerenza questo aspetto, in input da tastiera il tasto <Invio> (che genera un <CR>) è normalmente convertito in new-line, mentre in output il carattere new-line è normalmente convertito nella sequenza "ritorno carrello" + "new-line", che provoca l’effettivo ritorno a capo del cursore e l’avanzamento alla prossima riga. È opportuno precisare meglio il ruolo del carattere di eof ; com’è noto dallo studio delle chiamate di sistema per la gestione dei canali di I/O, la condizione di end of file (fine di file) si verifica se e solo se la chiamata sistema read(2) ritorna 0 byte letti; in un normale contesto tty (con icanon asserito), il driver di terminale ritorna alla read() i caratteri letti in una riga (digitati dall’utente), fino al <newline> incluso, ovvero fino al carattere di eof escluso; ad esempio, se l’utente digita abc<newline> la read() ritorna 4 caratteri letti (le tre lettere più il <newline>), mentre se digita abcˆD la read() ritorna 3 caratteri letti (le tre lettere e basta); ne segue che se un utente digita il carattere di eof ad inizio riga, la read() ritornerà 0 byte letti, quindi la condizione di end of file. Per vedere tutti i parametri attuali di un terminale, come detto basta digitare il comando $ stty -a $ Consultare la pagina di manuale stty(1) per maggiori dettagli. 2.4 Primi comandi Sono stati introdotti già due comandi: • passwd per modificare una password • stty per modificare o consultare i parametri del driver di linea seriale (set/show tty). Si può fin da ora citare qualche altro comando utile per i primi contatti con il sistema. • date stampa la data e l’ora conosciute dal sistema. • who stampa l’elenco degli utenti attualmente connessi ("loggati") al sistema. • tty stampa il nome della linea seriale (o pseudo-linea seriale) sulla quale si sta lavorando (teletype). • grep ricerca un pattern, cioè un modello di selezione di una stringa di caratteri, in un file; il pattern è specificato come un’espressione regolare (regular expression − vedere l’Appendice 4 sulle espressioni regolari). • cal stampa il calendario corrispondente al mese o all’anno considerato (calendar). V. Asta - Introduzione a Unix e GNU/Linux 23 • man stampa le pagine del manuale corrispondenti al comando precisato (manual). • cat visualizza un file ascii sullo schermo. • less24 visualizza un file ascii paginato sullo schermo. • pr formatta un testo per la stampa (inserendo la data, la numerazione di pagine e altro) (print). • lpr25 spooler di stampa (line printer). • echo stampa i suoi argomenti. • wc conta i numeri di riga, le parole ed i caratteri contenuti nei file passati in argomento (word count). • mail permette di inviare un messaggio ad un utente o di leggere il proprio messaggio26. Esempio: L’utente luca digita: $ mail gianni questo è un messaggio per gianni bla bla bla ciao ˆD $ Questo testo sarà inviato all’utente gianni, che potrà leggerlo digitando semplicemente $ mail • write permette di comunicare con un altro utente loggato sul sistema27. Esempio: L’utente luca digita: $ write gianni L’utente gianni riceve28: <Bell> message from luca on ttya3 gianni digita allora: $ write luca A partire da questo momento tutto ciò che luca digita fino al ritorno carrello (Carriage 24. less(1) è generalmente usato nei sistemi GNU, tra cui GNU/LINUX; sostituito dal programma pg in System V; un altro programma equivalente è more, tipico dei sistemi BSD. 25. sostituito dal programma lp in System V 26. Esistono molti altri programmi analoghi, e più user-friendly: si citano elm, pine, oltre ai moduli di mailing dei browser WEB (Netscape e altri). Come si vedrà nel seguito e negli esempi, il comando mail(1) viene solitamente utilizzato in contesti non interattivi, in particolare da procedure di comandi che devono inviare messaggi di e-mail in modo automatico. 27. Il programma talk fornisce funzionalità analoghe, ma in modalità full-screen e con possibilità di dialogare tra utenti di macchine diverse, connesse in rete. 28. <Bell> indica il carattere di avvisatore acustico, o "alert" (bell - ASCII 007, o ˆG) V. Asta - Introduzione a Unix e GNU/Linux 24 Return − <CR>) è inviato a gianni, e viceversa. Per evitare di confondere caratteri e ruoli dei due utenti, si utilizza spesso la seguente convenzione: (o) alla fine di ciascuna replica (per "over", cioè "passo") e (oo) a fine conversazione (per "over and out", cioè "passo e chiudo"). Per terminare la connessione, ciascuno digita ˆD e l’altro riceve allora la stringa "<EOT>" (end of text). • mesg se si desidera lavorare senza essere disturbati da nessun messaggio sul terminale si può digitare: $ mesg n $ poi $ mesg y $ allorchè si è nuovamente disposti a ricevere dei messaggi (message). 2.5 Primi file di sistema 2.5.1 /etc/passwd Questo file (vedi la Figura 1) contiene delle informazioni su tutti gli utenti autorizzati. È un file di testo che si può editare normalmente e che è strutturato nel modo seguente: — una linea per utente — sette campi per riga, separati dal carattere ’:’ - ogni linea ha la forma seguente: nome:password:UID:GID:commento:HOME directory:comando nome nome dell’utente, da dare al login; deve essere al massimo di 8 caratteri e in minuscolo. password parola d’ordine da dare al login, memorizzata in forma criptata. Dopo la creazione di un account, questo campo viene lasciato vuoto, sarà riempito dal programma passwd che cripterà esso stesso la parola d’ordine data. UID numero di identificazione dell’utente: deve essere unico (User IDentity). GID numero di identificazione del gruppo (Group IDentity). commento questo campo è facoltativo ed in genere non è utilizzato da alcun programma di sistema. È spesso utilizzato per indicare il nome completo dell’utente. HOME directory è la directory in cui sarà posto l’utente al momento del login; di solito questa directory appartiene all’utente stesso, che quindi è in grado di creare nuovi file e directory a partire da questa, che è dunque la directory di partenza di tutta la sua zona di lavoro su disco. comando questo comando sarà lanciato al momento del login; si tratta generalmente di un interprete di comandi. Se questo campo è vuoto, il programma /bin/sh sarà eseguito per default. V. Asta - Introduzione a Unix e GNU/Linux 25 mario:FBSyjXpjW1JUo:44:22:Mario Rossi:/home/mario:/bin/bash ˆ ˆ ˆ ˆ ˆ ˆ ˆ | | | | | | | | | | | | | | USERNAME | | | | | | | UID | COMMENTO | SHELL | | | PASSWORD GID HOME DIRECTORY Fig. 1 2.5.2 /etc/profile È un file di comandi che viene eseguito ad ogni login. Permette di aggiornare parametri, di controllare se c’è posta in arrivo per l’utente, ed altre operazioni comuni per tutti gli utenti del sistema. 2.5.3 .profile È un file di comandi analogo al precedente che, se esiste nella home directory dell’utente, è eseguito anch’esso ad ogni login29. Permette di aggiornare altri parametri, di stampare la data e altre personalizzazioni dell’ambiente di lavoro, differenziate per ciascun utente. 2.5.4 /etc/motd Motd sta per "message of the day", cioè messaggio del giorno. È un file testuale che viene gestito liberamente dall’amministratore del sistema; il suo contenuto è visualizzato sul terminale durante la procedura di login, infatti in /etc/profile c’è normalmente una riga contenente cat /etc/motd 29. A seconda della shell, vengono talvolta utilizzati altri files, con nomi leggermente diversi, per questo stesso scopo; in particolare, la shell bash (v. oltre) utilizza normalmente, al posto di .profile, il file .bash_profile V. Asta - Introduzione a Unix e GNU/Linux 26 3. L’interprete di comandi - 1 L’interprete di comandi (denominato anche CLI, o "command line interpreter") in UNIX è un programma, normalmente chiamato sh, dunque un semplice file eseguibile. È in generale chiamato automaticamente al momento del login (secondo l’ultimo campo del file /etc/passwd). Può anche essere chiamato interattivamente, come gli altri comandi digitando: $ sh Esistono diverse varianti della shell, tra cui: Bourne shell - /bin/sh scritta da Steve Bourne ai Bell Laboratories, è la shell classica, ma anche la meno potente ed elastica. C-shell - /bin/csh scritta da Bill Joy, dell’Università di California a Berkeley30, è molto popolare negli UNIX di derivazione BSD; superiore al Bourne shell come interprete interattivo, ma meno adatta all’uso non interattivo (per procedure di comandi). Korn shell - /bin/ksh scritta da David Korn, dei Bell Laboratories, raduna le qualità della Bourne shell, con cui mantiene la compatibilità, e della C-shell. Bourne-Again shell - /bin/bash scritta da Richard Stallman e dalla comunità del progetto GNU, è molto simile alla Korn shell, a cui aggiunge peraltro varie funzionalità, ed è la shell di elezione nei sistemi GNU/LINUX. In questo testo la descrizione della shell è essenzialmente basata, salvo avviso contrario, sulle caratteristiche comuni della Bourne-Again shell e della Korn shell. È possibile pensare la shell come un sottosistema dove si entra sovrapponendo il proprio ambiente corrente, e da dove si può uscire operando in senso inverso. La shell è un programma che permette la comunicazione tra l’utente ed il Sistema Operativo UNIX. Legge sulla tastiera le righe di comando digitate e le interpreta come richieste per eseguire dei programmi. Un comando è un programma contenuto in un file. I comandi standard si trovano raggruppati in alcune directory (inizialmente /bin e /usr/bin). I comandi aggiunti dagli utenti si trovano normalmente in /usr/local/bin o in una directory denominata bin, a partire dalla loro home directory (questa non è che una convenzione che permette di restare in coerenza con la gerarchia standard di un sistema UNIX). La shell deve dunque ricercare il comando in tutte le directory possibili, e per questo usa un elenco, il cui formato è: dir1:dir2:dir3:... Questo elenco deve essere facilmente modificabile e deve poter essere differente per ciascun utente; è dunque posto in una variabile shell, chiamata PATH. È possibile verificare il suo valore digitando: $ echo $PATH /bin:/usr/bin:/usr/local/bin $ o modificare il suo valore; per esempio:31 $ PATH=$PATH:/home/gianni/bin $ 30. Bill Joy, che è anche l’autore dell’editor vi e di molte altre utilities classiche di BSD UNIX, ha successivamente lasciato l’Università per fondare la SUN Microsystems Inc. 31. È possibile anche scrivere export PATH=$PATH:/home/gianni/bin ; il comando export, introdotto più avanti nel testo (paragrafo 5.12 pag. 53 e paragrafo 6. pag. 54), permette di esportare il valore della variabile anche ai processi generati dalla shell, cioè ai comandi da questo lanciati. V. Asta - Introduzione a Unix e GNU/Linux 27 Dopo l’esecuzione, il prompt ritorna indicando che la shell è pronta ed attende un’altra riga di comando. 3.1 Separatore di comandi Per potere digitare più di un comando sulla stessa riga, si può utilizzare il carattere ; − detto operatore di esecuzione sequenziale; questo indica alla shell di eseguire il primo comando, di attendere la sua fine, e poi di eseguire il prossimo comando. $ date ; who $ I comandi date e who sono eseguiti in sequenza. Dopo la data e l’ora correnti, l’elenco delle persone connesse sul sistema è visualizzato sul terminale. 3.2 Ridirezione delle uscite verso un file Le uscite dei programmi sono tradizionalmente inviate dal sistema UNIX su un canale di I/O chiamato uscita standard, o standard output (abbreviato in stdout). Normalmente questo canale è assegnato allo schermo del terminale di lavoro. È possibile cambiare temporaneamente questa uscita domandando alla shell di "ridirigere" l’uscita standard verso un altro canale, specificato sulla riga di comando. comando >file La sintassi di questo comando ha per effetto di non inviare le informazioni prodotte da comando sul terminale ma di accantonarle nel file file. Se il file non esiste, sarà creato dalla shell. Se esiste già, la shell cancellerà il suo contenuto per sostituirvi l’uscita di comando. Esempio32: $ who >who.temp $ Questo comando non produce alcuna uscita sul terminale; tutte le informazioni del comando who sono accantonate nel file "who.temp" Si noti che il programma who si ritrova invocato senza argomenti: la parte ">who.temp" della riga di comando è gestita direttamente dalla shell, prima di lanciare il programma, e non è mai passata in argomento al programma stesso. $ cat who.temp gianni pts/0 luca pts/2 marion pts/6 marion ttyS0 $ jul jul jul jul 31 31 31 31 09:46 09:17 09:34 09:02 Visualizza il contenuto del file who.temp, vale a dire le persone loggate ad un istante t. È possibile ridirigere più di un comando in un file, per fare questo è sufficiente delimitare questi comandi con delle parentesi. Esempio: 33. Come già introdotto nel paragrafo 1.2 pag. 4, "Caratteristiche generali del kernel", e come si ridirà meglio nel paragrafo 10.1 pag. 67, "Il sistema di file", i nomi di file in UNIX possono essere composti con qualunque carattere, escluso `/’ (che indica il passaggio ad una componente successiva in un pathname) e ’\0’ (che termina le stringhe di caratteri); il carattere `.’ non ha nessun significato particolare, non esistendo nel kernel il concetto di "estensione di un nome di file"; tale concetto può invece esistere a livello applicativo, e infatti ad esempio il compilatore cc(1) fa questa distinzione per i nomi dei files passati in argomento: quelli che hanno estensione .c sono files contenenti programmi sorgente in linguaggio C, quelli con estensione .a sono files di sorgente in assembler, e così via. V. Asta - Introduzione a Unix e GNU/Linux 28 $ (date ; who) >who.temp $ In questo esempio le uscite dei comandi date e who saranno ridirette nello stesso file who.temp. $ cat who.temp Wed jul 31 10:33:20 MET 2000 gianni pts/0 jul 31 09:46 luca pts/2 jul 31 09:17 marion pts/6 jul 31 09:34 marion ttyS0 jul 31 09:02 $ Visualizza la data completa e le persone loggate. Si noti che se il comando non fosse stato delimitato da parentesi, la shell avrebbe eseguito date, stampato il risultato sul terminale, ed in seguito avrebbe lanciato who salvando il risultato sul file "who.temp". comando >>file Aggiunge l’uscita di questo comando alla fine del file file senza peraltro cancellare il suo contenuto. Se il file file non esiste, la shell lo crea. Esempio: $ date >date.temp $ cat date.temp Wed jul 31 10:45:21 MET 2000 $ date >>date.temp $ cat date.temp Wed jul 31 10:45:21 MET 2000 Wed jul 31 10:45:36 MET 2000 $ 3.3 Ridirezione dell’ingresso standard a partire da un file La shell permette anche di ridirigere l’ingresso standard verso un comando. Un comando prende i suoi dati d’ingresso a partire da un canale di I/O, denominato ingresso standard (standard input, o stdin), normalmente connesso alla tastiera del terminale, ma si può ugualmente utilizzare un file, o delle informazioni che sono state inserite in precedenza, come ingresso standard per questo comando. Esempi: $ mail marion <letter $ Per default, mail prende il testo di questo messaggio a partire dall’ingresso standard, vale a dire la tastiera; il fatto di avere specificato "<letter", ha per effetto di ridirigere l’ingresso del comando mail a partire dal file "letter". $ cat <who.temp Wed jul 31 10:33:20 MET 2000 gianni pts/0 jul 31 09:46 luca pts/2 jul 31 09:17 marion pts/6 jul 31 09:34 marion ttyS0 jul 31 09:02 $ Questo comando ha per effetto di visualizzare il contenuto del file "who.temp". Si noti che il comando cat si può utilizzare ugualmente senza il segno <, ma questa non è che una facilitazione poichè cat è un comando che legge, in assenza di argomenti, dal suo ingresso standard e scrive sulla sua uscita standard. V. Asta - Introduzione a Unix e GNU/Linux 29 Vista l’utilizzazione di questo comando il segno < è implicito. Più precisamente, si può dire che esistono sotto UNIX numerosi comandi, di cui cat è un esempio, che accettano uno o più file di input come argomento; se tali argomenti mancano del tutto, l’input è letto dallo stdin. Esempi, tra i comandi già visti, sono: cat, grep, more, pr, lpr, wc, mail. Un comando che è in grado di lavorare leggendo dallo stdin e scrivendo allo stdout è detto un filtro. Esempio: $ who >tmp gianni gianni gianni $ ; grep gianni <tmp ttya4 jul 31 10:50 ttyc6 jul 31 09:34 ttya2 jul 31 09:02 Questo comando crea un file di nome "tmp" contenente i riferimenti delle persone loggate; in seguito grep verifica se il pattern "gianni" è presente, leggendo dal proprio ingresso standard, che è costituito dal file "tmp". Si noti che l’operazione di ridirezione dell’ingresso/uscita standard, è effettuata dalla shell in modo totalmente indipendente dal contesto. Così è possibile digitare lo stesso comando nella forma seguente: $ >tmp who ; <tmp grep gianni gianni ttya4 jul 31 10:50 gianni ttyc6 jul 31 09:34 gianni ttya2 jul 31 09:02 $ Anche se questa forma non è particolarmente leggibile, essa è perfettamente valida per la shell. 3.4 Pipe: comunicazione tra processi La shell permette di mettere in comunicazione due processi, in modo che l’uscita standard di un comando costituisca l’ingresso standard di un secondo. Per illustrare questa possibilità della shell, riprendiamo l’esempio trattato precedentemente: $ who >tmp gianni gianni gianni $ rm tmp $ ; grep gianni <tmp ttya4 jul 31 10:50 ttyc6 jul 31 09:34 ttya2 jul 31 09:02 L’ultimo comando, rm, rimuove il file di appoggio "tmp", per evitare di conservare dei file temporanei ed inutili. L’utilizzazione del pipe permette di riassumere questi tre comandi in uno solo: $ who | grep gianni gianni ttya4 jul 31 10:50 gianni ttyc6 jul 31 09:34 gianni ttya2 jul 31 09:02 $ L’uscita prodotta dal comando who costituisce l’ingresso del comando grep. Quindi who dà l’elenco delle persone loggate ad un momento dato; grep, cerca invece se il pattern "gianni" è presente nel flusso di dati che riceve. Possiamo dunque considerare che il comando grep gioca il ruolo di filtro, come già detto. $ ls -l /tmp | grep marion | tee file -rw--------- 1 marion users 37888 jul 31 11:17 Ex01687 V. Asta - Introduzione a Unix e GNU/Linux 30 -rw--------- 1 marion $ cat file -rw--------- 1 marion -rw--------- 1 marion $ users 5120 jul 31 11:18 Ex01693 users users 37888 jul 31 11:17 Ex01687 5120 jul 31 11:18 Ex01693 Questo comando permette di mandare in uscita tutti i file presenti nella directory /tmp, questa uscita è utilizzata da grep che filtra tutti i file appartenenti a marion. Una volta operata questa selezione, la si dirige sul comando tee, che si limita a duplicare le sue uscite; vale a dire che invia le informazioni che riceve, non soltanto alla sua uscita standard ma anche al file "file", che viene creato se non esiste. Si noti che non si vedrà alcun risultato intermedio di questa serie di pipe. 3.5 Classificazione dei comandi Un approccio possibile per classificare i comandi, è di considerarli dal punto di vista dell’ingresso/uscita. 3.5.1 Comandi con ingresso e uscita I comandi di questa categoria hanno il proprio ingresso standard associato alla tastiera o ad un file, mentre l’uscita standard può essere sia lo schermo, sia un file. In base a queste caratteristiche essi sono suscettibili di essere combinati con un pipe. Esempi: wc $ cat /etc/passwd | wc 27 44 1186 $ wc </etc/passwd >tmp $ cat tmp 27 44 1186 $ rm tmp $ grep $ grep marion </etc/passwd marion:OaGKskj1Q1By2:243:100:Marion Rossi:/home/marion:/bin/ksh $ 3.5.2 Comandi aventi solo un’uscita Essi hanno soltanto uscite, e possono accettare degli argomenti, ma non hanno ingresso. who I parametri di esecuzione di questo comando sono degli argomenti che determinano il tipo di uscita, questa sarà effettuata per default sullo schermo, tuttavia essa potrà essere ridiretta. echo stampa i suoi argomenti sull’uscita standard. 3.5.3 Comandi aventi una sola entrata mail mail prende in argomento il nome della persona a cui è destinato il messaggio; prende in ingresso il messaggio stesso, che può dunque essere dato a partire da un file; non produce alcuna uscita. 3.5.4 Comandi senza nè ingresso nè uscita standard cd accetta un solo argomento indicante la nuova directory corrente; non è prodotta alcuna uscita. rm accetta solo nomi di file; non produce alcuna uscita. 3.6 I processi in background UNIX è un Sistema Operativo multi-task. Questo significa che si possono eseguire più processi V. Asta - Introduzione a Unix e GNU/Linux 31 simultaneamente. In effetti, com’è ben noto, il calcolatore tratta un solo processo alla volta34, ma lo scheduler di CPU del kernel permette al sistema di passare da un processo all’altro molto rapidamente. 3.6.1 Esecuzione asincrona di un processo UNIX non attribuisce un unico processo per utente ma permette ad uno stesso utente di eseguire più processi alla volta. Per fare questo è sufficiente utilizzare il metacarattere35 & alla fine di una riga di comando; questo comando sarà allora lanciato in modo asincrono, o in background: la shell lancia il comando e non ne aspetta la fine, ma è pronta ad accettare immediatamente altri comandi. Ecco un esempio di comando che può richiedere più di un minuto per essere eseguito: $ find / -name program.c -print & 3672 $ Questo comando cerca a partire dalla root directory (la radice del file sistem) tutti i file di nome "program.c"; per questo, il programma find esplorerà tutta la struttura ad albero, ed ogni volta che incontrerà un file di nome "program.c", visualizzerà il suo pathname completo sullo schermo36. Dopo aver convalidato questa riga con un <CR> (ritorno carrello), la shell visualizza un numero; questo rappresenta il numero che il sistema ha attribuito al vostro processo. Questo numero è denominato PID (Process IDentifier). Più comandi possono essere lanciati in background con l’aiuto di parentesi37. Esempio: $ (date; find / -type d -print) >/tmp/lista.directories & $ Questo comando memorizza, nel file specificato, dapprima la data e a seguire la lista di tutte le directory del sistema; l’insieme dei due programmi (date e find) è lanciato in background, con l’output ridiretto sul file. 3.6.2 Stato dei processi È possibile ottenere delle informazioni sui processi in esecuzione tramite il comando ps Esempio: $ ps -f UID PID PPID gianni 827 782 gianni 874 1 gianni 13148 827 gianni 13175 874 gianni 13187 13176 $ C STIME TTY 0 Dec30 pts/0 0 Dec30 pts/0 0 12:59 pts/0 0 13:01 pts/0 0 13:01 pts/0 TIME CMD 00:00:02 -bash 00:00:01 sh /home/gianni/bin/pconn 00:00:00 vi Disp_P2_01.mm 00:00:00 sleep 60 00:00:00 ps -f 34. Supponendo una macchina con una sola CPU; su una macchina multiprocessore, con N CPU, i processi in esecuzione simultanea saranno al massimo N . 35. Per metacarattere si intende qualsiasi carattere che abbia un significato speciale per la shell; vedi oltre, al paragrafo "caratteri speciali" (paragrafo 3.7 pag. 33). 36. Il comando find è presentato in dettaglio in un’Appendice del testo. 37. Come sarà precisato successivamente (vedi paragrafo 3.7 pag. 33), i comandi tra parentesi tonde vengono eseguiti in una subshell. V. Asta - Introduzione a Unix e GNU/Linux 32 Questo comando indica tra l’altro gli identificativi numerici dei processi (PID) collegati al terminale di lavoro, il nome del terminale stesso (qui pts/0), il PID del padre di ciascun processo (PPID), l’ora (o il giorno) di avvio del programma (STIME), il tempo CPU utilizzato fin qui (TIME), i nomi dei comandi in esecuzione e i loro argomenti.38 L’arresto di un processo in background, ove necessario, si effettua con l’aiuto del comando kill: kill numero-processo Il numero di processo fa riferimento al processo lanciato in background. Si noti che il fatto di lanciare un comando in background provoca la disattivazione dell’ingresso standard40, a meno che questo non sia esplicitamente ridiretto, mentre invece non modifica la sua uscita standard. Se il comando deve produrre un’uscita, può essere utile ridirigere l’uscita su di un file per non essere disturbati da questa, mentre si continua a lavorare. 3.7 Caratteri speciali Esistono dei caratteri, detti metacaratteri della shell, aventi per questa un significato speciale: * ? [ ] < > ; | ’ ` & " = $ / Pertanto è consigliabile non utilizzarli per attribuire il nome ad un file. *?[] Quando la shell incontra questi caratteri, li sostituisce con i nomi dei file che ha potuto trovare. Esempi: $ ls -l *.c -rw-r--r--rw-r--r--rw-r--r-$ 1 gianni 1 gianni 1 gianni dev dev dev 1024 jul 31 11:57 foo.c 2048 jul 31 11:57 prog.c 2048 jul 31 11:57 ap.c Il carattere ‘*’ seleziona tutte le stringhe (compresa quella nulla) salvo il carattere ‘/’. Per questo la riga di comando qui sopra, visualizza tutti i file nella directory corrente terminanti per ’.c’. $ ls -l ??.c -rw-r--r-$ 1 gianni dev 2048 jul 31 11:57 ap.c Il carattere ‘?’ seleziona un carattere qualunque. Questo esempio visualizza tutti i file il cui nome comporta due caratteri qualsiasi seguiti dal suffisso ’.c’. $ ls -l [a-c]?.[ac] -rw-r--r-1 gianni -rw-r--r-1 gianni $ dev dev 2048 jul 31 11:57 ap.c 3246 jul 31 11:59 bk.a Le parentesi quadre selezionano tutti i caratteri: — sia compresi in un dato intervallo, se il carattere - è presente: [a-c] — sia facenti parte di un elenco: [ac]. Così la riga di comando considerata visualizza tutti i file che iniziano per a, b o c, seguiti da un carattere qualunque, seguito a sua volta da un . e terminante per a 39. Il formato esatto dei dati visualizzati da ps varia a seconda dello specifico Sistema Operativo UNIX, e delle eventuali opzioni passate in argomento al programma; consultare ps(1) per i dettagli. 40. Più precisamente, lo standard input dei processi lanciati in background è ridiretto, per default, su /dev/null; quest’ultimo è uno pseudo-device (vedi paragrafo paragrafo 9. pag. 63 sui file speciali): la lettura da /dev/null provoca sistematicamente una condizione di end of file (0 byte letti). V. Asta - Introduzione a Unix e GNU/Linux 33 o c. V. Asta - Introduzione a Unix e GNU/Linux 34 4. Sintassi dei comandi 4.1 Comandi semplici Un comando semplice è una serie di parole separate da spazi o da tab; o, più in generale, da qualsiasi carattere precisato nella variabile IFS (vedi "Assegnazione e sostituzione di variabili", paragrafo 5.4 pag. 43). La prima parola è considerata come il nome del comando; le altre come gli argomenti di questo comando. Esempi: ls cd test cat fileA fileB chmod +x fileA who mv fileA fileZ wc -l fileZ grep jo fileZ man sort Un comando possiede uno status o valore significativo di ritorno. Questo valore è memorizzato in una variabile shell, ma non è stampato. Le terminologie utilizzate per designare lo ‘status’ sono variabili; è possibile trovare indifferentemente i termini ‘exit code’, ‘codice di uscita’, ‘exit status’, ‘return code’ o altro; qui, oltre a ‘status’, sarà usato talvolta il termine ‘codice di uscita’, o il suo equivalente ‘exit code’. Per convenzione, i comandi hanno uno status uguale a 0 in caso di successo (il programma si è eseguito normalmente fino in fondo), e diverso da 0 in caso d’errore (in tal caso, ove ciò sia utile, si possono utilizzare diversi valori per significare distintamente diverse possibili condizioni di errore). 4.2 Pipeline Un pipeline è un comando semplice o una serie di comandi semplici separati dal carattere | (denominato pipe). cmd1 | cmd2 L’uscita standard di cmd1 serve come ingresso standard per cmd2. Ogni comando è eseguito da un processo distinto. I due processi lavorano nello stesso tempo, e sono sincronizzati. La shell attende che l’ultimo sia terminato, prima di continuare. Esempi: $ ls | wc -l 20 $ ls -l | grep test -rw-r--r-1 gianni $ axis 2238 jul 29 15:03 test 4.3 Liste Una lista è un pipeline o una serie di più pipeline separati da ;, &, &&, ||, eventualmente terminanti con ; o &. V. Asta - Introduzione a Unix e GNU/Linux 35 — ; e & hanno la stessa priorità. — && e || hanno la stessa priorità. — ; e & hanno maggiore priorità rispetto a && e ||. ; : esecuzione in sequenza cmd1 ; cmd2 Dapprima viene eseguito cmd1, e successivamente cmd2. Esempio: $ cd ; ls Si pone nella HOME directory e lista il suo contenuto. & : esecuzione in parallelo, o asincrona cmd1 & cmd2 cmd1 e cmd2 sono lanciati contemporaneamente; entrambi si eseguono in modo asincrono rispetto all’altro. Caso particolare: & : esecuzione ’in background’ cmd & L’esecuzione di cmd sarà lanciata, poi la shell continuerà senza attendere la fine dell’esecuzione (è la shell stessa che è in parallelo con il comando). Esempio: $ find / -name ’*.o’ -print| sort >/tmp/obj-lista & 1401 $ Mette l’elenco di tutti i files che terminano per ".o" (files oggetto rilocabili), selezionati in ordine alfabetico, nel file "/tmp/obj-lista"; poichè il comando portà impiegare un certo tempo ad eseguirsi, è comodo mandare il programma in background, così la shell restituisce immediatamente il prompt ed è pronta ad accettare altri comandi mentre il primo si esegue. && : ’and’ logico cmd1 && cmd2 Se cmd1 ritorna 0 ( vero in shell), cmd2 sarà lanciato. Se cmd1 ritorna con un valore diverso da 0 ( falso in shell), cmd2 non sarà eseguito. In ogni caso, il codice di uscita del costrutto è quello ell’ultimo comando eseguito. Nota: non è usuale trovare 0 per rappresentare il valore ’vero’; ma questa scelta, come già detto, permette di restituire dei codici d’errore significativi, permettendo di determinare con precisione dove si è verificato l’errore. Esempio: $ [ -d tmp ] && cd tmp $ V. Asta - Introduzione a Unix e GNU/Linux 36 Se tmp esiste ed è una directory, posizionarsi qui come directory di lavoro; altrimenti restare nella directory attuale. (per la notazione [ -d tmp ], vedi l’Appendice 5 sul comando test o la pagina di manuale di test(1)). || : ’or’ logico cmd1 || cmd2 Se cmd1 risulta falso, cmd2 sarà lanciato. Se cmd1 risulta vero, cmd2 non sarà eseguito. In ogni caso, il codice di uscita del costrutto è quello ell’ultimo comando eseguito. Esempio: $ cd $HOME/tmp || cd /tmp Se non esiste la directory tmp nella HOME directory, portarsi nella directory comune /tmp. 4.4 Comandi Un comando è un comando semplice o uno dei costrutti seguenti, che permettono la programmazione strutturata in shell: 4.4.1 Il costrutto for for name [ in word... ] do list done for, in, do e done sono parole chiavi del linguaggio shell. La lista di comandi list viene eseguita tante volte quante sono le parole word che seguono in, con il parametro name che assume successivamente il valore delle singole parole. Ad esempio, nel costrutto seguente: "for I in a b c", il parametro I prenderà dapprima come valore ’a’, poi ’b’, ed infine ’c’. La sezione "in word..." è facoltativa; se viene omessa, essa è, per default, costituita dall’elenco degli argomenti che sono stati passati al comando (equivale cioè a "for I in $*" - vedi oltre, "Assegnazione e sostituzioni di variabili", paragrafo 5.4 pag. 43). L’esecuzione termina quando non ci sono più elementi nell’elenco. Esempi: for I in 1 2 3 do cp file$I /tmp done Copia i file file1, file2 e file3 nella directory /tmp. for I do echo $I done "for I" è, come detto, l’equivalente di "for I in $*"; viene stampata la lista degli argomenti, uno per riga. for I do grep $I *.c done cerca ciascun pattern passato in argomento in tutti i files che terminano per ’.c’. V. Asta - Introduzione a Unix e GNU/Linux 37 for i do echo $i: sed "s/$1/$2/g" $i done Sostituisce, in ogni riga di tutti i file passati in argomento, tutte le occorrenze della stringa "$1" con la stringa "$2"41. 4.4.2 Il costrutto case case word in pattern [| pattern ]...) [list];; ... esac case, in ed esac sono parole chiavi del linguaggio shell. Il comando case esegue l’elenco di comandi list associato al primo pattern che seleziona word. La forma di un pattern è la stessa di quella utilizzata per la generazione dei nomi di files (avvalendosi degli operatori *, ?, []), con l’aggiunta dell’operatore |, che indica alternativa. Come si vede, ciascun pattern è delimitato da una parentesi tonda chiusa, e ciascuna lista di comandi associata ad un pattern termina con un doppio punto e virgola (;;). Esempi: case $1 in a) X=alpha b) X=beta g) X=gamma *) X=$1 ;; esac echo $X ;; ;; ;; Se $1 è ’a’, stampa alpha, altrimenti se $1 è ’b’, stampa beta, altrimenti se $1 è ’g’, stampa gamma, altrimenti stampa $1 inalterato. case $# in 3) ed - $3 <<%% g/$1/s//$2/ w q %% ;; *) echo "usage: $0 s1 s2 file" ; exit 1;; esac Cambia, tutte le volte che si presenta, $1 con $2 nel file dato da $3. 41. $1, $2, $3 etc. indicano rispettivamente il primo, secondo, terzo etc. argomento di una procedura di comandi shell; vedi oltre, "Assegnazione e sostituzioni di variabili", paragrafo 5.4 pag. 43. V. Asta - Introduzione a Unix e GNU/Linux 38 FLAG= for A do case $A in -[ocO]) FLAG="$FLAG $A" ;; -*) echo unknown flag $A; exit 1 ;; *.c) cc $FLAG $A; FLAG= ;; *.s) as $FLAG $A; FLAG= ;; *) echo unexpected argument $A; exit 1 ;; esac done Compila o assembla i file passati in argomento con le opzioni valide. 4.4.3 Il costrutto if if list1 then list2 [elif list3 then list4] [else list5] fi if, then, elif, else e fi sono parole chiavi del linguaggio shell. list1 è eseguito, se il suo valore di ritorno è vero (0) list2 sarà eseguito; altrimenti list3 è eseguito, se il suo valore di ritorno è vero, list4 sarà eseguito, altrimenti sarà eseguito list5. Possono esserci più elementi "elif - then" di seguito. Esempio: V. Asta - Introduzione a Unix e GNU/Linux 39 for I do if test -f "$I" then echo -n "$I" is an ordinary file if test -r "$I" then echo -n readable fi if test -w "$I" then echo -n writable fi if test -x "$I" then echo -n executable fi echo elif test -d "$I" then echo "$I" is a directory else echo "$I" is unknown fi done Per ogni argomento, stampa il suo tipo (file o directory) ed i diritti di accesso che gli sono associati. (per test, vedi l’Appendice 5 su questo comando). 4.4.4 Il costrutto while while list1 do list2 done while, do e done sono parole chiavi del linguaggio shell. Finchè il valore di ritorno di list1 è vero, list2 è eseguito. Esempio: while : do if who | grep $1 > /dev/null then echo $1 è loggato exit else sleep 180 fi done Cerca se l’utente il cui nome è dato nel primo argomento è loggato, e questo fino a che lo sia effettivamente, riprovando ogni 3 minuti (180 secondi). L’argomento ‘:’ di questo loop while è uno pseudo-comando, built-in del linguaggio shell, che resta sempre vero, e non effettua alcuna azione. V. Asta - Introduzione a Unix e GNU/Linux 40 4.4.5 Il costrutto until until list1 do list2 done until, do e done sono parole chiavi del linguaggio shell. Finchè il valore di ritorno di list1 è falso, list2 sarà eseguito. Esempio: until who | grep $1 >/dev/null do sleep 300 done echo "$1 e’ arrivato" Ogni 5 minuti (300 secondi), controlla se l’utente il cui nome è stato dato in argomento ($1) risulta loggato nel sistema; continua fintantochè l’esito è negativo, poi quando finalmente l’utente risulta connesso, stampa a schermo il messaggio che è arrivato. 4.4.6 I sotto-processi ( lista ) Esempio: $ (date ; who) >file Esegue la lista di comandi in una sub-shell. La ridirezione dell’uscita standard ">file" si applica per tutti i comandi di questa sub-shell: in effetti, è lo stdout della sub-shell che che viene ridiretto sul file; tutti i programmi eseguiti al suo interno ereditano ovviamente l’ambiente, tra cui la ridirezione dell’uscita standard. 4.4.7 L’esecuzione semplice { lista } Esegue semplicemente la lista senza creare sub-shell. Nota: le parentesi graffe ({ }) sono riconosciute come delimitatori di lista solo se in una posizione sintatticamente equivalente ad un comando; pertanto devono obbligatoriamente trovarsi dopo un line feed, o dopo un ; o altri separatori di comando. V. Asta - Introduzione a Unix e GNU/Linux 41 5. L’interprete di comandi - 2 5.1 Funzionamento generale L’interprete shell viene impiegato per: — stampare il prompt — attendere una riga — trattare questa riga. In modo più dettagliato, trattare una riga significa: — Trattamento dei caratteri quotati. — Sostituzione delle variabili. — Sostituzione dei comandi. — Sostituzione dei nomi di file. — Se si tratta di un comando speciale: eseguirlo. Altrimenti - eseguire un fork a. per il processo padre: . se non si tratta di un processo lanciato in background attendere la fine del comando. . altrimenti fine. b. per il processo figlio: . redirezione dell’ingresso/uscita. . costruzione di una tavola di argomenti. . esecuzione del comando. Nei seguenti sottoparagrafi vengono dettagliate le operazioni introdotte. 5.2 Il Prompt Prima di attendere un comando, la shell stampa come prompt il valore della variabile shell PS1 (valore di default: PS1=’$ ’, salvo per l’utente root: PS1=’# ’). Dopo un ritorno carrello, se il comando è incompleto, la shell stampa come prompt secondario il valore della variabile PS2 (valore di default: PS2=’> ’). 5.3 Quoting Gli operatori seguenti hanno un significato speciale per la shell, e agiscono da separatori se i relativi caratteri non sono quotati. ; & | && || ( ) { } < > << >> * ? $ ’ " ` \ <newline> <space> <tab> — Un carattere preceduto da \ è quotato. — Tutti i caratteri inclusi tra una coppia di ’ sono quotati, eccetto ’ stesso. — Tutti i caratteri inclusi tra una coppia di " sono quotati, ma le sostituzioni dei parametri ($PARAM) e dei comandi ( `cmd arg...`, $(cmd arg ...) ) sono effettuate; i caratteri $ ` " \ sono quotati da \. Esempio: V. Asta - Introduzione a Unix e GNU/Linux 42 $ echo * Stamperà tutti i nomi di file della directory. $ echo ’*’ Stamperà il carattere *. 5.4 Assegnazione e sostituzioni di variabili Una variabile shell (o variabile di ambiente) VAR è rappresentata da un nome costituito da una serie di lettere, di cifre, underscore (_), oppure da uno dei caratteri: * @ # ? - $ ! VAR=value Assegna la stringa di caratteri value alla variabile VAR. Esempio: $ TDIR=/home/gianni/tmp $ $VAR sostituzione col valore della variabile VAR (detto anche interpolazione della variabile) Esempio: $ cd $TDIR $ pwd /home/gianni/tmp $ ${VAR} Identico a $VAR, ma utile a delimitare il nome del parametro se questo deve essere seguito da uno o più caratteri. Esempio: $ X=1 $ Y=2 $ echo $X$Y 12 $ echo $XO $ echo ${X}O 10 $ ${VAR:-word} Se VAR ha un valore, sostituire questo valore, altrimenti sostituirgli word. Esempio: $ mv file ${TDIR:-/tmp} Se TDIR non ha un valore, file sarà posto nella directory /tmp. ${VAR:=word} Se VAR non ha valore, assegnargli word come valore; sostituire VAR con il suo valore. Questo permette di stabilire dei valori di default. Esempio: $ cc ${FLAG:=-O} *.c Se FLAG non ha valore gli sarà assegnato -O e tutti i file della directory corrente che terminano per ’.c’ saranno compilati con $FLAG come opzione per cc. V. Asta - Introduzione a Unix e GNU/Linux 43 ${VAR:?word} Se VAR ha un valore, sostituirlo. Altrimenti stampare word ed uscire dalla shell. Se word è omesso, la shell stampa un messaggio per default. Esempio: $ mv file ${TDIR:?no dir} Se TDIR ha un valore, il file file è posto nella directory $TDIR; altrimenti il messaggio "no dir" è stampato. ${VAR:+word} Se VAR ha un valore, sostituirlo con word e rimandarlo, altrimenti non rinviare nulla. Nota: word è valutato solo se deve essere utilizzato. Alcune variabili sono posizionate automaticamente dalla shell: 0 1 2 3 ... parametri posizionali contenenti gli argomenti del comando lanciato. $0 è il nome del comando stesso, $1 è il suo primo argomento, $2 il secondo, e così via. # numero di parametri posizionali in decimale (ad esclusione di $0). Esempio: Per un comando che necessita di almeno un argomento: case $# in 0) echo usage: .... exit 1 ;; *) comando ... ;; esac Permette di sapere se è stato dato il numero esatto di argomenti al comando, e di visualizzare il modo di utilizzarlo. *,@ — $* equivale a "$1 $2 $3 ..." — $@ equivale a "$1" "$2" "$3" ... - opzioni passate alla shell alla sua chiamata o tramite il comando set (vedi oltre, "Comandi speciali", paragrafo 5.7 pag. 46). ? status di ritorno dell’ultimo comando eseguito, in decimale. $ numero di processo (PID) della shell. Nota: questa variabile può servire ad esempio per creare dei file temporanei di nome differente ed unico: $ >file$$ $ ls file* file1254 $ In generale viene usato negli shell script. ! numero di processo (PID) dell’ultimo comando lanciato in background. Ciò è molto utile per arrestare l’ultimo processo lanciato in background, senza dover eseguire il comando ps. Esempio: $ kill -15 $! V. Asta - Introduzione a Unix e GNU/Linux 44 Le variabili seguenti sono utilizzate dalla shell, ma sono posizionate solo da comandi espliciti. HOME HOME directory. PATH percorso per accedere ai comandi. CDPATH un PATH per il comando cd. Esempio: $ CDPATH=:/usr $ cd games /usr/games $ MAIL se questa variabile contiene il nome di un mailbox, la shell segnala l’arrivo di un messaggio in questo mailbox. PS1 prompt principale ( per default ’$ ’). PS2 prompt secondario ( per default ’> ’). IFS separatori di parole per la shell (per default spazio, <tab>, <newline>) Infine, le seguenti variabili, tra le altre, sono anch’esse utilizzate dalla shell, che le posiziona automaticamente (come per i parametri posizionali e gli altri parametri speciali visti in apertura di paragrafo): PWD posizionato al pathname della directory di lavoro corrente OLDPWD posizionato al pathname della directory di lavoro precedente 5.5 Sostituzione di comandi L’uscita standard di un comando posto tra ` ` o tra $( ) viene succcessivamente utilizzata come una parola o una serie di parole nel comando (i <newline> sono ignorati). Esempi: $ DIR=`pwd` $ echo $DIR /home/gianni $ $ A=1 $ A=$(expr $a + 1) $ echo $A 2 $ In uno shell script: for I in `ls -t` do ... ... done Per trattare tutti i file della directory corrente selezionati in ordine di data. set $(date) Assegna i parametri posizionali ai campi della data (vedi set nel paragrafo paragrafo 5.7 "Comandi speciali"). V. Asta - Introduzione a Unix e GNU/Linux pag. 46, 45 5.6 Sostituzione dei nomi di file I metacaratteri della shell per la sostituzione dei nomi di file (file globbing) sono: *, ?, [] Se uno di questi caratteri compare in una parola, questa è considerata come un pattern. Esso sarà sostituito dall’elenco di nomi dei file che selezionano questo pattern, messi in ordine alfabetico. Se la shell non trova alcun nome di file che seleziona il pattern, questo sarà conservato tale e quale. Il carattere ’.’ all’inizio del nome di file, così come il carattere ’/’, deve essere selezionato esplicitamente. — * seleziona qualsiasi stringa, compresa quella nulla. — ? seleziona qualsiasi carattere. — [...] seleziona uno qualsiasi dei caratteri tra le parentesi quadre. Una coppia di caratteri separati da ed entro parentesi quadre seleziona qualunque carattere incluso nell’intervallo lessicografico definito. Esempi: ls f* elenca tutti i file il cui nome comincia per ’f’ ls file? lista tutti i file il cui nome è composto da file, seguito da un carattere qualsiasi. ls file[A-Z] lista tutti i file il cui nome è composto da file, seguito da una lettera maiuscola. ls*.c lista tutti i file il cui nome termina con i due caratteri ".c". ls ?.c lista tutti i file il cui nome è formato da un carattere qualunque, seguito dai due caratteri ’.c’ ls ? ?? ??? lista tutti i file il cui nome è composto da 1, 2, o 3 caratteri. 5.7 Comandi speciali Questi comandi sono eseguiti nella stessa shell (e non in un sottoprocesso), e non permettono, in generale, di ridirigere gli ingressi/uscite. : questo comando non fa nulla; ritorna 0 . file legge file, ed esegue i comandi letti senza generare delle sotto-shell; è utile per eseguire ad esempio .profile $ cat >.profile PS1=’yes,dear? ’ ˆD $ chmod +x .profile $ .profile $ . .profile yes,dear? break [n] permette di uscire da n blocchi nidificati; se n non è precisato, 1 è preso per default, quindi permette di uscire dal blocco corrente. (Un blocco è un loop for, while o until) continue [n] permette di riprendere all’inizio dell’iterazione seguente nello n-mo blocco nidificato; se n non è precisato, 1 è preso per default. cd [dir] prende dir come directory corrente; se dir non è precisato, il valore di $HOME è preso per default; se dir vale -, è preso il valore di $OLDPWD; vedi anche la variabile CDPATH eval [arg] gli argomenti sono letti come input per la shell, valutati ed eseguiti. Serve per forzare una nuova valutazione di una stringa di comando, di solito costruita con variabili shell a cui sono stati assegnati valori con metacaratteri della shell, che debbono essere valutati in un secondo momento. V. Asta - Introduzione a Unix e GNU/Linux 46 Esempio: # con l’opzione -p, l’output viene paginato allo schermo; # altrimenti viene inviato allo stdin come un unico flusso OUT= case $1 in -p) OUT="|more" ;; ... esac eval commd args $OUT # si richiede qui di interpretare # il | come operatore di pipe exec [arg] il comando specificato da arg è eseguito al posto della shell. Non c’e quindi creazione di un nuovo processo. Gli ingressi/uscite possono essere ridiretti. Questo sarà l’ultimo comando eseguito dalla procedura (la shell fa una chiamata sistema exec(2), quindi sparisce). exit [n] termina una shell rimandando n come codice d’uscita. Se n è omesso, il codice è quello rinviato dall’ultimo comando eseguito. export [var...] le variabili specificate vengono poste nell’ambiente di lavoro attuale (working environment), dunque i processi figli le erediteranno. Se non ci sono argomenti, l’elenco delle variabili esportate viene stampato. newgrp arg equivale ad ’exec newgrp arg’42 read var [var ...] legge una riga sull’ingresso standard; le parole lette sono assegnate, in ordine, alle variabili specificate. Se ci sono più parole che variabili, tutte le parole restanti sono assegnate all’ultima variabile. Il valore di ritorno è 0 salvo in caso di fine file. Esempi: 42. Vedere newgrp(1) per dettagli. V. Asta - Introduzione a Unix e GNU/Linux 47 $ echo ciao bello | read AAA $ echo $AAA ciao bello $ echo ciao bello | read AAA BBB $ echo $AAA ciao $ echo $BBB bello $ echo ciao bello come stai | read AAA BBB $ echo $AAA ciao $ echo $BBB bello come stai # cat rubrica Gianni Bianchi via Giulio Cesare n. 43, Roma Luca Verdi via Marco Aurelio n. 54, Milano $ cat rubrica | while read NOME COGNOME INDIRIZZO > do > echo Nome: $NOME / Cognome: $COGNOME -- Ind.: $INDIRIZZO > done Nome: Gianni / Cognome: Bianchi -- Ind.: via Giulio Cesare n. 43, Roma Nome: Luca / Cognome: Verdi -- Ind.: via Marco Aurelio n. 54, Milano readonly [var...] le variabili specificate sono in sola lettura, e non si potrà dunque assegnar loro dei nuovi valori. Se non ci sono argomenti, l’elenco delle variabili in sola lettura viene stampato. set [-enptuvx [arg...]] ciascuna opzione viene posizionata servendosi di ’set -o’, e puo‘ essere annullata da ’set +o’ (dove o è un’opzione). -a ogni variabile creata o modificata nella shell corrente sarà automaticamente esportata. -e se la shell non è interattiva, uscire non appena un comando ritorna ’falso’. -f vieta l’uso dei metacaratteri per la generazione di nomi di file. Esempio: $ set -f $ cat * cat: can’t open * $ set +f $ -n legge i comandi ma non li esegue. -u considera le variabili non definite come un errore. -v stampa le righe shell così come sono lette. -x stampa i comandi ed i loro argomenti così come sono eseguiti. - annulla le opzioni -x e -v. Tutte queste opzioni possono essere date al momento della chiamata della shell. Esse sono V. Asta - Introduzione a Unix e GNU/Linux 48 custodite nella variabile $-. set può altrimenti essere utilizzato per posizionare esplicitamente i parametri posizionali, assegnando quindi nuovi valori a $1 $2 $3 etc, e agli altri parametri a questi correlati: $* $@ $#. Esempio: $ set abc def ghi $ echo $* / $# abc def ghi / 3 shift [n] i parametri posizionali sono scalati di n posizioni verso sinistra; $# è diminuito di n. Se n è omesso, i parametri sono scalati di 1 per default. Esempio: $ set a.out aaa.c bbb.c file1.txt file2.txt $ echo $* / $# a.out aaa.c bbb.c file1.txt file2.txt / 5 $ shift $ echo $* / $# aaa.c bbb.c file1.txt file2.txt / 4 $ shift 2 $ echo $* / $# file1.txt file2.txt / 2 $ time stampa l’accumulo di tempo reale, del tempo utente e del tempo sistema utilizzato per i processi lanciati a partire dalla shell. trap [arg] [n] arg è un comando (o un insieme di comandi) che sarà letto ed eseguito dalla shell quando questa riceverà il segnale n. Se arg è omesso, il segnale riprende il suo valore iniziale. Se arg è una stringa vuota, il segnale è ignorato. trap senza argomento stampa l’elenco dei valori associati al segnale. Esempio: TFILE=/tmp/tf.$$ trap ’rm -f $TFILE; exit’ 2 3 # # # # # file temporaneo intercetta i segnali n. 2 e 3 (intr e quit); se interrotto, rimuovi il file prima di uscire # sezione critica, in cui viene fatto uso # del file temporaneo ... # fine sezione critica rm $TFILE trap 2 3 # non intercettare più # i segnali ... umask [nnn] la maschera per i permessi d’accesso dopo la creazione di un file prende il valore ottale nnn (vedi la chiamata sistema umask(2) per dettagli). Se nnn è omesso, umask stampa il valore corrente del comando stesso. V. Asta - Introduzione a Unix e GNU/Linux 49 Esempio: $ umask 0000 $ cc foo.c $ ls -l a.out -rwxrwxrwx $ umask 002 $ cc foo.c $ ls -l a.out -rwxrwxr-x $ umask 007 $ cc foo.c $ ls -l a.out -rwxrwx--$ wait [n] 1 gianni axis 1008 jul 3 16:39 a.out 1 gianni axis 1008 jul 3 16:39 a.out 1 gianni axis 1008 jul 3 16:39 a.out attende la fine del processo con PID pari a n e ritorna il suo codice d’uscita. Se n è omesso, attende la fine di tutti i processi figli. 5.8 Controllo degli ingressi/uscite È possibile richiedere alla shell, prima di eseguire un comando, di aprire un canale di I/O su un dato file (normale o speciale), e di associarlo a un dato file-descriptor (f.d.). Se il f.d. corrisponde già a un canale aperto, quest’ultimo viene chiuso prima della nuova associazione; in tal caso si parla di ridirezione degli ingressi/uscite. I caratteri speciali utilizzati possono essere situati ovunque in un comando semplice, prima o dopo il comando. Sono trattati dalla shell e non sono passati al comando. n<file apre il file file in sola lettura, e lo associa al file descriptor n; se n manca, il default è 0, cioè lo standard input. Esempio: $ mail gianni < letter $ n>file apre il file file in sola scrittura, e lo associa al file descriptor n; se n manca, il default è 1, cioè lo standard output. Se il file non esiste, la shell lo crea; se esiste, il contenuto precedente del file è soppresso. Esempio: $ date > info $ cat info wed jul 3 18:24:08 MET 2000 $ n>>file identico al caso precedente, ma se il file esiste i dati in uscita sono appesi dopo la fine del contenuto attuale. Esempio: $ who >> info $ cat info wed jul 3 18:24:08 MET 2000 root console jul gianni ttyc6 jul $ V. Asta - Introduzione a Unix e GNU/Linux 3 18:15 3 18:21 50 n<>file apre il file file in lettura/scrittura, e lo associa al file descriptor n; se n manca, il default è 0, cioè lo standard input. Se il file non esiste, la shell lo crea; se esiste, il contenuto precedente del file è soppresso. <<word le righe seguenti sono considerate come l’ingresso standard del comando, fino ad una riga contenente word, o fino alla fine del file. Se si vuole utilizzare word in queste righe, bisogna quotarlo. La sequenza di caratteri \<newline> è ignorata (ciò implica che una riga lunga può essere scritta come due righe separate, terminando la prima con \). I caratteri speciali $ e ` provocano la sostituzione dei comandi e dei parametri. Per utilizzare questi caratteri occorre quotarli. Esempio: $ cat foo titi toto $ ed foo <<% > s/to/ti/g > w > q > % 10 10 $ cat foo titi titi $ Nota: a partire da System V, la notazione alternativa <<-word può essere utilizzata. Essa permette di arrestare le operazioni tutte le volte che si presenta word, ovunque nella riga. Se questa notazione non è accettata, la stringa d’arresto scelta dovrà, per essere effettiva, trovarsi all’inizio della riga (a partire da colonna 1). n<&m Il file descriptor n diventa un duplicato del f.d. m; se n manca, il default è 0, cioè lo standard input. n>&m Il file descriptor n diventa un duplicato del f.d. m; se n manca, il default è 1, cioè lo standard output. n<&- il f.d. n è chiuso; se n manca, il default è 0. n>&- il f.d. n è chiuso; se n manca, il default è 1. Note: — Se il comando è lanciato in background, e non è stata precisata alcuna ridirezione, l’ingresso standard è ridiretto su /dev/null. — In tutti i casi, la shell chiama il comando dopo queste modifiche, e il comando non vede mai gli operatori di ridirezione (che sono appunto trattate dalla shell e fatte sparire dalla riga di comando). 5.9 Costruzione di una tavola di argomenti Nell’effettuare il parsing di ciascuna riga di comando, la shell divide quest’ultima in parole, secondo i separatori dati dalla variabile IFS. La prima parola è considerata come il nome di un comando, quelle seguenti come gli argomenti di questo comando. Gli argomenti costituiti da una stringa nulla sono conservati, se quotati tra " o ’, altrimenti sono ignorati. Esempio43: V. Asta - Introduzione a Unix e GNU/Linux 51 $ cat proc #! /bin/sh echo $1 / $# $ ls -l proc -rwxr-xr-x 1 gianni $ $ PAR= $ proc "$PAR" / 1 $ proc $PAR / 0 $ proc ’’ / 1 axis 24 Nov 21 19:06 proc La shell costruisce una tavola di argomenti, dove il nome del comando occupa lo slot 0, ed i suoi argomenti gli slot da 1 a n (ove n è il numero d’argomenti passati al comando, ovvero $#). La shell chiama successivamente il comando e gli fornisce questa tavola ed il numero di slot occupati. 5.10 Esecuzione di un comando La variabile shell PATH contiene più pathname di directory, separati da :, ed è utilizzata per ricercare il comando da eseguire. Se il nome del comando da eseguire contiene uno /, PATH non sarà utilizzato. Altrimenti il file è ricercato in ogni directory specificata. Se è eseguibile, ma non in formato di file binario, si considera che si tratti di uno shell script. Una sotto-shell sarà lanciata per leggerlo. Se però il file contiene una prima riga con questo formato: #! prog si considera che si tratti di uno script da passare all’interprete prog, che verrà lanciato per leggerlo ed eseguirne le istruzioni. prog viene normalmente specificato col suo pathname assoluto, ed è così che si possono specificare shell script destinati ad un particolare tipo di shell, ad esempio #! /bin/sh #! /bin/ksh #! /bin/csh Questa convenzione è anche nota come ’sh-bang notation’, ed è utilizzata anche per altri interpreti, come ad esempio PERL: #! /usr/bin/perl 5.11 Chiamata Se il primo carattere del nome di invocazione del comando ($0) è ‘-’, la shell è una shell di login44; in tal caso, i comandi posti nei file /etc/profile e $HOME/.profile (se esiste) sono letti ed eseguiti. I flag seguenti sono riconosciuti alla chiamata della shell: 43. Per il significato della prima riga della procedura riportata, #! /bin/sh vedi paragrafo 5.10 pag. 52. 44. La shell si trova in questa condizione quando il programma login(1) effettua una chiamata di sistema execve(2) per eseguire il programma specificato nell’ultimo campo della riga di utente del file /etc/passwd; login prepende sempre il carattere ‘-’ all’argv[0] dell’array degli argomenti, cioè al nome di invocazione del programma da eseguire. V. Asta - Introduzione a Unix e GNU/Linux 52 -c string I comandi si trovano in string. -s I comandi sono letti dall’ingresso standard, la shell scrive sul file descriptor 2. -i La shell è una shell interattiva. Il segnale SIGTERM è ignorato, così come SIGINT e SIGQUIT. Stesso comportamento se l’ingresso e l’uscita standard sono assegnati ad un terminale. -r La shell è una shell interattiva ristretta, che impone diverse limitazioni, tra cui: non si può utilizzare cd nè PATH, non è permessa la ridirezione degli ingressi/uscite, etc. 5.12 Ambiente (environment) L’ambiente di un qualsiasi processo UNIX è un elenco di stringhe nome=valore, passato ad ogni processo dal processo padre. Al suo avvio, la shell percorre quest’elenco e crea delle variabili shell (o parametri d’ambiente) dando loro questi nomi e questi valori. I comandi eseguiti nella shell erediteranno questo ambiente. Se, nel corso dell’esecuzione di questa shell, l’utente modifica il valore di questi parametri o ne crea di nuovi, l’ambiente non sarà modificato. Dunque i programmi lanciati non erediteranno i valori modificati. Per riportare queste modifiche nell’ambiente, si utilizza il comando export. La chiamata di un comando semplice può essere prefissata tramite l’assegnazione di parametri, che in tal caso vengono posizionati ed esportati soltanto per il processo in cui si esegue il comando. Esempio: $ TERM=vt120 vi file Chiama l’editor vi per il file file, precisandogli che il terminale utilizzato è un DEC VT-120. Il parametro TERM ha questo valore solo per il processo che esegue vi. 5.13 Segnali Se un comando è lanciato in background, i segnali QUIT e INTERRUPT sono ignorati; altrimenti i valori delle azioni per questi segnali sono ereditati dal processo padre. Un segnale può essere esplicitamente inviato ad un dato processo, utilizzando il comando kill, che prende come argomento un numero di segnale (preceduto dal segno ‘-’) e uno o più numeri di processi (PID) a cui inviare il segnale (vedi il comando shell trap in "Comandi speciali", paragrafo 5.7 pag. 46). Ecco i principali segnali ed il loro significato: nome simbolo numero hangup SIGHUP 1 interrupt SIGINT 2 quit SIQUIT 3 kill term SIGKILL SIGTERM 9 15 note inviato dal kernel alla chiusura di una tty, a tutti i processi del process-group inviato da tastiera col carattere intr (di solito ˆC) inviato da tastiera col carattere intr (di solito ˆ\) non può essere ignorato o soppresso segnale di terminazione software (default per kill(1)) Vedere le chiamate di sistema kill(2) e signal(2) per maggiori dettagli. V. Asta - Introduzione a Unix e GNU/Linux 53 6. Ricapitolativo della shell: comportamento e sintassi Viene qui dato un riassunto globale delle caratteristiche della shell, sia come interprete di comandi che come linguaggio di programmazione; nel Tomo 2, dedicato ad esempi, complementi ed esercitazioni sulla programmazione in shell, vengono poi listate diverse procedure. • Doppio aspetto della shell: interprete di linguaggio di comandi + linguaggio di programmazione • Sintassi generale per ogni riga letta dalla shell: comando argomento argomento ... • Separatori di base tra token: spazio, <tab>, <newline> • Codice di uscita: 0 significa successo, != 0 insuccesso • Nozione di stdin, stdout, stderr, corrispondenti ai canali di I/O con file descriptor (fd) rispettivamente 0, 1, 2 • Operatore ; di esecuzione in serie (sequenziale); operatore & di esecuzione parallela; processi in background • Operatori di sostituzione di filename: *, ?, [abc], [A-Z], [!xyz] • Operatori per parametri di ambiente: operatore = di assegnazione; operatore $ di interpolazione (sostituzione del valore di un parametro). PAR=stringa, LOGNAME=gianni, HOME=/home/gianni, PS1=’$ ’; $PAR, echo $HOME; parametro PATH, PATH=$PATH:$HOME/bin Il comando export consente di esportare un parametro d’ambiente anche agli eventuali sottoprocessi che saranno generati. Il comando set permette di visualizzare tuti i parametri d’ambiente e i loro valori. • Operatori di ridirezione dei canali di I/O: <, >, >> In forma generalizzata: n<file, n>file, n>>file, n>&m "Here scripts": operatore <<string • Operatore | di creazione di un pipe; operatore (...) di esecuzione in una sub-shell; operatore {...} di raggruppamento di comandi • Operatori di quoting: \, ’...’, "..." (" interpreta $, ‘ e \; \ quota $, ‘, \ e " ) • Operatori di sostituzione di comandi: ‘...‘, $(...) Caso particolare: $(<file) equivale a $(cat file) • Parametri d’ambiente posizionati dalla shell: $0, $1, $2, ... $#, $*, $@; $?, $$, $!, $PWD, $OLDPWD • Operatori di AND e OR logico tra comandi: &&, || • Strutture di controllo: — if cmd-list then cmd cmd ... [else cmd cmd ...] fi V. Asta - Introduzione a Unix e GNU/Linux 54 — while cmd-list do cmd cmd ... done — until cmd-list do cmd cmd ... done — for var in word-list do cmd cmd ... done — case param in pattern1) cmd cmd ... ;; pattern2) cmd cmd ... ;; ... esac — break [n] — continue [n] — exit [code] • Comandi e operatori speciali: . : # export wait shift read set trap exec eval umask ulimit let cd • Funzioni shell: func_name() { cmd cmd ... return } V. Asta - Introduzione a Unix e GNU/Linux 55 7. Comandi di uso corrente I comandi qui succintamente presentati sono utilizzati comunemente sia in modo interattivo, da terminale, sia nella programmazione in shell. Il carattere [ precede gli argomenti opzionali. Si consiglia di consultare le pagine di manuale per approfondire almeno il ruolo e l’uso dei flag segnalati per ciascun programma. 7.1 Manipolazione di file e directory • cd [dir cambia directory di lavoro in dir; se non c’è argomento, va in $HOME; se dir vale -, va in $OLDPWD (cioè nella directory precedente, prima dell’ultimo cd). • cp file1 file2 cp file1 file2 ... dir copia file1 in file2, oppure tutti i file specificati, con lo stesso nome, nella directory dir. Flag importanti: -p -a -R • mv file1 file2 mv file1 file2 ... dir muove (rinomina o sposta) file1 in file2, oppure tutti i file specificati, con lo stesso nome, nella directory dir. • rm file [file ... rimuove (elimina) i file specificati. Flag importanti: -r -f -i • ln file1 file2 ln file1 file2 ... dir aggiunge un link (un nuovo nome) da file1 (che di norma deve già esistere) a file2, oppure da tutti i file specificati, con lo stesso nome, alla directory dir. Un link può essere di tipo hard (hardlink, default) oppure soft (o symlink, se si usa il flag -s). Flag importanti: -s -f • mkdir dir [dir ... crea le directory specificate. Flag importanti: -p • rmdir dir [dir ... rimuove le directory specificate, che devono essere vuote (altrimenti, usare rm -r). 7.2 Informazioni su file e directory • pwd scrive a stdout la directory di lavoro corrente. • ls [file_dir file_dir ... scrive a stdout la lista dei file dati in argomento; se si tratta di una directory, scrive la lista di tutti i file in essa contenuti; di default, scrive la lista dei file della directory corrente. Flag importanti: -l -a -A -d -i -s -t -r -1 -x -R • file file [file ... descrive il tipo di ciascuno dei file passati in argomento (la codifica dei tipi di file, detta "magic number", è in /etc/magic). 7.3 Manipolazione delle informazioni su file e directory V. Asta - Introduzione a Unix e GNU/Linux 56 • chmod mode file [file ... modifica i diritti di accesso ai file (o bit di protezione) secondo il modo mode, che può essere specificato in ottale oppure in modo simbolico. • chown user[:group] file [file ... cambia il proprietario (ed eventualmente il gruppo di appartenenza) dei file. • chgrp group file [file ... cambia il gruppo a cui appartengono i file. • touch file [file ... cambia la data di ultima modifica (o di ultimo accesso) al file Flag importanti: -a; -r reffile; -t date 7.4 Visualizzazione del contenuto di file 45. • cat [file file ... − copia i file allo stdout, concatenandoli; se non ci sono argomenti, legge da stdin. Flag importanti: -v -n • pg [file file ... − visualizza i file a schermo, pagina per pagina. presente solo in UNIX System V. Se non ci sono argomenti, legge da stdin. • more [file file ... − visualizza i file a schermo, pagina per pagina; come pg(1), ma più potente e versatile. Se non ci sono argomenti, legge da stdin. • less [file file ... − visualizza i file a schermo, pagina per pagina; come more(1), ma più potente e versatile. Se non ci sono argomenti, legge da stdin. • tail [file copia a stdout le ultime righe (10 di default) del file; se non ci sono argomenti, legge da stdin. Flag importanti45: -N -f • head [file copia a stdout le prime righe (10 di default) del file; se non ci sono argomenti, legge da stdin. Flag importanti: -N • od [file effettua un dump a stdout del contenuto del file; se non ci sono argomenti, legge da stdin; di default, i dati sono interpretati come parole da 16 bit e visualizzati in ottale, ma è possibile ad esempio interpretarli anche come singoli byte e/o stamparli in esadecimale o come caratteri ASCII. Flag importanti: -b -c -d -h • tee [file legge da stdin e scrive i dati letti a stdout e nel file file, se specificato. Utile soprattutto per debugging di pipe. • lpr [file file ... invia il o i file allo spooler di stampa; se non ci sono argomenti, legge da stdin. • lp [file file ... invia il o i file allo spooler di stampa; se non ci sono argomenti, legge da stdin. Come lpr(1), ma versione System V; i flag sono completamente diversi. Nei flag dei programmi introdotti, N indica ovunque un numero. V. Asta - Introduzione a Unix e GNU/Linux 57 7.5 Informazioni sul contenuto di file • wc [file file ... conta i caratteri, le parole e le righe dei file; se non ci sono argomenti, conta leggendo da stdin. Flag importanti: -c -w -l • cmp file1 file2 confronta i due file, byte a byte. Flag importanti: -s -l • diff file1 file2 confronta i due file di testo, riga a riga. Flag importanti: -b -c -e -h 7.6 Manipolazione del contenuto di file • cut [file file ... seleziona porzioni di ciascuna riga dei file, e le scrive a stdout; se non ci sono argomenti, legge da stdin. Flag importanti: -c list; -f list; -d char • grep pattern [file file ... scrive a stdout le righe dei file che sono selezionate dall’espressione regolare (vedi l’Appendice 4) specificata in pattern; se non ci sono argomenti, legge da stdin. Flag importanti: -i -n -l -v Varianti: fgrep (il pattern è una stringa fissa), egrep (il pattern è un’espressione regolare estesa). • sed -e cmd [-e cmd ...] [file] stream editor; legge il file e applica a ciascuna riga i comandi cmd dati come argomento a ciascuna opzione -e; i comandi hanno la stessa sintassi dell’editor di riga ed. Le righe così trattate sono scritte a stdout. Se non ci sono argomenti, legge da stdin. Flag importanti: -n; -f file • awk program [file ... linguaggio di gestione e trattamento di pattern; è un vero linguaggio autocontenuto, con una sintassi specifica; il programma può essere specificato nell’argomento program, perchè spesso si tratta di programmi brevi (una riga o poco più). Applica il programma ai file e stampa il risultato a stdout; se non ci sono argomenti, legge da stdin. Flag importanti: -F char; -f program-file • sort [file ... ordina le righe dei file, in ordine alfanumerico o numerico (sono possibili anche altri modi); se non ci sono argomenti, legge da stdin. Flag importanti: -f; -n; -o file; -r • uniq [file [outfile copia file a stdout (o scrive in outfile se specificato), escludendo le righe adiacenti identiche; se non ci sono argomenti, legge da stdin. Flag importanti: -i; -u; -d; -fN • pr [file file ... formatta file di testo per la stampa; scrive il risultato a stdout, che di solito è inviato in pipe a lpr(1). Se non ci sono argomenti, legge da stdin. Flag importanti: -l; -w; -f; -h header 7.7 Modifica del contenuto di file • ed file editor di testo line-oriented. V. Asta - Introduzione a Unix e GNU/Linux 58 • vi file [file ... editor di testo screen-oriented. 7.8 Comandi di utilità per shell script • true non fa nulla, con successo: termina con exit code pari a 0. • false non fa nulla, con insuccesso: termina con exit code diverso da 0. • echo [ word word ... scrive a stdout i suoi argomenti. Flag importanti: -n-e • test expression valuta l’espressione booleana data in argomento, e termina con exit code pari a 0 se l’espressione è vera, pari a 1 se è falsa, pari a 2 se è mal formata. L’espressione è composta con operatori unari e binari, che permettono di effettuare test su numeri, file, stringhe. Vedi l’Appendice 5. • expr expression valuta l’espressione numerica data in argomento, e ne scrive il valore a stdout. • bc calcolatore a precisione infinita; legge da stdin espressioni matematiche, e ne stampa il valore a stdout. Esce con la parola chiave ‘quit’; permette di gestire fino a 26 variabili (tutte le lettere minuscole). • find path [path ...] expr action cerca file a partire dalle directory specificate in path; i file sono selezionati mediante l’espressione di selezione expr; su ciascun file trovato viene applicata un’azione action (l’azione di default è la scrittura a stdout del nome del file). Vedi l’Appendice 5. • line legge una riga da stdin e la scrive a stdout. • printf format [argument ... scrive a stdout una stringa formattata; lavora esattamente come printf (3). • getopt optstring parameters effettua il parsing delle opzioni di una procedura shell; lavora esattamente come getopt(3). • sleep seconds attende seconds secondi, poi esce. • basename pathname [suffix seleziona e scrive a stdout la sola ultima componente di pathname; se un suffisso suffix è specificato, anche questo viene tolto. • dirname pathname seleziona e scrive a stdout le componenti di un pathname, salvo l’ultima. 7.9 Comandi generici • ps scrive a stdout lo stato dei processi attivi nel sistema; i processi e il formato di presentazione possono essere variamente selezionati con i flag del programma. Flag importanti: -u user; -t tty; -e; -f; -l; -w N.B. − esistono almeno due stili diversi di sintassi: quello di derivazione BSD, e quello di derivazione System V. I flag e il loro significato cambiano a seconda dello stile, per cui è opportuno consultare la pagina di manuale ps(1). Si veda la nota n. 39 pag.33. V. Asta - Introduzione a Unix e GNU/Linux 59 • kill [-sig] pid [pid ... invia il segnale n. sig ai processi specificati tramite i pid; il segnale di default è il n. 15 (SIGTERM). I segnali possono essere specificati tramite il loro numero o tramite il loro simbolo (senza la stringa iniziale SIG); ad esempio, i due comandi qui riportati sono equivalenti: $ kill -1 798 $ kill -HUP 798 $ • who scrive a stdout la lista degli utenti attivi nel sistema. Flag importanti: -u • date scrive a stdout la data e l’ora corrente; può essere anche utilizzato per posizionare la data e l’ora del sistema ad un nuovo valore. • cal [month] [year] scrive a stdout il calendario del mese o dell’intero anno specificato. • tty scrive a stdout il nome della tty di lavoro. • stty permette di consultare o di posizionare i parametri di funzionamento della tty di lavoro. Flag importanti: -a • id scrive a stdout l’identità di utente e di gruppo della sessione di lavoro. Flag importanti: -u -g -G -n • mail [user [user ... Mail User Agent di base; permette di inviare posta elettronica agli utenti specificati, leggendo il messaggio da stdin; senza argomenti, permette di leggere la posta in arrivo e di disporne. Flag importanti: -s subject • write user [tty permette di scrivere messaggi a un altro utente connesso al sistema. • mesg y | n abilita (y) o disabilita (n) la ricezione di messaggi sul terminale da parte di altri utenti. V. Asta - Introduzione a Unix e GNU/Linux 60 8. Gli utenti speciali Il file /etc/passwd contiene un elenco di tutte le persone che hanno un account nel sistema. Viene utilizzato al momento del login, dal comando login(1), oltre al comando crypt(1) e a tutti i comandi che realizzano il mapping UID-nome dell’utente, come ad esempio ls(1) con l’opzione -l. Non ci soffermeremo qui sulla struttura di questo file, ma ci occuperemo di alcuni degli utenti incontrati in /etc/passwd. A titolo di esempio e prima di passare a considerare questi utenti detti speciali ecco un esempio di file /etc/passwd: root:YcxCJXNuc.F5g:0:0:root:/root:/bin/bash bin:ScI25kc9qOiO2:1:1:bin:/bin: daemon:*:2:2:daemon:/sbin: adm:*:3:4:adm:/var/adm: lp:*:4:7:lp:/var/spool/lpd: sync:*:5:0:sync:/sbin:/bin/sync shutdown:*:6:0:shutdown:/sbin:/sbin/shutdown halt:*:7:0:halt:/sbin:/sbin/halt mail:*:8:12:mail:/var/spool/mail: news:*:9:13:news:/usr/lib/news: uucp:*:10:14:uucp:/var/spool/uucppublic: operator:*:11:0:operator:/root:/bin/bash games:*:12:100:games:/usr/games: man:*:13:15:man:/usr/man: postmaster:*:14:12:postmaster:/var/spool/mail:/bin/bash nobody:*:65534:100:nobody:/dev/null: ftp:*:404:1::/home/ftp:/bin/bash guest:*:405:100:guest:/dev/null:/dev/null marion:OaGKskj1Q1By2:243:100:Marion Rossi:/home/marion:/bin/ksh gianni:X0Al4t9f4aWCA:209:101:Gianni Bianchi:/home/gianni:/bin/bash luca:0Gu/rjtYBjkwk:209:101:Luca Verdi:/home/luca:/bin/bash 8.1 root Chiamato anche superuser, root è un identificativo di utente utilizzato in genere dall’amministratore del sistema per svolgere delle mansioni di maintenance. Il suo user-ID (UID) è sempre pari a 0. Infatti la regola è la seguente: Nessuno dei meccanismi di protezione attivati dal kernel, viene applicato se lo user-ID effettivo del processo attivo è uguale a zero. Questi meccanismi riguardano la protezione dei file, la trasmissione dei segnali, delle restrizioni su alcune chiamate di sistema (mknod(2), chown(2) etc.) e, con System V, le protezioni per l’accesso e la manipolazione dei meccanismi d’IPC46 (UNIX è probabilmente uno dei pochi sistemi in cui è possibile distruggere tutto il sistema di file, ovviamente senza ricorrere alla formattazione del disco, con un solo comando e senza nemmeno un warning). Questo approccio ha il merito di essere particolarmente semplice e coerente, tuttavia conviene usare la massima prudenza, quando si deve lavorare come superuser. In pratica questo stato deve essere utilizzato il meno possibile, e solo per motivi precisi. In genere, ogni riga di /etc/passwd definisce un utente e l’UID di ogni utente è unico; tuttavia, in alcuni casi, è possibile creare più utenti aventi lo stesso UID, ad esempio se l’ultimo campo è diverso per questi utenti. La home directory di root è normalmente / o /root. 8.2 bin L’utente bin è un altro identificativo usato spesso dall’amministratore del sistema; dal punto di vista del kernel, si tratta di un utente qualunque, ma bin è il proprietario di un gran numero di files dell’arborescenza di base. È così spesso possibile, oltre che consigliabile, effettuare delle manipolazioni su alcuni file di 46. Inter Process Communication: code di messaggi, semafori, memoria condivisa. V. Asta - Introduzione a Unix e GNU/Linux 61 sistema senza essere root, diventando bin (grazie al comando su(1)). 8.3 daemon Lo pseudo utente daemon è di norma il possessore delle directory di spool e dei file che gli sono associati, anche se ciò varia a seconda dei sistemi. In generale uno pseudo utente come daemon è utile ogni volta che deve essere garantito l’accesso indiretto a dei file. È sufficiente quindi dare questi file al daemon, di garantirgli l’accesso esclusivo, ed in fine posizionare il bit "set effective user-ID", per gli eseguibili che manipolano i file. Ma non bisogna dimenticare in questo caso che nessuno deve potersi loggare come il "daemon" in questione. Esistono vari metodi per garantire che un ingresso di /etc/passwd non sarà utilizzato da login(1), come ad esempio utilizzare il programma /bin/false come ultimo campo della riga in questione. 8.4 Gli altri utenti speciali Esistono numerosi altri utenti speciali, quali adm, sys, lp, uucp etc.; in genere questi utenti esistono per dare la proprietà di alcuni file a specifici applicativi. Ad esempio, lo special file /dev/lp è di proprietà dello pseudo-user lp, ed ha bit di protezione pari a 0600, cioè sono il proprietario può leggervi e scrivervi; lo spooler di stampante è un programma che gira con i diritti dell’utente lp. Ciò fa sì che nessun utente normale possa scrivere direttamente verso la periferica /dev/lp, cortocircuitando lo spooler, ma debba invece passare per i programmi preposti. Su più sistemi è poi possibile incontrare degli pseudo-utenti associati ad un programma di login specifico e non necessariamente interattivo, ciò che permette di lanciare dei comandi come sync(1), who(1), shutdown(8), halt(8) senza essere loggati. V. Asta - Introduzione a Unix e GNU/Linux 62 9. I file speciali 9.1 Cosa è un file speciale I file UNIX sono essenzialmente di tre tipi: — i file normali — le directory — i file speciali (special files) Così come i file normali associano un inode ed un nome ad una serie di byte in memoria secondaria (vale a dire su disco), i file speciali associano un inode ed un nome ad una periferica47. I file speciali sono gestiti dalle stesse primitive dei files normali (open(2), read(2), write(2), close(2)...). Viene utilizzato lo stesso meccanismo di protezione. Infatti, sono presenti tutte le caratteristiche di un file associate ad un inode ad eccezione degli indirizzi dei blocchi di dati contenuti nell’inode di un file normale. Al posto degli indirizzi dei blocchi di dati si trovano due numeri che individuano il device associato ad un file, cioè al suo contenuto virtuale. Questi due numeri sono chiamati rispettivamente major device number e minor device number48, ed occupano l’inizio dell’area destinata agli indirizzi dei blocchi di dati. Così come il nome di un file normale non fornisce delle specifiche sul suo contenuto, il nome di un file speciale non garantisce nulla sulla specifica periferica fisica a cui è associato. Solo l’esame del MDN e del mdn permette di assicurarsene. Ogni periferica può essere associata ad uno o più file speciali; ciò sia per il meccanismo dei link, sia perchè più file speciali con nomi diversi possono possedere degli MDN e mdn identici. Se il MDN di più file è indentico, ma il mdn è diverso, si tratta, come si vedrà, di più unità di uno stesso device, o di diversi modi di utilizzare uno stesso device. Il MDN determina il device driver che sarà utilizzato per accedere al device. Il mdn è un argomento passato tale e quale al driver selezionato dal MDN, e dunque il suo significato sarà totalmente determinato da questo. Normalmente, il mdn permette di selezionare una delle unità gestite da uno stesso controllore. Se necessario, il mdn sarà diviso in campi49 che permettono di codificare altre suddivisioni o degli attributi propri di un device particolare. Tuttavia dato che il senso del mdn e del MDN dipendono interamente da ciascun sistema UNIX generato, è possibile trovare dei sistemi che presentano casi particolari. (Ad esempio, nulla impedisce allo stesso MDN di riferire due tipi di periferiche fondamentalmente diverse, essendo queste distinte solo dal loro mdn). I file speciali sono raggruppati nella directory /dev ed i loro nomi sono in genere abbastanza stabili da un sistema ad un altro. Questa localizzazione dei file speciali non è obbligatoria, si tratta di una convenzione che permette una gestione coerente ed efficace dei device. Ci sono egualmente delle convenzioni che riguardano l’organizzazione di /dev, e la scelta dei nomi da associare ai diversi tipi di device. I file speciali si ividono in due classi distinte: — i file speciali in modo carattere50 — i file speciali in modo blocco51 Dunque si tratta essenzialmente: 47. I termini "device" e "periferica" saranno utilizzati indifferentemente, per evitare se necessario, l’impiego di locuzioni come: "handler di periferiche", peraltro insolite in un contesto UNIX. 48. Numero di periferica maggiore e minore. Si indicheranno con MDN e mdn. 49. Nel senso di campi di bit 50. Le altre denominazioni possibili comprendono: raw, non-strutturata. 51. O ancora, strutturati. V. Asta - Introduzione a Unix e GNU/Linux 63 — per i file speciali in modo carattere, di una interfaccia non strutturata (ciò che purtroppo non è indicato dal loro nome), vale a dire che i dati passano direttamente dalla periferica al processo utente52, o viceversa, senza transitare attraverso la memoria di cache del kernel. — Per i file speciali in modo blocco, invece, di una interfaccia strutturata, vale a dire che la richiesta dell’utente è filtrata dal kernel, che assicura un servizio di cache per tutti i dati scambiati con i device. La classe di appartenenza di uno special file è specificata nell’inode del file53. Sarebbe così più giusto dire che i file UNIX sono di quattro tipi base: — i file normali — le directories — i file speciali in modo carattere — i file speciali in modo blocco I file speciali sono creati dal comando mknod(1)54 che prende in argomento il nome del file, la sua classe (blocco o carattere), il suo mdn ed il suo MDN. Il comando ls permette di conoscere il tipo di un file, e per i file speciali i loro mdn e MDN. Il tipo di un file è indicato dal primo carattere del campo mode dell’output del comando ls -l. I MDN e mdn di un file speciale sostituiscono il campo size. $ cd $ cd tmp $ ls -l total 4 -rw-r----- 1 marion axis ˆ file normale -rw-r----- 1 marion axis drwxr-x--- 2 marion axis ˆ directory 144 Jul 23 6456 Jul 23 32 Jul 23 14:01 bar 14:01 foo 14:01 the_dir 52. Alla zona di memoria dello spazio dati di un programma in modo utente puntato dal secondo argomento di un read(2) o di un write(2) 53. Vedi il paragrafo paragrafo 3.6.2 pag. 33 sull’organizzazione del filesystem 54. Il comando mknod è la versione "shell" della chiamata di sistema mknod(2) V. Asta - Introduzione a Unix e GNU/Linux 64 $ ls -lsi total 16 4001 1 -rw-r--- 1 ˆ il campo 4064 14 -rw-r-- 1 4065 1 drwxr-x- 2 marion axis 144 mode inizia qui marion axis 6456 marion axis 32 Jul 23 14:01 bar Jul 23 14:01 foo Jul 23 14:01 the_dir $ cd /dev $ ls -l root brw------- 2 deamon sys ˆ file speciale in modo blocco 0, 0 Jul 23 09:32 root $ ls -l tty crw-rw-rw- 1 deamon sys 2, MDNˆ 0 Jul 23 ˆmdn 14:10 tty $ ls -lsi *rmt0 203 0 crw-rw---- 2 deamon axis 5, 4 Aug 14 2000 nrmt0 ˆfile speciale in modo carattere 202 0 crw-rw---- 2 deamon axis 5, 0 Jul 22 14:20 rmt0 ˆsize in blocchi, zero per un file speciale ˆ i-number E per la creazione: # cd /dev # mknod mem c 3 0 ˆ la chiamata di sistema mknod(2) è riservata al superuser, ed il comando /etc/mknod non è set-user-id root! Ciò in realtà non è totalmente esatto; qualunque processo infatti può invocare mknod(2), per creare un named pipe (ma non per creare uno special file). # mknod mt0 b 1 0 ˆil nome del file # mknod nmt0 b 1 4 ˆil modo (blocco o carattere), qui blocco # mknod nrmt0 c 5 4 ˆ ˆ MDN e mdn espressi in decimale # ls -l [nm]* crw-r----- 1 brw-r----- 1 brw-r----- 1 crw-r----- 1 root root root root sys sys sys sys V. Asta - Introduzione a Unix e GNU/Linux 3, 1, 1, 5, ˆ 0 0 4 4 ˆ Jul 23 14:47 mem Jul 23 14:48 mt0 Jul 23 14:48 nmt0 Jul 23 14:48 nrmt0 anche ls dà il MDN ed il mdn in decimale. 65 9.2 Device e pseudo-device Uno special file può anche riferirsi ad una periferica fittizia, alla quale non corrisponde un’entità fisica; in tal caso lo special file viene comunemente detto uno pseudo-device. Un esempio evidente è /dev/null, che rappresenta una periferica con le seguenti proprietà: — ogni scrittura verso /dev/null provoca l’eliminazione dei dati — ogni lettura da /dev/null ritorna con 0 byte letti (condizione di end-of-file). La distinzione fatta tra device e pseudo-device non è netta come quella definita tra device in modo carattere ed in modo blocco. Non si tratta di una differenza strutturale chiaramente identificabile (un campo di bit in un campo mode di inode), ma di una differenza concettuale, tra device associati a una unità fisica reale e devices associate ad una periferica virtuale. Ad esempio, a condizione di non considerare la memoria come una periferica reale, un driver che implementa un disco virtuale, è uno pseudo-device. La nozione di periferica virtuale non è sempre chiara; se la memoria può essere considerata come una periferica (si tratta comunque di un dispositivo materiale), un device che implementa un meccanismo di semafori non può essere visto come una periferica. È possibile anche considerare uno pseudo-device semplicemente come un servizio di kernel, a cui si accede da un file piuttosto che da una chiamata di sistema. Esiste un certo numero di pseudo-device standard, presenti in tutte le versioni di UNIX e sempre con lo stesso nome; tra di essi ricordiamo: — /dev/null — /dev/full (ogni scrittura fallisce; ogni lettura ritorna byte nulli) — /dev/zero (ogni scrittura elimina i dati; ogni lettura ritorna byte nulli) — /dev/mem, /dev/kmem (accesso alla memoria fisica e alla memoria vista nello spazio di indirizzamento virtuale del kernel) — /dev/tty (si riferisce al terminale di controllo del processo attivo) V. Asta - Introduzione a Unix e GNU/Linux 66 10. Organizzazione dei filesystems di UNIX 10.1 Il sistema di file Questo paragrafo illustra l’implementazione "standard" di un filesystem UNIX di tipo AT&T (Version 7, System V; si veda il classico articolo di Ken Thompson [1978]). Le versioni odierne di UNIX, incluso LINUX ovviamente, generalmente utilizzano implementazioni che sono tutte derivate da questa, con l’introduzione di varie migliorìe. Peraltro, gli esempi di codice qui presentati sono tutti presi da un sistema LINUX. Nel sistema UNIX un file è un array (unidimensionale) di bytes. Nessun’altra organizzazione, o tipo, di file è implicata dal Sistema Operativo. Ma questo semplice modello permette l’aggiunta di una qualunque struttura di dati a livello utente. Vedere, per esempio, /usr/include/ar.h, che descrive l’organizzazione di un file di libreria (archivio) di UNIX, $ cat /usr/include/ar.h ... /* Archive files start with the ARMAG identifying string. Then follows a ‘struct ar_hdr’, and as many bytes of member file data as its ‘ar_size’ member indicates, for each member file. */ #define ARMAG "!<arch>\n" #define SARMAG 8 #define ARFMAG "‘\n" /* String that begins an archive file. /* Size of that string. */ */ /* String in ar_fmag at end of each header. */ __BEGIN_DECLS struct ar_hdr { char ar_name[16]; /* Member file name, sometimes / terminated. */ char ar_date[12]; /* File date, decimal seconds since Epoch. */ char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */ char ar_mode[8]; /* File mode, in ASCII octal. */ char ar_size[10]; /* File size, in ASCII decimal. */ char ar_fmag[2]; /* Always contains ARFMAG. */ }; ... o ancora /usr/include/elf.h, per una struttura manipolata anche dal kernel stesso (si tratta dell’organizzazione dei dati in un file binario eseguibile). I files sono connessi in un qualunque punto (ed eventualmente più volte) su una gerarchia di directories. Le directories, dal punto di vista del filesystem e del sistema, sono semplicemente dei files che gli utenti non possono scrivere. Nessun utente (nemmeno root) può scrivere direttamente in una directory; peraltro i meccanismi di protezione sono trattati in modo normale quando una scrittura è fatta tramite chiamate sistema. Il file sistem UNIX è costituito da una struttura di dati su disco55 a cui si accede esclusivamente attraverso il sistema d’I/O in modo blocco, cioè attraverso il passaggio sistematico per la cache di sistema. Un volume è quindi sempre montato utilizzando l’interfaccia strutturata (cioè appunto quella in modo blocco). Il modo canonico di vedere un disco è come un array di blocchi da N bytes indirizzabili aleatoriamente, ove N vale di solito 512 o 102456. 55. Ovunque nel testo, con il termine "disco" si intende un’unità logica (disco logico), così come è vista dal Sistema Operativo; questa di solito non coincide con l’intero disco fisico, che è di norma diviso in più partizioni, ciascuna delle quali costituisce un disco logico. 56. LINUX, come altre versioni recenti di UNIX, utilizza anche i valori 2048 e 4096. V. Asta - Introduzione a Unix e GNU/Linux 67 Un file system viene creato tramite il comando mkfs(8); questa operazione organizza il disco in 4 regioni autoidentificate. La prima regione (blocco numero 0) non è utilizzata dal file system. Questo blocco è messo da parte per le procedure di bootstrap. La seconda regione (blocco numero 1) contiene il "superblock". Questo blocco contiene una struttura di dati con tutto ciò che serve per gestire un filesystem; tra le altre cose, contiene la lunghezza del filesystem stesso e le frontiere delle altre regioni57. La struttura del superblock è descritta nel file /usr/include/sys/filsys.h (in LINUX, per un filesystem di tipo System V: /usr/include/linux/sysv_fs.h). $ cat /usr/include/linux/sysv_fs.h ... #define SYSV_NICINOD #define SYSV_NICFREE 100 50 /* number of inode cache entries */ /* number of free block list chunk entries */ /* SystemV4 super-block data on disk */ struct sysv4_super_block { u16 s_isize; /* index of first data zone */ u16 s_pad0; u32 s_fsize; /* total number of zones of this fs */ /* the start of the free block list: */ u16 s_nfree; /* number of free blocks in s_free, <= SYSV_NICFREE */ u16 s_pad1; u32 s_free[SYSV_NICFREE]; /* first free block list chunk */ /* the cache of free inodes: */ u16 s_ninode; /* number of free inodes in s_inode, <= SYSV_NICINOD */ u16 s_pad2; sysv_ino_t s_inode[SYSV_NICINOD]; /* some free inodes */ /* locks: */ char s_flock; /* lock during free block list manipulation */ char s_ilock; /* lock during inode cache manipulation */ char s_fmod; /* super-block modified flag */ char s_ronly; /* flag whether fs is mounted read-only */ u32 s_time; /* time of last super block update */ s16 s_dinfo[4]; /* device information ?? */ u32 s_tfree; /* total number of free zones */ u16 s_tinode; /* total number of free inodes */ u16 s_pad3; char s_fname[6]; /* file system volume name */ char s_fpack[6]; /* file system pack name */ s32 s_fill[12]; s32 s_state; /* file system state: 0x7c269d38-s_time means clean */ s32 s_magic; /* version of file system */ s32 s_type; /* type of file system: 1 for 512 byte blocks 2 for 1024 byte blocks */ }; ... #define FSTYPE_XENIX 1 #define FSTYPE_SYSV4 2 #define FSTYPE_SYSV2 3 #define FSTYPE_COH 4 #define SYSV_MAGIC_BASE 0x012FF7B3 #define SYSV4_SUPER_MAGIC (SYSV_MAGIC_BASE+FSTYPE_SYSV4) ... 57. Poichè l’integrità del superblock è critica per l’intero filesystem (nel senso che se viene corrotto il superblock, c’è rischio di perdere il contenuto di tutti i files), molte implementazioni più recenti, incluso il classico filesystem ext2 di LINUX, mantengono copie del superblock in vari altri blocchi sparsi nel disco. V. Asta - Introduzione a Unix e GNU/Linux 68 I vari pseudo-tipi utilizzati in questa struttura, così come in altre strutture che avremo occasione di esaminare più avanti, sono definiti nel file /usr/include/sys/types.h (e in altri files da questo inclusi). $ cat /usr/include/sys/types.h ... /* * These aren’t exported outside the kernel to avoid name space clashes */ #ifdef __KERNEL__ typedef signed char s8; typedef unsigned char u8; typedef signed short s16; typedef unsigned short u16; typedef signed int s32; typedef unsigned int u32; typedef signed long long s64; typedef unsigned long long u64; #define BITS_PER_LONG 32 #endif /* __KERNEL__ */ ... /* inode numbers are 16 bit */ typedef u16 sysv_ino_t; ... La terza regione è denominata i-list, e contiene una lista di strutture di controllo di files. Ognuna di queste è una struttura di 64 bytes, chiamata i-node. $ cat /usr/include/linux/sysv_fs.h ... /* System V inode data on disk */ struct sysv_inode { u16 i_mode; /* file type, set-UID etc., and protection bits */ u16 i_nlink; /* link count */ u16 i_uid; /* numeric UID */ u16 i_gid; /* numeric GID */ u32 i_size; /* file size, in bytes */ union { /* directories, regular files, ... */ unsigned char i_addb[3*(10+1+1+1)+1]; /* zone numbers: max. 10 data blocks, * then 1 indirection block, * then 1 double indirection block, * then 1 triple indirection block. * Then maybe a "file generation number" ?? */ /* devices: major + minor device number */ dev_t i_rdev; } i_a; u32 i_atime; /* time of last access */ u32 i_mtime; /* time of last modification */ u32 i_ctime; /* time of creation */ }; ... L’offset di un i-node particolare all’interno della i-list è chiamato il suo i-number. La combinazione del V. Asta - Introduzione a Unix e GNU/Linux 69 nome del device (numero maggiore e numero minore) e dello i-number serve a identificare univocamente un file. Dopo la i-list e fino alla fine del disco, viene la quarta regione, che comprende i blocchi di allocazione libera che sono disponibili per il contenuto dei files. Qualora la dimensione totale del filesystem (specificata in argomento a mkfs(8)) sia inferiore alla dimensione totale del disco, può infine esistere una quinta regione, che si estende dalla fine del filesystem alla fine del disco; tale regione può essere utilizzata come spazio di swap del sistema58. In altre parole, un primo sguardo su un filesystem mostra una struttura molto semplice e piatta: una sola grande directory di lunghezza fissa contenente descrittori di files. Per chiamare un file in questo contesto si potrebbe dire: disco (modo blocco) IDE, unità 1, volume 2, file 354... Lo spazio libero su disco è mantenuto con una lista collegata dei blocchi disco disponibili. Ogni blocco di questa catena contiene l’indirizzo disco del blocco seguente nella catena. Il rimanente spazio contiene l’indirizzo di un massimo di 50 (parametro SYSV_NICFREE) blocchi disco che sono anch’essi liberi. La testa di questa lista si trova nel superblock, come pure un contatore che indica il numero di blocchi liberi restanti, tra i SYSV_NICFREE possibili. Vedere la struct sysv4_super_block. La tabella s_free contiene fino a 49 numeri di blocchi liberi, da s_free[1] a s_free[s_nfree-1]. s_free[0] è il numero del blocco che è in testa alla catena dei blocchi costituenti la free list. Così con una operazione di I/O, il sistema ottiene 50 blocchi liberi ed un puntatore per trovarne ancora. L’algoritmo di allocazione del disco è molto semplice. Per allocare un blocco: Decrementare s_nfree, ed il nuovo blocco è s_free[s_nfree]. Se il numero del nuovo blocco è 0 non vi sono più blocchi disponibili. Se s_nfree diventa uguale a 0, leggere il blocco designato col numero del nuovo blocco, rimpiazzare s_nfree con la prima parola di questo blocco e copiare i numeri di blocco nei 50 elementi della tabella s_free. Per liberare un blocco: Se s_nfree è uguale a 50 (SYSV_NICFREE), copiare s_nfree e la tabella s_free in questo blocco, scriverla, e mettere s_nfree a 0. In tutti i casi mettere in s_free[s_nfree] il numero del blocco liberato ed incrementare s_nfree. Dato che tutte le allocazioni sono in blocchi di lunghezza fissa e che viene effettuato un calcolo rigoroso dello spazio, non è necessario compattare o fare garbage-collecting. Tuttavia mano mano che lo spazio su disco si disperde (creando, modificando e rimuovendo files), i tempi di accesso aumentano gradualmente, e si assiste ad un fenomeno di degradazione a lungo termine delle prestazioni del filesystem. Per ovviare a questo problema, può essere opportuno ricompattare di tanto in tanto lo spazio su disco, per ridurre i tempi di risposta. Su System V esiste un comando che permette di ricopiare un filesystem riorganizzandolo. In questo caso i blocchi di dati dei files non sono semplicemente allocati consecutivamente, ma distribuiti in modo ottimale sul disco, e sono inoltre possibili varie altre ottimizzazioni. Si tratta del comando dcopy(8). Se questo comando non esiste, un backup del filesystem per mezzo di tar(1) (seguito da un restore su di un filesystem riinizializzato) rigenera i files con blocchi allocati consecutivamente, e ciò avrà lo stesso effetto di dcopy(8) se il filesystem è stato rigenerato precisando la dimensione del gap di rotazione, dato che la free list è creata tenendo conto di questo gap. Un i-node contiene 13 indirizzi di blocchi disco. La dichiarazione di i_addb come un array di 40 caratteri, piuttosto che di 13 daddr_t (equivalenti ad unsigned long) è necessaria per ottenere una lunghezza pari a 64 (8 o 16 i-nodes per ogni blocco) per la struttura sysv_inode. Infatti gli indirizzi disco sono limitati ad una lunghezza reale di 24 bits e i_addb contiene 13*3 bytes, che saranno convertiti in interi a 32 bit prima di usarli. Esistono funzioni di libreria per realizzare questa conversione (l3tol, ltol3, vedi l3tol(3C)). I 10 primi indirizzi puntano direttamente verso i primi 10 blocchi di un file. Se un file è più grande di 10 58. Più comunemente, alla zona di swap si dedica un’intera partizione del disco. V. Asta - Introduzione a Unix e GNU/Linux 70 blocchi (5.120 bytes, con blocchi da 512 byte), l’undicesimo indirizzo punta su un blocco che contiene gli indirizzi dei 128 blocchi successivi del file. Se il file è ancora più grande di (10+128)*512 = 70.656 bytes, il dodicesimo indirizzo punta ancora su 128 blocchi, ognuno dei quali punta su 128 blocchi di questo file. Per files ancora più grandi, oltre (10+128+1282)*512 = 16522*512 = 8.459.264 bytes, il tredicesimo indirizzo è utilizzato per realizzare analogamente una tripla indirezione. L’algoritmo si ferma qui, con una lunghezza massima di file pari a (10+128+1282+1283)*512 = 2.113.674*512 = 1.082.201.088 bytes (il doppio, cioè circa 2 Gbytes, con blocchi da 1024 byte; circa 8 Gbytes per file con blocchi da 4 kbytes). Una gerarchia logica ad albero è aggiunta a questa struttura fisica piatta, aggiungendo semplicemente un nuovo tipo di file: la directory. L’accesso ad una directory è esattamente come quello di un file ordinario. Essa contiene degli slots di 16 bytes, costituiti da un nome di 14 caratteri qualsiasi59 e da un i-number. Si rammenta ancora una volta (vedi anche nota 33 nel paragrafo 3.2 pag. 28) che dal punto di vista del kernel non vi è distinzione alcuna tra caratteri per il nome di un file, che possono essere di qualunque tipo, compresi caratteri di controllo e/o non stampabili, salvo il carattere `/’ (che indica il passaggio ad una componente successiva in un pathname) e ’\0’ (che termina le stringhe di caratteri). Il carattere `.’ non ha nessun significato particolare, non esistendo nel kernel il concetto di "estensione di un nome di file", salvo a livello applicativo. $ cat /usr/include/linux/sysv_fs.h ... /* Among the inodes ... */ /* 0 is non-existent */ #define SYSV_ROOT_INO 2 /* inode of root directory */ /* SystemV directory entry on disk */ #define SYSV_NAMELEN 14 /* max size of name in struct sysv_dir_entry */ struct sysv_dir_entry { sysv_ino_t inode; char name[SYSV_NAMELEN]; /* up to 14 characters, the rest are zeroes */ }; ... La radice di una gerarchia si trova in un i-node noto (lo i-node numero 2). Com’è facile capire, l’i-number identifica univocamente un i-node (almeno all’interno di un dato filesystem), e quindi un file. È perfettamente legale avere più slots di una directory, ovvero slots di diverse directories, che contengono lo stesso i-number, e che rappresentano quindi più punti di accesso (detti link) a uno stesso file; ciascun file può pertanto presentarsi con diversi nomi, e/o sotto diverse directories. Il numero di link di un file (link count) è memorizzato nell’i-node (campo i_nlink); l’aggiunta di un nuovo link viene effettuata mediante la chiamata sistema link(2), ovvero con il comando ln(1); la rimozione, mediante la chiamata unlink(2) o con il comando rm(1). La chiamata unlink(2), nel caso generale, non fa che decrementare il link count e rimuovere lo slot della directory; solo nel caso particolare in cui il link count, decrementato, scenda a 0 (ultimo link per quel file), il sistema provvede anche a rimuovere effettivamente il file, deallocando l’i-node e i blocchi di dati. Si accenna, infine, che il programma ls(1) permette di visionare il valore del link count, con l’opzione -l (è il secondo campo visualizzato, dopi i bit di protezione), e l’i-number, con l’opzione -i. 59. Il limite di 14 caratteri è stato tipicamente portato a 256 nelle versioni attuali di UNIX, incluso LINUX. V. Asta - Introduzione a Unix e GNU/Linux 71 $ ls -l *.ps 1 gianni -rw-r--r-- axis 51718 Nov 7 19:21 FileSystem.ps $ ls -i *.ps 75763 FileSystem.ps $ ls -li *.ps 75763 -rw-r--r-1 gianni axis 51718 Nov 7 19:21 FileSystem.ps La struttura del filesystem permette, in linea di massima, la formazione di un grafo arbitrario di directories con i files regolari collegati a punti qualsiasi di questo grafo. In effetti i primi sistemi UNIX utilizzavano una struttura di questo tipo, ma l’amministrarla divenne così caotico che i sistemi seguenti furono limitati ad una arborescenza di directories. È sempre possibile per root, tramite le chiamate sistema link(2) e unlink(2), manipolare un filesystem come un grafo qualsiasi, creando link anche per files di tipo directory (questa è appunto un’operazione consentita solo al superuser). System V possiede persino i comandi link(8) e unlink(8), riservati al superuser, che offrono una interfaccia diretta con le chiamate sistema direttamente dalla shell. Tuttavia si tratta di manipolazioni molto pericolose le cui implicazioni non sono spesso ovvie, dato che i comandi di amministrazione del filesystem manipolano un albero piuttosto che un grafo. Il filesystem consente una creazione facile, una soppressione facile, un accesso aleatorio facilitato ed una allocazione dello spazio molto facile. Con la maggior parte degli indirizzi fisici confinati in una piccola parte contigua del disco, è altrettanto facile fare il dump, il restore, e verificare la coerenza del filesystem. I grossi files sono penalizzati dall’indirizzamento indiretto, ma la memoria di cache evita la maggior parte degli I/O fisici che ne derivano senza aggiungere troppa esecuzione di codice. Le caratteristiche di questo modello, in termini di proporzione di occupazione dello spazio, sono piuttosto buone. Ken Thompson, nell’articolo citato in apertura, fa questo esempio: "In un dato filesystem, ci sono 25000 files che contengono 130M bytes di dati. Lo spazio extra occupato (i-node, blocchi di indirezione, e complemento sull’ultimo blocco) è circa 11.5M bytes. La struttura di directories che supportano questi files comprende circa 1500 directories contenenti 0.6M bytes di dati e 0.5M bytes per l’accesso alle directories. In totale si arriva a meno del 10% di rapporto con i dati realmente memorizzati. Molti sistemi tradizionali hanno tutto questo overhead semplicemente per gli spazi di padding delle righe." 10.2 Implementazione del filesystem Dato che l’i-node definisce un file, l’implementazione del filesystem è centrato intorno all’accesso all’inode. Il sistema gestisce una tavola di tutti gli i-nodes attivi. Gli i-nodes contenuti in questa tavola (quindi in memoria) hanno una struttura diversa da quella degli inodes descritti fino ad ora, che erano quelli che si possono trovare su un filesystem, considerato da un punto di vista statico. La dichiarazione di un i-node in memoria si trova nel file /usr/include/sys/inode.h (in LINUX, in /usr/include/linux/fs.h): $ cat /usr/include/linux/fs.h ... /* * This */ #define #define #define #define #define is the inode attributes flag definitions ATTR_FLAG_SYNCRONOUS ATTR_FLAG_NOATIME ATTR_FLAG_APPEND ATTR_FLAG_IMMUTABLE ATTR_FLAG_NODIRATIME 1 2 4 8 16 /* Syncronous write */ /* Don’t update atime */ /* Append-only file */ /* Immutable file */ /* Don’t update atime for directory */ struct inode { V. Asta - Introduzione a Unix e GNU/Linux 72 struct list_head struct list_head struct list_head unsigned long unsigned int kdev_t umode_t i_hash; i_list; i_dentry; i_ino; i_count; i_dev; i_mode; /* linked lists */ /* i-number */ /* reference count */ /* device where inode lives */ /* file type + setUID etc. + protection bits */ /* nr. of hard links */ /* numeric UID */ /* numeric GID */ /* device, if special file */ /* file size */ /* time stamps */ nlink_t i_nlink; uid_t i_uid; gid_t i_gid; kdev_t i_rdev; off_t i_size; time_t i_atime; time_t i_mtime; time_t i_ctime; unsigned long i_blksize; /* block size for file system I/O */ unsigned long i_blocks; /* number of blocks allocated */ unsigned long i_version; /* Dcache version management */ unsigned long i_nrpages; struct semaphore i_sem; /* access control */ struct semaphore i_atomic_write; struct inode_operations *i_op; /* pointers to functions */ struct super_block *i_sb; struct wait_queue *i_wait; /* wait queue */ struct file_lock *i_flock; struct vm_area_struct *i_mmap; /* memory areas */ struct page *i_pages; struct dquot *i_dquot[MAXQUOTAS]; unsigned unsigned unsigned unsigned long int char char int unsigned int __u32 union { struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct struct void } u; i_state; i_flags; i_pipe; i_sock; /* state - see below */ i_writecount; /* number authorized to write */ i_attr_flags; /* attr. flags - see before */ i_generation; /* file-system specific information */ pipe_inode_info pipe_i; minix_inode_info minix_i; ext2_inode_info ext2_i; hpfs_inode_info hpfs_i; ntfs_inode_info ntfs_i; msdos_inode_info msdos_i; umsdos_inode_info umsdos_i; iso_inode_info isofs_i; nfs_inode_info nfs_i; sysv_inode_info sysv_i; affs_inode_info affs_i; ufs_inode_info ufs_i; efs_inode_info efs_i; romfs_inode_info romfs_i; coda_inode_info coda_i; smb_inode_info smbfs_i; hfs_inode_info hfs_i; adfs_inode_info adfs_i; qnx4_inode_info qnx4_i; socket socket_i; *generic_ip; }; /* Inode state bits.. */ V. Asta - Introduzione a Unix e GNU/Linux 73 #define I_DIRTY #define I_LOCK #define I_FREEING ... 1 2 4 $ cat /usr/include/linux/stat.h ... /* Inode mode bits... */ #define S_IFMT 00170000 #define S_IFSOCK 0140000 #define S_IFLNK 0120000 #define S_IFREG 0100000 #define S_IFBLK 0060000 #define S_IFDIR 0040000 #define S_IFCHR 0020000 #define S_IFIFO 0010000 #define S_ISUID 0004000 #define S_ISGID 0002000 #define S_ISVTX 0001000 /* type of file */ /* socket */ /* symbolic link */ /* regular file */ /* block special */ /* directory */ /* character special */ /* named pipe - FIFO */ /* set-UID bit */ /* set-GID bit */ /* save swapped text even after use - sticky bit */ #define #define #define #define #define #define #define S_ISLNK(m) S_ISREG(m) S_ISDIR(m) S_ISCHR(m) S_ISBLK(m) S_ISFIFO(m) S_ISSOCK(m) (((m) (((m) (((m) (((m) (((m) (((m) (((m) & & & & & & & S_IFMT) S_IFMT) S_IFMT) S_IFMT) S_IFMT) S_IFMT) S_IFMT) == == == == == == == S_IFLNK) S_IFREG) S_IFDIR) S_IFCHR) S_IFBLK) S_IFIFO) S_IFSOCK) #define #define #define #define S_IRWXU S_IRUSR S_IWUSR S_IXUSR 00700 00400 00200 00100 /* user permission bits */ #define #define #define #define S_IRWXG S_IRGRP S_IWGRP S_IXGRP 00070 00040 00020 00010 /* group permission bits */ #define #define #define #define ... S_IRWXO S_IROTH S_IWOTH S_IXOTH 00007 00004 00002 00001 /* others’ permission bits */ Quando si accede ad un nuovo file il sistema localizza l’i-node corrispondente, alloca uno slot nella tavola di sistema degli i-nodes, e legge l’i-node in memoria primaria. Come per i buffers della cache di sistema, lo slot nella tavola è considerato la versione corrente dell’i-node. Le modifiche dell’i-node sono fatte nella tavola. Quando viene effetuato l’ultimo accesso al file, l’i-node è ricopiato nella i-list in memoria secondaria, e lo slot è deallocato nella tavola. Tutte le operazioni di I/O sui files sono effettuate con l’aiuto dello slot corrispondente nella tavola degli inodes. L’accesso ad un file è una semplice implementazione dell’algoritmo ora menzionato. L’utente non deve occuparsi dello i-node nè dell’i-number. I riferimenti al filesystem sono fatti in termini di path-name nell’albero delle directories. Convertire un path-name in uno slot nella tavola degli i-nodes è anch’esso molto semplice: partendo da qualche i-node conosciuto (la radice dell’arborescenza, per path-name assoluti, o la directory di lavoro attuale di un processo, per path-name relativi) il prossimo componente del path-name viene ricercato leggendo la directory. Ciò fornisce un i-number ed un device implicito (il disco della directory). Così si può accedere allo slot successivo nella tavola degli i-nodes. Se si tratta dell’ultima componente del path-name, allora questo i-node è il risultato cercato. Altrimenti questo i-node è la directory necessaria per cercare il successivo componente del path-name, e si ripete l’algoritmo. V. Asta - Introduzione a Unix e GNU/Linux 74 L’utente accede al filesystem con alcune primitive di sistema. Le più comuni sono open(2), create(2), read(2), write(2), lseek(2) e close(2). Nella zona dei dati di sistema associati ad un processo (nella tavola dei processi attivi) c’è spazio per un certo numero di files aperti (in genere dai 50 in su). Questa tavola dei files aperti per processo consiste di puntatori che possono essere utilizzati per accedere all’entrata corrispondente nella tavola degli i-nodes. Associato ad ognuno di questi files aperti c’è un puntatore di I/O. Si tratta di un offset in bytes nel file, per la successiva operazione di I/O. Il sistema tratta le richiesta di lettura/scrittura come aleatorie con un seek implicito al puntatore di I/O. L’utente vede in generale i suoi files come sequenziali, col puntatore di I/O che conta automaticamente il numero di bytes che sono già stati letti/scritti sul file. L’utente può ovviamente fare dei veri I/O aleatori posizionando il puntatore di I/O prima di una lettura/scrittura (con la primitiva lseek(2)). A causa del meccanismo di condivisione di files aperti, è necessario autorizzare i processi in relazione padre-figlio a condividere un puntatore di I/O comune, e avere ugualmente puntatori di I/O differenti per i processi indipendenti che accedono ad uno stesso file. Con queste due condizioni, il puntatore di I/O non può trovarsi nella tavola degli i-nodes, nè nella tavola dei files aperti per processo. Una nuova tavola (la tavola dei files aperti di sistema) è stata quindi introdotta, con lo scopo di contenere il puntatore di I/O, il modo di apertura del file (O_RDONLY, O_RDWR etc.) e alcune altre informazioni. La definizione della struttura degli slot di questa tavola si trova nel file /usr/include/sys/file.h (in LINUX, in /usr/include/linux/fs.h): $ cat /usr/include/linux/fs.h ... struct file { struct file *f_next, **f_pprev; struct dentry *f_dentry; struct file_operations *f_op; /* pointers to functions */ mode_t f_mode; /* O_RDONLY, etc. */ loff_t f_pos; /* I/O pointer */ unsigned int f_count, /* reference count */ f_flags; unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; struct fown_struct f_owner; unsigned int f_uid, f_gid; int f_error; unsigned long f_version; /* needed for tty driver, and maybe others */ void *private_data; }; ... I processi che condividono uno stesso file aperto (i risultati della primitiva fork(2)) condividono una entrata comune nella tavola dei files aperti di sistema. Un’apertura separata dello stesso file condividerà soltanto lo slot nella tavola degli i-nodes, ma avrà uno slot separato nella tavola dei files aperti. Le principali primitive di gestione del filesystem sono implementate nel modo seguente: open(2) converte un path-name del filesystem in uno slot nella tavola degli i-nodes. Un puntatore verso lo slot nella tavola degli i-nodes viene inizializzato in uno slot della tavola dei files aperti, allocato per l’occasione (in LINUX, ciò avviene attraverso un’ulteriore struttura di dati: il membro f_dentry della struct file punta ad una struct dentry, di cui il membro d_inode punta all’i-node). $ cat /usr/include/linux/dcache.h ... struct dentry { V. Asta - Introduzione a Unix e GNU/Linux 75 int d_count; unsigned int d_flags; struct inode * d_inode; /* pointer to inode structure */ struct dentry * d_parent; /* parent directory */ struct dentry * d_mounts; /* mount information */ struct dentry * d_covers; struct list_head d_hash; /* lookup hash list */ struct list_head d_lru; /* d_count = 0 LRU list */ struct list_head d_child; /* child of parent list */ struct list_head d_subdirs; /* our children */ struct list_head d_alias; /* inode alias list */ struct qstr d_name; unsigned long d_time; /* used by d_revalidate */ struct dentry_operations *d_op; struct super_block * d_sb; /* The root of the dentry tree */ unsigned long d_reftime; /* last time referenced */ void * d_fsdata; /* fs-specific data */ unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ }; ... Un puntatore verso lo slot nella tavola dei files aperti è inizializzato nella zona di dati sistema del processo; tale tavola è dichiarata in /usr/include/sys/user.h (in LINUX, in /usr/include/linux/sched.h). $ cat /usr/include/linux/sched.h ... /* * Open file table structure */ struct files_struct { atomic_t count; int max_fds; int max_fdset; int next_fd; struct file ** fd; /* current fd array */ fd_set *close_on_exec; fd_set *open_fds; fd_set close_on_exec_init; fd_set open_fds_init; struct file * fd_array[NR_OPEN_DEFAULT]; }; ... creat(2) inizia col creare un nuovo i-node, scrive lo i-number in una directory, e costruisce poi la stessa struttura che per una open(2). read(2) e write(2) utilizzano l’i-node in memoria come descritto precedentemente. lseek(2) manipola semplicemente il puntatore di I/O nella tavola (membro f_pos della struct file): nessuno spostamento fisico viene effettuato. close(2) libera le strutture costruite da open(2) e create(2), e provoca tra l’altro la riscrittura in memoria secondaria dell’i-node. Gli slot della tavola dei files aperti e della tavola degli i-nodes gestiscono anche dei reference counts (membri i_count di struct inode e f_count di struct file), per liberare queste strutture quando l’ultimo riferimento viene meno (quando cioè il reference count, decrementato, scende a zero). unlink(2) decrementa semplicemente il conto del numero di directories che puntano sull’i-node dato (link count; membro i_count di struct inode). Quando l’ultimo riferimento ad uno slot nella tavola degli i-nodes sparisce, se l’i-node non ha più directories che puntano su di lui, il file viene soppresso e l’i-node è liberato. Questa proroga nella soppressione dei files evita i problemi posti dalla soppressione di files attivi. Un file può essere soppresso anche se ancora aperto. Il file senza nome che ne risulta sparirà quando il file sarà chiuso. Questo è un metodo tipico per creare e gestire dei files temporanei. V. Asta - Introduzione a Unix e GNU/Linux 76 Esiste un tipo di file FIFO senza nome (cioè senza directory entry) chiamato pipe. La implementazione dei pipes consiste in spostamenti (seeks) impliciti prima di ogni read/write per implementare il meccanismo first-in-first-out. Ci sono anche alcune verifiche, ed una sincronizzazione per impedire al produttore di dati di produrre troppo per il consumatore e per impedire al consumatore di prendere troppi dati. A partire da System III esistono anche dei named pipes (pipes nominati), chiamati anche FIFO files. Si tratta di pipes che hanno un i-node e una directory entry su disco. Un altro tipo di file, introdotto in BSD UNIX, è costituito dai symbolic link files (link simbolici, anche detti symlink); si tratta di files che contengono, come dati, il path-name del file reale a cui si riferiscono. Quando viene fatta una open(2) di un file di questo tipo, il sistema riconosce dall’i-node che si tratta di un symlink, e legge allora nel file il path del vero file da aprire. Alcune versioni di UNIX hanno introdotto ulteriori tipi particolari di file. 10.3 File systems montati Il filesystem di un sistema UNIX parte con un block device qualsiasi formattato come sopra descritto per contenere una arborescenza. La radice di questa struttura è la radice del sistema UNIX. Un secondo block device formattato può essere montato su qualunque foglia dell’arborescenza attuale. Ciò rappresenta quindi un’estensione logica dell’arborescenza preesistente. L’implementazione del montaggio è banale: il sistema gestisce una tavola di volumi montati che contiene coppie di i-node/foglie e di block devices; tale tavola è dichiarata in /usr/include/sys/mount.h (in LINUX, in /usr/include/linux/mount.h). $ cat /usr/include/linux/mount.h ... struct vfsmount { kdev_t mnt_dev; /* Device this applies to */ char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */ char *mnt_dirname; /* Name of directory mounted on */ unsigned int mnt_flags; /* Flags of this device */ struct super_block *mnt_sb; /* pointer to superblock */ struct quota_mount_options mnt_dquot; /* Diskquota specific mount options */ struct vfsmount *mnt_next; /* pointer to next in linkedlist */ }; ... Quando un path-name e è convertito in i-node, si fa una verifica per vedere se il nuovo i-node è una di queste foglie. In caso affermativo, esso viene sostituito con l’i-node della radice del block device. L’allocazione dello spazio per un file è fatta nel pool dei blocchi liberi sul device nel quale il file si trova. Così un filesystem costituito da più devices montati non ha un pool comune di spazio libero in memoria secondaria. Questa separazione dello spazio su più devices è necessaria per permettere smontaggi facili di un device. V. Asta - Introduzione a Unix e GNU/Linux 77 11. Avvio del sistema All’avvio fisico di un sistema UNIX vengono percorsi i seguenti passi: — il kernel viene caricato in memoria dal programma di bootstrap — il kernel si inizializza e lancia un certo numero (che varia a seconda del tipo e versione del Sistema Operativo) di threads o processi di sistema; tipicamente, si tratta del processo init e poi di altri processi che hanno a che fare con lo scheduling, la paginazione e/o lo swapping, la gestione della cache di I/O. In un tipico UNIX System V, troviamo: - scheduler/swapper (in-swapper) (PID = 0) - init (PID = 1); vedi oltre - page daemon (out-swapper) (PID = 2) - update; si occupa di eseguire un codice che effettua periodicamente (di solito ogni 30 secondi) una chiamata di sistema sync(2); ciò serve a garantire la sincronizzazione periodica tra memoria di cache di I/O e contenuto della memoria secondaria. In LINUX, i processi sono invece i seguenti: - idletask (PID = 0); è il thread a cui viene data la CPU quando il sistema non ha nulla da fare - init (PID = 1) - kflushd (PID = 2), kupdate (PID = 3) − hanno entrambi a che fare con la scrittura su disco dei buffer di cache modificati ("dirty buffers"); sono quindi l’equivalente del task update, ma lavorano con un algoritmo più sofisticato, che tiene conto dell’età di residenza in memoria dei buffer (oltre una certa età, viene forzata la scrittura su disco) e comunque di una sincronizzazione periodica (30 secondi per il blocchi di dati, 5 secondi per i superblock) - kpiod (out-pager) (PID = 5) - kswapd (PID = 6), che si ocupa di liberare e rendere disponibili per un nuovo uso pagine libere di memoria. — come detto, uno di questi processi (presente in qualunque versione di UNIX) è /etc/init (o /sbin/init), che ha il compito di lanciare tutti gli altri processi di sistema e quelli a livello utente per permettere l’avvio dei server e dei programmi per il login degli utenti. — init consulta il file /etc/inittab, che contiene le istruzioni sul da farsi. — tra le cose da fare al bootstrap, init lancia uno o più shell script che si trovano nella directory /etc/rc.d; l’organizzazione dei file in questa directory può variare da un sistema all’altro, ma il meccanismo di base è identico — gli shell script in /etc/rc.d - effettuano varie inizializzazioni (ad esempio configurando le interfacce di rete con gli appropriati indirizzi IP), cleanup di file e directory temporanee o di spool, etc. - lanciano i server di sistema; dapprima quelli locali, poi eventualmente quelli di rete - lanciano, per ogni linea utente (tty), un’istanza del programma getty (che si trova normalmente in /etc o in /sbin, e di cui possono esistere più varianti) in sottoprocessi figli diretti di init; — il programma getty viene invocato col nome della tty come argomento, ed effettua i seguenti passi: - apre (per la prima volta, stabilendo così un process group) lo special file della linea - lo ridirige sul suo stdin, stdout e stderr (aprendo i canali n. 0, 1 e 2) V. Asta - Introduzione a Unix e GNU/Linux 78 - consulta il file /etc/gettydefs60 e configura i parametri di funzionamento della linea (velocità di trasmissione, traslazione <CR> -> <NL> in input, etc.61) - scrive a stdout la stringa di invito al login (ad es. "Login: ") - legge una riga dallo stdin (il nome dell’utente) ed effettua una chiamata di sistema execve(2) e lancia il programma /bin/login con argomento il nome d’utente letto — il programma login, invocato come detto, effettua a sua volta i seguenti passi: - scrive a stdout la stringa di richiesta di password (ad es. "Password: ") - legge una riga dallo stdin (la password) - apre il file /etc/passwd; per convenienza, si denominino come segue i campi di ciascuna riga di questo file: User:Passwd:Uid:Gid:Comment:Home:Program e legge in memoria la riga il cui campo User corrisponde al nome dell’utente; - cripta la password letta, e la confronta col campo Passwd (si noti che il confronto è tra le forme criptate delle password); se la password letta non corrisponde con quella del file /etc/passwd, il login viene abortito - effettua le chiamate sistema setgid(2) e poi setuid(2) per assumere l’identità di gruppo e di utente specificata dai campi Gid e Uid (si noti che il programma finora stava girando con i diritti del superuser, essendo figlio diretto di init) - effettua una chiamata sistema chdir(2) per posizionarsi nella directory del campo Home - posiziona alcune variabili di environment, quali il parametro LOGNAME, il parametro HOME etc. - esegue infine una chiamata execve(2) per lanciare il programma specificato nel campo Program (di solito una shell); come già detto, login prepende il carattere ‘-’ all’argv[0] dell’array degli argomenti, cioè al nome di invocazione del programma da eseguire, e ciò permette a quest’ultimo di rendersi conto che sta girando come programma di login; nel caso di una shell, l’effetto risultante è che i comandi posti nei file /etc/profile e $HOME/.profile (se esiste) sono letti ed eseguiti all’avvio della shell. Pertanto, alla fine di questa sequenza, la situazione risultante è, normalmente, che una shell di login si trova avviata su ciascuna linea utente, all’interno di un processo che è figlio diretto di init (all’interno di questo processo, si sono succeduti dapprima il programma init, poi getty, poi login e infine la shell), con i diritti dell’utente che si è connesso e del relativo gruppo di login. — all’uscita della shell, il processo init rilancia, in loop, la stessa sequenza sulla linea utente — i programmi init, getty e login aggiornano due file di sistema che contengono le informazioni sugli utenti collegati: - utmp (path abituale: /etc/utmp, o /var/run/utmp) - wtmp (path abituale: /etc/wtmp, o /var/log/wtmp) il primo contiene essenzialmente un record (si veda utmp(5) per i dettagli di formato interno) per ogni linea utente, con le informazioni sull’utente collegato, la tty, l’ora di inizio della sessione di lavoro, il PID del processo iniziale e altro; il record è inizializzato con i dati rilevanti ad ogni login, ed è poi invalidato ad ogni logout; utmp contiene quindi la fotografia istantanea degli utenti collegati 60. Alcune versioni di getty utilizzano altri file di configurazione, con differente formato 61. Questi parametri sono precisati in una struttura dati di sistema, struct termio, e vengono posizionati o consultati mediante la chiamata di sistema ioctl(2) V. Asta - Introduzione a Unix e GNU/Linux 79 al sistema. Il secondo file (wtmp) ha formato identico al primo, ma ad ogni login e logout un nuovo record viene accodato al file (nel caso dei logout, il campo col nome dell’utente è una stringa nulla); tale file quindi registra tutti i login e logout del sistema, ed è un file che cresce indefinitamente (pertanto, insieme ai file di log del sistema e ad altri file, deve essere riazzerato e preventivamente salvato, con cadenza periodica). Il comando who non fa altro che leggere il file utmp e visualizzarne il contenuto in formato leggibile da terminale. V. Asta - Introduzione a Unix e GNU/Linux 80 12. Il programma init Il programma init funziona secondo una logica a livelli, che regolano il modo di funzionamento del sistema. Ad ogni livello (detto anche run-level) corrispondono varie applicazioni, o sottosistemi, da lanciare o da fermare; tutto ciò è precisato nel file di configurazione /etc/inittab. All’avvio, init si configura per un livello di default, e lancia tutte le applicazioni relative a questo livello; per cambiare di livello, l’amministratore di sistema lancia il programma /sbin/telinit (che altro non è che un link allo stesso eseguibile di init), con un argomento pari al nuovo livello voluto; telinit comunica tale informazione al processo init attivo, il quale provvede a lanciare le applicazioni relative al nuovo livello. Ad esempio, il comando telinit 3 fa sì che il processo init originale (PID = 1) si sposti al livello 3, avviando tutti e soli i sottosistemi predisposti per questo livello; infine, il comando telinit q viene lanciato normalmente dopo una modifica al file inittab, per notificare a init che deve rileggere questo file e comportarsi di conseguenza (ad esempio, dopo aver abilitato una nuova linea di login); ciò non implica nessun cambiamento di livello. Il livelli sono normalmente sette, numerati da 0 a 6 (il livello 1 è anche denominato livello s, o single-user); l’utilizzo di questi livelli può variare a seconda del tipi di sistema UNIX; un esempio tipico (per un sistema GNU/LINUX) è il seguente: 0 power off - shutdown del sistema 1s mono-utente (modo di maintenance, login disabilitati salvo sulla console di sistema) 2 modo normale multi-utente (login abilitati) 3 modo normale multi-utente con supporto di rete (NFS abilitato62) 4 test locale di nuove applicazioni (spesso inutilizzato) 5 modo multi-utente con ambiente grafico X1163 abilitato 6 reboot del sistema Il formato del file /etc/inittab è il seguente (si tratta anche qui, come per /etc/passwd e /etc/group, di righe di testo con campi separati dal carattere ‘:’): Id:Runlevels:Action:Command id è una sequenza unica da 1 a 4 caratteri, che identifica il record Runlevels lista dei livelli per i quali l’azione deve essere avviata; se il campo è nullo, l’azione va avviata per qualunque livello Action descrive il tipo di azione da avviare Command specifica il processo da eseguire per la data azione (comando e argomenti). Ad esempio, la riga seguente g1:235:respawn:/sbin/getty tty1a L19200 62. NFS = Network File System; si tratta di un sottosistema che permette il montaggio di filesystem remoti, attraverso una rete TCP/IP 63. X11 è l’ambiente grafico dei sistemi UNIX; si tratta di un grosso package di librerie grafiche, ambienti desktop e programmi; X11 è stato sviluppato inizialmente all’M.I.T. (Massachusetts Institute of Technology); i sorgenti sono di dominio pubblico V. Asta - Introduzione a Unix e GNU/Linux 81 indica che la riga denominata "g1" specifica un’azione da attivare per i livelli di run n. 2, 3 e 5; l’azione consiste nel lancio in loop continuo ("respawn") del programma getty, con argomenti tty1a (nome dello special file da aprire, in /dev/) e L19200 (che è una label che rinvia ad una riga del file /etc/gettydefs, con i dettagli sulla configurazione di linea da adottare). Le azioni principali previste da init sono le seguenti (si veda inittab(5) per ulteriori dettagli): respawn Lancia Command, non aspettare che finisca; quando termina, rilancialo. Quest’azione è quella tipica delle sequenze di login, dove il comando prevede appunto l’avvio in ciclo continuo del programma getty. off Se Command è attivo, terminalo; altrimenti, ignora questa riga. Utilizzato spesso per disabilitare una entry. wait Lancia Command, e aspetta che termini prima di continuare. once Lancia Command una sola volta all’ingresso nel dato run-level, non aspettare che esca, e non rilanciarlo. initdefault Specifica il run-level di default; in questo caso, il campo Command è ignorato. sysinit Lancia Command al bootstrap del sistema, prima di ogni altra cosa (prima ancora di accedere alla console di sistema); il campo Runlevels è ignorato. boot Lancia Command al bootstrap del sistema, dopo le eventuali entry sysinit. bootwait Analogo al caso precedente, ma aspetta che Command termini prima di continuare. powerfail Lancia Command alla ricezione del segnale n. 30 (SIGPWR - segnale di assenza di alimentazione elettrica), che viene normalmente inviato da un apposito daemon connesso ad un dispositivo di gruppo di continuità (UPS - Uninterruptible Power System). Il comando tipicamente prevede un messaggio di avvertimento a tutti gli utenti connessi, e l’avvio della procedura di shutdown entro pochi minuti (comunque entro il tempo di autonomia dello UPS, cioè prima che l’energia elettrica manchi del tutto e si verifichi quindi uno spegnimento forzato della macchina). powerwait Analogo al caso precedente, ma aspetta che Command termini prima di continuare powerokwait Lancia Command non appena informato che l’alimentazione elettrica è stata ripristinata (ovviamente durante il tempo di autonomia dello UPS); aspetta che il comando termini prima di continuare. Il comando di norma prevede l’annullamento della procedura di shutdown precedentemente avviata. ctrlaltdel64 Lancia Command alla ricezione del segnale n. 2 (SIGINT); ciò avviene normalmente quando l’utente digita la combinazione <CTRL-ALT-DEL> sulla console di sistema. Il comando tipico è l’avvio della procedura di shutdown. A titolo di esempio, si riporta qui un file inittab completo: # inittab # This file describes how the INIT process should set up the system in a certain run-level. id:5:initdefault: # System initialization. si::sysinit:/etc/rc.d/rc.sysinit l0:0:wait:/etc/rc.d/rc.0 l1:1:wait:/etc/rc.d/rc.1 l2:2:wait:/etc/rc.d/rc.2 64. Questo tipo di azione, com’è facile intuire, è un’estensione propria delle architetture IBM-PC, e non è supportato da tutte le versioni di init V. Asta - Introduzione a Unix e GNU/Linux 82 l3:3:wait:/etc/rc.d/rc.3 l4:4:wait:/etc/rc.d/rc.4 l5:5:wait:/etc/rc.d/rc.5 l6:6:wait:/etc/rc.d/rc.6 # Things to run in every runlevel. ud::once:/sbin/update # Trap CTRL-ALT-DELETE ca::ctrlaltdel:/sbin/shutdown -t3 -r now # When our UPS tells us power has failed, assume we have a few minutes # of power left. Schedule a shutdown for 2 minutes from now. # This does, of course, assume you have powerd installed and your # UPS connected and working correctly. pf::powerfail:/sbin/shutdown -f +2 "Power Failure; System Shutting Down" # If power was restored before the shutdown kicked in, cancel it. pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled" # If power comes back in single user mode, return to multi user mode. ps:S:powerokwait:/sbin/init 3 # Run gettys for logins in standard runlevels g1:235:respawn:/sbin/getty tty1 M19200 g2:235:respawn:/sbin/getty tty2 M19200 g3:235:respawn:/sbin/getty tty3 M19200 g4:235:respawn:/sbin/getty tty4 M19200 g5:235:respawn:/sbin/getty tty5 M19200 g6:235:respawn:/sbin/getty tty6 Ldefault # Dialin lines -- modems for external access, esp. for Internet s0:235:respawn:/usr/local/sbin/mgetty -x 9 -D /dev/ttyS16 UU38400 s1:235:respawn:/usr/local/sbin/mgetty -x 9 -D /dev/ttyS17 UU38400 s2:235:respawn:/usr/local/sbin/mgetty -x 9 -D /dev/ttyS18 UU38400 s3:235:respawn:/usr/local/sbin/mgetty -x 9 -D /dev/ttyS19 UU38400 # Run xdm in runlevel 5 # xdm is now a separate service x:5:respawn:/etc/X11/prefdm -nodaemon Com’è facile vedere, tipicamente ad ogni avvio di livello corrisponde l’avvio di una specifica procedura shell; queste procedure, come già anticipato, si trovano tutte nella directory /etc/rc.d ed hanno nomi del tipo rc.# dove # è una cifra, corrispondente al livello da raggiungere. In linea generale, questi script lanciano a loro volta una serie di altri script, spesso uno per ciascun sottosistema (ad esempio, uno per montare tutti i filesystem, uno per avviare lo spooler della stampante, uno per il collegamento in rete, etc.), i quali effettuano due tipi di azioni: — terminazione dei programmi di tutti i sottosistemi relativi a livelli precedenti, e che non devono essere presenti nel nuovo livello — avvio dei programmi dei sottosistemi pertinenti al nuovo livello. Qui di seguito si riporta un semplice esempio di script per l’avvio di un livello multi-utente completo (ad esempio livello 3, nell’esempio tipico ipotizzato): #! # # rc.3 # # # # /bin/sh This file is executed by init(8) when the system is being initialized for one of the "multi user" run levels (i.e. levels 2 through 5). It usually does mounting of file systems et al. V. Asta - Introduzione a Unix e GNU/Linux 83 # Tell the viewers what’s going to happen... echo "Going multiuser..." # stop any unwanted sub-systems if [ -x /etc/rc.d/rc.stop ] then . /etc/rc.d/rc.stop fi # Look for a CD-ROM in a CD-ROM drive, and if one is found, # mount it under /cdrom. This must happen before any of the # binaries on the CD are needed. if [ -x /etc/rc.d/rc.cdrom ] then . /etc/rc.d/rc.cdrom fi # mount all required file-systems if [ -x /etc/rc.d/rc.mount ] then . /etc/rc.d/rc.mount else # no specific script -- hope this will do... /bin/mount -a fi # If there’s no /etc/HOSTNAME, fall back on this default: if [ ! -r /etc/HOSTNAME ] then echo "localhost.localdomain" >/etc/HOSTNAME fi # Set the hostname. This might not work correctly if TCP/IP is not # compiled in the kernel. /bin/hostname ‘cat /etc/HOSTNAME | cut -f1 -d .‘ # Initialize the NET subsystem. if [ -x /etc/rc.d/rc.inet1 ] then . /etc/rc.d/rc.inet1 . /etc/rc.d/rc.inet2 else # no NET subsystem - local config. only; start just local daemons if [ -x /usr/sbin/syslogd ] then # system logging daemons /usr/sbin/syslogd /usr/sbin/klogd -c 3 fi if [ -x /usr/sbin/lpd ] then # line printer spooler daemon /usr/sbin/lpd fi fi # Start Cron daemon /usr/sbin/crond -l10 >>/var/adm/cron 2>&1 # Start At daemon (manages jobs scheduled with ’at’): /usr/sbin/atd -b 15 -l 1 # Remove stale locks and junk files (must be done after mount -a!) /bin/rm -f /var/lock/* /var/spool/uucp/LCK..* /tmp/.X*lock \ /tmp/core /core >/dev/null 2>&1 V. Asta - Introduzione a Unix e GNU/Linux 84 # Ensure basic filesystem permissions sanity. chmod 755 / chmod 1777 /tmp /var/tmp # Update all the shared library links automatically /sbin/ldconfig # Check quotas and then turn quota system on: if fgrep quota /etc/fstab >/dev/null 2>&1 then if [ -x /sbin/quotacheck ] then echo "Checking filesystem quotas..." /sbin/quotacheck -avug fi if [ -x /sbin/quotaon ] then echo "Activating filesystem quotas..." /sbin/quotaon -avug fi fi # Start the sendmail daemon (SMTP - E-mail handling): if [ -x /usr/sbin/sendmail ] then echo "Starting sendmail daemon (/usr/sbin/sendmail -bd -q15m)..." /usr/sbin/sendmail -bd -q15m fi # Load a custom keymap if the user has an rc.keymap script. if [ -x /etc/rc.d/rc.keymap ] then . /etc/rc.d/rc.keymap fi # iBCS Emulation for Linux if [ -x /etc/rc.d/rc.ibcs2 ] then . /etc/rc.d/rc.ibcs2 fi # Start Web server: if [ -x /etc/rc.d/rc.httpd ] then . /etc/rc.d/rc.httpd fi # Start Samba (a file/print server for Win95/NT machines): if [ -x /etc/rc.d/rc.samba ] then . /etc/rc.d/rc.samba fi # If there are SystemV-style init scripts for this runlevel, run them. if [ -x /etc/rc.d/rc.sysvinit ] then . /etc/rc.d/rc.sysvinit fi # Start the local setup procedure (any local subsystems, specific to # this machine). if [ -x /etc/rc.d/rc.local ] then . /etc/rc.d/rc.local fi V. Asta - Introduzione a Unix e GNU/Linux 85 # All done. V. Asta - Introduzione a Unix e GNU/Linux 86 APPENDICE A Cronologia dettagliata di UNIX Nota − per comodità di consultazione e per completezza, in questa Appendice vengono ripetute sostanzialmente anche le informazioni già esposte nel paragrafo 1.1 pag. 3 e in parte nei paragrafi 1.2 pag. 4 e 1.3 pag. 9. Il Sistema Operativo UNIX è stato realizzato a partire dai primi anni ’70 nei Bell Laboratories, ad opera soprattutto di Ken Thompson e Dennis Ritchie, dapprima per soli usi interni; è stato poi commercializzato dalla Western Electric, casa madre dei Bell Labs e della AT&T, e successivamente dalla AT&T stessa; si è affermato commercialmente a partire dagli anni ’80. Le prime versioni giravano su sistemi della Digital Equipment Corporation (DEC): dapprima su un PDP-7, poi (dal 1971) su quelli della famiglia PDP-11, quindi (1979) sui VAX-11. Nel 1981, con la versione PC/IX della Interactive Systems per macchine IBM PC/AT, UNIX fa la sua prima apparizione nella gamma dei Personal Computer IBM e compatibili. La storia della nascita e dell’evoluzione di questo sistema è reperibile in molti testi, alcuni dei quali citati in bibliografia; esistono inoltre due numeri speciali del The Bell System Technical Journal, dedicati esclusivamente a UNIX, che illustrano molti aspetti interessanti del sistema, della sua storia, delle sue motivazioni originali e delle sue caratteristiche ([BSTJ 1978], [BSTJ 1984]). 1968: Bell Labs abbandona il progetto MULTICS, nato da uno sforzo congiunto insieme all’MIT e a General Electric per la realizzazione di un Sistema Operativo di tipo interattivo e multiutente65. Ken Thompson (seguito poi da Dennis Ritchie) inizia a lavorare su un sistema di filesystem e di gestione dei processi, scritto parte in assembler e parte in Fortran, per il Sistema Operativo GECOS, che sarà all’origine della progettazione di UNIX. 1969-1970: Thompson e Ritchie producono una prima versione sperimentale di un Sistema Operativo, scritta in Assembler per un PDP-766 della Digital Equipment Corporation (DEC). I concetti di base derivano in parte dai sistemi CTSS, TENEX, MULTICS. L’obiettivo è di realizzare un Sistema Operativo interattivo che offra su dei minicomputers un ambiente di programmazione equivalente a quello di cui disponevano i grandi calcolatori. 1971: Viene effettuato il porting su PDP-11; la prima edizione ufficiale67 di UNIX, diffusa solo all’interno dei Bell Labs, viene realizzata su un DEC PDP-11/20. Si tratta di una versione che permette solo la monoprogrammazione68, ma è capace di girare con un quantità minima di risorse hardware: 16 kbytes per il kernel, 8 kbytes per i programmi utente, un disco da 512 kbytes, 64 kbytes come limite massimo di lunghezza di un file. Tutte le distribuzioni includevano la totalità del codice sorgente del kernel e dei programmi: il sistema era concepito da programmatori per l’uso dei programmatori, dunque la migliore (e in certi casi 65. Una versione iniziale di MULTICS funzionò nel 1969 su macchine GE 645 della General Electric, ma il prodotto risultante, benchè interessante dal punto di vista concettuale, risultò troppo complesso ed elefantiaco, e sostanzialmente non raggiunse gli scopi che il progetto si era prefisso. 66. La macchina era equipaggiata con 4 kbytes di memoria RAM in tutto. 67. Le prime versioni, fino al 1979, saranno identificate col numero di edizione del manuale, per cui è più corretto dire ad esempio "UNIX 6. edizione" anzichè "UNIX Versione 6". 68. Il nome UNIX è stato originalmente coniato come contraposizione umoristica a MULTICS, che poteva fare molte cose per molti utenti, mentre UNIX all’origine era un sistema piccolino, che faceva una sola cosa alla volta per un solo utente. V. Asta - Introduzione a Unix e GNU/Linux 87 unica) forma di documentazione era la consultazione diretta dei sorgenti. 1971-1972: Thompson lavora su un linguaggio interpretato, chiamato "linguaggio B", derivato dal linguaggio BCPL, sviluppato da M. Richards (sempre internamente ai Bell Labs). Ritchie scrive un compilatore per un nuovo linguaggio, derivato a sua volta dal linguaggio B, che permetta ad un tempo di realizzare software di base (sistemi, utilities, etc.) e di realizzare delle applicazioni ad alto livello. Nasce così il linguaggio C. 1972: Seconda edizione di UNIX, che permette la multiprogrammazione. È disponibile su diverse macchine della serie PDP-11 (ma non tutte): PDP-11/34, 40, 45, 60, 70. 1973: Riscrittura di UNIX (kernel e applicativi) in linguaggio C, ad opera essenzialente di Thompson, Ritchie e alcuni altri ricercatori dei Bell Labs, tra cui Brian Kernighan. La dimensione del kernel aumenta del 30%69, ma il sistema diventa realmente multitasking e multiutente. Due progetti indipendenti si innestano sull’evoluzione di UNIX: — MERT, derivazione orientata al tempo reale — PWB (Programmer’s WorkBench), versione specializzata destinata a fornire un ambiente di programmazione per i teams di sviluppo software. 1973-1974: UNIX Versione 3 (terza edizione del manuale), poi 4 e 5; si tratta ancora di versioni ad uso interno, modificate dagli utenti secondo i loro propri bisogni (MERT, PWB/UNIX). Pur non essendo comercializzate, vengono distribuite (in forma di codice sorgente) ad organizzazioni senza scopo di lucro che riescono ad ottenerne la licenza, in particolare l’Università di California a Berkeley (UCB) e la Columbia University. La reputazione e la robustezza di UNIX si è inizialmente costruita su questa base universitaria, che ha fornito un prezioso feedback ai Bell Labs. Val la pena di evidenziare che si è qui in presenza di un’anticipazione storica del modello cooperativo del Software Libero, sia pure su scala molto più ridotta e controllata. 1975: Messa a punto di UNIX Versione 6 (V.6); prima versione commercializzata, senza installazione, senza manutenzione, senza aggiornamenti70. Primissime imitazioni di UNIX (sistemi UNIX-like), in particolare IDRIS, della Whitesmiths Ltd, che è il capostipite di questo tipo di sistemi. 1977: Primi adattamenti di UNIX su calcolatori non DEC; la prima nuova macchina è un Interdata 8/3271. L’approccio ai problemi di portabilità porta delle modifiche, a livello di linguaggio C, per rendere il sistema e i programmi indipendenti dalle caratteristiche dell’hardware; il kernel viene ristrutturato per isolare le parti dipendenti dalle macchine. 69. La dimensione massima della memoria è sempre stato un problema non indifferente, anche negli anni seguenti, per tutte le macchine DEC della serie PDP (di cui i PDP-11 sono stati la serie più evoluta): l’architettura hardware dei PDP-11 dell’epoca prevedeva un bus di dati a 16 bit e, soprattutto, un bus di indirizzi a 18 bit; pertanto la memoria fisica massima installabile era di 256 kbytes, divisa in 4 pagine da 64 kbytes (ma molte macchine disponevano di 128 kbytes in tutto). Il codice del kernel doveva rimanere compreso comunque in un’unica pagina da 64 kbytes. 70. L’unico tipo di licenza disponibile era la licenza sorgente, che comprendeva il codice di tutto il kernel e di tutte le utilities applicative; la distribuzione consisteva in un nastro magnetico bootstrappabile, e due fogli di carta stampati con stringatissime istruzioni di installazione, che terminavano con la frase ormai celeberrima "Good luck." ("Buona fortuna."). Tutta la manualistica era on-line, e andava stampata a cura dell’acquirente. Il contratto di licenza specificava chiaramente che nessuna forma di assistenza era prevista. Le cose sono cambiate a partire da System III, che AT&T dichiarò considerare il primo vero prodotto commerciale UNIX. V. Asta - Introduzione a Unix e GNU/Linux 88 Nei Bell Labs, il BISP (Business Information System Program) utilizza PWB/UNIX, che permette lo sviluppo dei programmi su minicomputer PDP-11 e la loro esecuzione su mainframe IBM 370 e UNIVAC 1100. PWB/UNIX apporta molte modifiche e novità rispetto a ’*(uX V6, in particolare: — funzionalità per il supporto di progetti software di grandi dimensioni (numero massimo di utenti, dimensione massima dei files, etc.) — Utilities di gestione delle versioni dei sorgenti (si tratta del package SCCS Source Code Control System, l’antenato di RCS e di CVS), e di lancio dell’esecuzione di jobs a distanza (RJE - Remote Job Entry). 1979: Rilascio di UNIX V.7; si tratta di una versione più stabile e con migliori prestazioni rispetto alla versione precedente, sviluppato per PDP-11/45 e PDP-11/7072. La V.7 è la prima versione riscritta senza alcuna dipendenza concettuale dall’architettura dei PDP-11; vengono introdotte numerose modifiche tecniche, tra cui l’ulteriore soppressione di limiti massimi di sistema (ripresa ed estesa da PWB/UNIX), e modifiche per la portabilità. UNIX V.7, con una versione denominata "32/V", viene adattato da John Reiser e Tom London ai nuovi minicalcolatori a 32 bit di DEC, i VAX-11 (dapprima il VAX 780, poi i modelli inferiori). Prime licenze con diritto di ridistribuzione binaria (fino ad allora, l’unico tipo di distribuzione era tramite i sorgenti): aziende di software e hardware vendors possono acquistare la licenza sorgente del sistema (per un prezzo che, all’epoca della V.7, era di diverse decine di migliaia di dollari73, licenza per una sola CPU), e successivamente ottenere il diritto di realizzare versioni modificate e di venderle in forma di prodotto binario (mediante l’ulteriore esborso di alcune centinaia di migliaia di dollari, in termini di anticipo sulle royalties dovute per ogni licenza venduta). Risale a questo periodo l’inizio di quel vasto fenomeno che ha portato lentamente ma inesorabilmente tutti i costruttori di calcolatori a realizzare una propria versione del Sistema Operativo UNIX, realizzando così quell’importantissimo fenomeno che ha condotto verso l’interoperabilità e compatibilità dei sistemi, anche di marche differenti, e che ha determinato la caduta della supremazia quasi monopolistica di IBM, incotrastato dominatore del mercato dell’hardware e del software di base fin dagli albori della storia dell’informatica: per la prima volta, tutti i concorrenti si sono trovati uniti in una causa comune, quella di UNIX contro i sistemi proprietari di IBM, che ha dovuto alla fine capitolare e adottare anch’essa sistemi UNIX per le proprie macchine. Entrano così in scena i sistemi "UNIX-based", cioè prodotti venduti con licenza binaria, ma dipendenti dal meccanismo di licensing della Western Electric che percepisce una royalty per ogni sistema venduto. Tali sistemi, implementati e proposti sul mercato su vari tipi di macchine, per motivi contrattuali non possono 71. La macchina della Interdata (si veda [Johnson e Ritchie 1978], pag. 2038), che era stata scelta proprio perchè aveva caratteristiche hardware notevolmente diverse da quelle dei PDP-11, così da testare adeguatamente l’effettiva portabilità del sistema. Tra l’altro, oltre alla differente lunghezza di parola (32 bit, contro i 16 bit del PDP-11), l’Interdata aveva lo stack che cresceva verso l’alto, al contrario del PDP-11 e della maggior parte dei calcolatori oggi sul mercato; la gestione dell’hardware di memory management era differente; i tipi di trap disponibili, e il metodo di passaggio degli argomenti nelle chiamate sistema, erano differenti. Ciò nonostante, delle 7000 righe del kernel interno, solo 350 (circa il 5%) erano differenti tra le due implementazioni. 72. I PDP-11/45 e 70 erano di tipo "split I&D" (Split Instructions and Data), cioè con un set di registri di Memory Management (MMU) capace di gestire separatamente le sezioni di testo (codice dei programmi) e quelle di dati. La Versione 7 di UNIX, nella sua implementazione iniziale, aveva bisogno di questa capacità per funzionare correttamente. 73. Le Università pagavano invece un prezzo simbolico: la licenza, per numero illimitato di CPU, veniva proposta loro per qualche centinaio di dollari. V. Asta - Introduzione a Unix e GNU/Linux 89 chiamarsi UNIX (tale nome restava riservato alla sola licenza originale, sempre in forma sorgente); nascono così prodotti i cui nomi spesso riecheggiano in qualche modo la parola UNIX, pur con delle modifiche: tra di essi, IS/174 (Interactive Systems), Xenix e SCO Unix (Microsoft e SCO - Santa Cruz Operation75), Unixware (Novell, poi SCO), Zeus (Zilog), Uniplus+ (Unisoft Systems), Ultrix (Digital Equipment Corporation), HP/UX (Hewlett-Packard), DG/UX (Data General), AIX (IBM e Bull), SunOS e SOLARIS (SUN Microsystems), IRIX (Silicon Graphics), e molti altri. Nel frattempo, l’Università di California a Berkeley (UCB) lavora all’implementazione di una nuova versione di UNIX 32/V per VAX, che supporta la paginazione76 (implementata da Ozalp Babaoglu e Bill Joy); nel mese di dicembre inizia la distribuzione di UNIX 3BSD (3. Berkeley Software Distribution), la prima distribuzione BSD per VAX-1177. 1980: Microsoft introduce sul mercato Xenix, una delle più famose versioni commerciali di tipo UNIX-based78; tale attività viene dapprima svolta autonomamente, poi tramite società partner, tra cui in particolare la SCO79. Rilascio di UNIX 4BSD, con varie migliorìe nelle prestazioni. 1981: AT&T subentra a Western Electric (che finora era stato l’organismo di vendita delle licenze del sistema) per la commercializzazione di UNIX. 1982: Nuova versione, denominata System III; nuova politica di commercializzazione e di distribuzione di UNIX, che è ora proposto per macchine PDP-11, VAX-11 (e più tardi per la serie 3B della AT&T) con condizioni di prezzo delle licenze binarie derivate (in termini di royalties) più favorevoli in funzione delle quantità vendute. System III ha le seguenti caratteristiche: - riunisce tutte le caratteristiche di UNIX V.7 con quelle delle altre varianti interne ai Bell Labs (in particolare PWB/UNIX); - supporta tutta la gamma delle macchine PDP-11 e VAX-11, dal PDP-11/23 al VAX/11 780 - contiene numerose estensioni di sistema, tra cui il supporto dei named pipes e del meccanismo a più modi del sistema, tramite la tavola inittab per il processo init. 74. Il sistema IS/1 era commercializzato dalla Interactive Systems Corp., che è stata in assoluto la prima società rivenditrice di UNIX come VAR (Value-Added Reseller). 75. SCO è stata acquisita nel 2001 da Caldera International. 76. L’implementazione del kernel di UNIX 32/V era funzionalmente identica a quello della V.7 per PDP-11, e quindi il sistema era di tipo "segment sharing" (con un meccanismo di swapping totale dei processi: ciascun processo era o totalmente in RAM, o totalmente nell’area di swap), sebbene l’hardware dei VAX permettesse di gestire la paginazione. 77. Le distribuzioni BSD sono sempre state pressochè gratuite (il prezzo di realizzazione e spedizione di un nastro magnetico), ma essendo dipendenti dal codice di UNIX 32/V, cioè in ultima analisi di UNIX V.7, l’Università di Berkeley poteva distribuire le sue versioni (che erano anch’esse solo in forma sorgente) esclusivamente a chi aveva precedentemente acquisito una licenza Bell Labs per UNIX V.7 o 32/V. Molte aziende pagavano il prezzo forte per la licenza Bell Labs solo per poter avere poi la versione di Berkeley UNIX (la distribuzione Bell non veniva nemmeno installata). Questa situazione si è protratta di fatto fino al 1994, anno in cui UCB ottenne ufficialmente, al termine di un processo, il diritto di distribuire liberamente la propria versione di UNIX [McKusick 1999]. 78. È abbastanza ironico osservare che Microsoft sia stata tra le società private principali responsabili dell’affermarsi e del successo di UNIX allora, e quindi indirettamente di quello di LINUX oggi. 79. Contrariamente a quanto molti ritengono, Xenix non è nato sulla piattaforma IBM-PC; è stato invece la prima versione commerciale binaria di UNIX su macchine PDP-11, proposto sul mercato da alcune società partner, tra cui la più autorevole era la HCR (Human Computing Resources Corp.) di Toronto, Canada. V. Asta - Introduzione a Unix e GNU/Linux 90 Interactive Systems Corp. commercializza PC/IX, una versione di System III che è la prima implementazione di UNIX su IBM PC o compatibili. 1983: Versione System V release 180, A partire dal mese di gennaio, AT&T amplifica ulteriormente la sua politica commerciale, proponendo per la prima volta, insieme alla licenza d’uso del sistema, contratti per prestazioni di installazione, assistenza e aggiornamenti del software. Berkeley pubblica la versione 4.2BSD per VAX, una delle più importanti, che vede tra l’altro la prima implementazione dei protocolli di rete TCP/IP, finanziata dal DARPA (Defense Advanced Research Projects Agency − un organismo governativo americano per il sovvenzionamento dei progetti di ricerca, dipendente dal Dipartimento della Difesa). 1984: System V rel. 2 1985: Nascono i primi standard di riferimento per UNIX; tra di essi, SVID (System V Interface Definition) e X-OPEN 1986: System V rel. 3.0; molti sistemi UNIX commerciali, tra cui SCO UNIX per architettura IBM-PC, si basano su questa versione 4.3BSD RFS (Remote File System - AT&T) NFS (sviluppato dalla SUN, per BSD UNIX); STREAMS; IPC; COFF POSIX 1.st draft (IEEE) - P1003 ANSI X3J11 C 1988: System V rel. 3.2 1989: System V rel. 4, versione che riunifica tutte le caratteristiche di System V e quelle di Berkeley Unix. 1991: Linus Torvalds, all’epoca studente di informatica all’università di Helsinki, inizia a sviluppare LINUX, una riscrittura indipendente di UNIX, con l’intento di renderne liberi i sorgenti; è rapidamente seguito da molte altre persone che collaborano al progetto. 1994: Versione 1.0 di LINUX 2001: Versione 2.4 di LINUX 80. La versione System IV non ha mai visto la luce al difuori dei Bell Laboratories. V. Asta - Introduzione a Unix e GNU/Linux 91 APPENDICE B La definizione di Free Software Questa definizione è disponibile on-line come http://www.gnu.org/philosophy/free-sw.it.html Cos’è il Software Libero? Sosteniamo questa definizione di software libero per indicare chiaramente ciò che deve essere vero di un particolare programma software perchè sia considerato software libero. Il "Software libero" è una questione di libertà, non di prezzo. Per capire il concetto, bisognerebbe pensare alla "libertà di parola" e non alla "birra gratis" [NdT: il termine "free" in inglese significa sia "gratuito" che "libero", in italiano il problema non esiste]. L’espressione "software libero" si riferisce alla libertà dell’utente di eseguire, copiare, distribuire, studiare, cambiare e migliorare il software. Più precisamente, esso si riferisce a quattro tipi di libertà per gli utenti del software: — Libertà di eseguire il programma, per qualsiasi scopo (libertà 0). — Libertà di studiare come funziona il programma e adattarlo alle proprie necessità (libertà 1). L’accesso al codice sorgente ne è un prerequisito. — Libertà di ridistribuire copie in modo da aiutare il prossimo (libertà 2). — Libertà di migliorare il programma e distribuirne pubblicamente i miglioramenti, in modo tale che tutta la comunità ne tragga beneficio (libertà 3). L’accesso al codice sorgente ne è un prerequisito. Un programma è software libero se l’utente ha tutte queste libertà. In particolare, se è libero di ridistribuire copie, con o senza modifiche, gratis o addebitando delle spese di distribuzione a chiunque ed ovunque. Essere liberi di fare queste cose significa (tra l’altro) che non bisogna chiedere o pagare nessun permesso. Bisogna anche avere la libertà di fare modifiche e usarle privatamente nel proprio lavoro o divertimento senza doverlo dire a nessuno. Se si pubblicano le proprie modifiche, non si deve essere tenuti a comunicarlo a qualcuno in particolare o in qualche modo particolare. La libertà di usare un programma significa libertà per qualsiasi tipo di persona od organizzazione di utilizzarlo su qualsiasi tipo di sistema informatico, per qualsiasi tipo di attività e senza dover successivamente comunicare con lo sviluppatore o con qualche altra entità specifica. La libertà di ridistribuire copie deve includere le forme binarie o eseguibili del programma e anche il codice sorgente, sia per le versioni modificate che non modificate. È legittimo anche se non c’è alcun modo di produrre una forma binaria o eseguibile (dal momento che alcuni linguaggi non supportano questa caratteristica), ma si deve avere la libertà di ridistribuire tali forme nel caso si trovi o si sviluppi un modo per farlo. Affinchè le libertà di fare modifiche e di pubblicare versioni migliorate abbiano senso, si deve avere accesso al codice sorgente del programma. Perciò, l’accessibilità al codice sorgente è una condizione necessaria per il software libero. Queste libertà per essere reali devono essere irrevocabili fin tanto che non si fa qualcosa di sbagliato: se lo sviluppatore del software ha il potere di revocare la licenza anche senza che l’utente sia causa di tale revoca, il software non è libero. Tuttavia, certi tipi di regole sul come distribuire il software libero sono accettabili quando non entrano in conflitto con le libertà principali. Per esempio, il permesso d’autore(1) è (detto in due parole) la regola per cui, quando il programma è ridistribuito, non è possibile aggiungere restrizioni per negare ad altre persone le libertà principali. Questa regola non entra in conflitto con V. Asta - Introduzione a Unix e GNU/Linux 92 le libertà principali, anzi le protegge. Indipendentemente dal fatto che si siano ottenute copie di software GNU a pagamento o gratuitamente, si ha sempre la libertà di copiare e cambiare il software, e anche di venderne copie. "Software libero" non vuol dire "non-commerciale". Un programma libero deve essere disponibile per uso commerciale, sviluppo commerciale e distribuzione commerciale. Lo sviluppo commerciale di software libero non è più inusuale: questo software commerciale libero è molto importante. Regole su come fare un pacchetto di una versione modificata sono accettabili, a meno che esse in pratica non blocchino la libertà di distribuire versioni modificate. Regole del tipo "se rendi disponibile il programma in questo modo, lo devi rendere disponibile anche in quell’altro modo" possono essere pur esse accettabili, con le stesse condizioni. (Si noti che tale regola lascia ancora aperta la possibilità di distribuire o meno il programma.) È anche accettabile che la licenza richieda che, se avete distribuito una versione modificata e un precedente sviluppatore ne richiede una copia, dobbiate inviargliene una. Nel progetto GNU, noi usiamo il permesso d’autore per proteggere queste libertà legalmente per tutti. Ma esiste anche software libero senza permesso d’autore. Crediamo che ci siano importanti ragioni per cui sia meglio usare il permesso d’autore, ma se un programma è software libero senza permesso d’autore, possiamo comunque utilizzarlo. Si veda http://www.gnu.org/philosophy/categories.it.html (classificazione del software libero) per una descrizione dei rapporti fra "software libero", "software con permesso d’autore" e altre categorie di software. Qualche volta le leggi sul controllo delle esportazioni e le sanzioni sul commercio possono limitare la libertà di distribuire copie di programmi verso paesi esteri. I programmatori non hanno il potere di eliminare o di aggirare queste restrizioni, ma quello che possono e devono fare è rifiutare di imporle come condizioni d’uso del programma. In tal modo, le restrizioni non influiranno sulle attività e sulle persone al di fuori della giurisdizione degli stati che applicano tali restrizioni. Quando si parla di software libero, è meglio evitare di usare espressioni come "gratuito", perchè esse pongono l’attenzione sul prezzo, e non sulla libertà. Parole comuni quali "pirateria" implicano opinioni che speriamo non vogliate sostenere. Si veda http://www.gnu.org/philosophy/words-to-avoid.html (Confusing Words and Phrases that are Worth Avoiding) per una discussione su queste parole. Abbiamo anche una lista di traduzioni in varie lingue dell’espressione "software libero". Infine, si noti che criteri come quelli indicati in questa definizione di software libero richiedono un’attenta interpretazione. Per decidere se una determinata licenza software si qualifichi come licenza per il software libero, noi la consideriamo basata su questi criteri al fine di determinare se corrisponde al loro spirito così come alle precise parole. Se una licenza include restrizioni irragionevoli, la rifiutiamo, anche se in questi criteri non anticipiamo il problema. Qualche volta le richieste di una licenza sollevano un problema che richiede un’analisi dettaglia, oltre a discussioni con un avvocato prima di poter decidere se la richiesta sia accettabile. Quando raggiungiamo una conclusione riguardo ad un nuovo problema, spesso aggiorniamo questi criteri per fare in modo che sia più facile capire perchè determinate licenze siano adeguate o meno. Se siete interessati a sapere se una determinata licenza abbia le caratteristiche per essere una licenza di software libero, consultate il nostro elenco delle licenze (http://www.gnu.org/licenses/license-list.it.html). Se la licenza che vi interessa non vi è elencata, potete interpellarci inviandoci un’e-mail a [email protected]. (1) [NdT: si tratta di un gioco di parole, che qui viene reso con "permesso di autore": copyright (diritto di autore) è formato dalle parola "copy" (copia) e "right" (diritto, ma anche destra), opposto di "left" (sinistra, ma anche lasciato).] V. Asta - Introduzione a Unix e GNU/Linux 93 Un altro gruppo ha cominciato ad utilizzare l’espressione "open source" per indicare un concetto vicino (ma non identico) a quello di "software libero". Preferiamo l’espressione "software libero" perchè, una volta sentito che si riferisce alla libertà piuttosto che al prezzo, richiama alla mente l’idea di libertà. Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA La copia letterale e la distribuzione di questo articolo nella sua integrità sono permesse con qualsiasi mezzo, a condizione che questa nota sia riprodotta. V. Asta - Introduzione a Unix e GNU/Linux 94 APPENDICE C La definizione di Open Source Questa definizione è disponibile on-line come http://www.opensource.org/docs/osd-italian.html Definizione di Open Source - Versione 1.9 Le sezioni indentate e in corsivo qui sotto appaiono come note della definizione di Open Source (OSD) e non sono una parte della OSD. Introduzione Open source (sorgente aperta) non significa semplicemente accesso al codice sorgente. La distribuzione in termini di programmi open-source deve soddisfare i seguenti criteri: 1. Ridistribuzione Libera e Gratuita Le licenze non potranno limitare alcuno dal vendere o donare i programmi come componenti di una distribuzione aggregata di software contenenti programmi di varia origine. La licenza non potrà richiedere royalties o altri pagamenti per tali vendite. Motivo: Imponendo la licenza a richiedere una ridistribuzione libera e gratuita, eliminiamo la tentazione di gettare alle ortiche molti guadagni di lungo periodo per fare qualche lira di breve periodo con le vendite. Se non lo facessimo ci sarebbe una forte pressione all’abbandono sui collaboratori. 2. Codice Sorgente Il programma deve includere il codice sorgente, e deve permetterne la distribuzione così come per la forma compilata. Dove alcune forme di un prodotto non sono distribuite con codice sorgente, ci deve essere un modo ben pubblicizzato di ottenerne il codice sorgente per niente più di una ragionevole riproduzione; preferibilmente, per via dei costi, scaricandolo da Internet gratis. Il codice sorgente deve essere la forma preferita in cui un programmatore modificherebbe il programma. Codice sorgente deliberatamente obnubilato non è permesso. Forme intermedie come l’output di un preprocessore o traduttore non sono permesse. Motivo: Richiediamo accesso al codice sorgente in chiaro perchè non puoi migliorare i programmi senza modificarli. Siccome il nostro obiettivo è rendere l’evoluzione facile, richiediamo che le modifiche siano facilitate. 3. Prodotti Derivati La licenza deve permettere modifiche e prodotti derivati, e deve permettere loro di essere distribuiti sotto le stesse condizioni della licenza del software originale. Motivo: La sola possibilità di leggere il codice non è abbastanza per sostenere un confronto paritario e una selezione evolutiva rapida. Per far avvenire questa rapida evoluzione, la gente deve poter sperimentare con le modifiche e redistribuirle. 4. Integrità del Codice Sorgente dell’Autore La licenza potrà impedire il codice sorgente dall’essere redistribuito in forma modificata solo se la licenza consentirà la distribuzione di pezze ("patch files") con il codice sorgente V. Asta - Introduzione a Unix e GNU/Linux 95 al fine di modificare il programma all’installazione. La licenza deve esplicitamente permettere la distribuzione del software costruito da un diverso codice sorgente. La licenza può richiedere che i lavori derivati abbiano un nome diverso o versione diversa dal software originale. Motivo: Incoraggiare il miglioramento è bene, ma gli utenti hanno diritto di sapere chi è responsabile del software che stanno usando. Gli autori e i tecnici hanno diritto reciproco di sapere cosa è loro chiesto di supportare e di proteggere la loro reputazione. Perciò, una licenza open-source deve garantire che il codice sorgente sia prontamente disponibile, ma può richiedere che esso sia distribuito come sorgente originale di base più le pezze. In questo modo, i cambiamenti "non ufficiali" possono essere disponibili ma prontamente distinti dal codice di base. 5. Nessuna Discriminazione contro Persone o Gruppi La licenza non deve discriminare alcuna persona o gruppo di persone. Motivo: Per ottenere il massimo beneficio dal processo, la massima diversità di persone e gruppi deve avere eguale possibilità a contribuire ai codici sorgente. Quindi proibiamo qualsiasi licenza open-source dall’escludere chiunque dal processo. Alcuni paesi, inclusi gli Stati Uniti, hanno restrizioni alle esportazioni per certi tipi di software. Una licenza conforme all’OSD può avvertire i licenziati di restrizioni applicabili e ricordare loro che sono obbligati a rispettare la legge; comunque, non potrà incorporare tali restrizioni essa stessa. 6. Nessuna Discriminazione contro Campi d’Applicazione La licenza non deve impedire ad alcuno da far uso del programma in un ambito specifico. Per esempio, non potrà impedire l’uso del programma nell’ambito di un’impresa, o nell’ambito della ricerca genetica. Motivo: La prima intenzione di questa clausola è di proibire trappole nella licenza che impediscano all’open-source di essere usato commercialmente. Vogliamo che gli utenti commerciali si uniscano alla nostra comunità, non che se ne sentano esclusi. 7. Distribuzione della Licenza I diritti allegati a un programma devono valere a tutti coloro cui il programma è redistribuito senza necessità dell’emissione di una addizionale licenza da parte dei licenziatari. Motivo: Questa clausola intende proibire la chiusura del software per mezzi indiretti come una richiesta di un accordo di non diffusione. 8. La Licenza non deve essere Specifica a un Prodotto I diritti allegati al programma non devono dipendere dall’essere il programmma parte di una particolare distribuzione di software. Se il programma è estratto da quella distribuzione e usato o distribuito all’interno dei termini delle licenze del programma, tutte le parti cui il programma è ridistribuito dovranno avere gli stessi diritti che sono garantiti nel caso della distribuzione di software originale. Motivo: Questa clausola impedisce ancora un’altra classe di licenze-trappola. V. Asta - Introduzione a Unix e GNU/Linux 96 9. La Licenza non deve Porre Vincoli su Altro Software La licenza non deve porre restrizioni su altro software che è distribuito insieme al software licenziato. Per esempio, la licenza non dovrà insistere che tutti gli altri programmi distribuiti sugli stessi supporti siano software open-source. Motivo: I distributori di software open-source hanno il diritto di fare le loro scelte riguardo al loro software. Sì, la GPL è conforme con questo requisito. Il software collegato con librerie a licenza GPL eredita la GPL solo se forma un solo prodotto, non ogni software con cui è semplicemente distribuito. V. Asta - Introduzione a Unix e GNU/Linux 97 APPENDICE D Alcuni comandi notevoli 1. Il comando test 1.1 Sintassi generale test <expr> test valuta <expr>, che è un’espressione booleana costruita con gli operatori unari e binari descritti nel seguito, e rinvia come codice d’uscita il risultato di questa valutazione: - ritorna 0 se <expr> è vera - ritorna 1 se <expr> è falsa - ritorna 2 se <expr> è mal formata (errore di sintassi). test chiamato senza argomento ritorna 1. Il programma ha un alias, consistente in una parentesi quadra aperta: [ . Per correttezza sintattica dal punto di vista della shell, un eventuale ultimo argomento consistente in una parentesi quadra chiusa viene ignorato dal programma, sicchè le due forme seguenti sono equivalenti: if test $PAR = string if [ $PAR = string ] Segue una lista dei principali operatori. 1.2 Test su dei file test -e <file> vero se il file <file> esiste test -r <file> vero se il file <file> esiste ed è accessibile in lettura. test -w <file> vero se il file <file> esiste ed è accessibile in scrittura. test -x <file> vero se il file <file> esiste ed è accessibile in esecuzione. test -f <file> vero se il file <file> esiste ed è un file regolare. test -d <file> vero se il file <file> esiste ed è una directory. test -c <file> vero se il file <file> esiste ed è uno special file in modo carattere. test -b <file> vero se il file <file> esiste ed è uno special file in modo blocco. test -p <file> vero se il file <file> esiste ed è un named pipe. test -L <file> vero se il file <file> esiste ed è un link simbolico. test -s <file> vero se il file <file> esiste ed è di lunghezza superiore a 0 byte. test <file1> -ef <file2> vero se i file <file1> e <file2> sono lo stesso file (stesso file-system e stesso inumber) ("equal file") test -t <fildes> vero se <fildes> è il file descriptor di un terminale. 1.3 Test sulle stringhe V. Asta - Introduzione a Unix e GNU/Linux 98 test -z <s1> vero se la stringa <s1> è vuota (ha una lunghezza di 0 byte). test -n <s1> vero se la stringa <s1> non è vuota . test <s1> = <s2> vero se le due stringhe <s1> ed <s2> sono identiche. test <s1> != <s2> vero se le stringhe <s1> ed <s2> sono differenti. test <s1> vero se la stringa <s1> non è quella nulla. 1.4 Test sui numeri test <n1> -eq <n2> verifica se <n1> è uguale ad <n2>, essendo queste due stringhe di carattere interpretate come due numeri decimali. test <n1> -ne <n2> verifica se la stringa <n1> è diversa da <n2> test <n1> -gt <n2> verifica se la stringa <n1> è superiore a <n2>. test <n1> -lt <n2> verifica se la stringa <n1> è inferiore a <n2> test <n1> -ge <n2> verifica se la stringa <n1> è superiore o uguale ad <n2> test <n1> -le <n2> verifica se la stringa <n1> è inferiore o uguale ad <n2>. 1.5 Combinazioni di primitive É possibile combinare tutte queste primitive tramite gli operatori: — ! negazione — -a and logico — -o or logico — ( expr ) Note: per raggruppare logicamente più test. — L’operatore -a ha maggiore priorità di -o . — Ogni primitiva, operatore o operando deve costituire un singolo token per la shell, dunque deve essere separato da spazi. — Le parentesi devono essere quotate per evitare che la shell le interpreti. 2. Il comando find 2.1 Sintassi generale find <path-name> <expr> find cerca ricorsivamente path-name nella struttura ad albero, secondo quanto specificato in expr. In expr è possibile precisare sia delle direttive di selezione, sia delle azioni da eseguire su ogni file selezionato. 2.2 I criteri di ricerca -name <pattern> seleziona unicamente i file il cui nome seleziona il pattern. N.B. - il pattern deve essere interpretato dal comando find e non dalla shell. -name ’*.c’ la shell non effettua alcuna operazione e find cercherà nella struttura ad albero i file che selezionano *.c Esempio : find /home/pippo -name ’*.c’ -print V. Asta - Introduzione a Unix e GNU/Linux 99 -perm <numero ottale> seleziona i file di cui i diritti di accesso sono esattamente quelli indicati dal numero ottale. Esempio : find /home/pippo -perm 0777 -print ... stampa tutti i file che sono autorizzati in read, write ed execute per l’utente, le persone del suo gruppo e tutti gli altri. -type <carattere> seleziona i file il cui tipo è quello indicato dal carattere, vale a dire : - c per un file speciale di tipo carattere - b per un file speciale di tipo blocco - p per un file named pipe - l per un file link simbolico - d per una directory - f per un file normale Esempio : find /home/pippo -type d -print ...stamperà tutte le directory e le sotto-directory dell’utente pippo. -links <numero decimale> seleziona tutti i file di cui il numero di link è dato dal numero decimale. Se il numero è preceduto da un + o da un -, questo significa qualunque valore superiore (o inferiore) al numero dato. Esempio : find /home/pippo -link +2 -print ...stamperà tutti i file che hanno più di due link. -group < gruppo> seleziona i file che appartengono al gruppo <gruppo>. Esempio: find /dev -group other -print ...stamperà tutti i file speciali appartenenti al gruppo other. -user <utente> seleziona i file di cui l’utente proprietario è <utente>. Esempio : find /dev -user pippo -print ...stamperà tutti i file speciali appartenenti a pippo. -size <numero decimale> seleziona i file la cui grandezza è di <numero decimale> blocchi. -inum <numero decimale> seleziona i file aventi <numero decimale> per i-number -atime <numero decimale> seleziona i file che sono stati utilizzati negli <numero decimale> ultimi giorni. Esempio : find /home/pippo -atime -2 -print -mtime <numero decimale> seleziona i file che sono stati modificati negli <numero decimale> ultimi giorni. -newer <file> seleziona i file che sono più recenti di quello passato in argomento. V. Asta - Introduzione a Unix e GNU/Linux 100 2.3 Combinazione di criteri Alcuni di questi caratteri possono essere raggruppati dagli operatori ( e ) . Attenzione: ( e), essendo dei caratteri speciali per la shell, devono essere quotati. Se più criteri sono messi in serie, find seleziona i file che rispondono a tutti i criteri. Lo "and logico" è dunque implicito. Esempio : find /home/pippo \( -name ’*.c’ -mtime -3 \) -print ... stamperà i file che terminano per .c e modificati negli ultimi tre giorni. Lo ’or logico’ è rappresentato dall’operatore -o. Esempio : find /home/pippo \( -name ’*.txt’ -o -name -’*.doc’ \) -print ... stamperà tutti i file che terminano per .txt o .doc Il ’not logico’ è l’operatore ! Esempio : find /home/gianni ! -user gianni -print ... stamperà tutti i file che non appartengono a gianni, ma che si trovano nella struttura ad albero sotto la sua home directory. 2.4 Le azioni possibili sui file selezionati -print stampa il nome dei file selezionati sull’uscita standard; questa è l’azione di default. Esempio: find /home/pippo -print ... stamperà tutta la struttura ad albero. -exec <comando> esegue il comando <comando> su tutti i file selezionati. <comando> deve essere della forma: <comando shell> ; Nel comando shell, {} sarà sostituito dal nome del file selezionato. Esempio : find /home/pippo -name ’*.txt’ -exec cat {} \; ... stamperà il contenuto di tutti i file che terminano per .txt in tutta la struttura ad albero /home/pippo. -ok <comando> stessa cosa di -exec, ma richiede conferma prima di ogni esecuzione. Esempio : find /home/pippo -name ’*.save’ -ok rm {} \; ... distruggerà (o no) ogni file che termina per .save, nella struttura ad albero /home/pippo, dopo aver richiesto conferma. 2.5 Un ultimo esempio find / \( -name a.out -o -name ’*.o’ \) -atime +7 -exec rm -f {} \; find cerca a partire dalla radice "/" tutti i file il cui nome è a.out, o che terminano per ".o". Se in più questi file non sono stati aperti da sette giorni, find lancerà il comando rm su questi file. V. Asta - Introduzione a Unix e GNU/Linux 101 APPENDICE E Espressioni Regolari 1. Espressioni regolari di tipo ed Le espressioni regolari sono definite da 11 regole. Un carattere designa, nelle regole seguenti, qualsiasi carattere, salvo <newline>. 1. Tutti i caratteri, salvo quelli speciali, selezionano se stessi. I seguenti caratteri sono speciali: /\[.ˆ$*] - ˆ è un carattere speciale solo se è posto all’inizio del pattern o se segue immediatamente [ - $ è un carattere speciale solo se è posto a fine pattern - * è un carattere speciale solo se segue un pattern - ] è un carattere speciale solo se c’è stata un’apertura [ 2. Un punto seleziona qualsiasi carattere. 3. Un \ seguito da un qualsiasi carattere eccetto ( e ) seleziona questo carattere (anche se è speciale). 4. Una stringa s non vuota entro [s] o [ˆs] seleziona qualsiasi carattere di questa stringa (o salvo quelli della stringa). Nella stringa s, \ non ha alcun valore speciale, non si possono dunque quotare dei caratteri. Per integrare ] alla stringa bisognerà porla in prima posizione. Una stringa a-b, dove <a> e <b> sono classificate in ordine ASCII crescente, designa l’intervallo che include quei caratteri ASCII. 5. Una espressione regolare della forma da 1 a 4 seguita da* selezionerà una sequenza da 0 a n caratteri, selezionati dall’espressione regolare. Dato che espressioni regolari definite dalle regole da 1 a 4 selezionano un solo carattere, * opera una ripetizione solo sul carattere precedentemente selezionato. 6. Per una espressione regolare x definita dalle regole da 1 a 8, \(x\) selezionerà ciò che x seleziona. 7. Un \ seguito da una cifra <n> seleziona ciò che l’espressione regolare che inizia all’n-ma \( ha selezionato. Per esempio, \([a-z]\1 selezionerà "rr" se \([a-z]\) ha selezionato "r" 8. Una espressione regolare x, definita dalle regole da 1 a 8 seguita da un’espressione regolare y, definita dalle regole da 1 a 7 selezionerà ciò che seleziona x, seguito da ciò che seleziona y, essendo inteso che ciò che seleziona x dovrà essere più lungo possibile, senza impedire ad y di selezionare. 9. Una espressione regolare definita dalle regole da 1 a 8 e preceduta da ˆ o seguita da $ deve selezionare all’inizio o alla fine della riga. 10. Una espressione regolare definita dalle regole da 1 a 9 seleziona più caratteri possibili cominciando da sinistra. 11. Una espressione regolare vuota designerà l’ultima espressione regolare utilizzata. un’espressione regolare vuota). (// è 2. grep fgrep egrep grep e la sua famiglia sono dei programmi che ricercano una stringa di caratteri nelle righe di uno o più files. V. Asta - Introduzione a Unix e GNU/Linux 102 — fgrep seleziona unicamente delle stringhe costanti. — grep seleziona delle espressioni regolari di tipo ed. — egrep seleziona delle espressioni regolari più complesse di ed. 3. Espressioni regolari di tipo egrep Un carattere seleziona qualunque carattere ad eccezione di <newline>. 1. Un \ seguito da un carattere seleziona questo carattere. 2. Un ˆ o $ seleziona l’inizio o la fine di una riga. 3. Un . seleziona qualsiasi carattere. 4. Tutti i caratteri ad eccezione di quelli speciali selezionano se stessi. 5. Una stringa tra parentesi quadre [] seleziona qualsiasi carattere di questa stringa. Un intervallo di caratteri di valore ASCII crescente può essere abbreviato da - (per esempio [a-z]). Un ] deve essere messo immediatamente dopo [ per poter essere incluso nell’intervallo. 6. Un’espressione regolare, seguita da * (+ ?) seleziona una sequenza da 0 a n (1 a n, 0 o 1) caratteri selezionati da questa espressione. 7. Due espressioni regolari concatenate selezionano ciò che è selezionato dalla prima, seguito da ciò che seleziona la seconda. 8. Due espressioni regolari separate da | o da un <newline> selezionano ciò che seleziona la prima, o ciò che seleziona la seconda. 9. Un’espressione regolare tra () seleziona ciò che seleziona questa espressione regolare. 10. L’ordine di precedenza degli operatori, per uno stesso livello di parentesi è: [] *+? concatenazione | e <newline> V. Asta - Introduzione a Unix e GNU/Linux 103 BIBLIOGRAFIA — [Asta 1982] V. Asta, "UNIX Utilisation", Axis Digital, Editions LASER 1982 / 1995 — [Asta 1984] V. Asta, "Administration UNIX", Axis Digital, Boulogne 1984 / 1992 — [Bach 1986] M. Bach, "The Design of the Unix Operating System", Prentice-Hall 1986 — [Behlendorf 1999] B. Behlendorf, "Open Source as a Business Strategy", in [DiBona et al. 1999] — [Bernardini2001] N. Bernardini, comunicazione privata, Roma 2001 — [Bovet e Cesati 2001] D.P. Bovet - M. Cesati, "Understanding the Linux Kernel", O’Reilly 2001 — [BSTJ 1978] AA.VV., "Unix Time-Sharing System", The Bell System Technical Journal, vol. 57 n. 6 part 2, Luglio-Agosto 1978 — [BSTJ 1984] AA.VV., "The Unix System", The Bell System Technical Journal, vol. 63 n. 8 part 2, Ottobre 1984 — [Cooper 2002] M. Cooper, "Advanced Bash-Scripting Guide", vers. 1.2, marzo 2002 (disponibile online come http://personal.riverusers.com/ thegrendel/abs-guide-1.2.tar.gz) — [Cortiana 2002] F. Cortiana, proposta di legge "Norme in materia di pluralismo informatico sull’adozione e la diffusione del software libero e sulla portabilità dei documenti informatici nella pubblica amministrazione" (XIV Legislatura Atto Senato n. 1188) (disponibile on-line come http://www.nwork.it/documento.asp?id=233 ; http://www.softwarelibero.it/altri/cortiana.shtml), 2002 — [Dalle e Jullien 2000] J.M. Dalle - N. Jullien, "NT vs. Linux, or Some Explorations into the Economics of Free Software", in Ballot G., Weisbuch G. (editors), Application of simulation to social sciences, Hermès, Paris, pp. 399-416 — [DiBona et al. 1999] C. DiBona - S. Ockman - M. Stone (editors), "Open Sources - Voices from the Open Source Revolution", O’Reilly 1999 (disponibile on-line come http://www.oreilly.com/catalog/opensources/book/toc.html) — [Garzarelli 2002] G. Garzarelli, "The Pure Convergence of Knowledge and Rights in Economic Organization: the Case of Open Source Software Development", DRUID Summer Conference on "Industrial Dynamics of the New and Old Economy - Who is Embracing Whom?" Copenhagen/Elsinore 6-8 June 2002 — [Giacomini 2002] D. Giacomini, "Appunti di Informatica Libera", versione 2002.03.10 (disponibile on-line come http://a2.swlibero.org) — [Grasso 2002] F. Grasso, "Il Software Open Source (OSS) - Scenario e Prospettive", rapporto AIPA (Autorità per l’Informatica nella Pubblica Amministrazione) (disponibile on-line come http://www.aipa.it/servizi[3/notizie[2/scenariooss.pdf ) — [Johnson e Ritchie 1978] S.C. Johnson - D.M. Ritchie, "Portability of C Programs and the Unix System," in [BSTJ 1978] — [Kernighan e Pike 1984] B.W. Kernighan - R. Pike, "The UNIX Programming Environment", Prentice-Hall 1984 — [Kuan 2001] J. Kuan, "Open Source Software as Consumer Integration Into Production", rapporto dello Institute for Economic Policy Research Stanford University, http://papers.ssrn.com/sol3/papers.cfm?abstract_id=259648 — [Lohr 2002] S. Lohr, "An alternative to Microsoft gains support in high places", New York Times, 5 settembre 2002 (abstract disponibile on-line come http://query.nytimes.com/search/abstract?res=F30B1FF83F5A0C768CDDA00894DA404482; articolo intero disponibile come http://www.axdigital.com/ vito/nytimes_linux.html) V. Asta - Introduzione a Unix e GNU/Linux 104 — [Maxwell 1999] S.E. Maxwell, "Linux Core Kernel Commentary", Coriolis Group 1999 — [McKusick 1999] M.K. McKusick, "Twenty years of Berkeley UNIX", in [DiBona et al. 1999] — [Nutt 2001] G. Nutt, "Kernel Projects for Linux", Addison Wesley 2001 — [Potortì 2002] F. Potortì, "Cos’è il http://softwarelibero.it/documentazione/softwarelibero.html , 2002 software libero", — [Raymond 1997] E.S. Raymond, "The Cathedral & the Bazaar", http://tuxedo.org/ esr/writings/cathedral-bazaar/cathedral-bazaar/ , 1997-2000, ristampato in [Raymond 2001]; trad. it. in http://www.apogeonline.com/openpress/doc/cathedral.html — [Raymond 2001] E.S. Raymond, "The Cathedral & the Bazaar - Musings on Linux and Open Source by an Accidental Revolutionary", Revised Edition, O’Reilly 2001 — [Ritchie 1979] D. Ritchie, "The Unix I/O System", in Unix Programmer’s Manual, 7. Edition, 1979 — [Rubini e Corbet 2001] A. Rubini - J. Corbet, "Linux Device Drivers", 2. Edition, O’Reilly 2001 — [Silberschatz, Galvin e Gagne 2003] A. Silberschatz - P.B. Galvin - G. Gagne, "Operating System Concepts", 6. Edition, Addison Wesley 2003, cap. 20 e Appendice A — [Thompson 1978] K. Thompson, "Unix implementation", The Bell Systems Technical Journal, vol. 57 n. 6 part 2, July-August 1978, pag. 1931-1946 — [Torvalds 1999] L. Torvalds, "The Linux Edge", in [DiBona et al. 1999] — [Valloppillil 1998] V. Valloppillil, "Open Source Software, a (new?) Development Methodology", Microsoft report, http://www.opensource.org/halloween/halloween1.html — M. Welsh, M.K. Dalheimer, T. Dawson, L. Kaufman, "Running Linux", 4. Edition, O’Reilly 2002 — [Wirzenius et al. 2001] L. Wirzenius - J. Oja - S. Stafford, "The Linux System Administrators’ Guide", vers. 0.7, novembre 2001 (disponibile on-line come http://people.debian.org/ bagpuss/ ) V. Asta - Introduzione a Unix e GNU/Linux 105 INDICE Principali Revisioni ............................................................................................................................ 2 1. Introduzione ....................................................................................................................................... 1.1 Storia ......................................................................................................................................... 1.2 Caratteristiche generali del kernel ............................................................................................ 1.3 Tipi di UNIX .............................................................................................................................. 1.4 Utilità dello studio di LINUX .................................................................................................... 1.5 Il fenomeno del Software Libero ............................................................................................ 1.6 Tipi di distribuzioni di GNU/LINUX ........................................................................................ 3 3 4 9 9 10 19 2. Come iniziare ................................................................................................................................... 2.1 Login ....................................................................................................................................... 2.2 La manualistica on-line ........................................................................................................... 2.3 Utilizzazione del terminale ..................................................................................................... 2.4 Primi comandi ......................................................................................................................... 2.5 Primi file di sistema ................................................................................................................ 20 20 20 21 23 25 3. L’interprete di comandi - 1 .............................................................................................................. 27 3.1 Separatore di comandi ............................................................................................................ 28 3.2 Ridirezione delle uscite verso un file ...................................................................................... 28 3.3 Ridirezione dell’ingresso standard a partire da un file ........................................................... 29 3.4 Pipe: comunicazione tra processi ............................................................................................ 30 3.5 Classificazione dei comandi ................................................................................................... 31 3.6 I processi in background ......................................................................................................... 31 3.7 Caratteri speciali ..................................................................................................................... 33 4. Sintassi dei comandi ........................................................................................................................ 4.1 Comandi semplici ................................................................................................................... 4.2 Pipeline ................................................................................................................................... 4.3 Liste ........................................................................................................................................ 4.4 Comandi .................................................................................................................................. 35 35 35 35 37 5. L’interprete di comandi - 2 .............................................................................................................. 5.1 Funzionamento generale ......................................................................................................... 5.2 Il Prompt ................................................................................................................................. 5.3 Quoting ................................................................................................................................... 5.4 Assegnazione e sostituzioni di variabili .................................................................................. 5.5 Sostituzione di comandi .......................................................................................................... 5.6 Sostituzione dei nomi di file ................................................................................................... 5.7 Comandi speciali .................................................................................................................... 5.8 Controllo degli ingressi/uscite ................................................................................................ 5.9 Costruzione di una tavola di argomenti .................................................................................. 5.10 Esecuzione di un comando ..................................................................................................... 5.11 Chiamata ................................................................................................................................. 5.12 Ambiente (environment) ......................................................................................................... 5.13 Segnali .................................................................................................................................... 42 42 42 42 43 45 46 46 50 51 52 52 53 53 6. Ricapitolativo della shell: comportamento e sintassi ....................................................................... 54 7. Comandi di uso corrente .................................................................................................................. 7.1 Manipolazione di file e directory ............................................................................................ 7.2 Informazioni su file e directory ............................................................................................... 56 56 56 i 7.3 7.4 7.5 7.6 7.7 7.8 7.9 Manipolazione delle informazioni su file e directory ............................................................. Visualizzazione del contenuto di file ...................................................................................... Informazioni sul contenuto di file ........................................................................................... Manipolazione del contenuto di file ....................................................................................... Modifica del contenuto di file ................................................................................................. Comandi di utilità per shell script ........................................................................................... Comandi generici .................................................................................................................... 56 57 58 58 58 59 59 8. Gli utenti speciali ............................................................................................................................. 8.1 root .......................................................................................................................................... 8.2 bin ........................................................................................................................................... 8.3 daemon .................................................................................................................................... 8.4 Gli altri utenti speciali ............................................................................................................ 61 61 61 62 62 9. I file speciali ..................................................................................................................................... 63 9.1 Cosa è un file speciale ............................................................................................................. 63 9.2 Device e pseudo-device .......................................................................................................... 66 10. Organizzazione dei filesystems di UNIX .......................................................................................... 10.1 Il sistema di file ....................................................................................................................... 10.2 Implementazione del filesystem ............................................................................................. 10.3 File systems montati ............................................................................................................... 67 67 72 77 11. Avvio del sistema ............................................................................................................................. 78 12. Il programma init ............................................................................................................................. 81 APPENDICE A: Cronologia dettagliata di UNIX ................................................................................... 87 APPENDICE B: La definizione di Free Software .................................................................................. 92 APPENDICE C: La definizione di Open Source .................................................................................... 95 ............................................................................................. 98 1. Il comando test ................................................................................................................................. 1.1 Sintassi generale ..................................................................................................................... 1.2 Test su dei file ......................................................................................................................... 1.3 Test sulle stringhe ................................................................................................................... 1.4 Test sui numeri ........................................................................................................................ 1.5 Combinazioni di primitive ...................................................................................................... 98 98 98 98 99 99 2. Il comando find ................................................................................................................................ 99 2.1 Sintassi generale ..................................................................................................................... 99 2.2 I criteri di ricerca .................................................................................................................... 99 2.3 Combinazione di criteri ........................................................................................................ 101 2.4 Le azioni possibili sui file selezionati ................................................................................... 101 2.5 Un ultimo esempio ................................................................................................................ 101 APPENDICE D: Alcuni comandi notevoli APPENDICE E: Espressioni Regolari .................................................................................................. 102 ....................................................................................................... 102 1. Espressioni regolari di tipo ed 2. grep fgrep egrep ............................................................................................................................. 102 3. Espressioni regolari di tipo egrep .................................................................................................. BIBLIOGRAFIA ........................................................................................................................... 103 104 ii