Unix Uso e Programmazione

download Unix Uso e Programmazione

If you can't read please download the document

Transcript of Unix Uso e Programmazione

Alessandra Demaria Franco Sbiroli

Chiara Regale Federico Stirano

UNIXun sistema operativo multiutente Guida all'uso e alla programmazione concorrente

Febbraio 1997

2

PREMESSA

PARTE I - I COMANDI UNIX

1

GESTIONE DI INPUT E OUTPUT1.1 REDIREZIONE DI INPUT E OUTPUT 1.2 PIPE

2

GESTIONE DEI DATI2.1 FILE SYSTEM 2.2 OPERAZIONI SUI FILE ORDINARI2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.4.1 2.4.2 2.4.3 2.4.4 CONCATENAMENTO: CAT VISUALIZZAZIONE: PG E MORE STAMPA: PR, LP E LPR COPIA E SPOSTAMENTO: CP, MV E RM USO DEI METACARATTERI DIVISIONE: SPLIT E CSPLIT CONFRONTO: DIFF SICUREZZA: CHMOD E UMASK LINE, HEAD E TAIL GREP CUT PASTE SORT, UNIQ E MERGE SED E TR VISUALIZZAZIONE DELLA DIRECTORY: PWD LISTING DEI FILE: LS CAMBIO DI DIRECTORY: CD NASCITA E RIMOZIONE DI DIRECTORY: MKDIR E RMDIR

2.3 OPERAZIONI SU FILE DI DATI

2.4 OPERAZIONI SULLE DIRECTORY

3

EDITING3.1 VI3.1.1 MODO TESTO 3.1.2 MODO COMANDI

4

PROGRAMMAZIONE4.1 VARIABILI4.1.1 VARIABILI FILE3

4.1.2 4.1.3 4.1.4 4.1.5

VARIABILI LOCALI E DI ENVIRONMENT HISTORY SINONIMI ARGOMENTI

4.2 4.3 4.4 4.5

OPERATORI ARITMETICI OPERATORI DI CONFRONTO JOB CONTROL STRUTTURE DI CONTROLLOIF-THEN-ELSE FOREACH WHILE SWITCH

4.5.1 4.5.2 4.5.3 4.5.4

4.6 DEBUGGING: CSH 4.7 STRUMENTI PER PROGRAMMARE IN C SOTTO UNIX4.7.1 COMPILAZIONE E LINK: GCC 4.7.2 LIBRERIE 4.7.3 DEBUGGING

PARTE II - IL SISTEMA OPERATIVO E LA GESTIONE DEI PROCESSI

5

IL SISTEMA5.1 IDENTIFICATORI

6

SYSTEM CALL PER LA GESTIONE DEI PROCESSI6.1 FORK 6.2 EXEC 6.3 WAIT6.3.1 WAITPID 6.3.2 PROCESSI ZOMBIE E USCITE PREMATURE 6.3.3 WAIT3

6.4 SIGNAL E KILL6.4.1 SEGNALI 6.4.2 USO DELLA SIGNAL E DELLA KILL

6.5 ESEMPI DI PROGRAMMAZIONE6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6ESP_FOR1.C ESP_FOR2.C ESP_FOR3.C ESPEXL.C E ESPEXV.C ESP_WAI1.C ESP_WAI2.C

77.1 7.2 7.3 7.4

INPUT E OUTPUTCONDIVISIONE DEI FILE APERTURA E CHIUSURA DEI FILE (OPEN CREAT CLOSE) LETTURA E SCRITTURA (READ E WRITE) LOCKING DEI FILE

7.4.1 LOCKF 7.4.2 FCNTL4

7.5 ESEMPI DI PROGRAMMAZIONE7.5.1 7.5.2 7.5.3 7.5.4 7.5.5ESPFILE4.C ESPFILE5.C ESPFILE6.C ESPFILE7.C LOC4.C

8

INTER-PROCESS COMMUNICATION FACILITIES8.1 SEMAFORI8.1.1 8.1.2 8.1.3 8.1.4 8.1.5 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.1 8.3.2 8.3.3 8.3.4 8.3.5 8.3.6 SUPPORTO OFFERTO DA UNIX CREAZIONE OPERAZIONI DI CONTROLLO ALTRE OPERAZIONI SEMAFORI.H MODELLO A SCAMBIO DI MESSAGGI SUPPORTO OFFERTO DA UNIX CREAZIONE OPERAZIONI SULLE CODE MESSAGGI.H SUPPORTO FORNITO DA UNIX CREAZIONE COLLEGAMENTO E SCOLLEGAMENTO SCOLLEGAMENTO DEALLOCAZIONE MEMCOND.H

8.2 CODE DI MESSAGGI

8.3 MEMORIA COMUNE

8.4 PIPE 8.5 ESEMPI DI PROGRAMMAZIONE8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.5.6PSEM.C PRODCON1.C PRODCON2.C MESSAGGI.C LSMEM.C ESPIPE1.C ESPIPE2.C

9

INTRODUZIONE AL CONCETTO DI MONITOR9.1 DEFINIZIONE DI MONITOR 9.2 LIMITI DEL COSTRUTTO MONITOR

1010.1 10.2 10.3 10.4 10.5

SISTEMI DISTRIBUITINETWORKS: PROTOCOLLI TCP/IP - THE INTERNET PROTOCOLS NETWORK LAYER IP INDIRIZZI INTERNET

10.5.1 SUBNET ADDRESSES:

10.6 TRASMISSIONE DATI 10.7 NUMERI DI PORTA 10.8 LIVELLI DI APPLICAZIONE5

1111.1 11.2 11.3 11.4

I SOCKETSTRUTTURE DEI SOCKET CREAZIONE EREDITARIETA E TERMINAZIONE SPECIFICA DI UN INDIRIZZO LOCALE

11.4.1 ROUTINE DI CONVERSIONE 11.4.2 ROUTINE DI MANIPOLAZIONE DI INDIRIZZI IP

11.5 CONNESSIONE DI UN SOCKET AD UN INDIRIZZO REMOTO 11.6 COME OTTENERE LE INFORMAZIONI RELATIVE AD UN HOST11.6.1 11.6.2GETHOSTBYNAME, GETHOSTBYADDR GETHOSTNAME, SETHOSTNAME

11.7 OPZIONI SUI SOCKET 11.8 LISTEN 11.9 ACCEPT

1212.1 12.2 12.3 12.4

IL MODELLO CLIENT/SERVERSINGLE-THREADED SERVER MULTI-THREADED SERVER TIERED SERVER PEER-TO-PEER SERVER

13

DAEMON

13.1 CODIFICA 13.2 INIZIALIZZAZIONE ED ESECUZIONE 13.3 TERMINAZIONE

1414.1 14.2 14.3 14.4 14.5

ESEMPI DI PROGRAMMAZIONE IN CESERCITAZIONE 1 ESERCITAZIONE 2 ESERCITAZIONE 3 ESERCITAZIONE 4 ESONERO MAGGIO 96

1515.1 15.2 15.3 15.4

ESEMPI DI PROGRAMMAZIONE IN C++ESERCITAZIONE 1 ESERCITAZIONE 2 ESONERO GIUGNO 96 CLASSI DI IPC

15.4.1 SEMAFORI : 15.4.2 CODE DI MESSAGGI : 15.4.3 MEMORIA CONDIVISA: 15.4.4 SOCKET: 15.4.5 ESONERO CONCORRENTE MAGGIO 96 IN C++

166

ESEMPI DI PROGRAMMAZIONE IN ASSEMBLER

PREMESSAI modelli di programmazione concorrente (es. client-server, semafori, memoria condivisa, etc.) studiati in questo lavoro, sono stati tradotti in linguaggio C, compilati e fatti girare su terminali che utilizzano il sistema operativo UNIX; per questa ragione, a introduzione del lavoro svolto, la prima parte vuole descriverne brevemente i comandi e le istruzioni principali: la cosiddetta "shell". Ne esistono svariate versioni, poco differenti l'una dall'altra. Ci riferiremo alla C-shell, la pi nota, per omogeneit di interpretazione. Per le altre versioni si rimanda il lettore ai testi citati in bibliografia.Ci sembra opportuno ora dare alcune avvertenze di carattere generale, prima di addentrarci nei particolari: due o pi comandi possono essere eseguiti in modo sequenziale; basta separarli con dei punti e virgola. Esempio: istruzione1; istruzione2; istruzione3 le varie versioni di shell sono caratterizzate da un tipo diverso di prompt: % C shell $ Bourne shell e Korn shell il sistema operativo distingue il minuscolo dal maiuscolo ( case-sensitive), sia per le istruzioni che per i nomi dei file; le istruzioni sono sempre in caratteri minuscoli, mentre i nomi dei file possono contenere i pi svariati caratteri. si possono inserire dei commenti, facendoli precedere dal simbolo # come per qualunque altro strumento informatico, il modo migliore per impadronirsene non leggere pedissequamente libri o guide come questa, ma quello di imparare "by example", sperimentando davanti al calcolatore (learning by doing). Tuttavia, soprattutto per chi alle prime armi, suggeriamo di consultare l'help in linea, digitando man al comparire del prompt. Per ulteriori informazioni sull'uso di man, si digiti man man. Nella seconda parte vengono affrontati i vari strumenti che permettono di programmare in modo concorrente: vengono affrontati i temi riguardanti la creazione e la gestione dei vari processi ed alcune chiamate di sistema che permettono a questi processi di comunicare tra loro. Vengono inoltre affrontati i problemi riguardanti la comunicazione tra processi che si trovano su macchine diverse (vedi i capitoli riguardanti i sistemi distribuiti, i socket ed i vari modelli client-server). Sono stati poi introdotti alcuni capitolo di esempi di esercitazione di programmazione in C, in C++ e in Assembler nel quale sono state riportate alcune esercitazioni svolte nellanno accademico 1995/96.

7

8

Parte I

I comandi UNIX

10

1 Gestione di input e output

1.1 Redirezione di input e outputIl sistema operativo vede tutte le periferiche di input e output come dei file virtuali. Queste, infatti, possono essere considerate, pi astrattamente, come dei flussi di informazione, sotto forma di sequenze di bytes, in ingresso (esempio: la tastiera) o in uscita (esempio: il video o la stampante). Durante l'esecuzione di una qualunque istruzione UNIX che richieda un ingresso o un'uscita di dati, se non viene specificata la provenienza o la destinazione di tale informazione, il sistema operativo assume come provenienza stdin (di norma la tastiera) e come destinazioni stdout o stderr (entrambi coincidenti con il video); la differenza tra stdout e stderr sta nel fatto che il secondo, oltre a segnalare un messaggio all'utente, blocca l'esecuzione del processo per effetto di un errore. E possibile ridirezionare stdin, stdout , stderr su altre periferiche o su dei file ordinari, utilizzando i simboli < > e >>. Esempi: istruzione < infile istruzione > outfile istruzione >>outfile utilizza infile (esistente!) come stdin crea outfile e lo utilizza come stdout utilizza outfile come stdout, accodando i dati a quelli gi presenti o creandolo se non esiste ancora. In realt possibile utilizzare anche il simbolo elenco Per maggiori informazioni sul comando cat, si veda il paragrafo 2.2.1.

12

disney topolinia

us1 paperopoli

nipoti

2 Gestione dei dati

2.1 File systemCome in DOS, la struttura ad albero e, quindi, di tipo gerarchico (si veda a tal proposito la figura 2.1).

semaforo.h

client_server.h

pippo.h minni.h

paperino.h paperone.h qui.h

qua.h quo.h fig. 2.1 - Esempio di struttura ad albero del file system

In UNIX ci sono tre tipi di file: ordinari : directory : speciali : sono semplici sequenze di caratteri; sta all'utente usarli come ritiene opportuno contengono l'elenco dei nomi dei file associati ai nodi della struttura ad albero le stesse periferiche di I/O (tastiera, video o stampante) sono viste come

dei file fittizzi Il nome di un file pu essere composto da un massimo di 14 caratteri, i quali possono anche non essere alfanumerici; comunque buona norma evitare caratteri come i seguenti: & ! {} ^ * () \ | #

2.2 Operazioni sui file ordinari2.2.1 Concatenamento: cat Sta per "Concatenate And Print". Questo comando apre il file (o i file) indicati come argomento e li copia su stdout, a meno di diverso indirizzamento dell'output tramite una pipe o una normale redirezione. $ cat capitolo_1 capitolo_2 capitolo_3 > libro Nell'esempio precedente, i tre file usati come argomento vengono aperti, accodati nell'ordine con cui compaiono come argomenti di cat e convogliati nel file di output libro. E possibile raggiungere lo stesso scopo in modo pi rapido; si veda a tal proposito il paragrafo 2.2.5, relativo all'uso dei metacaratteri.

2.2.2 Visualizzazione: pg e more Pg e more sono due comandi UNIX molto utili per la visualizzazione del listato di un programma in C o di qualunque file troppo lungo per essere contenuto in una sola schermata. Analogamente a quanto avviene in DOS, al termine di ogni pagina viene automaticamente inserita una pausa. Se programma.c il file da visualizzare, la grammatica la seguente: $ pg programma.c oppure $ more programma.c

2.2.3 Stampa: pr, lp e lpr Per impaginare un file e prepararlo alla stampa, si usa il comando pr, che provvede a mandare su stdout il file passato come argomento: $ pr programma.c Un uso pi sofisticato di pr consiste nelle specifiche di lunghezza e di divisione delle pagine.14

Esempio: $ pr -140 -o5 programma.c Con le opzioni aggiuntive dell'esempio precedente, il file programma.c viene diviso in pagine di 40 righe e stampato con un margine sinistro pari a cinque caratteri. Consultando man si ottengono informazioni sulle pi svariate opzioni dellistruzione pr. Un'altro utile uso di pr la formattazione della lista di file associati a una directory: $ ls -a | pr -2 . .hidden.doc memcond.h --e16 .. clientserver.h semaforo.h

Con questa linea di comandi UNIX in pipeline si vuole ottenere l'elenco ordinato dei file associati al nodo corrente, compreso quello nascosto .hidden.doc, e visualizzarli incolonnati per due e su un campo di sedici caratteri; per maggiori dettagli sull'uso di ls si veda il paragrafo 2.4.2.

2.2.4 Copia e spostamento: cp, mv e rm L'istruzione cp l'acronimo dell'inglese "CoPy". Si comporta come l'istruzione copy del DOS. Serve a copiare un file in un altro o in una directory remota, individuabile con il pathname, gi visto a proposito dell'istruzione cd. Esempi: $ cp origine.doc documento.doc $ cp f1 f2 f3 ../archivio/documento Copia il file origine.doc in documento.doc Copia i file f1, f2 e f3 in documento, accodandoli nello stesso ordine

L'istruzione mv l'acronimo dell'inglese "MoVe". Si comporta come cp, ma, dopo la sua esecuzione, non lascia una copia del file nella sua sede originaria: si limita a spostarlo letteralmente da un sito all'altro, con la possibilit di cambiargli nome. Esempi: $ mv relazione.doc riassunto.doc a: $ mv bozza.tex ../archivio/documento con Sposto i file relazione.doc e riassunto.doc sul dischetto Copia il file bozza.tex nel nodo archivio il nuovo nome (documento) Nel secondo esempio si presti attenzione al fattto che non deve esistere nella radice del dischetto una subdirectory documento o un altro file con nome documento. Nel primo caso l'istruzione mv si limiterebbe a spostare il file bozza.tex senza modificarne il nome o eliminarne l'estensione. L'istruzione rm (ReMove) identica all'istruzione del del DOS. Serve a cancellare uno o pi file dalla directory corrente. Esempi:

$ rm nomefile $ rm file1 file2 file3

2.2.5 Uso dei metacaratteri I metacaratteri servono ad abbreviare alcuni comandi UNIX, rendendo pi snella la programmazione; alcuni sono identici a quelli usati in DOS: * ? [a-z]* [abc+-*/] il "jolly": sostituisce qualunque sequenza di caratteri di qualsiasi lunghezza, tanto nel nome dei file, quanto nella loro estensione. si comporta come *, ma sostituisce un solo carattere; possibile anche accostarne pi di uno (vedi gli esempi seguenti). sostituisce tutti i caratteri compresi tra 'a' e 'z' nella tabella dei codici ASCII. sostituisce tutti i caratteri elencati tra parentesi quadre. E pi utile della sequenza precedente, quando questi caratteri non costituiscono una sequenza progressiva sulla tabella ASCII, ma sono piuttosto un insieme disordinato.

Esempi: $ cat *.txt > testo.txt Concatena tutti i file con estensione .txt secondo l'ordine della tabella ASCII e ridireziona il risultato sul file testo.txt. $ cp qu?.h c:/disney/paperopoli/nipoti Copia tutti i file con nome composto dalla striga qu pi un altro carattere incognito ed estensione .h , in c:/disney/paperopoli/nipoti. $ mv qu[ioa].h c:/disney/paperopoli/nipoti Copia tutti i file con nome composto dalla stringa qu, seguita da un terzo carattere che deve appartenere alla stringa contenuta tra le parentesi quadre, e con estensione .h in c:/disney/paperopoli/nipoti. Rispetto all'esempio precedente questo metacarattere pi restrittivo: la scelta consentita avviene solo tra caratteri ben determinati, tutelando il programmatore da spostamenti indesiderati di altri file (ad esempio que.h oppure quy.h). $ cat capitolo?? > tesina Concatena tutti i file con il nome composto da dieci caratteri, dei quali i primi otto corrispondono alla stringa capitolo, e ridireziona l'output sul file tesina. In questo caso il doppio metacarattere ? pu essere utile per sostiture tutti i numeri da 0 a 99. Se, ad esempio, vogliamo unire in un unico file tutti i capitoli della tesina di Sistemi Informativi II, digitando questa istruzione, li otterremo ordinati secondo l'ordine numerico; infatti, nella tabella dei codici ASCII, le cifre compaiono ordinate da 0 a 9. $ rm capitolo_[0-9]* Cancella tutti i file con estensione e nome che inizia con la stringa capitolo_ e termina con una cifra, cio con un carattere che, sulla tabella dei codici ASCII, figura tra il posto 48 e il posto 57. $ cat cap*.* > libro Concatena tutti i file con qualsiasi estensione e con il nome composto da una stringa di16

lunghezza qualunque, ma che inizia con la stringa cap, e ridireziona l'output sul file libro. Si faccia attenzione all'eccessivo grado di libert di cui gode questa istruzione; all'interno del file libro si potranno ritrovare anche file come cappello.doc o capretta.txt, probabilmente poco utili ai fini di una pubblicazione bibliografica! 2.2.6 Divisione: split e csplit Al contrario dell'istruzione cat, gi esaminata nel paragrafo 2.2.1, split permette di dividere file troppo lunghi in altri file di dimensioni desiderate. Esempio: $ split -100 libro ; ls libro xaa xab xac .... xqz Se volessimo dividere il file libro secondo una certa logica, ad esempio per capitoli o per argomenti, l'istruzione split risulta insoddisfacente. Infatti molto raro trovare un libro con dei capitoli di uguale lunghezza! A questo scopo si utilizza l'istruzione csplit, che sta per "context split". Esempio: $ csplit -f Cap libro "/Capitolo 1/" "/Capitolo 2/" ; ls libro Cap00 Cap01 Cap02 Nell'esempio precedente, csplit crea tre file, perch la numerazione parte da zero; tuttavia il primo dei tre un file vuoto, privo di significato. Grazie all'opzione -f, il sistema operativo crea un nuovo file quando trova l'intestazione "Capitolo".

2.2.7 Confronto: diff Processa due file evidenziandone le differenze Esempio: $ diff file1 file2 elenca le differenze tra file1 e file2

2.2.8 Sicurezza: chmod e umask Nei sistemi gestiti dal sistema operativo UNIX possibile proteggere alcuni file o alcune directory da altri utenti del sistema; la protezione pu riguardare la lettura (R), la scrittura (W) o l'esecuzione (X). Esistono tre livelli di sicurezza: il primo relativo all'utente (USER oppure OWNER), il secondo agli utenti appartenenti allo stesso gruppo (GROUP) e il terzo agli utenti estranei al gruppo (WORLD). In definitiva ci sono tre tipi di protezione per tre livelli di sicurezza.

Inoltre il permesso binario: o accordato (1) o non lo (0). Bastano quindi nove bit, raggruppabili in tre cifre in base otto, per descrivere lo stato di protezione di una file o di una directory. Chmod, acronimo di "CHange security MODe", l'istruzione UNIX che serve a fissare i bit di sicurezza. Esempio: $ chmod 777 miofile.exe Con questo comando, il file miofile.exe viene reso completamente trasparente agli occhi di tutti; infatti, traducendo il numero 7 in binario si ottiene la terna 111, che, per quanto detto precedentemente, equivale al permesso di lettura, scrittura ed esecuzione. Visto che il numero 7 riferito a tutti e tre i livelli (OWNER, GROUP e WORLD), chiunque in condizione di fare quello che vuole del file miofile.exe: leggerlo, modificarlo, mandarlo in esecuzione o addirittura cancellarlo. Esempio: $ chmod 666 miofile.exe E analogo all'esempio precedente, tranne per il fatto che l'esecuzione disabilitata per tutti, utente compreso. Infatti, traducendo in binario la cifra ottale 6, si ottiene la terna di bit 110, (lettura concessa, scrittura concessa, esecuzione non concessa). Esempio: $ chmod 755 miofile.exe In questo caso l'utente pu fare tutto ( 78 = 1112 ), mentre gli utenti appartenenti al suo stesso gruppo possono leggere ed eseguire senza scrivere (infatti 58 = 1012 ). Si veda a tal proposito la figura 2.2 num ottale 0 1 2 3 4 5 6 7 read 0 0 0 0 1 1 1 1 write execute 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 fig 2.2 conversione ottale-binario

E possibile evitare l'uso del codice ottale, grazie alle opzioni che seguono il comando chmod. Esempi: $ chmod ug+x,o-rwx miofile.exe Con le lettere u, g e o si intendono lo user, il group e il world; r, w e x rappresentano le protezioni di lettura, scrittura ed esecuzione; i caratteri + e - provvedono rispettivamente ad accordare o non accordare i permessi. Nell'esempio precedente user e group ricevono la possibilit di eseguire miofile.exe, mentre gli altri hanno negato qualunque permesso sullo stesso file.18

Anche i file directory sono soggetti ad analoghi privilegi di sicurezza. L'interpretazione leggermente diversa: l'abilitazione in lettura (READ) corrisponde alla possibilit di visualizzare i file associati al nodo corrispondente; l'abilitazione in scrittura (WRITE) consente di aggiungerne dei nuovi o cancellarne dei vecchi; l'abilitazione in esecuzione (EXECUTE) provvede a far comparire il nome del file direttorio nei pathname che ne rendano eseguibili i file associati: coincide in pratica con labilitazione ad accedere a una lista di file. Per fissare le protezioni di un nuovo file o di una nuova directory, si usa l'istruzione umask, che, per default, fissa a 666 la sicurezza dei file e a 777 quella delle directory. E per possibile rendere queste protezioni pi restrittive gi in partenza, aggiungendo all'istruzione umask il codice ottale relativo ai permessi negati a user, group e world. Attenzione: mentre la terna di cifre ottali che segue chmod da intendere in positivo, quella che segue umask da intendere in negativo. Esempio: $ umask 022 miofile.exe Ci vuol dire che, ripetto alla terna di default per le directory 777, viene negato a tutti gli altri utenti, compresi quelli dello stesso gruppo, il permesso di scrittura. La visualizzazione dello stato di protezione di un file o di una directory visualizzabile con l'istruzione ls-l, che d la lista dei file associati al nodo corrente con tutte le informazioni accessorie. I permessi di lettura, scrittura ed esecuzione negati a user, group e world sono sostituiti da un trattino, come si pu vedere nell'esempio del paragrafo 2.4.2.

2.3 Operazioni su file di datiSpesso risulta molto utile memorizzare dei dati in una forma pi sofisticata di quella sequenziale utilizzata dai file ordinari, ad esempio una tabella a due variabili (si veda la tabella 2.1). ALUNNO BIANCHI ROSSI VERDI ITALIANO MATEMATICA INGLESE sex sex cinque sette sex sette sette otto otto fig. 2.1- Esempio di tabella a due variabili CONDOTTA otto nove nove

A tal proposito UNIX consente di trattare questi file un p speciali come se fossero delle matrici con righe e colonne, grazie alle istruzioni descritte nei prossimi paragrafi.

2.3.1 Line, head e tail Il comando line d come output la prima riga di un file, che, nel caso di un file tabella, contiene l'intestazione del database. Con head e tail si possono visualizzare rispettivamente le prime e le ultime dieci righe del file. Per tutte e tre queste istruzioni ci sono due forme equivalenti: istruzione nomefile oppure istruzione > nomefile

2.3.2 Grep Questa istruzione risulta molto utile per trovare una stringa in uno o pi file. Senza alcuna opzione aggiuntiva, grep indirizza su stdout il nome del file su cui ha trovato la stringa passata a grep come argomento e l'intera linea di file che la contiene. La forma la seguente: $ grep Si faccia attenzione a racchiudere dentro doppie virgolette la stringa da cercare qualora questa fosse composta da pi di una parola, cio nel caso in cui contenga degli spazi (blanks) o dei caratteri di tabulazione (tabs). Si ricorda inoltre la possibilit di utilizzare i metacaratteri (paragrafo 2.2.5). Esempi: $ grep fata cenerentola.fav cenerentola.fav: Cenerentola aveva un'amica fata. Mentre piangeva, cenerentola.fav: La fata le disse: "Attenta! A mezzanotte in punto, dovrai In questo esempio senza nessuna opzione, grep ricerca e descrive tutte le occorrenze della parola fata nel file cenerentola.fav. $ grep -l principe *.* biancaneve.fav cenerentola.fav machiavelli.let Con l'uso del metacarattere *.* e dell'opzione -l, grep elenca tutti i file che contengono la parola principe, senza riportare testualmente la riga corrispondente all'occorrenza trovata. $ grep -n "sette nani" biancaneve.fav 52: Arrivata sera, rientrarono in casa i sette nani, cantando 68: era il pi piccolo dei sette nani: si chiamava Cucciolo Con l'opzione -n, grep sostituisce la visualizzazione del nome del file con il numero della riga in cui si verifica l'occorrenza della stringa sette nani. Si noti la presenza delle doppie virgolette, senza le quali il sistema operativo interpreterebbe sette come la parola da cercare e nani come il file sul quale operare la ricerca; in tal caso verrebbe segnalata l'impossibilit di aprire il file nani, con molta probabilit inesistente .

2.3.3 Cut Cut opera sullinput eliminando caratteri (opzione -c) o campi (opzione -f); questa istruzione risulta comoda se combinata in pipeline con altre istruzioni UNIX per liberarsi da informazioni ridondanti e inutili. Si invita il lettore a guardare sul manuale luso delle istruzioni join e awk.

2.3.4 Paste E un'istruzione molto duttile e applicabile in vari casi e con diverse sfumature. Paste lavora tanto sui file quanto su stdin; in grado di combinare insieme due o pi file, solo alcune20

righe, parti di file delimitati da opportuni caratteri, righe introdotte da stdin. Esempi: $ paste biancaneve.fav cenerentola.fav pinocchio.fav > fiabe.doc Usato in questo modo, paste si comporta esattamente come cat; apre i tre file biancaneve.fav, cenerentola.fav e pinocchio.fav e li unisce nel file di output fiabe.doc. $ ls > listafile; paste -s -d "\ t \ t \ t \n" listafile Incolla tutte i file trovati nel file direttorio listafile, creato ridirezionando l'output di ls, separandoli con i delimitatori evidenziati nella stringa che segue l'opzione -d. Nel caso specifico, ci sono tre caratteri di tabulazione e uno di "vai a capo". Un modo pi semplice per realizzare ci il seguente: $ ls | paste -- -- -biancaneve.fav cappuccetto.fav cenerentola.fav peter_pan.fav pinocchio.fav In questo caso paste lavora sull'output del comando ls grazie alla pipe . I file associati al nodo corrente vengono visualizzati tre per riga fino ad esaurimento e separati da caratteri di tabulazione.

2.3.5 Sort, uniq e merge Spesso risulta utile ordinare l'informazione contenuta in un file secondo una certa chiave. Le istruzioni sort e uniq permettono di farlo; la prima riproduce integralmente anche le righe ripetute, la seconda le rimuove. L'istruzione merge consente di fondere due file gi ordinati, mantenendone l'ordine. Esempi: $ sort -nr risultati.dat $ sort +0.0 -0.99 risultati $ sort -t: +0 -5 risultati ordina il file risultati.dat in ordine numerico inverso ordina i primi cento caratteri del file risultati ordina i primi cinque campi del file risultati partendo dal primo, caratterizzato dal numero d'ordine zero, a patto che siano separati dal carattere ':'.

Attenzione: le seguenti due istruzioni sono equivalenti: $ merge file1 file2 $ sort -m file1 file2

2.3.6 Sed e tr Sed (Stream EDitor) e tr (TRansform character by character) danno la possibilit di trasformare in modo semplice una grossa quantit di dati provenienti da stdin o da un file, dopo opportuna redirezione dell'input. Esempio:

$ sed -e "s/cenerentola/Cenerentola" fiaba.fav > fiaba_2.fav In questo modo tutte le occorrenze della parola cenerentola nel file fiaba.fav vengono automaticamente sostituite con la parola Cenerentola; l'output viene direzionato sul file fiaba_2.fav. Se le sostituzioni sono parecchie e complesse, pi conveniente prenderle da un file di comandi UNIX appositamente scritto a questo scopo. Per questo motivo l'opzione che segue sed non sar pi -e, ma -f. Esempio: $ sed -f miofile fiaba.fav > fiaba_2.fav dove miofile fatto cos: s/cenerentola/Cenerentola s/maga/fatina s/principe/Principe In questo modo tutte le occorrenze di cenerentola, maga e principe verranno trasformate in Cenerentola, fatina e Principe. Come sed, anche tr opera delle trasformazioni sui file, ma lo fa carattere per carattere e non su intere stringhe. Esempio: $ tr "[a-z]" "[A-Z]" < input.txt > output.txt Nell'esempio precedente tutte le lettere minuscole di un dato file vengono trasformate in maiuscolo. In quello seguente, dato in input il file cenerentola.fav, si ottiene un elenco ordinato alfabeticamente di tutte le parole che compaiono nella fiaba, con il conteggio delle occorrenze delle varie parole. E un'ulteriore dimostrazione dell'utilit e della duttilit della shell di UNIX e pu essere molto utile per eliminare le occorrenze di parole troppo usate. Esempio: $ tr [ ] [ \ 012] < cenerentola.fav | sort | uniq -c > finale Inizialmente ogni spazio trovato nel file cenerentola.fav (utilizzato come input con la redirezione) viene trasformato in un carattere di new line, il cui codice appunto 012; in questo modo ogni riga viene occupata da una sola parola. L'output dell'operazione di trasformazione viene utilizzato, grazie alla pipeline, come input dell'istruzione sort, che ordina alfabeticamente tutte le parole del testo. Il comando uniq serve a eliminare le parole ripetute; l'opzione -c provvede a contarne le occorrenze. I risultati vengono direzionati sul file finale.

2.4 Operazioni sulle directory2.4.1 Visualizzazione della directory: pwd Il comando pwd, dall'inglese "Print Working Directory", visualizza la directory corrente a partire dalla radice USR. In riferimento alla figura 2.1, se sono al nodo nipoti, digitando pwd, vedr sul video:

22

/disney/paperopoli/nipoti

2.4.2 Listing dei file: ls Il comando ls (LiSt of file) Visualizza i nomi delle subdirectory e dei file associati alla directory corrente in ordine alfabetico (nel senso dei caratteri ASCII). Trovandomi al nodo paperopoli e digitando ls, vedr: nipoti paperino.h paperone.h E possibile ottenere maggiori informazioni su file e directory grazie all'uso delle opzioni, precedute da un segno -. Quelle pi comuni ed usate sono -l e -ld. La prima visualizza la lista dei file e delle directory in formato esteso,mostrando cio i privilegi di lettura, scrittura ed esecuzione di user, group e world, il nome dello user, la lunghezza dei file in byte, le date e gli orari relativi allultima modifica effettuata sul file (o sulla directory); la seconda visualizza solo le directory, tralasciando i file ordinari. Ovviamente queste ed altre opzioni possono essere combinate fra loro come dimostra il secondo dei due esempi seguenti. Esempi: $ ls -l drwxrwxrwx -rw-r--r--rw-r--r-$ ls -ld drwxrwxrwx

2 sidue19 2048 Feb 1 18:45 nipoti 1 sidue19 1483 Feb 5 11:56 paperino.h 1 sidue19 6295 Feb 6 09:24 paperone.h 2 sidue19 2048 Feb 1 18:45 nipoti

Esiste inoltre la possibilit di nascondere dei file (i cosiddetti hidden file). Questi file sono visualizzabili con il comando ls grazie all'opzione -a e sono caratterizzati da un punto all'inizio del nome. Esempio: $ ls -a .segreto.doc nipoti paperino.h paperone.h

2.4.3 Cambio di directory: cd Se il lettore ha gi dimestichezza con i comandi DOS, pu risparmiarsi la lettura di gran parte di questo paragrafo; l'uso di cd (Change Directory) identico a quello che se ne fa in DOS, con due importanti sostituzioni. La prima quella di / (backslash) con / (slash). Inoltre, digitando il comando cd senza altri parametri, il DOS restituisce il nodo di lavoro (l'equivalente di pwd), mentre UNIX ritorna al nodo radice (l'equivalente di cd senza opzioni nel DOS). Per gli altri diremo che questo comando UNIX permette di muoversi sull'albero delle directories, con le seguenti estensioni:

. .. /

nodo corrente nodo padre nodo radice

Inoltre il simbolo / usato per formare i pathname; basta interporlo tra i nomi di directories legati da un rapporto di tipo padre-figlio. Per una maggiore comprensione del funzionamento di questo comando, si segua la seguente successione di comandi, con riferimento allo schema di figura 2.1. Esempio: $ cd disney/paperopoli/nipoti partendo dal nodo radice, sono sceso di due livelli sull'albero delle directories $ cd . sono rimasto allo stesso nodo; notare lo spazio prima del punto! $ cd .. sono risalito al nodo padre, cio paperopoli $ cd oppure cd / indipendentemente dal nodo in cui mi trovavo, sono ritornato alla radice $ cd disney/topolinia sono andato in topolinia passando per disney $ cd ../paperopoli/nipoti sono risalito a disney e, di l, attraverso paperopoli, sono ridisceso in nipoti Un caso particolare l'esecuzione di un'istruzione che coinvolga directory remote con la conservazione dell'ambiente originario. Supponendo di essere in USR (nella radice), si digiti il comando: (cd /disney/topolinia ; operazione) Il comando operazione sar eseguito a partire dal nuovo ambiente, creato con il cambio di directory. Alla fine dell'esecuzione di operazone, per effetto delle parentesi tonde, si ripristiner l'ambiente precedente; nel nostro esempio si ritorner automaticamente al nodo radice usr.

2.4.4 Nascita e rimozione di directory: mkdir e rmdir I comandi mkdir (MaKe DIRectory) e rmdir (ReMove DIRectory) servono a creare o cancellare delle subdirectories nel nodo di lavoro; sono seguiti dal nome della subdirectory da creare o da eliminare. Si noti che, affinch rmdir abbia successo, necessario che la directory da rimuovere sia vuota, cio priva di file o ulteriori subdirectories.

24

3 Editing

3.1 ViIl vi il text editor di default di UNIX. Si chiama semplicemente digitandone il nome; se si vuole aprire un file in particolare, basta digitare: $ vi nomefile E completamente governabile con la tastiera elementare; tuttavia i tasti cambiano significato a seconda del modo di funzionamento. Vi ne ha due.

3.1.1 Modo testo Il modo di inserimento del testo il modo di funzionamento pi comune; vengono inseriti nel file i caratteri digitati da tastiera. Si definisce parola una sequenza di caratteri delimitata da caratteri di punteggiatura, dai simboli $, %, dagli spazi (blank), dai caratteri di tabulazione (tab) o dal carattere di end-ofline (CR); si definisce linea una sequenza di parole delimitata da CR; infine si definisce pagina una sequenza di linee visibili sul video.

3.1.2 Modo comandi Quando l'editor si trova in questo stato, ogni carattere inviato da tastiera viene interpretato come un comando di servizio. Per ritornare al modo testo, basta premere ESC. Salva e/o esce ZZ :wq :w :w nomefile :q! Cancella e annulla x dw dd Salva ed esce dall'editor. Attenzione: le due lettere sono maiuscole! Come sopra, con lettere minuscole. Salva sul file corrente senza uscire. Salva con nome nomefile senza uscire. Esce senza salvare. Cancella il carattere. Cancella la parola. Cancella la linea.

Modifica r carattere cw parola c testo Inserisci i testo q testo o testo O testo

Modifica il carattere. Modifica la parola. Modifica la linea.

Inserisce il testo prima del cursore. Inserisce il testo dopo il cursore. Inserisce il testo sotto la linea corrente. Inserisce il testo sopra la linea corrente.

Taglia, seleziona e incolla dd Taglia (cut). y Mette in memoria il testo selezionato senza tagliarlo. p Incolla (paste). Ripeti e annulla . u U Ripeti l'ultima operazione. Elimina l'ultima operazione. Ripristina l'ultima operazione (cio Annulla Annulla).

Gestione di pi file :e nomefile Apre il file nomefile. :e# Ritorna al file precedente. :r nomefile Include il file nomefile Definizione di macro E possibile definire una sequenza di comandi per il vi che serve far eseguire in varie occasioni. La sintassi la seguente: : map < nomemacro > < comandi>

26

4 ProgrammazioneCon UNIX possibile creare delle sequenze di comandi in grado di soddisfare innumerevoli esigenze. Poche righe di istruzioni sono in grado di sostituire programmi ben pi grossi in C o in qualche altro linguaggio. Gli script file sono file contenente comandi UNIX, analoghi ai file batch del DOS. E possibile eseguire i comandi in essi contenuti digitando il nome del file, come avviene per qualunque istruzione semplice. Si possono anche inserire dei commenti, purch preceduti dal simbolo di cancelletto (#) inserito in prima colonna.

4.1 VariabiliLe variabili UNIX possono essere di vari tipi: ascii Coincidono con un carattere e occupano un solo byte. stringa Sono sequenze di caratteri, con un carattere di fine stringa. intere Sono numeri interi con segno. file Sono nomi di file, comprensivi del loro pathname. Sono forse le variabili pi importanti e, come tali, meritano di essere esaminate in un paragrafo a parte. A differenza delle variabili dei soliti linguaggi di programmazione, (ad esempio il C) non c' bisogno di dichiarare le variabili prima di usarle. Si faccia attenzione alla differenza tra il nome della variabile e il suo valore numerico, al quale si pu fare riferimento premettendo il carattere dollaro ($) al nome assegnato alla variabile. Esempio: $ set x=20 $ set y=$x+7 $y Assegna alla variabile x il valore 20. Assegna alla variabile y il valore di x aumentato di 7 unit. Visualizza il valore numerico di y, cio 27.

Una variabile pu anche essere composta di pi elementi; si veda, ad esempio, il caso del vettore: $ set vett=(20,15,40) $ $vett $ $vett[1] assegna alla variabile vettoriale vett i valori 20, 15 e 40. visualizza i tre valori numerici contenuti nel vettore vett. visualizza il valore numerico contenuto nella seconda

$ #vett

posizione del vettore vett; infatti la numerazione parte da zero, come in C. visualizza il numero di elementi che compongono vett

4.1.1 Variabili file Spesso risulta utile utilizzare delle variabili per accedere pi facilmente ai pathname dei file. Se digito l'istruzione UNIX: $ set f=/usr/disney/topolinia/pippo.h assegno alla variabile f il corrispondente pathname, che sar visualizzabile, come visto, nel modo seguente: $ $f /usr/disney/topolinia/pippo.h Tuttavia possibile evidenziare la parte dell'informazione che interessa maggiormente, aggiungendo delle opzioni precedute dai due punti (:). $ $f : h visualizza solo la sequenza dei nodi senza il nome del file: /usr/disney/topolinia $ $f : r visualizza anche il nome del file, ma senza l'estensione: /usr/disney/topolinia/pippo $ $f : e h $ $f : t pippo.h visualizza solo l'estensione ( complementare alla precedente): visualizza solo il nome del file, estensione compresa:

Inoltre possibile costruire delle variabili logiche con i nomi dei file, derivandone informazioni molto importanti: - r nomefile -w nomefile -x nomefile -e nomefile -f nomefile -d nomefile -o nomefile -z nomefile vero se nomefile leggibile; vero se nomefile scrivibile; vero se nomefile eseguibile; vero se nomefile esiste; vero se nomefile un file ordinario; vero se nomefile un file directory; vero se l'utente proprietario del file; vero se nomefile vuoto;

A differenza del C, lo zero coincide con TRUE, mentre l'uno con FALSE.

4.1.2 Variabili locali e di environment UNIX definisce al login alcune variabili locali utili per la programmazione. cwd directory corrente home directory al login logname nome dello user path percorso di ricerca28

prompt shell status term

contiene il prompt shell per esecuzione dei file script o subshell error code dell'ultimo comando. La variabile zero se tutto va bene, diversa da zero se si verificato un errore tipo di terminale usato

Tutte le variabili di ambiente (in grassetto) sono visualizzabili grazie al comando setenv e modificabili digitando: $ setenv < variabile > < nuovo-valore >

4.1.3 History History l'istruzione che consente di mantenere in memoria gli ultimi comandi corretti digitati sulla shell ed evitare in tal modo di doverli riscrivere interamente se li si vuole riutilizzare. Esempi: $ set history=20 mantiene gli ultimi venti comandi corretti in memoria; $ history elenca gli ultimi venti comandi; $ !12 esegue il dodicesimo dei venti comandi in memoria; $ !! esegue l'ultimo comando; $ !cp esegue il comando pi recente tra quelli che iniziano con la stringa cp; $ !?cp? esegue il comando pi recente tra quelli che contengono la stringa cp;

4.1.4 Sinonimi Per velocizzare i tempi di programmazione possibile definire una tabella personalizzata di sinonimi di comandi UNIX. L'istruzione che si usa a questo scopo alias. Esempi: $ alias h history $ alias $ unalias h definisce h come sinonimo di history; elenca la lista di tutti i sinonimi definiti al momento; annulla il sinonimo h.

Se devo iniziare una sessione di comandi UNIX nella quale prevedo di dover richiamare molto frequentemente l'istruzione history, mi conviene definire il sinonimo precedentemente evidenziato: si batte un solo tasto invece di sette.

4.1.5 Argomenti Mandando in esecuzione un file script, oltre a digitarne il nome, possibile passare al sistema operativo fino a un massimo di nove parametri (o dieci, se si considera lo stesso nome del file come parametro numero zero), utilizzabili come variabili interni al file stesso. Esempi: $ argv $* il numero di argomenti alla chiamata dello script; indica tutti gli argomenti;

$0 $3

nome del file; terzo argomento;

4.2 Operatori aritmeticiGli operatori aritmetici fondamentali sono i seguenti: + * / %

Il simbolo / restituisce il quoziente della divisione, mentre % d il resto. Inoltre sono usati anche gli operatori di incremento e decremento del C: x++ x-equivale a equivale a x=x+1 x=x-1

Come per il linguaggio C possibile abbreviare alcune espressioni: x+=5 x*=2 x-=3 x/=2 equivale a equivale a equivale a equivale a x=x+5 x=x*2 x=x-3 x=x/2

E importante ricordare che ogni espressione matematica deve sempre essere preceduta dal simbolo chiocciola (@).

4.3 Operatori di confrontoGli operatori di confronto sono identici a quelli del linguaggio C: < > = != ==

Sono molto usati nelle istruzioni di controllo,descritte nel paragrafo 4.5.

4.4 Job controlSi definisce job un singolo processo oppure un gruppo di processi trattabili come un unico processo ai fini della struttura di input e output In parole pi semplici un job l'insieme dei comandi lanciati da una stessa linea in modo sequenziale o in pipeline. Esempio: $ ls-l | more | lp Ci sono tre stati di job: esempio di job

30

Foreground Background

L'utente non ha accesso al terminale: pu solo leggere gli eventuali messaggi di output sul video. L'utente ha accesso totale al terminale, mentre il processo evolve. Per attivare un job in background basta digitare il carattere speciale & dopo la riga di istruzioni. Uno o pi comandi possono essere eseguiti in modo "background", facendoli seguire dal carattere &. Il sistema operativo garantisce all'utente la possilbilit di continuare a lavorare contemporaneamente all'esecuzione dell'istruzione precedente. $ comando_1&; comando_2&

Esempio: Nel caso in cui i comandi prevedano un output sul video, quest'ultimo deve essere opportunamente ridiretto su un file, o su un altra perifica, come illustrato nel paragrafo 1.1. stopped Esempi: $ ls $ ls > lista \& l'utente. Per sospendere un processo in foreground basta digitare . Per sospendere un processo in background bisogna digitare stop, seguito dal simbolo '%' e dal numero relativo al job in questione. Per conoscere questo numero si impartisce l'istruzione jobs. Esempio: $ jobs visualizza tutti i job attivi: [1] running lp [2] running ls-l | more | lp [3] running cat f1 f2 > elenco $ stop %2 $ fg %2 $ bg %2 sospende il secondo job; riattiva il secondo job in foreground; riattiva il job n2 in background; Il job sospeso. Esempio di job in foreground. Esempio di job in background; L'output del comando ls ridiretto sul file lista, perch il terminale deve essere disponibile per

4.5 Strutture di controlloCome accade per gli operatori logici e relazionali, anche le strutture di controllo di UNIX sono in gran parte ereditate dal linguaggio C; tuttavia si presti attenzione ad alcune differenze e alcune peculiarit. 4.5.1 If-then-else Il modo pi semplice di usare il salto condizionato il seguente: if < espressione > < istruzione >

Se, al posto di una istruzione semplice, bisogna inserire una sequenza di istruzioni, necessario usare questa forma: if < espressione > then ..... ..... endif La forma completa della struttura del salto condizionato quella del costrutto if < espressione > then ..... < istruzioni > ..... else ..... < istruzioni > ..... endif if-then-else:

4.5.2 Foreach E un'utilissima estensione del costrutto for del C. Invece che lavorare su un contatore intero, foreach in grado di esaminare, ad esempio, una serie completa di directory. Nell'esempio seguente, sono combinati i due costrutti if e foreach in modo tale che, ogni volta che il sistema operativo incontra un file con estensione tmp lo cancella. Esempio: foreach dir (topolinia,paperopoli,) if -f *.tmp then rm $dir/*.tmp endif end

4.5.3 While Il costrutto while simile a if, tranne che per la presenza dell' end al posto dellendif. while < espressione > ..... < istruzione > ..... end

4.5.4 Switch Come nei classici linguaggi di programmazione,switch pu essere sostituito da una successione di if-then-else annidati, ma risulta decisamente pi leggibile.32

switch < stringa > case pattern1

< istruzioni > breaksw case pattern1 : < istruzioni > breaksw case pattern1 : < istruzioni > breaksw default : < istruzioni > endsw Se stringa uguale a pattern1, pattern2 o pattern1, verranno eseguite le istruzioni corrispondenti; in altri casi verranno eseguite quelle di default. Si rammenta la necessit di chiudere lo switch con l'endsw.

:

4.6

Debugging: csh

Se si scrivono file script abbastanza lunghi, risulta utile avere uno strumento di ricognizione degli errori. In UNIX disponibile il comando csh seguito da un'opportuna opzione: $ csh -n nomefile $ csh -v nomefile inizialmente $ csh -x nomefile inizialmente genere compila il file script senza eseguirlo; per ogni linea del file viene mostrato come essa appare e come appare dopo ogni sostituzione di history; per ogni linea del file viene mostrato come essa appare e come appare dopo ogni sostituzione di qualunque (variabili, nomi dei file, comandi, ecc.);

4.7 Strumenti per programmare in C sotto UNIXLa programmazione in C su macchine che utilizzano UNIX concettualmente diversa da quella realizzata in ambiente Microsoft o Borland su PC.Infatti la peculiarit di UNIX proprio la programmazione concorrente, nella quale pi processi concorrono al risultato finale lavorando "in parallelo"; le virgolette sono d'obbligo, perch, come noto, UNIx un sistema time sharing (a divisione di tempo). Ci vuol dire che viene dedicato un piccolo intervallo di tempo di CPU a tutti i processi attivi, secondo certe politiche di assegnazione, pi o meno democratiche; il risultato apparente quello dell'esecuzione in parallelo dei vari processi.I passi della programmazione in C sotto UNIX sono i seguenti: Editing Compiling sorgente. oggetto ("object"), Link E la fase della scrittura del file sorgente ("source") con estensione .c, svolta grazie agli editor descritti nel capitolo 3; il pi usato, anche da noi, il vi. La compilazione consiste nell'esame grammaticale del file Si realizza con il compilatore c e produce il file con estensione .o. Il link l'inclusione di file, presenti in libreria o creati precedentemente dall'utente, utili per l'esecuzione del programma. Il compilatore gcc assolve anche alle operazioni di linking. Il prodotto il file eseguibile, che ha il nome a.out per default.

Make

E un comando che permette di compilare e linkare un programma con ununica operazione dellutente.

In questa sezione verranno trattati gli argomenti pi utili per capire come sono stati raggiunti i risultati descritti nella seconda parte di questo nostro lavoro. 4.7.1 Compilazione e link: gcc Il compilatore disponibile "on line" su sistemi che utilizzano UNIX gcc. Pu essere usato in vari modi, utilizzando le seguenti opzioni: gcc file.c Compila ed effettua il link del file sorgente file.c, producendo il file eseguibile a.out. gcc -c file.c Si limita a compilare il file sorgente file.c, producendo il file oggetto file.o. gcc -o pippo.exe file.c Compila ed effettua il link di file.c, producendo il file eseguibile pippo.exe. gcc -g file.c Prepara file.c per la fase di debugging. gcc -O file.c Ottimizza la compilazione; si presti attenzione alla differenza tra le opzioni -O e -o; un ulteriore riprova della profonda importanza di distinguere il maiuscolo dal minuscolo. gcc -I directory file.c Dichiara la directory per gli eventuali file da includere in file.c; questi file hanno tipicamente estensione .h. gcc -o pippo.exe f1.o f2.o f3.o Effettua il link di f1.o, f2.o e f3.o in pippo.exe. gcc -Ix file.c Effettua il link dei file di libreria, cercando nelle subdirectory /usr/ccs/lib oppure /usr/lib. gcc -L directory -Ix Effettua il link dei file di libreria, cercando nella directory evidenziata. gcc -G directory -Ix Permette la compatibilit con gli altri compilatori C

4.7.2 Librerie Si distinguono due tipi di librerie: statiche e dinamiche. statiche Sono incluse durante la compilazione, pertanto il programma ne contiene fisicamente il contenuto; conviene usarle soltanto quando non se ne pu fare a meno, perch si rischia di appesantire oltremodo il codice. dinamiche Ne viene eseguito il link solo durante l'esecuzione. Il vantaggio consiste in una maggiore brevit del codice eseguibile; tuttavia, durante l'esecuzione, il sistema operativo deve gestire l'accesso ai file di libreria in modo efficiente e veloce al tempo stesso.

4.7.3 Debugging Il debugger presente nei sistemi UNIX gdb; per preparare il file al debugging si deve usare il compilatore gcc con loperazione -g.

34

Parte II

Il sistema operativo e la gestione dei processi

37

5 Il sistemaCerchiamo innanzitutto di chiarire quali sono le funzioni di un sistema operativo (S.O.). Il S.O. svolge un ruolo molto importante nel funzionamento di un sistema di elaborazione; esso si occupa della gestione delle risorse hardware (CPU, memoria, dispositivi di I/O ...) e software (file, strutture dati ...) assegnandole in modo opportuno ai vari utenti del sistema. Il S.O. mette a disposizione di ogni utente una macchina virtuale, nascondendo all'utente tutti i particolari pi complessi del funzionamento della macchina. UNIX un S.O. di tipo time sharing, ovvero una specializzazione dei sistemi multiprogrammati che consente una interazione diretta tra l'utente e il sistema di elaborazione. In questi sistemi la CPU viene assegnata ciclicamente ad ogni utente per un certo periodo di tempo (meccanismo di schedulazione Round Robin) ,in tal modo ogni utente ha la sensazione di avere una macchina a propria disposizione. In questo modo il S.O. ottimizza lo sfruttamento della CPU mantenendo pi processi di utente nella memoria centrale ed eseguendo solo quelli effettivamente attivi, senza bloccare l'elaboratore su un processo che aspetta una stampante o un input da tastiera. Il S.O. costituisce l'interfaccia tra il sistema di elaborazione e l'utente, sollevando quest'ultimo dalla responsabilit di gestire i conflitti tra processi di utenti diversi. Dobbiamo chiarire la differenza sostanziale esistente tra un programma e un processo al fine di evitare confusioni in seguito: programma un'entit passiva, un file contenente una serie di istruzioni che descrive le operazioni che l'elaboratore deve eseguire; pu essere un insieme di comandi della shell oppure un insieme di comandi in linguaggio macchina ottenuto attraverso le fasi di compilazione e linkaggio un'entit attiva, cio l'esecuzione delle azioni descritte nel programma.

processo

5.1 IdentificatoriPoich il sistema Unix un sistema multiutente e multiprocesso, per una corretta gestione di tutto il sistema il kernel assegna a processi e utenti dei numeri interi detti identificatori. Per la gestione dei processi il kernel assegna a ciascuno di essi un identificatore di processo (PID). Il valore del PID varia di solito fra 0 e 30000. Esistono dei processi particolari che hanno un PID predefinito, il pi importante dei quali il processo init che mantiene il controllo degli accessi al sistema il cui PID 1. Un altro processo il cui PID prefissato lo scheduler il quale ha PID 0 e si occupa della gestione delle risorse, regolando l'assegnazione della CPU ai vari processi. Per ogni processo oltre al proprio PID disponibile il PID del processo genitore del quale si pu ottenere il valore attraverso la chiamata getppid.

Ogni processo inoltre appartiene ad un gruppo di processi. Un gruppo di processi l'insieme di tutti i processi figli dello stesso padre, che detto leader del gruppo. L'identificatore del gruppo corrisponde al PID del suo leader. La shell che viene utilizzata sul nostro sistema rende ogni suo figlio, cio ogni processo lanciato dall'utente, leader di un gruppo. Ad ogni utente corrisponde un identificatore di utente reale ed un identificatore di utente effettivo.Tali identificatori solitamente coincidono ma talvolta, se un processo utilizza un file di un utente diverso dal suo proprietario, possibile fargli cambiare l'identificatore di utente effettivo con quello del proprietario del file che viene utilizzato. Gli utenti del sistema inoltre vengono raggrupati in gruppi di lavoro (per esempio sidue su cclix1), perci ad ogni utente viene assegnato anche un identificatore di gruppo reale ed uno effettivo che, come per gli identificatori di utente, solitamente coincidono ma talvolta possono differire tra di loro. Un particolare identificatore di utente quello costituito dal valore 0, che identifica il superuser che si occupa di gestire il sistema e pu accedere a tutti i file ed ha privilegi particolare su tutti i processi (per esempio pu far terminare qualunque processo).

39

6 System call per la gestione dei processiUn processo pu ottenere svariati servizi dal kernel attraverso le chiamate di sistema. Una system call praticamente permette ad un processo di eseguire una operazione normalmente riservata al sistema operativo, in realt il kernel che la esegue restituendo al processo che ha richiesto tale servizio solo il risultato. Le modalit di passaggio dei parametri e dei risultati non solo possono variare in modo enorme da una versione ad un'altra del S.O., ma anche da una macchina all'altra. Dal punto di vista del programmatore ogni system call si comporta esattamente come una comune procedura che restituisce un valore al termine della propria esecuzione e che pu necessitare di alcuni parametri per essere avviata. Useremo quindi in modo equivalente i termini funzione e chiamata di sistema in quanto sono entit effettivamente coincidenti nell'uso pratico. Le varie system call hanno in comune il fatto di restituire il valore -1 in caso di errore; per permettere di conoscere la causa che ha generato l'errore, il sistema mette a disposizione una variabile globale (errno) in cui viene impostato un numero identificativo dell'errore. L'associazione tra tipo di errore e valore della variabile posta nel file errno.h. Per mezzo della funzione perror possibile visualizzare un breve messaggio che specifica il tipo di errore. In caso di riuscita invece ogni funzione restituir un valore appropriato, che potrebbe essere il PID del processo creato, l'identificatore di un gruppo di semafori e cos via. Alcune di esse oltre a un valore indicante la riuscita dell'operazione forniscono delle informazioni aggiuntive restituendo anche un puntatore a una struttura di informazioni.

6.1 ForkCrea un nuovo processo duplicando quello che esegue la fork.. Il processo che esegue la fork viene detto genitore o padre, il nuovo processo cos creato viene detto figlio. Il figlio ha le stesse strutture dati del padre, ma queste sono entit completamente separate, ci significa che le modifiche delle variabili di un processo non influenzano le variabili del parente. I file che il padre aveva aperto prima della fork sono condivisi dal figlio. La fork non duplica il file su disco, ma duplica il processo in memoria. Per usarla bisogna includere la libreria: . Sintassi int pid; pid=fork(); La funzione restituisce due valori, pid=0 al figlio e il process identifier di quest'ultimo al

padre. Se per qualche motivo non possibile generare il figlio, la fork restituisce il valore -1. Il processo figlio differisce dal padre per le seguenti caratteristiche: ha un nuovo PID ha le proprie copie dei descrittori di file del genitore il tempo rimasto fino a una segnalazione di allarme posto a zero nel figlio.

6.2 ExecLa chiamata di questa funzione permette, al processo chiamante, di far eseguire un programma completamente scollegato dal codice che lo invoca. Il processo che esegue la chiamata viene completamente sostituito da quello nuovo, il quale eredita esclusivamente il PID del chiamante. Questo significa che le strutture dati preesistenti scompaiono, dopo la chiamata, non vi perci alcun modo per il chiamante di riacquistare il controllo, di conseguenza la chiamata della exec determina la terminazione del processo chiamante. Il numero di processi presenti nel sistema dopo l'esecuzione della funzione rimane inalterato. Eistono varie implementazioni della funzione exec; queste differiscono per la modalit di passaggio dei parametri, le due pi importanti sono execl e execv. Per poterlo usare si deve includere il file: Sintassi int execl( char *pathname, char *arg0,....,char *argn, (char *)0); pathname programma arg0 stampare tramite la arg1,...., argn la stringa che contiene il percorso di localizzazione del seguire. la stringa che contiene il nome del programma da eseguire; se il percorso o il programma non esistono viene opportunamente impostata la variabile di errore che possiamo far funzione perror. la lista dei parametri richiesta dal programma; la fine della lista deve essere indicata con un puntatore a NULL (char*)0 passato come ultimo parametro.

int execv(char *pathname,char *argv[]); argv[] essere un un vettore di puntatori a carattere, i suoi elementi puntano alle stringhe contenenti il nome del programma (deve essere il primo) e nell'ordine i parametri necessari alla sua esecuzione, l'ultimo elemento deve puntatore a NULL.

Come si pu osservare l'uso della execl meno flessibile in quanto necessario fissare il numero di parametri passati al programma, mentre nel caso della execv il numero di parametri presenti nel vettore non fisso, (l'importante infatti che l'ultimo sia un puntatore a NULL) questo ci permette per esempio di richiedere i parametri all'utente che pu cos decidere di volta in volta quante e quali opzioni utilizzare per l'avviamento del programma. Osservazioni: La exec restituisce il controllo al processo chiamante esclusivamente in caso di errore. Qualsiasi segnale che fosse stato predisposto per terminare il processo chiamante far terminare il nuovo processo. Cio un qualsiasi evento del sistema che dovesse causare la terminazione del processo originario determiner la terminazione del nuovo processo.41

Qualsiasi segnale predisposto per essere ignorato dal processo chiamante verr ignorato anche dal nuovo processo. I segnali predisposti per essere catturati faranno terminare il programma.

6.3 WaitProvoca l'arresto di un processo fino a che uno dei suoi figli giunge al termine dell'esecuzione. Se il processo non ha alcun figlio attivo la funzione restituisce il valore -1, mentre se il genitore ha pi figli esso viene sospeso fino a che uno qualunque non termina; in questo caso viene restituito il PID del processo che terminato. Per il suo utilizzo necessario includere il seguente file: Sintassi int wait(int *stato); es: int num=0; union wait stato; ..... num= wait(&stato); La wait restituisce il PID di un processo in due casi: 1. un processo figlio terminato con exit; in tal caso il valore passato come argomento della exit viene memorizzato nella variabile stato. 2. un processo figlio stato terminato con un segnale.

6.3.1 Waitpid Questa chiamata permette di attendere la terminazione di un particolare figlio specificandone il PID. Sintassi int wait(int pid,int *stato,int flag); es: int num=0; union wait stato; ...... num=wait(pid,&stato,0666); pid il PID del processo del quale si vuole attendere la terminazione stato la variabile in cui viene memorizzato lo stato di terminazione del figlio flags pu assumere i seguenti valori WNOHANG non sospende il chiamante se lo stato del figlio specificato non immediatamente disponibile WUNTRACED riporta anche lo stato dei figli che sono sospesi, ma non hanno ancora riportato il loro stato da quando sono stati sospesi.

A seconda del valore assunto da pid si possono verificare i seguenti comportamenti 1. pid=-1 il comportamento identico alla wait 2. pid>0 si attende la terminazione del corrispondente processo 3. pid=0 si attende la terminazione di tutti i processi il cui identificatore di gruppo di processi uguale a quello del processo chiamante (vedi paragrafo5.1) 4. pid 0 PID_Figlio = 0. 2) Questa gestire un duplicato invece il differenza ci consente di utilizzare un ciclo IF per il lavoro dei due processi. Inizialmente viene effettuato controllo per verificare che la "fork" abbia realmente il processo: un valore negativo del PID identifica questa situazione. Gli altri due casi dell'IF distinguono codice del padre da quello del figlio.*/

6.5.2 esp_for2.c Questo programma che stato sviluppato in modo da non causare problemi al sistema, mostra gli effetti di una fork all'interno di un ciclo. Il padre originale genera MAX figli, ognuno di essi potr generare solo pi MAX-1 figli (perch?). In questo modo si evita di creare una quantit di processi tale da bloccare il47

sistema. Aumentando il parametro MAX si pu osservare come il numero di processi aumenti velocemente, si vuole far notare qui il rischio che comporta l'inserimento di una fork all'interno di un ciclo. E importante ricordare che con un ciclo errato si potrebbe rendere necessario lo shutdown della macchina perch una serie di processi che continua a generare altri processi che continuano a generare altri processi che continuano a generare ...., non pi arrestabile; quindi attenzione!/* Questo secondo esempio illustra gli effetti dell'inserimento di una chiamata della System Call "Fork" all'interno di un ciclo FOR */ #include #include #define MAX 3 int main() { int f_pid[MAX]; int conta; for(conta = 0; conta < MAX; conta++) { if((f_pid[conta] = fork()) < 0) { perror("FORK fallita: "); exit(1); } else if(f_pid[conta] == 0) printf("Sono il figlio di livello %d.\n", conta); else printf("Sono il padre di livello %d.\n", conta); } } /* 1)Poiche' il ciclo FOR generera' un certo numero di processi, memorizzare i PID dei processi via via creati si e' preferito utilizzare il vettore di interi "f_pid[]", la cui dimensione viene predefinita come costante. per 2)Il ciclo FOR contiene sostanzialmente due operazioni; una chiamata alla "Fork" per la duplicazione del processo ed un ciclo di controllo e gestione Padre-Figlio. 3)Dopo la compilazione,che si consiglia di ripetere con valori gradualmente crescenti di MAX, si nota un effetto interessante: IL NUMERO DI FIGLI CRESCE ESPONENZIALMENTE AD OGNI LIVELLO. Il tutto si spiega constatando che: - al livello zero solo un processo esegue la "Fork"; - al livello uno la "Fork" viene eseguita sia dal padre che dal figlio di livello zero; - al livello due avro' due padri e due figli; - al livello tre quattro padri e quattro figli; - etc... NOTA : dopo la compilazione, contando i processi che presentano uno stesso numero di livello, e' facile rendersi conto della loro crescita esponenziale. */ NOTE:

IF

6.5.3 esp_for3.c Questo programma mette in evidenza il campo di visibilit dei processi in relazione a variabili locali e globali. L'esempio finalizzato a capire come una variabile globale venga progressivamente aggiornata da qualsiasi processo agisca su di essa, mentre la variabile locale venga ereditata sempre con lo stesso valore dai figli./* Creazione di figli e valutazione variabili. In questo esempio si vuole osservare la relazione esistente tra processi che presentano un legame di parentela di tipo Padre-Figlio in relazione all'incremento di variabili locali e globali */ #include #define MAXFIGLI 5 int var_global = 100; void crea_figlio(int); main() { crea_figlio(1); } void crea_figlio(int l) { int pid,var_local=10; if ((pid = fork()) < 0) { printf("Errore creazione figlio\n"); exit(1); } else if (pid == 0) { var_local++; var_global++; printf("Come FIGLIO level %d: var_global %d var_local %d\n", l,var_global,var_local); if (l < MAXFIGLI) crea_figlio(l+1); exit(0); } printf("Come PADRE: var_global %d var_local %d\n",var_global,var_local); } /* NOTE: 1) La funzione 'crea_figlio()' e' il corpo del programma e richiama se stessa ricorsivamente tante volte quante specificato nella costante 'MAXFILGLI'. 2) Il codice del figlio esegue l'incremento delle variabili 'var_local' e 'var_global' che sono rispettivamente locale e globale; e' dunque interessante notare come l'incremento della variabile globale sia effettivo, mentre quello della variabile locale conduca ad ogni iterazione allo stesso valore. 3) Il padre ed il figlio corrispondenti ad una stessa iterazione sono individuati da uno valore della 'var_global' che nel padre risulta inferiore di un'unita' rispetto al figlio. */

49

6.5.4 espexl.c e espexv.c Questi esempi illustrano un semplicissimo uso della system call exec; nel primo si richiede l'esecuzione del comando ls, nel secondo che utilizza la chiamata execv possibile specificare da tastiera le opzioni del comando. espexl.c#include #include main () { printf ("La prossima istruzione eseguira' ls con argomento -l"); execl ("/bin/ls","ls","-l",(char *) 0); /* se fallisce il programma continuera' il suo codice */ printf ("EXECL fallita !"); exit (1); }

espexv.c

/* uso di execv per lanciare ls */ #include #include #include #define MAX 10 main() { int i=1; char *vet[MAX], s[3], *zero = "0", *p="ls"; int size = sizeof(s); printf("Introdurre i parametri per l'esecuzione di ls (uno per riga seguito da ENTER)"); scanf("%s",s); while(strcmp(s+(i-1)*size, zero) != 0) { vet[i] = s+(i-1)*size; scanf("%s", s+i*size); i++; } vet[i++] = (char *)0; vet[i] = '\0'; vet[0] = p; execv("/bin/ls",vet); /* se la execv il programma continuera'il suo codice */ printf("execv fallita!"); exit(1);

}

6.5.5 esp_wai1.c Questo programma illustra il meccanismo con cui un processo attende la terminazione di due figli. Il padre non pu sapere a priori quale dei due figli terminer per primo: pertanto costretto a confrontare il valore restituito dalla wait con i pid dei suoi due figli. Nel caso ci siano molti figli da attendere un meccanismo analogo si pu ottenere contando il numero di figli creati quindi inserendo la wait all'interno di un ciclo for.

/* Questo esempio illustra il meccanismo con cui un processo padre aspetta due figli creati con la System Call 'Wait' */ #include #include #include #include #include

main() { int child0, child1; int child_term; union wait stato; if((child0 = fork()) < 0) { perror("Errore nella fork: "); exit(1); } else if(child0 == 0) { /* CODICE DEL PRIMO FIGLIO */ printf("Sono il primo figlio.\n"); exit(0); } else { if((child1 = fork()) < 0) { perror("Errore nella fork: "); exit(1); } else if(child1 == 0) { /* CODICE DEL SECONDO FIGLIO */ printf("Sono il secondo figlio.\n"); exit(0); } else { /* CODICE DEL PADRE */ child_term= wait(&stato); if(child_term == child0) printf("Il primo figlio ha terminato l'esecuzione.\n"); else printf("Il secondo figlio ha terminato l'esecuzione.\n"); wait(&stato); printf("Anche l'altro figlio ha terminato l'esecuzione.\n"); } }

}

6.5.6 esp_wai2.c Questo programma rappresenta una piccola variazione del precedente: l'attesa dei due figli creati da parte del padre viene implementata tramite un ciclo for./* Questo programma rappresenta una piccola variazione del precedente nella quale l'attesa dai due figli creati da parte del padre viene implementata tramite un ciclo 'FOR' #include #include 51

*/

#include #include #include main() { int pid1, i, pid2; int pidfine; union wait stato; pid1 = fork(); if(pid1 < 0) { perror("Errore nella fork: "); exit(1); } else if(pid1 == 0) { printf("Sono il figlio1.\n"); exit(0); } else { if((pid2 = fork()) < 0) { perror("Errore nella fork: "); exit(1); } else if(pid2 == 0) printf("Sono il figlio2.\n"); else for(i = 0; i < 2; i++) { pidfine = wait(&stato); if(pidfine == pid1) printf("fine figlio1.\n"); else printf("fine figlio2.\n"); } } }

53

7 Input e outputIn Unix la parola file ha un significato pi ampio, non rappresenta solo un insieme di record su di un supporto di qualsivoglia tipo. Il S.O. considera come file anche tutti i dispositivi periferici, stampanti, schermo, tastiera,.... Di conseguenza tutte le operazioni di input-output su tali dispositivi vengono viste dall'utente come operazioni su di un file. Sono disponibili due metodologie di accesso ai file, la prima fornita dal S.O. con le system call che permettono un input-output di basso livello e la seconda costituita dalle librerie standard di I/O che permettono una manipolazione pi evoluta e garantiscono una certa portabilit tra i vari sistemi. Per poter lavorare con i file necessario aprirli, se non esistono ancora bisogna prima crearli, al termine delle operazioni buona norma chiuderli anche se il sistema operativo generalmente provvede a chiudere i file che non sono stati chiusi dall'utente. Naturalmente non necessario aprire i file standard di input output cio la tastiera e il video perch vengono automaticamente aperti dalla shell all'avviamento della sessione; ad essi sono associati i descrittori 0 e 1.

7.1 Condivisione dei filePrima di descrivere le operazioni per la manipolazione dei file vale la pena di spendere un paio di parole sulla condivisione dei file tra processi parenti.

Posizione Puntatore Posizione Puntatore corrente Puntatore alli-node corrente corrente alli-node all i-node Id n

Id 1

Tabella dei procesi

Id n

informa= informa= zioni informa= zioni di i-node dizioni i-node di i-node

Tabella dei file node

Tabella degli i-

Figura 7.1 Condivisione dei file tra i processi Osservando la figura 7.1 si pu notare come il kernel gestisca le informazioni relative ai file aperti dai processi. Tale figura si riferisce al caso in cui pi processi aprono lo stesso file; di tali processi due sono parenti (padre e figlio), mentre un terzo scorrelato dagli altri due. Il S.O. innanzitutto crea una tabella in cui sono contenute tutte le informazioni relative a ciascun processo presente in memoria. Per ogni processo tra le varie cose si ha un vettore di file pointer; i descrittori di file non sono altro che gli indici di questo vettore. Ogni puntatore punta ad un elemento in una tabella dei file; ognuno di questi elementi contiene la posizione corrente all'interno del file. Il puntatore all' i-node fa riferimento ad un elemento in una tabella degli i-node, ognuno di questi elementi contiene tutte le informazioni fisiche del file. La tabella dei file necessaria per permettere a pi processi la condivisione del file. Infatti se la posizione corrente fosse mantenuta all'interno della tabella degli i-node tutti i processi sarebbero obbligati ad accedere allo stesso punto del file. In questo modo invece ogni processo che tramite una open accede ad un file ha un suo elemento della file table e una sua posizione corrente, ovviamente l'i-node puntato sar lo stesso poich il file fisico sempre lo stesso. L'unico modo in cui un processo pu avere pi descrittori di file che identificano un solo elemento della tabella dei file tramite la chiamata di sistema dup; questo il caso del terzo processo che possiede due descrittori di file che puntano allo stesso elemento nella tabella dei file. Se il processo invece apre pi volte lo stesso file avr descrittori di file che identificano elementi della tabella dei file diversi. Un elemento della tabella dei file pu essere puntato da pi processi solo se questi processi sono parenti. Il processo figlio infatti eredita tutti i descrittori di file aperti dal padre; questo il caso dei primi due processi in figura. Normalmente processi non legati da parentela non possono condividere un elemento della tabella dei file.55

Tutte le operazioni effettuate da uno qualunque dei processi imparentati sul file condiviso influenzeranno le operazioni degli altri, poich la posizione corrente all'interno del file viene essa stessa condivisa (vedi figura 7.1).

7.2 Apertura e chiusura dei file (open creat close)Per l'utilizzo di tutte queste system call bisogna includere il file < fcntl.h > Open Sintassi int open(char *name, int flags,int perm); In caso di successo della chiamata viene restituito il descrittore del file, al quale si far riferimento per operare sul file. In caso contrario viene restituito il valore -1 Esaminiamo ora i parametri necessari alla chiamata. name rappresenta il nome del file che si desidera aprire. flags un intero che specifica il modo di apertura del file. Esso pu assumere i seguenti valori: O_RDONLY apre per la sola lettura O_WRONLY apre per la sola scrittura O_RDWR apre per lettura e scrittura O_APPEND il puntatore al file viene automaticamente posizionato alla fine del file ogniqualvolta si effettua una operazione di scrittura O_CREAT crea il file se non esiste gi O_ TRUNC tratta il file preesistente come se fosse vuoto (il file viene ripulito dal suo contenuto: in questo modo scrivendo sul file non si corre il rischio di avere al fondo parti del file preesistente) O_EXCL provoca errore se si tenta di creare un file che esiste gi perm un parametro che viene utilizzato per specificare i diritti di accesso quando si crea il file. E possibile specificare pi flag contemporaneamente utilizzando la forma flag1|flag2| ..... Creat Questa chiamata obsoleta in quanto si pu ottenere lo stesso risultato per mezzo della chiamata open, sufficiente specificare i flag nel seguente modo O_CREAT|O_WRONLY| O_TRUNC. Viene mantenuta per la compatibilit, la sua sintassi Sintassi int creat(char *name, int perm); I parametri assumono lo stesso significato dei corrispondenti parametri della open. Close E la system call che permette di chiudere un file, cio di deallocare il corrispondente

descrittore. Sintassi int close(int fd);

7.3 Lettura e scrittura (read e write)Read Sintassi int read(int fd,char *buf, int n); fd buf n il descrittore del file restituito dalla open la stringa sulla quale verranno scritti i caratteri letti il numero di byte che si intende leggere

La chiamata restituisce il numero di byte effettivamente letti e tale valore pu essere inferiore a quello specificato. Se il valore restituito zero, stato incontrato un end of file, come al solito se restituisce -1, si verificato un errore. Write Sintassi int write(int fd, char *buf,int n); fd buf n il descrittore del file restituito dalla open la stringa dalla quale vengono presi i caratteri che verranno scritti sul file il numero di byte che si intende scrivere

La system call restituisce il numero di byte effettivamente scritti. Lseek La posizione corrente all'interno di un file misurata in numero di byte a partire dall'inizio del file; quando si crea un file essa viene posizionata all'inizio. Ogni operazione di lettura e scrittura aggiorna la posizione corrente all'interno del file, a seconda del numero di byte coinvolti nell'operazione. Volendo selezionare un punto preciso da cui iniziare la lettura o scrittura, possibile utilizzare la funzione lseek. Sintassi int lseek(int fd,long offset,int whence); fd offset, whence il descrittore del file sono due parametri interdipendenti, il comportamento della lseek risulta essere il seguente: se whence vale SEEK_SET la posizione viene fissata a un numero di byte pari a offset dall'inizio del file se whence vale SEEK_CUR la posizione viene fissata a un numero di byte pari a offset a partire dalla posizione corrente (offset pu essere sia positivo che negativo)57

se whence vale SEEK_END la posizione viene fissata a un valore pari alla lunghezza del file pi offset (offset positivo o negativo) Restituisce un long int che indica la nuova posizione.

7.4 Locking dei fileUno dei problemi pi importanti della programmazione multiutente si presenta nell'utilizzo di file comuni come ad esempio un database. In generale il problema che possiamo osservare che se una applicazione legge un file in memoria per compiere su di esso delle elaborazioni, generalmente impiegher un certo tempo prima di modificare il file su disco. Se nel frattempo un'altra applicazione accede allo stesso file e lo elabora, quando lo salver molto probabilmente distrugger le modifiche apportate dal primo processo. La soluzione pi immediata a questo problema quella di impedire l'accesso al file quando un utente sta gi lavorando su di esso. Questa pratica in certi casi pu risultare non molto funzionale, infatti finch due utenti si contendono l'editing di un file di testo palese che uno dei due debba venire interdetto fino a che l'altro non termini le operazioni, ma nel caso in cui le modifiche di una parte del file non siano strettamente correlate con le modifiche di altre parti (caso tipico dei database), non necessario, anzi sconsigliabile, che sul file lavori un utente per volta, in quanto in questo modo si costringerebbero gli utenti a lunghi tempi d'attesa. Per risolvere questi problemi esiste la possibilit di effettuare il locking di un file o di parte di esso. Come si gia detto in alcuni casi necessario impedire l'accesso a tutto il file, questo comporta l'attesa degli altri processi per tutto il tempo in cui viene utilizzato il file. Questo modo di operare pu essere utilizzato anche quando non strettamente necessario purch siano verificate le seguenti condizioni 1. gli utenti che accedono al file siano mediamente pochi 2. il file venga bloccato per tempi mediamente brevi ovviamente pochi e brevi sono quantit che dovranno essere quantificate volta per volta a seconda del grado di disponibilit del file che deve essere raggiunto. Un discorso di questo tipo non ha senso qualora il numero di utenti sia elevato. Supponiamo ad esempio di avere un sistema di prenotazioni aeree o ferroviarie; non certamente ragionevole bloccare tutto il file: se un'agenzia sta prenotando sul MilanoRoma non ha senso impedire ad un'altra di prenotare sul Roma-Milano. La cosa pi sensata in questo caso risulta essere il locking del record relativo al treno esaminato, al limite se il cliente fosse interessato ad un biglietto di prima classe si potrebbe bloccare solo la parte del record riguardante tale classe, aumentando cos, il numero di possibili utilizzatori del record. Volendo rendere accessibile il file al massimo numero di utenti si potrebbe ripetere il precedente ragionamento e bloccare solo una singola carrozza, solo uno scompartimento, il singolo posto; discrezione del programmatore effettuare questa scelta in modo da raggiungere un buon compromesso tra numero di utenti contemporanei e funzionalit dell'applicazione. Come si pu intuire questa scelta (tipico problema da risolvere anche con la teoria delle code (tempi medi di attesa, tempi medi di servizio ....)) non delle pi facili e deve essere effettuata anche in base all'organizzazione della base dati utilizzata. Dopo aver analizzato alcune delle problematiche connesse a questo argomento, vediamo

come sia possibile sotto Unix effettuare il locking dei file. Il sistema Unix mette a disposizione due tipi di locking: consultivo e ingiuntivo. Il locking consultivo fa in modo che non venga bloccato l'accesso al file sul quale stato effettuato, ma venga notificato che gi presente un processo che lo sta utilizzando. Sar compito del programmatore stabilire il comportamento del processo in presenza di tale tipo di locking. Il locking ingiuntivo blocca effettivamente l'accesso al file (o al record) a tutti gli altri processi. Il locking ingiuntivo pu causare dei problemi nel caso in cui il processo che lo ha effettuato termini per un qualche motivo senza rimuovere il locking, poich in tal caso il file rimane bloccato. E importante ricordare che i figli non ereditano i lock del padre, cio non possono accedere a file bloccati dal padre o togliere i lock preesistenti alla loro nascita.

7.4.1 Lockf Questa funzione permette di effettuare il locking del file o di parte di esso, per essere utilizzata necessario includere il seguente file < unistd.h > Sintassi int lockf( int fd, int funzione, long dimensione); fd funzione il descrittore del file l'operazione che si intende eseguire sul file, pu assumere i seguenti valori: F_ULOCK libera una regione precedentemente bloccata F_LOCK impone un lock su una regione del file, nel caso la regione (o parte di essa) sia gi bloccata, il processo chiamante viene posto in attesa F_TLOCK verifica se una regione gi sottoposta a locking e se possibile lo impone; nel caso in cui la regione sia gi bloccata non pone in attesa il processo chiamante e segnala l'errore F_TEST verifica se esiste un lock su una regione indica l'estensione della regione su cui si opera, a partire dalla posizione corrente (che pu essere specificata utilizzando lseek); se la dimensione zero la protezione viene imposta dall'offset corrente fino alla fine del Dimensione pu avere un valore positivo o negativo.

dimensione file.

Affinch il locking sia ingiuntivo anzich consultivo necessario che i permessi del file (che possono essere modificati con la chmod) siano impostati in modo tale da avere il bit set-GID a 1 e il bit group-execute a zero (cio il file non deve essere eseguibile per il gruppo). Quando si tenta di effettuare una read o una write su una regione sulla quale imposto un locking ingiuntivo, tali operazioni vengono bloccate fino a che non viene rimosso il lock. Un processo pu effettuare un lock su una regione che contiene tutta o una parte di una regione gi bloccata. Questi lock sovrapposti (o adiacenti) diventano una sezione unica. Quando una lockf tenta il rilascio di una parte di tale sezione solo questa viene liberata mentre le restanti rimangono bloccate. Nel caso in cui si rimuova la zona centrale di una sezione le altre due parti rimangono ancora bloccate, per necessario un altro elemento nella tabella dei lock attivi, se questa fosse piena la funzione segnala errore e la sezione non viene rilasciata.

59

7.4.2 Fcntl Anche tale funzione permette di effettuare le operazioni di locking, ma fornisce delle possibilit in pi rispetto alla precedente chiamata. Per utilizzarla necessario includere i seguenti file: < sys/types.h > < unistd.h > < fcntl.h > Sintassi int fcntl(int fd,int funzione, int arg); fd funzione il descrittore del file l'operazione che si vuole effettuare, pu essere F_GETLK fornisce le informazioni riguardanti le condizioni di lock di una certa regione specificata nella struttura di tipo flock. Se esiste locking tale struttura viene sovrascritta con le informazioni reali, se il lock non esiste il tipo di lock viene impostato a F_UNLOCK e il resto rimane inalterato F_SETLK imposta o elimina il lock di un segmento specificato all'interno della struttura F_SETLKW svolge le stesse funzioni specificate dal precedente comando per pone il processo in attesa se la regione specificata gi sottoposta a locking. E importante osservare che, se ci sono segnali predisposti per essere catturati, l'arrivo di uno di essi mentre si in attesa provoca la sospensione della chiamata e al ritorno dalla funzione che gestisce il segnale la fcntl restituisce un errore e non imposta il lock (non l'unica funzione che pu avere dei problemi quando viene interrotta da un segnale; questo un motivo per usare i segnali con molta attenzione e solo quando strettamente necessario). arg la quantit di byte su cui si vuole agire La struttura di tipo flock costituita dai seguenti campi short l_type pu assumere i seguenti valori F_RDLCK lock di tipo condiviso pi processi possono tenere contemporaneamente un lock di questo tipo. Per avere questo lock il file deve essere aperto in lettura. F_WRLCK lock di tipo esclusivo; solo un processo alla volta pu avere un lock di questo tipo. Per avere questo lock il file deve essere aperto in scrittura. F_UNLOCK indica che si vuole liberare la regione dal lock. short l_whence ha lo stesso significato del parametro whence della lseek vedi paragrafo 7.3 long l_start offset relativo in byte long 1_start lunghezza della sezione in byte zero significa di andare fino alla fine del file pid_t l_pid l'identificatore del processo che ha effettuato il lock (questo valore viene restituito quando si effettua la fcntl con parametro F_GETLK) Il locking ingiuntivo e consultivo vengono individuati nello stesso modo visto per la lockf. E

possibile trasformare un locking condiviso in un locking esclusivo utilizzando la chiamata fcntl e specificando il nuovo tipo di lock desiderato. Se un processo impone un locking ingiuntivo di tipo condiviso su un segmento di un file gli altri processi possono leggere ma le operazioni di scrittura vengono bloccate finch i lock sono attivi. Se invece il lock ingiuntivo di tipo esclusivo sia la lettura che la scrittura sono impedite agli altri processi. I lock di tipo consultivo non impediscono letture e scritture, essi possono essere utilizzati da processi cooperanti utilizzando il comando F_GETLCK ed osservando volontariamente delle regole di accesso comuni. Oltre alle funzioni lockf e fcntl esiste la funzione flock il cui locking per non compatibile con i due precedenti. Ogni volta che dei processi competono per l'utilizzo di una risorsa esiste la possibilit di deadlock. Le due funzioni viste verificano la possibilit di conflitto tra due utenti e in alcuni casi possono evitare la situazione di deadlock impedendo che la chiamata vada a buon fine e restituendo un errore al chiamante. E comunque opportuno prestare una certa attenzione per cercare di prevenire queste situazioni.

7.5 Esempi di programmazione7.5.1 espfile4.c In questo esempio due processi, padre e figlio, accedono in lettura ad un file aprendolo con la open, il figlio eredita il descrittore del file. Si pu osservare come la lettura da parte di un processo continui dal punto in cui l'altro stato sospeso. Non bisogna lasciarsi ingannare da quanto stampato a video, perch spesso capita che l'output dei processi venga intercalato. Per il funzionamento del programma necessario che esista il file nuovo.txt (che contiene 400 righe numerate contenenti lo stesso messaggio) o un file simile. Poich la read effettua la lettura di un numero di byte specificato necessario che le righe abbiano tutte la stessa lunghezza.#include #include #include #include #include #include

#define MAX 16 main() { int pid, letto, fd; char lett_figlio[MAX]; char lett_padre[MAX]; if((fd = open("nuovo.txt",O_RDONLY,0)) == -1) { perror("OPEN :"); exit(1); } if((pid = fork()) < 0) { perror("FORK :"); exit(1); } else if(pid == 0) 61

}

{ /* Codice del figlio */ while((letto = read(fd,lett_figlio,MAX)) != 0) printf("FIGLIO : %s",lett_figlio); printf("Il figlio non ha piu' nulla da leggere.\n"); close(fd); exit(0); } else { /* Codice del padre */ while((letto = read(fd,lett_padre,MAX)) != 0) printf("PADRE : %s",lett_padre); printf("Il padre non ha piu' nulla da leggere.\n"); close(fd); exit(0); }

7.5.2 espfile5.c In questo esempio si effettuano una serie di operazioni di scritture su un file aperto come nell'esempio precedente solo dal padre. Neanche in questo caso le operazioni effettuate dai due processi si sovrappongono, ma procedono alternate casualmente.#include #include #include #include #include #include 300

#define MAX

main() { int pid, i, scritto, fd; char msg_figlio[]="Sono il figlio.\n"; char msg_padre[]="Sono il padre.\n"; if((fd = open("testo1.txt",O_WRONLY|O_CREAT|O_TRUNC,0)) == -1) { perror("OPEN :"); exit(1); } if((pid = fork()) < 0) { perror("FORK :"); exit(1); } else if(pid == 0) { /* Codice del figlio */ for(i = 0;i < MAX; i++) { if((scritto = write(fd,msg_figlio,sizeof(msg_figlio))) == -1) { printf("Come figlio non posso scrivere sul file.\n"); perror("Perche' : "); exit(1); } close(fd); exit(0); } } else

}

{ /* Codice del padre */ for(i = 0; i < MAX; i++) { if((scritto = write(fd,msg_padre,sizeof(msg_padre))) == -1) { printf("Come padre non posso scrivere sul file.\n"); perror("Perche' : "); exit(1); } close(fd); exit(0); } }

7.5.3 espfile6.c Questo esempio illustra il caso in cui l'apertura del file venga effettuata separatamente da padre e figlio. In questo modo i due processi possiedono due diversi descrittori di file pertanto possono leggere dal file in modo indipendente.#include #include #include #include #include #include

#define MAX 16 main() { int pid, letto, fd; char lett_figlio[MAX]; char lett_padre[MAX]; if((pid = fork()) < 0) { perror("FORK :"); exit(1); } else if(pid==0) { /* Codice del figlio */ if((fd = open("nuovo.txt",O_RDONLY,0)) == -1) { perror("OPEN n1: "); exit(1); } while((letto = read(fd,lett_figlio,MAX)) != 0) printf("Figlio : %s",lett_figlio); printf("Il figlio non ha piu' nulla da leggere.\n"); close(fd); exit(0); } else { /* Codice del padre */ if((fd= open("nuovo.txt",O_RDONLY,0)) == -1) { perror("OPEN n2: "); exit(1); } while((letto = read(fd,lett_padre,MAX)) != 0) 63

}

printf("Padre : %s",lett_padre); printf("Il padre non ha piu' nulla da leggere.\n"); close(fd); exit(0); }

7.5.4 espfile7.c In questo caso si tenta di scrivere in un file aperto separatamente dai due processi, poich i processi operano indipendentemente si ha una sovrapposizione delle operazioni di scrittura. Per evitare questo inconveniente necessario ricorrere a operazioni di sincronizzazione (semafori, code di messaggi,...) o all'utilizzo del locking del file.#include #include #include #include #include #include 300

#define MAX

main() { int pid, i, scritto, fd; char msg_figlio[]="Sono il figlio.\n"; char msg_padre[]="Sono il padre.\n"; if((fd = open("testo1.txt",O_WRONLY|O_CREAT|O_TRUNC,0)) == -1) { perror("OPEN :"); exit(1); } if((pid = fork()) < 0) { perror("FORK :"); exit(1); } else if(pid == 0) { /* Codice del figlio */ for(i = 0; i < MAX; i++) { if((scritto = write(fd,msg_figlio,sizeof(msg_figlio))) == -1) { printf("Come figlio non posso scrivere sul file.\n"); perror("Perche' : "); ex