Sei sulla pagina 1di 349

Alessandra Demaria Franco Sbiroli

Chiara Regale Federico Stirano

UNIX
un sistema operativo multiutente Guida all'uso e alla programmazione concorrente

Febbraio 1997

PREMESSA

PARTE I - I COMANDI UNIX

GESTIONE DI INPUT E OUTPUT


1.1 REDIREZIONE DI INPUT E OUTPUT 1.2 PIPE

GESTIONE DEI DATI


2.1 FILE SYSTEM 2.2 OPERAZIONI SUI FILE ORDINARI
2.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

EDITING
3.1 VI
3.1.1 MODO TESTO 3.1.2 MODO COMANDI

PROGRAMMAZIONE
4.1 VARIABILI
4.1.1 VARIABILI FILE
3

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 CONTROLLO


IF-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 UNIX


4.7.1 COMPILAZIONE E LINK: GCC 4.7.2 LIBRERIE 4.7.3 DEBUGGING

PARTE II - IL SISTEMA OPERATIVO E LA GESTIONE DEI PROCESSI

IL SISTEMA
5.1 IDENTIFICATORI

SYSTEM CALL PER LA GESTIONE DEI PROCESSI


6.1 FORK 6.2 EXEC 6.3 WAIT
6.3.1 WAITPID 6.3.2 PROCESSI ZOMBIE E USCITE PREMATURE 6.3.3 WAIT3

6.4 SIGNAL E KILL


6.4.1 SEGNALI 6.4.2 USO DELLA SIGNAL E DELLA KILL

6.5 ESEMPI DI PROGRAMMAZIONE


6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6
ESP_FOR1.C ESP_FOR2.C ESP_FOR3.C ESPEXL.C E ESPEXV.C ESP_WAI1.C ESP_WAI2.C

7
7.1 7.2 7.3 7.4

INPUT E OUTPUT
CONDIVISIONE 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 FCNTL


4

7.5 ESEMPI DI PROGRAMMAZIONE


7.5.1 7.5.2 7.5.3 7.5.4 7.5.5
ESPFILE4.C ESPFILE5.C ESPFILE6.C ESPFILE7.C LOC4.C

INTER-PROCESS COMMUNICATION FACILITIES


8.1 SEMAFORI
8.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 PROGRAMMAZIONE


8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.5.6
PSEM.C PRODCON1.C PRODCON2.C MESSAGGI.C LSMEM.C ESPIPE1.C ESPIPE2.C

INTRODUZIONE AL CONCETTO DI MONITOR


9.1 DEFINIZIONE DI MONITOR 9.2 LIMITI DEL COSTRUTTO MONITOR

10
10.1 10.2 10.3 10.4 10.5

SISTEMI DISTRIBUITI
NETWORKS: 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 APPLICAZIONE


5

11
11.1 11.2 11.3 11.4

I SOCKET
STRUTTURE 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 HOST
11.6.1 11.6.2
GETHOSTBYNAME, GETHOSTBYADDR GETHOSTNAME, SETHOSTNAME

11.7 OPZIONI SUI SOCKET 11.8 LISTEN 11.9 ACCEPT

12
12.1 12.2 12.3 12.4

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

13

DAEMON

13.1 CODIFICA 13.2 INIZIALIZZAZIONE ED ESECUZIONE 13.3 TERMINAZIONE

14
14.1 14.2 14.3 14.4 14.5

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

15
15.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++

16
6

ESEMPI DI PROGRAMMAZIONE IN ASSEMBLER

PREMESSA
I 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.

Parte I

I comandi UNIX

10

1 Gestione di input e output

1.1 Redirezione di input e output


Il 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 <<, che risulta molto utile se si vuole delimitare, ad esempio, la fine un file con il simbolo di EOF. E possibile scrivere: ed nome_file << EOF! .... <fase di 'editing'> .... EOF! In questo modo, in coda al file nome_file, verr posto il delimitatore 'EOF!', digitando il quale si uscir automaticamente dalleditor. Per maggiori dettagli sull'uso degli editor, si rimanda il lettore al capitolo 3. Nella C-shell si possono ridirigere stderr e stdout, ponendo l'output dell'istruzione UNIX e gli eventuali errori verificatisi in un unico file, oppure accodandoli ad altri dati in un file gi esistente. Analogamente con quanto visto in precedenza, i simboli sono > e >> seguiti da &. La sintassi la seguente: istruzione >& outfile istruzione >>& outfile Pone output e messaggi di errore in outfile Accoda ouput e messaggi di errore in outfile

Inoltre, digitando un punto esclamativo, si possono scrivere o modificare anche file protetti dalla variabile noclobber, usata per proteggere dei file particolarmente importanti. La redirezione con il punto esclamativo impone forzatamente la scrittura o la modifica dei file; se

il file non protetto, il comportamento analogo a quello della redirezione senza punto esclamativo. Si hanno cos gli altri quattro casi: 1) 2) 3) 4) istruzione >! outfile istruzione > &! outfile istruzione >>! outfile istruzione >>&! outfile Pone loutput in outfile Pone output e messaggi di errore in outfile Accoda loutput in outfile Accoda output e messaggi di errore in outfile .

1.2 Pipe
Interpretando i comandi UNIX come dei filtri che utilizzano dei dati per produrne degli altri, viene naturale pensare ad un modo per metterli in serie. I vantaggi sono evidenti se si pensa al grosso risparmio di tempo nel caso di iterazioni della stessa sequenza di comandi su dati diversi. Infatti, a regime, verranno eseguiti in parallelo tanti processi quanti sono i blocchi del sistema. In figura 1.1 evidenziato l'esempio con tre generiche istruzioni UNIX in pipeline. In UNIX una pipe si realizza con il simbolo | interposto tra diversi comandi. stdin stdout istruzione pipe istruzione pipe istruzione

UNIX
stderr

UNIX
stderr

UNIX

Figura 1.1

Tre istruzioni in pipeline

Esempio: A partire dagli elenchi non ordinati degli alunni di tre classi, se ne vuole ottenere uno unico, che trasformi liniziale delle occorrenze del nome carlo in maiuscolo e ordini il tutto alfabeticamente in un file chiamato elenco. $ cat pri_a sec_c ter_b | sed -e "s/carlo/Carlo" | sort > 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 system


Come 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 ordinari


2.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 di
16

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 dati


Spesso 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 <opzione> <stringa> <nomefile> 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 alcune
20

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 directory


2.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 Vi
Il 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 <INVIO> 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 Programmazione
Con 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 Variabili
Le 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 ricerca
28

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 aritmetici


Gli 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 confronto


Gli 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 control


Si 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 <CTRL--Z>. 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 controllo


Come 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 ..... <istruzioni> ..... 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 UNIX


La 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 sistema
Cerchiamo 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 Identificatori
Poich 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 processi


Un 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 Fork
Crea 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: <unistd.h>. 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 Exec
La 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: <sys/types.h> 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 Wait
Provoca 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: <sys/wait.h> 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<-1 si attende la terminazione di tutti i figli il cui identificatore di gruppo di processi uguale al valore assoluto di pid

6.3.2 Processi zombie e uscite premature Vi sono due possibili situazioni: 1. un figlio termina quando il processo genitore non ha ancora eseguito la wait. In tal caso questi viene posto nello stato di zombie cio esso una cella di una tabella del kernel per il controllo dei processi, ma senza utilizzare risorse (memoria,....), quelle a lui legate vengono infatti rilasciate al momento dell'uscita. Nel momento in cui il padre invoca la wait il processo zombie viene definitivamente eliminato. 2. un padre termina quando uno o pi figli sono ancora attivi. In tal caso i figli vengono adottati (inclusi gli zombie) dal processo init il cui PID ha sempre valore 1. Quando un processo termina con exit viene automaticamente inviato un segnale SIGCLD al suo genitore. Affinch il processo genitore non crei figli zombie necessario che esegua: signal(SIGCLD,SIG_IGN) che informa il kernel che il padre non e` interessato alla terminazione con exit dei figli. Purtroppo abbiamo sperimentato che il nostro sistema non gestisce questo modo di funzionamento e i figli defunti non vengono eliminati, ma siamo riusciti ad aggirare il problema utilizzando la system call wait3.

6.3.3 Wait3 Per il suo uso necessario includere anche i seguenti file <sys/time.h> <sys/resource.h> Sintassi int wait3(int *stato, int flag, struct rusage *ruso); es: int num=0; union wait stato; ..... num= wait3(&stato,flag,&ruso); stato, flag ruso vale quanto precedentemente detto un puntatore a una struttura contenente una serie di informazioni riguardanti l'uso della CPU da parte del processo figlio

Questa chiamata di sistema rappresenta una alternativa alla wait, essa permette sia di conoscere lo stato dei figli in modo non bloccante (specificando il parametro WNOHANG), sia di conoscere lo stato dei figli bloccati per una qualsiasi ragione notificando il segnale che
43

ne ha causato il blocco (opzione WUNTRACED). Queste sue caratteristiche ci permettono di eliminare tutti i processi zombie senza bloccare il chiamante se ci sono altri figli ancora in esecuzione. Un esempio chiarificatore del modo di utilizzo di questa funzione si pu trovare nel programma server.c di esempio sull'utilizzo dei socket internet al capitolo 11. E disponibile anche una wait4 che raggruppa il comportamento della waitpid e della wait3 appena descritta.

6.4 Signal e kill


6.4.1 Segnali Un segnale un particolare messaggio che i processi si possono scambiare per comunicare il verificarsi di qualche evento. Poich esiste una analogia con gli interrupt spesso vengono anche chiamate interruzioni software.Solitamente si presentano in modo asincrono cio il processo non sa a priori quando il segnale gli si presenter.I processi di utente possono mandarsi segnali (anche a s stessi) per mezzo della system call kill, oppure li possono ricevere dal kernel. I segnali sono identificati da un nome, specificato nel file < signal.h >. Nella tabella 6.1 sono riportati tutti i segnali disponibili: nella colonna S.O. viene indicato se il segnale appartiene ad un particolare sistema; quando non riportato nulla significa che il segnale comune ai due sistemi. NOME S.O. DESCRIZIONE Azione di default SIGALARM Sveglia Termina SIGBUS Errore di bus Termina con core SIGCLD Morte di un processo figllio Segnale scartato SIGCONT 4.3BSD continua dopo SIGSTOP Segnale scartato SIGEMT Istruzione EMT Termina con core SIGFPE Istruzione fpe Termina con core SIGHUP Riaggancio Termina SIGILL Istruzione illegale Termina con core SIGINT Carattere di interruzione Termina SIGIO 4.3BSD I/O possibile su un desc di file Segnale scartato SIGIOT Istruzione IOT Termina con core SIGKILL Soppressione Termina SIGPIPE Scrittura suuna pipe senza lettori Termina SIGPOLL SYS V Evento in un dispositivo di stream Termina SIGPROF 4.3BSD Profilo di un allarme di timer Termina SIGPWR SYS V Mancanza di alim. elettrica Termina SIGQUIT Carattere di terminazione Termina con core SIGSEGV Violazione della segmentazione Termina con core SIGSTOP 4.3BSD Stop Sospende il proc. SIGGSYS Arg. non valido per la system call Termina con core SIGTERM Segnale di terminazione software Termina SIGTRAP Trappola di traccia Termina con core SIGTSTP 4.3BSD Stop generato da tastiera Sospende il proc. SIGTTIN 4.3BSD Background letto da terminale Sospende il proc. SIGTTOU 4.3BSD Background scritto su terminale Sospende il proc. SIGURG 4.3BSD Condizione urgente sul socket Segnale scartato SIGUSR1 Segnale uno definito dallutente Termina

Segnale due definito dallutente Termina 4.3BSD Superato tempo limite di CPU Termina 4.3BSD Superato limite di dim. file Termina Tabella 6.1 Segnali del S.O. UNIX Come vengono generati i segnali? Innanzitutto abbiamo gi detto che la chiamata di sistema kill consente ad un processo di inviare un segnale. Il suo nome (kill=uccidere) pu trarre in inganno, infatti non sempre una chiamata di kill serve per terminare un processo. Un altro modo di inviare un segnale ad un processo quello di utilizzare la tastiera del terminale; ad esempio con ^C si pu inviare il segnale di interruzione SIGINT, ^Z sospende il processo generando il segnale SIGTSTP ...... Anche alcune eccezioni hardware generano segnali, ad esempio errori di indirizzamento causati da un errato utilizzo dei puntatori generano il segnale SIGSEGV (segmentation fault). Come si comporta un processo quando riceve un segnale? Generalmente alla ricezione di un segnale viene eseguita l'azione di default come indicato nella tabella 6.1. Il processo pu aver bisogno di eseguire una funzione diversa da quella standard: questo comportamento viene anche indicato come cattura del segnale ed possibile facendo uso della chiamata signal. E anche possibile ignorare un segnale se il processo in esecuzione non interessato all'informazione portata da quel segnale. Ci sono alcuni segnali che non possono essere ignorati poich necessari alla gestione del sistema. Prima di esaminare la sintassi delle due system call signal e kill esaminiamo alcuni segnali un p pi nel dettaglio: SIGALRM Quando si utilizza la chiamata di sistema alarm (alarm(secs)) il processo ricever questo segnale dal kernel dopo il tempo prestabilito (dopo secs secondi), ci utile per rilevare una condizione di timeout oppure per eseguire una operazione ad intervalli di tempo regolari. Anche la sleep (sleep(secs)) utilizza questo segnale. Il processo viene messo in attesa per un certo tempo (secs secondi) allo scadere del quale viene inviato il segnale, essa lo cattura e provoca il risveglio del processo che era stato addormentato. SIGCLD Questo segnale viene inviato dal kernel al processo genitore in occasione della terminazione di un processo figlio. Tale segnale viene normalmente scartato, cio non viene intrapresa alcuna azione. Naturalmente si pu catturare specificando una funzione da eseguire alla terminazione del figlio. SIGKILL E uno di quei segnali che non possibile n catturare n ignorare, poich bisogna pur garantire all'amministratore del sistema un modo sicuro per terminare i processi. SIGPIPE Avvisa un processo che sta scrivendo su una pipe che non esiste un lettore. In 4.3BSD viene usato anche per indicare che si sta tentando di scrivere su un socket che stato sconnesso. SIGSTOP E un altro segnale che non pu essere n catturato n ignorato. Arresta l'esecuzione del processo a cui viene inviato. L'esecuzione pu essere ripresa inviando il segnale SIGCONT. SIGUSRn Sono segnali a disposizione dell'utente utilizzabili per la comunicazione tra i processi, il ricevente non sa chi sia il mittente del segnale. L'informazione trasportata viene ad essi associata volta per volta dal programmatore che li utilizzer per notificare l'avvenimento di qualche evento significativo.

SIGUSR2 SIGXCPU SIGXFSZ

45

6.4.2 Uso della signal e della kill Come accennato in precedenza, la signal viene utilizzata per catturare un segnale, cio per indicare quale funzione eseguire in vece di quella normalmente eseguita. Per il suo utilizzo necessario includere il file: < sys/signal.h > La chiamata avviene nel seguente modo: Sintassi int signal(int sig, void(*funz)); es: signal(SIGUSR1,funct1); sig funz rappresenta il segnale che si vuole gestire; la funzione da attivare al ricevimento del segnale; questo parametro pu assumere due valori particolari: SIG_DFL indica che il segnale viene gestito nel modo di default; SIG_IGN specifica di ignorare il segnale;

La funzione che viene chiamata per gestire il segnale riceve sempre come primo argomento il numero intero del segnale che gestisce. In questo modo possibile utilizzare una funzione per gestire pi segnali, distinguendoli per mezzo del loro identificatore. La funzione kill permette di inviare dei segnali a uno o pi processi; appare quasi superfluo dire che solo il superuser pu inviare segnali a chiunque, mentre un normale utente avr delle limitazioni. Infatti bisogna tutelare gli utenti dai propri colleghi che inavvertitamente potrebbero, inviando segnali ai processi sbagliati, causare gravi danni. Vediamo ora la semplice sintassi di tale funzione e poi chiariremo come vengono inviati i segnali. Sintassi int kill(int pid, int sig); pid sig l'identificatore del processo destinatario del segnale; il nome del segnale da inviare;

Se la variabile pid assume alcuni particolari valori si pu avere un comportamento diverso da quanto finora detto, infatti se pid=0 pid=-1 pid=-1 pid<-1 il segnale viene inviato a tuti i processi nel gruppo del trasmettitore se il mittente non il superuser, il segnale viene inviato a tutti i processi il cui UID reale uguale a quello effettivo se il mittente il superuser, il segnale viene inviato a tutti i processi di utente (non di sistema) il segnale viene inviato a tutti i processi il cui identificatore del gruppo di processi uguale al valore assoluto di pid.

6.5 Esempi di programmazione

6.5.1 esp_for1.c Questo programma mostra come un processo pu generare un figlio. 1. La Fork restituisce al processo chiamante il PID del processo creato e zero a quest'ultimo.Saremo percio' in questa situazione: PID_Padre > 0 PID_Figlio = 0 2. Questa differenza ci consente di utilizzare un ciclo IF per gestire il lavoro dei due processi. Inizialmente viene effettuato un controllo per verificare che la fork abbia realmente duplicato il processo: un valore negativo del PID identifica questa situazione. Gli altri due casi dell'if distinguono invece il codice del padre da quello del figlio.
/* Questo primo esempio illustra le modalita' che consentono di generare, a partire da un processo padre, il processo figlio */ #include <unistd.h> #include <errno.h> main() { int child_pid; if ((child_pid = fork()) < 0) { perror("FORK fallita: "); exit(1); } else if (child_pid==0) { /* CODICE DEL PROCESSO FIGLIO */ printf("Sono il figlio.\n"); exit(0); } else { /* CODICE DEL PROCESSO PADRE */ printf("Sono il padre.\n"); exit(0); } } /* NOTE: 1) La Fork restituisce al processo chiamante il PID del processo creato e zero a quest'ultimo. Saremo percio' in questa situazione: PID_Padre > 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 il
47

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 <unistd.h> #include <errno.h> #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 <unistd.h> #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 <unistd.h> #include <sys/types.h> 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<unistd.h> #include<string.h> #include<sys/types.h> #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 <stdio.h> <unistd.h> <sys/types.h> <sys/wait.h> <errno.h>

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 <stdio.h> #include <unistd.h> 51

*/

#include <sys/wait.h> #include <sys/types.h> #include <errno.h> 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 output
In 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 file


Prima 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 file


Uno 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 programmazione


7.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 <stdio.h> <unistd.h> <sys/stat.h> <sys/types.h> <fcntl.h> <errno.h>

#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 <stdio.h> <unistd.h> <sys/stat.h> <sys/types.h> <fcntl.h> <errno.h> 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 <stdio.h> <unistd.h> <sys/stat.h> <sys/types.h> <fcntl.h> <errno.h>

#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 <stdio.h> <unistd.h> <sys/stat.h> <sys/types.h> <fcntl.h> <errno.h> 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 */ if((fd = open("testo1.txt",O_WRONLY,0)) == -1) { perror("OPEN n2 : "); exit(1); } 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.5 loc4.c Questo programma costituisce un esempio del locking di un file. In esso i due processi, padre e figlio, sono in concorrenza per scrivere un messaggio sulla stessa riga di un file. Entrambi aprono il file utilizzando due descrittori diversi; ci consente loro di accedere al file in modo del tutto indipendente l'uno dall'altro. Il programma deve essere eseguito in background per poter vedere le modifiche apportate al file prima da uno e poi dall'altro processo; ci risulta possibile digitando il comando suggerito dal messaggio a video. Le operazioni di scrittura dei due processi sono racchiuse tra due chiamate della system call lockf che hanno rispettivamente il compito di imporre e rimuovere il locking su una quantit di byte specificata. Il vantaggio dell'utilizzo di tale chiamata sta nella possibilitdi scegliere la quantit di byte da bloccare all'interno del file, non necessariamente tutto il file.
#include #include #include #include #include <stdio.h> <unistd.h> <fcntl.h> <errno.h> <sys/types.h>

main() { char msg_padre[]="SONO IL PADRE\n"; char msg_figlio[]="SONO IL FIGLIO\n"; int pid, i, fdp, fdf; int dim_padre=sizeof(msg_padre), dim_figlio=sizeof(msg_figlio); if((fdp = open("testo.txt",O_CREAT|O_WRONLY|O_TRUNC|0644)) == -1) /* apertura del file in scrittura e lettura */ { perror("OPEN :"); exit(1); } if((pid = fork()) < 0) { perror("FORK :"); exit(1); } else if (pid == 0) { /* codice del figlio */ if((fdf = open("testo.txt",O_CREAT|O_WRONLY|O_TRUNC|0644)) == -1) /* apertura del file in scrittura e lettura */ { perror("OPEN :"); exit(1); } lockf(fdf, F_LOCK,dim_figlio); printf("figlio, record bloccato\n"); printf("Digita : vi testo.txt !!!!!\n"); write(fdf,msg_figlio,dim_figlio); sleep(15); lockf(fdf, F_ULOCK,-dim_figlio); printf("figlio, termino...\n"); exit(0); } 65

/* codice del padre */ lockf(fdp, F_LOCK,dim_padre); printf("padre, record bloccato\n"); write(fdp,msg_padre,dim_padre); printf("Digita : vi testo.txt !!!!!\n"); printf("Nota le modifiche del padre!!\n"); sleep(15); lockf(fdp, F_ULOCK,-dim_padre); printf("padre, termina...\n"); exit(0); }

8 Inter-Process Communication Facilities


Il sistema operativo Unix mette a disposizione del programmatore alcune risorse di sistema per permettere le comunicazioni tra i vari processi. Queste risorse sono: semafori memoria condivisa code di messaggi

Queste risorse comuni sono fisicamente limitate, vengono assegnate su richiesta e devono essere opportunamente rilasciate al termine del loro uso. La rimozione o deallocazione delle risorse deve essere effettuata, nell'ambiente dei processi, dall'ultimo processo che rimane attivo (solitamente il processo padre) e deve essere una delle ultime operazioni del programma per evitare di richiamare una risorsa quando essa gi stata deallocata. importante ricordare che quando un programma viene fatto terminare da parte dallesterno mediante un segnale di terminazione oppure esso stesso ha una terminazione anomala, le risorse di sistema che gli erano state allocate non vengono rilasciate. Questo pu portare rapidamente alla saturazione del sistema, con la conseguenza che nessun utente sar in grado di ottenerne altre. Per evitare questo inconveniente che pu causare notevoli disagi agli altri utenti del sistema necessario controllare che non siano allocate risorse che non servono pi. A questo scopo sono disponibili due comandi: ipcs: visualizza lo stato delle risorse del sistema, permette di conoscere il tipo, il proprietario, i diritti e altre informazioni utili; ipcrm: permette al proprietario di eliminare una risorsa dal sistema. L'utilizzo alquanto semplice: per eliminare la risorsa si usa il comando ipcrm seguito da un parametro che identifica il tipo di risorsa (-s -m -q) che si vuole eliminare e dallidentificatore della risorsa ottenuto mediante il comando ipcs.

8.1 Semafori
La soluzione pi intuitiva per la gestione delle sezioni critica luso dei semafori. Grazie a questa risorsa possibile, in determinate condizioni, bloccare un processo permettendo agli altri processi in esecuzione di terminare, evitando di creare cos conflitti nelluso di risorse e di bloccare il sistema. Il processo viene riattivato dagli altri processi una volta terminata la loro sezione critica Concettualmente il semaforo una variabile intera non negativa sulla quale sono possibili tre sole operazioni: Inizializzazione: essa deve essere eseguita immediatamente dopo la creazione della risorsa e il valore di inizializzazione del semaforo pu essere una qualsiasi costante intera non nulla (ovviamente il valore di questa costante dipende dalluso che si dovr fare del semaforo allinterno del programma). wait(s): if s > 0, s = s - 1; tale operazione decrementa di una unit il valore del semaforo. Se la wait trova la variabile a zero, il processo che esegue la wait si blocca e il suo PID viene inserito nella lista dei processi in attesa su quel semaforo. signal(s): tale operazione incrementa di una unit il valore del semaforo. Nel caso ci sia almeno un processo in attesa la variabile non viene incrementata e il primo processo della lista viene posto in esecuzione. Il motivo per cui la signal non incrementa il valore della variabile nel caso in cui esistano gi processi in attesa giustificato dal fatto che il processo che entra nella propria sezione critica non ripete la wait, di conseguenza un qualsiasi processo che eseguisse la wait nel tentativo di accedere alla risorsa troverebbe il semaforo posto a uno causando cos, un conflitto fatale per il corretto funzionamento del programma. Le operazioni di wait e signal non creano problemi per quanto riguarda le sezioni critiche in quanto essendo operazioni atomiche un solo processo alla volta pu modificare il valore del semaforo. Poich il valore del semaforo non pu essere modificato con altre operazioni che non quelle viste in precedenza, in un dato istante questo legato al numero di wait e signal eseguite su di esso. Se il semaforo s stato inizializzato al valore s0 vale la relazione val(s) = s0 + nsignal(s) - nwait(s)
67

inoltre, essendo per definizione val(s) 0 si ha nwait(s) s0 + nsignal(s) N.B. Le operazioni di wait e di signal viste in questo contesto sono totalmente diverse da quelle viste in precedenza parlando di creazione di processi e di segnali. Esempi di utilizzo dei semafori Con il meccanismo di sospensione di un processo in seguito ad una wait, o di riattivazione in seguito ad una signal effettuata sullo stesso semaforo da un altro processo il S.O. in grado di evitare che un qualunque processo sprechi tempo di CPU cercando di ottenere una risorsa: viene quindi eliminata ogni forma di attesa attiva. Infatti, quando un processo si sospende, la CPU si dedica all'esecuzione delle operazioni degli altri processi attivi. Pu capitare, per, che i processi siano contemporaneamente sospesi, cio nessun processo in grado di fare una signal per sbloccarne un altro. In questo caso necessaria una gestione molto oculata dello sbloccaggio per poter arrivare alla soluzione del problema: si verificato il fenomeno della starvation o attesa indefinita, nel quale ogni processo in attesa di sbloccarsi, ma ci non porr accadere poich tutti sono bloccati. Se si verifica ci solitamente interviene il S.O. il quale fa in modo che il primo processo sospeso sia anche il primo a essere riattivato nel momento in cui la risorsa diventa disponibile. E tuttavia compito del programmatore gestire le interazioni tra processi utilizzando le risorse che ha a disposizione in maniera opportuna. Lo studio del meccanismo di sospensione e riattivazione dei processi pu risultare concettualmente impegnativo e deve essere affrontato attraverso l'analisi delle varie situazioni. Esempi: Competizione per l'uso di una risorsa condivisa. Siano P1,...,PK i processi che condividono la risorsa R e A1,...,AM le procedure che accedono alla risorsa stessa. Le procedure A1,...,AM, accedendo alla stessa risorsa R, devono essere eseguite in mutua esclusione. Ognuna di esse deve essere costituita da una sezione critica, quindi l'insieme {Ai | i=1,...,M} una classe di sezioni critiche. Utilizzando un semaforo s, inizializzato al valore 1, possibile fare in modo che un solo processo alla volta modifichi, con una delle Ai, la risorsa R. Lo scopo si ottiene includendo la parte di codice delle procedure Ai che lavorano direttamente su R tra una wait(s) e una signal(s). Poich il semaforo appena definito pu assumere solo i valori 0 e 1, viene denominato semaforo binario. Il programma 1 riportato alla fine del paragrafo un esempio molto semplice di risoluzione di un problema di questa categoria. La risorsa R un file sul quale alcuni processi scrivono un messaggio personalizzato. Non solo sul file, ma anche sul video, si nota che lavora un processo figlio alla volta, mentre gli altri risultano sospesi. Allocazione dinamica di un insieme di risorse equivalenti. Siano P1,...,PK processi che competono per utilizzare le risorse comuni R1,...,RL; siano A1,...,AM le procedure che accedono a tali risorse. Si deve avere un gestore dell'insieme di risorse in grado di allocarle dinamicamente, dedicando ognuna di esse ad un processo alla volta. Il gestore della risorsa , esso stesso, una risorsa condivisa. Il gestore implementato per mezzo di un semaforo inizializzato al valore L (quantit di risorse disponibili). Si utilizza un vettore di booleani anch'esso di dimensione L, nel quale l'elemento di indice i sar FALSE se la risorsa occupata e TRUE se libera. E necessario per fare una simulazione affidabile che i valori degli elementi del vettore siano modificati da un processo alla volta pertanto le parti di codice che modificano tali valori devono essere considerate sezioni critiche. Per evitare di avere problemi si usa un semaforo

binario mutex. Abbiamo quindi le tre variabili: mutex: semaforo inizializzato a 1; risorse: semaforo inizializzato a 2; libero[]: array[1,...,L] of boolean. Nel codice di ciascuno dei Pi sar presente una parte di codice di questo tipo: Processo Pi { int x; . . Richiesta(x); /* uso la risorsa x con una qualsiasi Ai i=1,..,M */ Rilascio(x); . . } Le funzioni Richiesta(x) e Rilascio(x) hanno invece un codice di questo tipo: Richiesta(x) { int i; wait(risorse); wait(mutex); /* cerca il primo i tale che libero[i] = TRUE */ libero[i]= FALSE; x=i; signal(mutex); } Rilascio (x) { wait (mutex); libero (x) = TRUE; signal (mutex); signal (risorse); }

Le sezioni racchiuse tra wait(mutex) e signal(mutex) nelle funzioni Richiesta(x) e Rilascio(x) sono eseguite in mutua esclusione le une dalle altre e ci garantisce che il vettore libero[i] venga modificato in maniera atomica. Una certa risorsa viene allocata per un dato processo all'inizio della funzione Richiesta(x) e deallocata alla fine della funzione Rilascio(x). Se la wait(risorse) rende nullo il valore del semaforo ci significa che non ci sono pi risorse disponibili ed il processo che la esegue deve aspettare che se ne liberi una, ovvero che un altro processo esegua la signal(risorse).

8.1.1 Supporto offerto da Unix Il sistema operativo UNIX mette a disposizione del programmatore alcune primitive che permettono di implementare le funzioni che si possono compiere sui semafori. Per poter utilizzare queste primitive sono per necessari alcuni header che definiscono le strutture utilizzate e permettono la gestione delle risorse di sistema. Gli header da includere allinterno di un listato sono: <sys/ipc.h> <sys/sem.h> <sys/types.h> <unistd.h>

69

8.1.2 Creazione I semafori non vengono allocati singolarmente, bens come insieme mediante la funzione semget. Allinterno di ogni insieme i semafori sono numerati progressivamente a partire da 0, e tutto linsieme accessibile mediante un identificatore. Il numero di semafori per ogni insieme viene stabilito dal programmatore. Sintassi: int semget (key_t chiave, int num_di_sem, short flags); La funzione restituisce lidentificatore dellinsieme: se tutto andato bene questo un numero non negativo, se invece ci sono stati errori allora viene restituito il valore -1. I parametri neccesari al corretto funzionamento della system call sono: chiave del tipo predefinito key_t; pu assumere un qualunque valore, ma esiste la funzione C ftok creata appositamente per la sua inizializzazione. Questa funzione richiede come parametri il nome di un file esistente ed un carattere (entrambi di tipo stringa) e restituisce la chiave. num_di_sem specifica il numero di semafori di cui costituito l'insieme. flags specifica il comportamento da seguire nella gestione dell'insieme di semafori. Con tali flag possibile specificare i privilegi sulla risorsa (espressi in forma ottale) nonch le operazioni di controllo all'atto della creazione. I flag pi usati sono IPC_CREAT (crea semaforo) e IPC_EXCL (verifica se esiste). Si possono impostare contemporaneamente pi flag mediante loperatore | del C, ad esempio 0666 | IPC_CREAT. N.B. Il flag IPC_CREAT tenta di creare linsieme, se questo esiste gi allora restituisce il suo identificatore; fallisce solamente nel caso in cui non sia possibile crearlo. Il flag IPC_EXCL effettua solamente un controllo sullesistenza dellinsieme e restituisce errore nel caso in cui linsieme esista gi.

8.1.3 Operazioni di controllo Esiste poi una system call che permette di effettuare delle operazioni di controllo sullinsieme di semafori: la semctl. Sintassi: int semctl (int sem_id, int num_del_sem, int operazione, union semun *argomenti); I parametri che questa funzione di sistema necessita sono: sem_id identificatore dellinsieme; num_del_semnumero del semaforo su cui si vuole operare. operazione specifica il tipo di operazione che si desidera effettuare. I valori pi utili per questo campo sono: IPC_RMID rimuove l'insieme dei semafori dal sistema. GETVAL legge il valore del semaforo. SETVAL imposta il valore del semaforo. GETPID legge il PID dell'ultimo processo che ha agito sul semaforo. argomenti una variabile di tipo union semun della quale si utilizza soprattutto il campo val: nel caso in cui si utilizzi la funzione con parametro SETVAL in esso viene memorizzato il valore da impostare, se invece si utilizza il parametro GETVAL in esso la funzione memorizza il valore letto.

8.1.4 Altre operazioni Analizziamo ora come si eseguono le operazioni fondamentali sul semaforo: per poter effettuare una wait oppure una signal sul semaforo necessario una system call ad hoc: Sintassi: int semop (int sem_id, struct sembuf *operazione, int num_elementi); dove i parametri da utilizzare sono: sem_id identificatore dell'insieme di semafori. operazione una struttura che specifica l'operazione da eseguire ed il semaforo interessato; essa costituita da due campi: sem_num numero del semaforo sul quale si vuole operare. sem_op specifica l'operazione da eseguire (se negativo implica l'esecuzione di una wait sul semaforo, se positivo implica l'esecuzione di una signal). sem_flg sono i flag con cui l'operazione va eseguita, i possibili valori sono: SEM_UNDO quando il processo termina ripristina il vecchio valore di sem_val. IPC_NOWAIT questo flag permette di non sospendere il processo che effettua la wait, restituisce il valore -1. Ci permette di effettuare delle wait non bloccanti. Abbiamo dunque visto le primitive che permettono di generare le funzioni attraverso le quali si possono implementare le funzioni fondamentali che si possono fare sui semafori: inizializzazione, wait, signal e rimozione. Per semplificare il pi possibile il compito al proggramatore si consiglia di racchiudere queste funzioni in appositi header in modo da avere una visione pi globale delle strutture da utilizzare. Questa strategia stata adottata da noi e di seguito viene riportato i file .h che abbiamo utilizzato negli esempi di programmazione al fondo del capitolo: Nel capitolo 15 sono state incluse anche le versioni di queste librerie in C++. Consigliamo a chi dovr usare poi queste librerie di comprenderle al meglio e, soprattutto, di personaliizarle e migliorarle.

8.1.5 semafori.h Questo file contiene le funzioni per la creazione e gestione di un insieme di semafori le funzioni in esso contenute sono: sem_init crea ed inizializza linsieme di semafori, restituendo lidentificatore dellinsieme, richiede il numero di senafori dellinsieme, il valore iniziale (uguale per tutti i semafori dellinsieme), un nome di file per la funzione ftok e i flag sem_wait effettua la wait su un semaforo, richiede linsieme di appartenenza del semaforo, il numero del semaforo e i flag sem_signal effettua la signal su un semaforo, richiede gli stessi parametri della sem_wait sem_release elimina linsieme di semafori, richiede lidentificatore dellinsieme
#include <sys/types.h> #include <unistd.h> 71

#include #include #include #include

<sys/wait.h> <sys/ipc.h> <sys/sem.h> <errno.h>

int sem_init(int num_sem, int *val_iniz, char *nome_file,int flag_get); void sem_wait(int semgroup,int sem_num,int flag_wait); void sem_signal(int semgroup,int sem_num,int flag_signal); void sem_release(int semgroup); union semun seminit; /* struttura che contiene le informazioni relative al set di semafori, va passata come argomento a semctl il campo utile di questa struttura e` VAL */ struct sembuf wait_b,signal_b; /* strutture predefinite nelle quali sono contenute informazioni importanti da passare alla semget */ int sem_init(int num_sem, int *val_iniz, char *nome_file,int flag_get) { key_t s_key; /* valore da passare a semget per creazione semafori */ int i,sem_group; s_key= ftok(nome_file,"a"); /* creazione chiave */ if((sem_group= semget(s_key,num_sem, flag_get)) > 0) { for(i=0;i<num_sem;i++) { seminit.val=val_iniz[i]; if(semctl(sem_group,i,SETVAL,&seminit)==-1) { perror("Errore in inizializzazione.\n"); exit(1); } } return(sem_group); } else { perror("Errore in creazione semafori.\n"); exit(1); } } void sem_wait(int semgroup,int sem_num,int flag_wait) { wait_b.sem_op= -1; wait_b.sem_flg= flag_wait; wait_b.sem_num= sem_num; if(semop(semgroup,&wait_b,1)==-1) { perror("Errore nella wait.\n"); exit(1); }

void sem_signal(int semgroup,int sem_num,int flag_signal) { signal_b.sem_op= 1; signal_b.sem_flg= flag_signal; signal_b.sem_num= sem_num;

if(semop(semgroup,&signal_b,1)==-1) { perror("Errore nella signal.\n"); exit(1); } } void sem_release(int semgroup) { union semun arg; int i=0; if(semctl(semgroup,i,IPC_RMID,&arg)==-1) { perror("Errore in deallocazione.\n"); exit(1); } }

8.2 Code di messaggi


8.2.1 Modello a scambio di messaggi Il modello a scambio di messaggi uno strumento utile per la comunicazione tra calcolatori diversi che possiedono risorse private, collegati tra loro mediante una rete. Una macchina concorrente che fornisca le risorse idonee a supportare questo modello diventa quindi adatta per la creazione di un sistema di calcolatori in rete. Si parla di modello ad ambiente locale poich con questa espressione si vuole intendere la possibilit che ogni processo ha di operare su risorse non direttamente modificabili da altri processi. In questo caso le interazioni tra i processi avvengono attraverso uno scambio diretto di messaggi. Essendo tutte le risorse che si utilizzano nel corso dell'elaborazione di tipo privato, viene automaticamente eliminata la competizione per il loro uso. Non pi necessario affrontare problemi relativi alla mutua esclusione. Ogni processo pu dunque essere considerato come gestore di quelle risorse presenti nella sua memoria. Alcune di queste potranno essere ad uso privato mentre altre potrebbero essere utilizzate per fornire servizi ad altri processi. A differenza del modello a memoria comune che analizzeremo in seguito si pu vedere come in questo caso sono i processi stessi che gesticono le varie risorse, mentre nellaltro modello il gestore delle risorse (larea di memoria allocata per la comunicazione) sar anche lui una risorsa condivisa. Per quanto riguarda le prestazioni vi sono studi che dimostrano l'interscambiabilit dei due modelli. E infatti possibile trasformare un programma scritto utilizzando il modello a memoria comune in un programma che usa il modello a scambio di messaggi senza che si modifichino sostanzialmente le prestazioni. Il modello a scambio di messaggi pu per essere usato ancheper la comunicazione fra pi calcolatori, mentre il modello a memoria comune destinato ad un uso su di ununica macchina. Naturalmente i due programmi presenteranno strutture fortemente diverse, non perci facile fare un raffronto fra il codice che si ottiene nei due modelli. Per poter avere un buon funzionamento del modello a scambio di messaggi i processi che inviano e ricevono messaggi devono essere sincronizztati in modo da non perdere messaggi. Il rispetto di questa condizione viene imposto dal programmatore attraverso alcuni costrutti linguistici. Il vincolo pi importante da rispettare per la sincronizzazione dei processi e la loro
73

comunicazione il rispetto della precedenza esistente tra linvio e la ricezione di un messaggio, operazioni svolte mediante system call. 8.2.2 Supporto offerto da Unix Essendo il sistema operativo Unix un sistema multitasking allora possibile usare il modello a scambio di messaggi per la comunicazione tra i processi. Ovviamente non possibile avere infinite code (non una risorsa infinita) per cui sar necessario alla fine dellesecuzione dellultimo processo che utilizza la coda eliminarla per non avere intasamento nel sistema. Per l'utilizzo delle code bisogna includere i seguenti header che definiscono le strutture usate e permettono la gestione delle code: <sys/ipc.h> <sys/msg.h> <sys/types.h> <unistd.h> <errno.h>

8.2.3 Creazione Per poter usare una coda di messaggi necessario prima crearla; per effettuare questa operazione si usa la system call msgget. Sintassi: int msgget (key_t chiave, int flag) La chiamata restituisce un intero che identifica la coda di messaggi, nel caso in cui non sia possibile creare la codaallora la chiamata fallisce e restituisce il valore -1 e i cui parametri sono rappresentati da: chiave ha lo stesso significato visto precedentemente per i semafori; flag determina il comportamento da seguire nella gestione della coda. Con tali flag possibile specificare i privilegi sulla risorsa (anche in questo caso espressi in ottale) e le operzioni di controllo allatto della creazione. Le opzioni principali sono IPC_CREAT (crea coda) e IPC_EXCL (verifica se esiste). Si possono impostare contemporaneamente pi flag, ad esempio 0666| IPC_CREAT. N.B. Il flag IPC_CREAT tenta di creare la coda e se questa esiste gi allora restituisce il suo identificatore; fallisce soltanto nel caso in cui non sia possibile crearla. Il flag IPC_EXCL invece effettua solamente un controllo sullesistenza della coda e restituisce errore nel caso questa esista gi.

8.2.4 Operazioni sulle code Le operazioni di invio e ricezione di un messaggio su di una coda utilizzano una struttura del tipo: struct messaggio { long mtype; char mtext[]; } message; dove il campo type serve per selezionare i messaggi in ricezione e il campo mtext il messaggio vero e proprio da inviare.

La chiamata di sistema che permette linvio di un messaggio la seguente: Sintassi: int msgsnd (int msgq_id, struct messaggio &message, int size, int flags); msgq_id message identificatore della coda la struttura del messaggio vista in precedenza nella quale stato memorizzato il mesaggio da inviare; size dimensione in byte del messaggio inviato flag parametro per mezzo del quale si gestisce l'operazione. Il valore pi utile IPC_NOWAIT che non blocca il processo mittente in caso di impossibilit a trasmettere. In assenza di tale flag al verificarsi di situazioni particolari come il superamento del limite di byte nella coda il processo viene bloccato, altrimenti restituisce -1 senza bloccarlo. Per ricevere un messaggio si usa invece Sintassi: int msgrcv (int msgq_id, struct messaggio &message, int size, long tipo, int flags) msgq_id l'identificatore della coda; message struttura vista in precedenza nella quale verr memorizzato il messaggio letto; size dimensione massima del messaggio da leggere in byte; tipo indica quale messaggio toglier dalla coda. E un modo per gestire priorit, oppure identificare il mittente: se il valore positivo viene tolto dalla coda il primo messaggio con il campo mtype uguale a quello specificato, se il valore 0 viene letto il primo messaggio in assoluto sulla coda, infine se il valore negativo viene estratto il primo messaggio avente nel campo mtype un valore inferiore al modulo del parametro tipo. flag parametro per mezzo del quale si gestisce l'operazione, il valore pi utile IPC_NOWAIT che non blocca il processo mittente in caso di impossibilit a trasmettere. In assenza di tale flag al verificarsi di determinate condizioni come, ad esempio il superamento del massimo numero di byte nella coda, il processo si blocca fino a quando non avr la possibilit di inviare il messaggio. Rimane ancora da vedere loperazione di eliminazione della coda. Questa operazione avviene mediante la chiamata: Sintassi: int msgctl (int msgq_id, int operazione, struct msqiud_ds *buf); msgq_id operazione buf identificatore della coda; specifica loperazione da fare sulla coda, per eliminarla si fissa al valore IPC_RMID; struttura che consente operazioni, non strettamente necessaria per la rimozione, ma comunque indispensabile indicarla.

8.2.5 messaggi.h Il file seguente contiene alcune funzioni che facilitano lutilizzo delle code di messaggi: crea_coda riceve un intero chive, eventualmente ottenuto utilizzando la funzione ftok e uno short costituito dallOR degli eventuali flags che il
75

programmatore intende definire per la sua coda di messaggi, la funzione restituisce un intero: lidentificatore della coda di messaggi scrive scrive un messaggio, costituito da una stringa di caratteri sulla coda di messaggi. Riceve nellordine: lintero identificatore della coda di messaggi sulla quale si desidera scrivere, un long int che costituisce il tipo del messaggio, il puntatore alla stringa che si intende scrivere, la dimensione di tale stringa e gli eventuali flags legge legge una stringa da una coda di messaggi. Riceve in ingresso i parametri analoghi a quelli della funzione scrive, con la differenza che si fornisce il puntatore alla stringa nella quale si intende memorizzare il messaggio letto elimina_coda elimina la risorsa coda di messaggi che non deve essere pi utilizzata. Riceve in ingresso solo lidentificatore della coda da eliminare
#include #include #include #include #include <sys/types.h> <sys/msg.h> <sys/ipc.h> <unistd.h> <errno.h>

int crea_coda(int, short); void scrive(int, long, char *, int, short); long legge(int, long, char *, int, short); void elimina_coda(int); int crea_coda(int chiave, short flags) { int coda_msg; if((coda_msg=msgget(chiave, flags))==-1) { perror("NON RIESCO A CREARE LA CODA\n"); exit(1); } else return (coda_msg);

void scrive(int coda_msg, long tipo, char *testo, int dim, short flags) { int i; struct messaggio { long type; char msg_text[dim]; }; struct messaggio message; message.type=tipo; i=0; while(testo[i]!='\0') { message.msg_text[i]=testo[i]; i++; } message.msg_text[i]=testo[i]; if(msgsnd(coda_msg, &message, dim, flags)==-1) { perror("MESSAGGIO NON INVIATO\n");

exit(1); } else return;

long legge(int coda_msg, long tipo,char *testo_letto, int dim, short flags) { int i,retval; struct messaggio { long type; char msg_text[dim]; }; struct messaggio message; message.type=tipo; if((retval=msgrcv(coda_msg, &message, dim, tipo, flags))==-1) { perror("LETTURA ERRATA\n"); exit(1); } else { for(i=0;i<dim;i++) { testo_letto[i]=message.msg_text[i]; } return(message.type); }

void elimina_coda(int coda_msg) { struct msqid_ds buf; if(msgctl(coda_msg,IPC_RMID,&buf)==-1) { perror("CODA NON ELIMINATA\n"); exit(1); } else return;

8.3 Memoria comune


Un altro mezzo con cui cessi possono comunicare tra loro si basa sulla condivisione di un area di memoria, scambiandosi dati scrivendoli e leggendoli su una medesima area dati comune. Le aree di memoria comune presentano il vantaggio di alleviare il carico di lavoro del sistema, in quanto richiedono lutilizzo delle system call solamente per quanto riguarda lattivazione, ma non per il loro utilizzo a differenza di quanto avviene per le pipe o le code di messaggi (le altre forme di comunicazione tra processi). Le system call infatti si comportano allo stesso modo delle chiamate di funzione, provocando l'arresto dell'esecuzione corrente, il conseguente salvataggio dei registri, l'esecuzione della funzione chiamata e finalmente il ripristino delle condizioni iniziali per permettere la continuazione del calcolo. Da questo si pu capire quanto sia pi veloce la lettura o scrittura di celle di memoria senza utilizzare una chiamata di sistema (E per questo motivo che in C++ si introdotta la dichiarazione inline
77

delle funzioni). Luso di processi che sfruttano la memoria condivisa potrebbe limitare la portabilit del codice in quanto i limiti alle dimensioni e al numero di aree di memoria condivise pu variare da sistema a sistema; inoltre questi limiti possono essere variati dallamministratore di sistema configurando un nuovo kernel. Luso della memoria condivisa, quindi, deve essere fatto con maggiore attenzione a questo problema rispetto alluso delle altre forme di comunicazione. 8.3.1 Supporto fornito da Unix Se si vuole usare questa possibilit per far comunicare due processi non sufficiente in questo caso creare solamente larea di memoria condivisa o shared memory; necessario collegare questarea di memoria allarea dati che i processi che dovranno utilizzarla. Gli header necessari per la definizione delle strutture per lutilizzo e la gestione delle aree di memoria condivisa sono i seguenti: <sys/types.h> <sys/ipc.h> <sys/shm.h>

8.3.2 Creazione Per creare un'area di memoria comune si utilizza la system call shmget che restituisce un identificatore per mezzo del quale possibile riferirsi all'area di memoria cos, allocata per le successive operazioni di connessione e sconnessione. Sintassi: int shmget (key_t chiave, int dimensione, int flag); La chiamata restituisce un intero che identifica larea, nel caso in cui non sia possibile creare la codaallora la chiamata fallisce e restituisce il valore -1 e i cui parametri sono rappresentati da: chiave parametro con lo stesso significato visto per le altre IPC (vedi i semafori e le code di messaggi) dimensione ampiezza in byte dell'area di memoria che si desidera creare flag permessi e operazioni analoghe a quelle gi viste per ci che riguarda i semafori e le code di messaggi.

8.3.3 Collegamento e scollegamento Come gi sottolineato, dopo essere stata creata, l'area di memoria deve essere collegata all'area dei dati dei processi che intendono utilizzarla. Per fare ci si usa la system call shmat che restituisce il puntatore alla prima cella dell'area. Poich il tipo restituito dalla funzione un puntatore a carattere, il test per verificare se la funzione ha dato errore, deve essere effettuato in questo modo: indirizzo == (char *) -1. Sintassi: char *shmat (int mem_id, char *ind_att, int flag); mem_id ind_att identificatore dell'area indirizzo da cui far partire l'allocazione; la scelta di questa valore libera per il programmatore, bene per utilizzare il valore zero in quanto permette

al sistema di ottimizzare l'uso della memoria scegliendo automaticamente l'indirizzo da cui partire e garantisce una maggiore portabilit del programma. flag permettono di specificare ad esempio l'accesso in sola lettura

8.3.4 Scollegamento Quando un processo non usa pi la risorsa pu scollegarsi utilizzando la funzione shmdt. Sintassi: int shmdt (char *indirizzo); indirizzo indirizzo a cui era collegato il segmento di memoria (valore ottenuto dalla shmat) La sconnessione di un segmento di memoria condivisa avviene anche in occasione dell'esecuzione delle system call exec e exit.

8.3.5 Deallocazione Il processo che effettua queste chiamate scollega solamente dalla propria area dati il segmento di memoria condivisa, ma non lo dealloca in quanto questo potrebbe servire ad altri processi ancora in corso. Quando anche lultimo processo ha terminato luso dellarea condivisa allora questa deve essere deallocata per evitare una possibile saturazione del sistema. Sintassi: int shmctl (int mem_id, int operazione, struct shmid_ds *argomento); mem_id operazione l'identificatore del segmento indica che operazione si vuole svolgere sullarea di memoria; per distruggerla si usa loperazione IPC_RMID, gi incontrata anche per le altre IPC; argomento puntatore a una struttura usata per compatibilit, utile con comandi diversi dalla rimozione. Come gi consigliato precedentemente utile creare un file header da includere, contenente alcune funzioni volte a semplificare l'uso delle system call precedentemente viste. Viene qui allegato il file da noi realizzato, per meglio comprenderne il significato sarebbe meglio che ciascuno realizzi il proprio utilizzando questo come esempio.

8.3.6 memcond.h Il file memcond.h contiene le funzioni utili per un uso facilitato della risorsa memoria condivisa, tali funzioni sono state realizzate per gli usi `comuni' e quindi nella funzione collega si assume che i permessi siano impostati al valore 07448 e si d zero come indirizzo di partenza dell'area di memoria condivisa in modo che sia il sistema stesso a fissare l'indirizzo pi opportuno. Si sono implememtate le seguenti funzioni crea_area alloca in memoria lo spazio necessario per larea di memoria condivisa restiruendone lidentificatore. Richiede come parametri la chiave creata con la funzione ftok, la dimensione in byte, e i flag collega collega larea di memoria al processo chiamante e richiede solo
79

lidentificatore scollega scollega larea di memoria al processo chiamante e richiede solo lidentificatore elimina_area distrugge larea di memoria comune indicata dallidentificatore
#include #include #include #include #include <sys/types.h> <sys/shm.h> <sys/ipc.h> <unistd.h> <errno.h>

int crea_area(int chiave, int dimensione, int flags) { int shm_id; if((shm_id=shmget(chiave, dimensione, flags))==-1) { perror("AREA NON CREATA!!"); exit(1); } else return shm_id; } char *collega(int shm_id) { char *indirizzo; if((indirizzo=shmat(shm_id,0,0744))==(char *)-1) { perror("INDIRIZZO NON COLLEGATO!!!!"); exit(1); } else return indirizzo; } void scollega(char *indirizzo) { if(shmdt(indirizzo)==-1) { perror("INDIRIZZO NON SCOLLEGATO\n"); exit(1); } else return; } void elimina_area(int shm_id) { struct shmid_ds *argomento; if(shmctl(shm_id,IPC_RMID,argomento)==-1) { perror("AREA NON ELIMINATA\n"); exit(1); } else return; }

8.4 Pipe
Rimane da parlare delle pipe. In relt le pipe non sono delle vere e proprie IPC, ma vengono trattate in questo capitolo in quanto anche loro permettono la comunicazione tra i processi.

Tutte le versioni di UNIX permettono di aprire una pipe per la trasmissione di elevate quantit di dati. Una pipe pu essere vista come una generalizzazione del concetto di file, essa deve essere creata per mezzo di una chiamata di sistema: Sintassi: int pipe (int *descf); descf vettore di due interi ai cui elementi verranno assegnati i descrittori della pipe, descf[0] per la lettura e descf[1] per la scrittura. Una pipe viene gestita in modalit FIFO (first-in first-out) e funzione esattamente come se fosse un file: per leggere e scrivere si usano le chiamate read e write; inoltre, come tutti i descrittori, anche quelli della pipe vengono ereditati dai figli che possono in tal modo utilizzarla. Durante le operazioni di lettura e scrittura si possono verificare i seguenti comportamenti: se si effettua una lettura di un numero di byte inferiore a quelli contenuti nella pipe, i restanti a vengono lasciati a disposizione per una successiva lettura; se si tenta di leggere pi dati di quelli presenti nella pipe, allora vengono letti solo i dati disponibili, e il processo lettore dovr essere quindi in grado di gestire un valore restituito minore di quello atteso; una lettura su una pipe vuota pone in attesa il lettore, mentre se nessun processo ha la pipe aperta in scrittura, il lettore riceve un valore pari a zero indicante che non ci sono scrittori. Per evitare che il lettore si ponga in attesa possibile specificare il flag O_NDELAY (per mezzo della fcntl): in questo caso, per, si riceve 0 il lettore non pi in grado di distinguere una pipe vuota dall'assenza di scrittori; se un processo tenta di scrivere su una pipe piena si pone in attesa e attende che ci sia spazio disponibile; se si scrive su una pipe una quantit di dati inferiore alla capacit della pipe (che di almeno 4096 byte, ma pu variare da sistema a sistema), garantita l'atomicit dell'operazione di scrittura. E cio garantito che i dati di diversi scrittori non vengano mescolati. se uno scrittore scrive su una pipe che non ha lettori viene generato il segnale SIGPIPE la cui azione di default di terminare il processo che lo riceve; bisogna perci fare in modo che il segnale venga ignorato oppure gestito opportunamente.

8.5 Esempi di programmazione


8.5.1 psem.c Questo programma rappresenta una possibile soluzione al problema dei lettori e scrittori. Si lavora con le seguenti ipotesi: La precedenza data ai lettori. Possono accedere al file pi lettori contemporaneamente. Uno scrittore pu accedere al file solo se nessun lettore sta lavorando in quel momento su di esso. I processi lettori e scrittori sono generati dal processo padre che esegue un'operazione di fork come conseguenza di un ordine ricevuto da tastiera. Tutti i processi generati sono dunque fratelli; di conseguenza risultano essere totalmente indipendenti l'uno dall'altro. Questo fatto complica il normale svolgimento del programma, infatti si dovuto risolvere il problema di aggiornare la situazione di un processo al momento della sua creazione. I lettori devono conoscere il numero di lettori vivi per poter bloccare o consentire l'accesso al
81

file agli scrittori eventualmente in attesa di operare. Come soluzione di questo problema si deciso di far tenere al processo padre il conto dei lettori `vivi': il padre infatti incrementa di uno il valore di un contatore quando viene generato un nuovo processo lettore, quindi la decrementa quando tale processo ha terminato di operare. Le operazioni di incremento e di decremento sono atomiche e l'atomicit garantita dalla presenza di un semaforo mutex. Il programma, per come strutturato, risulta essere particolarmente facile da modificare nel caso in cui si decida di variare le specifiche date come ipotesi iniziali. Nel caso in cui si voglia, ad esempio, dare la precedenza ai processi scrittori, risulta immediato risolvere simmetricamente il problema: possibile, ad esempio, incrementare il contatore per i processi scrittori anzich per i lettori,.... Se si desidera consentire il lavoro di un massimo numero di lettori contemporaneamente, sufficiente che il padre esegua un controllo sulla variabile contatore prima della fork.
#include #include #include #include #include #include #include #include #define #define #define #define #define <errno.h> <fcntl.h> <unistd.h> <sys/signal.h> <sys/stat.h> <sys/types.h> <sys/wait.h> "semafori.h" DIM_STR 5 DIVIS 67108864 MAXRIG 10 MUTEX 0 ACC_FILE 1

/* Prototipi di funzioni */ void crea_file(char *,int); int apri_file(char *,int); void scrivi_file(int); void scrivi(int,char*); void crea_lettore(); void crea_scrittore(); void lettura(char *); void scrittura(char *); void decrementa(void); void incrementa(void); void faiwait(void); int cont=0,cont_scritt=0,fd,fdw; int padre,pausa,semafori; long int casuale; void main (void) { int i; int val_iniz[]={1 , 1}; int cont_proc=0; char c; union wait stato; padre=getpid(); /* in questa fase del programma viene creato il file su cui si svolgeranno le operazioni di lettora e scrittura crea_file("sem.txt",0644); */

fd=apri_file("sem.txt",O_RDONLY); fdw=apri_file("sem.txt",O_WRONLY);

/* descrittore usato in Lettura */ /* descrittore usato in Scrittura*/

lseek(fdw,0L,2); /* posizionamento della testina per una corretta scrittura del messaggio */ /* CREAZIONE SEMAFORI ED INIZIALIZZAZIONE CAMPI */ semafori= sem_init(2,val_iniz,"sem_esp.c",0777|IPC_CREAT|IPC_EXCL); printf("Opzione>\n"); c=getchar(); while(c!='x' && c!='X') /* ciclo eseguito finche` non si digita 'x' */ { cont_proc++; switch(c) { case 'r':case 'R': { casuale=rand(); pausa=casuale/DIVIS; signal(SIGUSR1,decrementa); signal(SIGUSR2,incrementa); crea_lettore; break; } case 'w':case 'W': { cont_scritt++; casuale=rand(); pausa=casuale/DIVIS; crea_scrittore; break; } default: cont_proc--; printf("Carattere inutile\n"); } getchar(); printf("Opzione>\n"); c=getchar(); signal(SIGCLD,faiwait); } /* Il padre aspetta la terminazione dei processi figli creati */ printf("Sono stati creati %d processi figli.\n", cont_proc); for(i=0;i<cont_proc;i++) { wait(&stato); printf("E'terminato un processo.\n"); } /* fase di rilascio delle risorse descrittori di file */ close(fd); close(fdw); sem_release(semafori); } exit(0); /* rimuovo le risorse semaforiche usate */

/* La funzione che segue viene chiamata nella gestione del segnale SIGCLD, cioe' ogni qualvolta termina un figlio. Il suo scopo `e quello di fare una wait per eliminare i processi zombie. */ void faiwait(void) 83

{ }

union wait stato; wait(&stato);

/* Le due funzioni qui di seguito eseguono un'unica operazione ciascuna ovvero incrementano e decrementano il contatore dei processi Lettori. La funzione decrementa inoltre esegue anche il controllo per verificare se tali processi sono finiti, in tal caso abilita l'accesso al file per gli scrittori. */ void decrementa(void) { cont--; if(cont==0) { sem_signal(semafori,ACC_FILE,SEM_UNDO); } } void incrementa(void) { cont++; } /* La funzione seguente provvede alla creazione del file su cui i vari processi lavoreranno. Viene inizialmente creato il file con la primitiva "creat" quindi, se la medesima e' andata a buon fine, viene chiamata una seconda funzione che esegue operazioni di scrittura.*/ void crea_file(char *nome_file,int perms) { int descr; if((descr=open(nome_file,O_CREAT|O_WRONLY|O_TRUNC,perms)) == -1) { perror("Errore in creazione file.\n"); exit(1); } else { scrivi_file(descr); close(descr); return; } } /* La funzione che segue provvede all'impostazione delle operazioni di scrittura sul file creato: si utilizza a questo proposito la funzione "scrivi" che viene richiamata per alcune volte. */ void scrivi_file(int fd1) { char conta; char conta1[2]; char *riga="riga\0"; char fine='\n'; for(conta='A'; conta<'L'; conta++) { conta1[0]=conta; conta1[1]='\0'; scrivi(fd1,riga);

scrivi(fd1,conta1); scrivi(fd1,&fine); }

/* Questa funzione cerca di scrivere una stringa di caratteri passata da tastiera sul file identificato dal descrittore anch'esso passato come parametro. */ void scrivi(int fd2,char *oggetto) { int scritto; if((scritto= write(fd2,oggetto,strlen(oggetto))) == -1) { perror("Non posso scrivere sul file.\n"); exit(1); } else return;

/* Questa funzione provvede ad aprire il file in modalita' precisate dal campo "flags" passato come parametro, facendo uso della primitiva "open". */ int apri_file(char *nome_file,int flags) { int descrittore; if((descrittore= open(nome_file,flags,0)) == -1) { perror("Errore in apertura file.\n"); exit(1); } else return(descrittore);

/* La funzione che segue viene chiamata dal padre quando viene inviato da tastiera il comando 'r' o 'R'. Viene generato un processo lettore che ,per prima cosa, esegue una WAIT sul semaforo Mutex. Tale operazione introduce una sezione critica nella quale il Lettore invia un segnale al processo padre, affinche'quest'ultimo incrementi il valore del contatore dei processi Lettori, quindi incrementa un proprio contatore locale.Segue un test su tale contatore locale: se infatti la variabile in questione e' uguale a 1, significa che tale Lettore e' il primo in coda per accedere al file, quindi puo' proseguire le operazioni eseguendo una WAIT sul semaforo che gestisce l'accesso al file. Dopo una SIGNAL sul semaforo Mutex, seguono le operazioni di lettura. Alla fine di tali operazioni il processo esegue un'altra sezione critica nella quale esegue il decremento dei contatori con le stesse modalita' dell'incremento viste prima. A questo punto, se la variabile contatore vale zero, non essendoci piu' Lettori in coda, l'accesso al file e' consentito ad eventuali processi scrittori,quindi il Lettore esegue una SIGNAL sul semaforo per l'accesso al file. */ void crea_lettore() { int lettore,pid,valore; char stringa[DIM_STR]=""; if((lettore=fork())<0) { printf("Errore nella fork.\n"); 85

exit(1); } else if(lettore==0) { sem_wait(semafori,MUTEX,SEM_UNDO); /* wait sul semaforo 'mutex' */ kill(padre,SIGUSR2); cont++; if(cont==1) { sem_wait(semafori,ACC_FILE,SEM_UNDO); } sem_signal(semafori,MUTEX,SEM_UNDO); lettura(stringa); pid=getpid(); printf("Sono il lettore con pid %d, ho letto la stringa %s\n", pid,stringa); sem_wait(semafori,MUTEX,SEM_UNDO); kill(padre,SIGUSR1); cont--; if(cont==0) { sem_signal(semafori,ACC_FILE,SEM_UNDO); } sem_signal(semafori,MUTEX,SEM_UNDO); exit(0); } else return; } /* Funzione che esegue operazioni di lettura */ void lettura(char *stringa) { int nbyte,pid; char c; pid=getpid(); printf("Sono il lettore con pid %d, dormo...\n",pid); sleep(pausa); if((nbyte=read(fd,stringa,DIM_STR))==0) { printf("Trovato EOF\n"); } if(nbyte==-1) { perror("NON RIESCO A LEGGERE\n"); exit(1); } read(fd,&c,1); return; } /* Questa funzione viene chiamata dal processo padre quando da tastiera e' digitato il comando 'w' o 'W'. Essa ha l'effetto di creare un processo Scrittore. Tale processo esegue un WAIT sul semaforo per l'accesso al file, quindi,se tale accesso e' consentito, svolgera' le operazioni di scrittura, diversamente attendera' in coda in attesa che i processi Lettori rilascino la risorsa. */ void crea_scrittore()

int scrittore,pid,valore; char strw[DIM_STR]=""; if((scrittore=fork())<0) { printf("Errore nella fork.\n"); exit(1); } else if(scrittore==0) { sem_wait(semafori,ACC_FILE,SEM_UNDO); scrittura(strw); sem_signal(semafori,ACC_FILE,SEM_UNDO); pid=getpid(); printf("Sono lo scrittore con pid %d, ho scritto %s\n",pid,strw); exit(0); }

} /*Questa funzione esegue operzioni di scrittura sul file */ void scrittura(char *strw) { int conta,pid; pid=getpid(); printf("Sono lo scrittore con pid %d, dormo...\n",pid); sleep(pausa); conta=cont_scritt%10; strw[0]='s';strw[1]='c';strw[2]='r';strw[3]='i'; strw[4]=(conta+'0'); write(fdw,strw,DIM_STR); write(fdw,"\n",1); } /* OSSERVAZIONI: il programma, per come strutturato, risulta essere particolarmente facile da modificare nel caso in cui si decida di variare le specifiche date come ipotesi iniziali. Nel caso in cui si voglia, ad esesmpio, dare la precedenza ai processi Scrittori, risulta immediato risolvere simmetricamente il problema: e' possibile,ad esempio, incrementare il contatore per i processi Scrittori anziche' per i Lettori,.... Se si desidera consentire il lavoro di un massimo numero di Lettori contemporaneamente, e' sufficiente che il padre esegua un controllo sulla variabile contatore prima della Fork. */

8.5.2 prodcon1.c prodcon2.c I due esempi seguenti sono una possibile soluzione al problema del roduttore e consumatore dove il buffer condiviso costituito da un'area di memoria comune. I problemi di mutua esclusione e sincronizzazione vengono gestiti tramite tre semafori: mutex gestisce la mutua esclusione durante le operazioni di lettura e scrittura empty indica il numero di celle vuote, il processo scrittore si blocca quando non vi sono pi celle libere, attendendo cos, che il lettore le svuoti. full indica il numero di celle occupate del buffer, il processo lettore si blocca quando full zero poich non ci sono dati da leggere Per mezzo di questi due semafori si evita che il processo scrittore sovrascriva dei dati che non sono ancora stati letti e che il processo lettore legga una cella che non ancora stata scritta
87

oppure dei dati gi letti. Questo sufficiente nel primo caso in cui il buffer di dimensione pari a un messaggio, nel secondo caso in cui si possono scrivere pi messaggi, necessario effettuare dei controlli sull'indirizzo in cui si sta scrivendo o leggendo, poich\'e il buffer circolare viene implementato con un'area di memoria di dimensioni limitate, giunti al termine si deve ripartire dalla prima cella. prodcon1.c
#include #include #include #include #include #include #include #include #include #include #include #include #define #define #define #define #define #define <unistd.h> <errno.h> <fcntl.h> <sys/types.h> <sys/signal.h> <sys/wait.h> <sys/ipc.h> <sys/sem.h> <sys/msg.h> <sys/stat.h> "semafori.h" "memcond.h" DIM_PROD 12 DIM_BUF 1 NOPER 5 MUTEX 0 FULL 1 EMPTY 2

void produttore(int,int,int,int); void consumatore(int,int,int,int); char *indirizzo; int pid_prod,padre; void main(void) { int key_mem, area, semgroup; int i; int valin[3]; /* Vettore dei valori iniziali dei semafori */ union wait stato; padre=getpid(); valin[MUTEX]=1; valin[FULL]=0; valin[EMPTY]=DIM_BUF; semgroup=sem_init(3,valin,"prod_cons1.c",0644|IPC_CREAT); key_mem=ftok("prod_cons1.c","d"); area=crea_area(key_mem,DIM_PROD*DIM_BUF,0644|IPC_CREAT); produttore(semgroup); consumatore(semgroup); wait(&stato); wait(&stato); sem_release(semgroup); elimina_area(area); exit(0);

/* Attende la terminazione dei due figli */

void produttore(int semgroup) { char stringa[12]="produttore"; char testo[5]; int producer,i,j; if((producer=fork())==-1) { perror("FORK FALLITA!\n"); exit(1); } else if(producer==0) { indirizzo=collega(area); pid_prod=getpid(); for(i=0;i<NOPER;i++) { stringa[10]=(i+1+'0'); stringa[11]='\0'; sem_wait(semgroup,EMPTY,SEM_UNDO); sem_wait(semgroup,MUTEX,SEM_UNDO); j=0; /* Scrive la stringa nel buffer */ while(stringa[j]!='\0') { indirizzo[j]=stringa[j++]; } indirizzo[j]=stringa[j]; printf("Ho scritto %s\n",indirizzo); sem_signal(semgroup,MUTEX,SEM_UNDO); sem_signal(semgroup,FULL,SEM_UNDO); } exit(0); } else return;

void consumatore(int semgroup) { char letta[12]=""; int consumer,i,j; if((consumer=fork())==-1) { perror("FORK FALLITA!\n"); exit(1); } else if(consumer==0) { indirizzo=collega(area); for(i=0;i<NOPER;i++) { sem_wait(semgroup,FULL,SEM_UNDO); sem_wait(semgroup,MUTEX,SEM_UNDO); /* Legge direttamente dalla cella di memoria, * come se fosse una normale variabile globale */ printf("Sono il consumatore, ho letto la stringa: %s\n",indirizzo); sem_signal(semgroup,MUTEX,SEM_UNDO); sem_signal(semgroup,EMPTY,SEM_UNDO); } scollega(indirizzo); 89

exit(0); } else return;

prodcon2.c
#include #include #include #include #include #include #include #include #include #include #include #include #include #define #define #define #define #define #define #define <stdio.h> <unistd.h> <errno.h> <fcntl.h> <sys/types.h> <sys/signal.h> <sys/wait.h> <sys/ipc.h> <sys/sem.h> <sys/shm.h> <sys/stat.h> "semafori.h" "memcond.h" DIM_PROD 12 DIM_BUF 5 NOPER 10 DIVIS 67108864 MUTEX 0 FULL 1 EMPTY 2

void produttore(int); void consumatore(int); char *root; int pid_prod,padre,area; int pausa; long int casuale; union wait stato; void main(void) { int key_mem,semgroup; int i; int valin[3]; /* Vettore dei valori iniziali dei semafori */ padre=getpid(); valin[MUTEX]=1; valin[FULL]=0; valin[EMPTY]=DIM_BUF; semgroup=sem_init(3,valin,"prod_cons2.c",0644|IPC_CREAT); key_mem=ftok("prod_cons2.c","d"); area=crea_area(key_mem,DIM_PROD*DIM_BUF,0644|IPC_CREAT); produttore(semgroup); consumatore(semgroup); wait(&stato); /* Aspetta la terminazione dei due figli */ wait(&stato); elimina_area(area); sem_release(semgroup); exit(0);

void produttore(int semgroup)

char stringa[12]="produttore", *indirizzo, *stop; char testo[5]; int producer,i,j; if((producer=fork())<0) { perror("FORK FALLITA!\n"); exit(1); } else if(producer==0) { root=collega(area); pid_prod=getpid(); for(i=1;i<NOPER;i++) { casuale=rand(); pausa=casuale/(2*DIVIS); /* Genera la pausa casuale */ stringa[10]=(i+'0'); stringa[11]='\0'; sem_wait(semgroup,EMPTY,SEM_UNDO); sleep(pausa); sem_wait(semgroup,MUTEX,SEM_UNDO); j=0; /* Posizionamento nella posizione corretta del * buffer circolare */ indirizzo=root+((i-1)%DIM_BUF)*DIM_PROD; /* Scrittura della stringa nella cella del buffer */ while(stringa[j]!='\0') { indirizzo[j]=stringa[j++]; } indirizzo[j]=stringa[j]; printf("Ho scritto %s\n",indirizzo); sem_signal(semgroup,MUTEX,SEM_UNDO); sem_signal(semgroup,FULL,SEM_UNDO); } exit(0); } else return;

void consumatore(int semgroup) { char letta[12]="", *indirizzo, *stop; int consumer,i,j; if((consumer=fork())==-1) { perror("FORK FALLITA!\n"); exit(1); } else if(consumer==0) { root=collega(area); for(i=1;i<NOPER;i++) { casuale=rand(); pausa=casuale/(2*DIVIS); sem_wait(semgroup,FULL,SEM_UNDO); 91

sleep(pausa); sem_wait(semgroup,MUTEX,SEM_UNDO); indirizzo=root+((i-1)%DIM_BUF)*DIM_PROD; /* Posizionamento */ printf("Sono il consumatore, ho letto la stringa: %s\n",indirizzo); sem_signal(semgroup,MUTEX,SEM_UNDO); sem_signal(semgroup,EMPTY,SEM_UNDO); } exit(0); } else return; }

8.5.3 messaggi.c Lo scopo di questo semplice programma di mostrare l'utilizzo delle code di messaggi. Un processo padre crea una coda di messaggi e su questa riceve una stringa inviata dal figlio. Un utilizzo pi complesso delle code di messaggi si pu trovare nel programma successivo, lsmem.c.
#include #include #include #include #include #include #include <unistd.h> <errno.h> <sys/types.h> <sys/ipc.h> <sys/msg.h> <sys/wait.h> "messaggi.h"

#define DIM 20 void main(void) { int figlio,chiave,coda_msg,flags; char testo[DIM]="Prova con i messaggi"; char testo_letto[DIM]; union wait stato; chiave=ftok("messaggi.c","a"); flags=0644|IPC_CREAT; coda_msg=crea_coda(chiave,flags); if((figlio=fork())<0) { perror("FORK FALLITA\n"); exit(1); } else if(figlio==0) { scrive(coda_msg,1,testo,DIM,IPC_NOWAIT); printf("Sono il figlio, ho scritto la stringa: %s\n",testo); exit(0); } else { wait(&stato); legge(coda_msg,1,testo_letto,DIM,0); printf("Sono il padre, ho letto la stringa: %s\n",testo_letto); elimina_coda(coda_msg); exit(0); }

8.5.4 lsmem.c Il programma lsmem.c vuole essere una possibile soluzione al problema dei lettori--scrittori nel quale si utilizza come risorsa comune un'area di memoria condivisa e in cui i semafori vengono realizzati utilizzando le comunicazioni fra i processi tramite code di messaggi. L'algoritmo seguito analogo a quello utilizzato nella soluzione del problema con l'utilizzo diretto dei semafori; la differenza sta nel fatto che qui necessario un processo di sincronizzazione che simuli il comportamento del sistema operativo quando si utilizzano i semafori. Quando un processo intende effettuare una WAIT o una SIGNAL su un semaforo chiama rispettivamente la funzione down o up: tali funzioni inviano, attraverso la coda di messaggi codasnd un messaggio al processo sincron, il cui testo contiene il nome del semaforo sul quale si vuole agire e il PID del processo che lo utilizza, inoltre il tipo di operazione viene determinato dal tipo del messaggio che pu appunto essere UP o DOWN. Il processo sincron sempre in ascolto della coda (non utilizza il flag IPC_NOWAIT) e non appena riceve un messaggio verifica di che semaforo si tratta e il tipo di operazione. Nel caso in cui l'operazione sia una DOWN sincron verifica se la variabile relativa a quel semaforo posta a zero: se cos, aggiunge il PID del processo alla lista dei processi in coda e non invia nessuna risposta alla procedura che ha eseguito la DOWN; in caso contrario (variabile diversa da zero) viene permesso al processo l'accesso alla risorsa (decrementando la variabile) e viene data risposta positiva al processo chiamante inserendo un messaggio con tipo pari al PID del processo destinatario nella coda codarcv: il processo destinatario in tal modo si sblocca e continua nella sezione critica. Nel caso in cui l'operazione sia una UP, se esistono processi in lista d'attesa, sincron libera il primo processo in lista inviando il messaggio di risposta sulla codarcv e lasciando invariata la variabile semaforica; se non esistono processi in lista, viene incrementata la variabile. Il processo sincron viene fatto terminare da un messaggio del padre quando questi riceve il comando di terminazione dall'utente ('x') e quando non sono pi presenti processi lettori `vivi'. Oltre al listato del programma, includiamo qui di seguito il listato del file updown.h. updown.h
#define UP 1 #define DOWN 2 void up(int codasnd,int codarcv,char *stringa) { char letto[1]; scrive(codasnd,UP,stringa,12,IPC_NOWAIT); legge(codarcv,getpid(),letto,1,0);

void down(int codasnd,int codarcv,char *stringa) { char letto[1]; scrive(codasnd,DOWN,stringa,12,IPC_NOWAIT); legge(codarcv,getpid(),letto,1,0); }

lsmem.c
93

#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define #define #define #define #define void void void void void

<fcntl.h> <string.h> <sys/ipc.h> <sys/signal.h> <sys/msg.h> <sys/types.h> <sys/shm.h> <sys/wait.h> <unistd.h> <errno.h> "memcond.h" "messaggi.h" "updown.h" "conv.h" MAXBUF 3 DIM 10 CHIUDI 4 TERMINA 3 DIVIS 67108864

crealettore(void); creascrittore(void); lettura(char *); scrittura(char *); sincron(void);

int codasnd,codarcv,mem; int padre,pausa,cont=0,contscri=0,contlett=0; long int casuale; void main(void) { int keymsg1,keymsg2,keymem; char c,stringa[12]; union wait stato; padre=getpid(); keymsg1=ftok("rw1.c","a"); codasnd=crea_coda(keymsg1,0744|IPC_CREAT); keymsg2=ftok("rw1.c","b"); codarcv=crea_coda(keymsg2,0744|IPC_CREAT); keymem=ftok("rw1.c","c"); mem=crea_area(keymem,MAXBUF*DIM,0744|IPC_CREAT); sincron(); printf("Opzione> "); c=getchar(); while(c!='x' && c!='X') { switch(c) { case 'r':case 'R': { contlett+=1; casuale=rand(); pausa=casuale/DIVIS; crealettore(); break;

} case 'w':case 'W': { contscri+=1; casuale=rand(); pausa=casuale/DIVIS; creascrittore(); break; default: printf("Carattere inutile\n"); break;

} while(cont!=0) { wait(&stato); } stringa[0]=' '; printf("Padre invia a %d %d\n",codasnd,TERMINA); scrive(codasnd,TERMINA,stringa,sizeof(stringa),IPC_NOWAIT); legge(codasnd,CHIUDI,stringa,sizeof(stringa),0); elimina_coda(codasnd); elimina_coda(codarcv); elimina_area(mem);

} getchar(); printf("Opzione> "); c=getchar();

void crealettore(void) { int lettore,proc; char stringamut[12]="mutex ",pid[5]; char stringa[DIM]; if((lettore=fork())<0) { perror("Fork fallita\n"); } else if(lettore==0) { proc=getpid(); itoa(proc,pid); strcat(stringamut,pid); lettura(stringa); printf("Sono il lettore con pid %d ho letto la stringa %s\n", getpid(),stringa); exit(0); } else return; } void lettura(char *stringa) /* Procedura di lettura dal buffer di memoria condivisa: la lettura e' implementata in modo tale che un processo legge da una cella del buffer determinata dal suo numero d'ordine */ { int j,proc; char *root,*indirizzo,*stop,pid[5]; char stringaemp[12]="empty", stringaful[12]="full", stringamut[12]="mutex"; proc=getpid(); 95

itoa(proc,pid); root=collega(mem); indirizzo=root+((contlett-1)%MAXBUF)*DIM; strcat(stringaemp,pid); strcat(stringaful,pid); strcat(stringamut,pid); down(codasnd,codarcv,stringaful); /* down su full */ down(codasnd,codarcv,stringamut); /* down su mutex */ j=0; while(indirizzo[j]!='\0') { stringa[j]=indirizzo[j++]; } stringa[j]=indirizzo[j]; sleep(pausa); up(codasnd,codarcv,stringaemp); /* up su empty */ up(codasnd,codarcv,stringamut); /*up su mutex */ } void creascrittore(void) { int scrittore,valore,proc; char pid[6]; char stringa[DIM]="Scritto"; if((scrittore=fork())<0) { perror("Fork fallita\n"); exit(1); } else if(scrittore==0) { proc=getpid(); itoa(proc,pid); scrittura(stringa); printf("Sono lo scrittore con pid %d, ho scritto %s\n", getpid(),stringa); exit(0); } else return; } void scrittura(char *stringa) /* Anche la scrittura sul buffer di memoria condivisa viene eseguita in modo tale che la cella di scrittura sia determinata dal numero d'ordine del processo scrittore. */ { int proc,j; char *root, *indirizzo, *stop; char pid[5],conta[3]; char stringaemp[12]="empty", stringaful[12]="full", stringamut[12]="mutex"; proc=getpid(); itoa(proc,pid); strcat(stringaemp,pid); strcat(stringaful,pid); /*Prepara le stringhe per utilizzare up e down*/ strcat(stringamut,pid);

root=collega(mem); indirizzo=root+((contscri-1)%MAXBUF)*DIM; itoa(contscri,conta); strcat(stringa,conta); down(codasnd,codarcv,stringaemp); /* down su empty */ down(codasnd,codarcv,stringamut); /* down su mutex */ j=0; while(stringa[j]!='\0') { indirizzo[j]=stringa[j++]; } indirizzo[j]=stringa[j]; /*sleep(pausa/2);*/ up(codasnd,codarcv,stringaful); /* up su full */ up(codasnd,codarcv,stringamut); /* up su mutex */ } void sincron(void) { typedef int sem; typedef struct lista *Plista; typedef struct lista { int processo; Plista next; } Lista; typedef Plista listamut; /* Ridefinisce il tipo Plista per ogni semaforo in modo da creare delle liste separate pur typedef Plista listaemp; /* essendo dello stesso tipo. */ typedef Plista listaful; int psinc,pid=0,pid1; long tipo; char stringa[12],semaforo[6],vuota[1]; sem mutex=1,full=0,empty=MAXBUF; vuota[0]=' '; if((psinc=fork())==-1) { perror("Processo sincronizzatore non creato"); exit(1); } else if(psinc==0) { listamut testamut,ultimomut,nuovomut,correntemut; listaemp testaemp,ultimoemp,nuovoemp,correnteemp; listaful testaful,ultimoful,nuovoful,correnteful; testamut=(listamut)malloc(sizeof(Lista)); testamut->next=NULL; ultimomut=testamut; testaemp=(listaemp)malloc(sizeof(Lista)); testaemp->next=NULL; ultimoemp=testaemp; testaful=(listaful)malloc(sizeof(Lista)); 97

*/

testaful->next=NULL; ultimoful=testaful; while(1) { tipo=legge(codasnd,0,stringa,sizeof(stringa),0); /* sincron si pone in ascolto */ if(tipo==TERMINA) /* Se e' il segnale del padre */ { printf("Sincron termina.....\n"); stringa[0]=' '; scrive(codasnd,CHIUDI,stringa,1,IPC_NOWAIT); exit(0); } sscanf(stringa,"%s %d",semaforo,&pid); if(strcmp(semaforo,"mutex")==0) { if(tipo==UP) { if(testamut->next!=NULL) /* esistono processi in lista viene estratto il primo */ { correntemut=testamut->next; pid1=correntemut->processo; testamut->next=correntemut->next; free(correntemut); scrive(codarcv,pid1,vuota,1,IPC_NOWAIT); } else /* non esistono processi in lista mutex viene posto a 1 { mutex==1; scrive(codarcv,pid,vuota,1,IPC_NOWAIT); } } if(tipo==DOWN) { if(mutex==1) { mutex==0; scrive(codarcv,pid,vuota,1,IPC_NOWAIT); } else /* Se mutex e' uguale a zero il processo viene aggiunto lista. */ { printf("Mutex=0, mi pongo in attesa...\n"); nuovomut=(listamut)malloc(sizeof(Lista)); nuovomut->processo=pid; nuovomut->next=NULL; ultimomut->next=nuovomut; ultimomut=nuovomut; } } if(strcmp(semaforo,"empty")==0) { if(tipo==UP) { if(empty>=0 && testaemp->next==NULL) empty++; printf("empty %d\n",empty); if(testaemp->next!=NULL) { correnteemp=testaemp->next; pid1=correnteemp->processo;

*/

alla

} } else return; }

} if(tipo==DOWN) { if(empty>=1) { empty--; printf("empty %d\n",empty); scrive(codarcv,pid,vuota,1,IPC_NOWAIT); } else { printf("empty %d\n",empty); nuovoemp=(listaemp)malloc(sizeof(Lista)); nuovoemp->processo=pid; nuovoemp->next=NULL; ultimoemp->next=nuovoemp; ultimoemp=nuovoemp; } } } if(strcmp(semaforo,"full")==0) { if(tipo==UP) { if(full<MAXBUF && testaful->next==NULL) full++; printf("full %d\n",full); if(testaful->next!=NULL) { correnteful=testaful->next; pid1=correnteful->processo; testaful->next=correnteful->next; free(correnteful); scrive(codarcv,pid1,vuota,1,IPC_NOWAIT); } scrive(codarcv,pid,vuota,1,IPC_NOWAIT); } if(tipo==DOWN) { if(full>=1) { full--; printf("full %d\n",full); scrive(codarcv,pid,vuota,1,IPC_NOWAIT); } else { nuovoful=(listaful)malloc(sizeof(Lista)); nuovoful->processo=pid; nuovoful->next=NULL; ultimoful->next=nuovoful; ultimoful=nuovoful; } } }

} scrive(codarcv,pid,vuota,1,IPC_NOWAIT);

testaemp->next=correnteemp->next; free(correnteemp); scrive(codarcv,pid1,vuota,1,IPC_NOWAIT);

99

8.5.5 espipe1.c In questo esempio il processo padre apre una pipe e poi genera un figlio che eredita l'identificatore; successivamente il padre chiude la pipe in scrittura e si predispone alla lettura. Il figlio inizia ad inserire messaggi nella pipe in un ciclo infinito. Il padre esegue una serie di letture predeterminata e al termine invia un segnale al figlio, interrompendo cos, la produzione di messaggi.
#include #include #include #include #include <stdio.h> <stdlib.h> <signal.h> <fcntl.h> <errno.h>

#define MAX 25 #define MAX2 30 main() { int pid, j, i; int fd[2]; char buf22[MAX2]="Padre:"; char buf2[MAX2]="sono_il_figlio\n"; pipe(fd); /* apertura della pipe */

if((pid=fork())<0) { perror("FORK :"); exit(1); } else if(pid==0) { /* CODICE DEL FIGLIO */ close(fd[0]); /* chiusura della pipe in lettura */ /* ciclo infinito in cui il figlio inserisce messaggi sulla

for( ; ; ) write(fd[1],buf2,strlen(buf2)); pipe */ } else { /* CODICE DEL PADRE */

close(fd[1]); /* chiusura della pipe in scrittura */ for(i=0;i<MAX;i++) { read(fd[0],&(buf22[7]),strlen(buf2)); write(1, buf22,30); /* stampa messaggio sullo standard output */ } printf("La dimensione di buf22 e': %d.\n",strlen(buf22)); printf("La dimensione di buf2 e': %d.\n",strlen(buf2)); kill(pid,SIGUSR1); /* segnale per uccidere il figlio */

/* a questo punto viene effettuato un ciclo che ha come unico scopo quello di contare il numero di messaggi rimasti sulla pipe */ for(i=0; read(fd[0],&(buf22[7]),strlen(buf2))==15; i++); printf("C'erano ancora %d messaggi nella pipe.\n",i); close(fd[0]); exit(0); } }

8.5.6 espipe2.c In questo secondo esempio le operazioni di scrittura e lettura sulla pipe sono effettuate da due figli. Il primo, come nell'esempio precedente produce messaggi e li inserisce nella pipe fino a che non viene terminato da un segnale inviatogli dal padre. Il secondo figlio esegue un numero prefissato di letture e al termine invia un segnale al padre il quale dopo aver inviato un segnale al figlio scrittore conta il numero di messaggi rimasti nella pipe.
#include #include #include #include #include #include #include <stdio.h> <stdlib.h> <signal.h> <fcntl.h> <sys/types.h> <sys/wait.h> <errno.h>

#define MAX 25 #define MAX1 30 void action(int sig) { signal(sig,action); } main() { int ppid, pid1, pid2, stato, len, i; int fd[2]; char buf_fig_2[MAX1]="Figlio_2: "; char buf_fig_1[MAX1]="sono_il_figlio_1\n"; char appo[MAX1]; signal(SIGUSR1,action); pipe(fd); if((pid1=fork())<0) { perror("FORK :"); exit(1); } else if(pid1==0) { /* CODICE DEL FIGLIO_1 */ close(fd[0]); /* chiusura pipe in lettura */ /* per gestire il segnale al suo arrivo */ /* apertura della pipe */

/* il figlio_1 inizia una serie infinita di inserimenti sulla pipe */

101

for( ; ; ) write(fd[1],buf_fig_1,strlen(buf_fig_1)); } else if((pid2=fork()) < 0) { perror("FORK :"); exit(1); } else if(pid2==0) { /* CODICE DEL FIGLIO_2 */ ppid=getppid(); close(fd[1]); /* ricava il pid del padre */ /* chiusura pipe in scrittura */

len= strlen(buf_fig_1)+strlen(buf_fig_2); for(i=0;i<MAX;i++) { read(fd[0],&(buf_fig_2[10]),strlen(buf_fig_1)); /* sleep(1); */ write(1,buf_fig_2,len); } kill(ppid,SIGUSR1); exit(0); } else { /* CODICE DEL PADRE */ pause(); kill(pid1,SIGUSR2); close(fd[1]); /* per uccidere il figlio_1 */ pipe in scrittura */ /* questa operazione blocca il processo non termina il processo identificato da /* invio del segnale al padre */

/* chiusura

waitpid(pid2,&stato,0); finche' pid2 */ di

/* il ciclo seguente ha come unico scopo quello di contare il numero messaggi rimasti nella pipe */ for ( i=0; read(fd[0],appo,strlen(buf_fig_1))==17; i++) ; printf("C'erano ancora %d messaggi nella pipe.\n",i); close(fd[0]); exit(0); }

103

9 Introduzione al concetto di monitor


Le operazioni di wait e signal sui semafori permettono di risolvere ogni operazione di sincronizzazione tra processi concorrenti. Bisogna per usarle con particolare attenzione, infatti un uso errato di questi strumenti pu portare a conseguenze poco piacevoli nell'uso di una risorsa. Ogni sezione critica (istruzioni di codice da eseguire in modo indivisibile, cio non concorrentemente ad altre) deve essere compresa tra le due operazioni wait e signal; l'errore pi comune che si pu commettere di invertire tali operazioni:

in questo modo si permette a pi processi di accedere contemporaneamente alla stessa risorsa. Altro tipico errore si ha quando l'operazione di signal viene sostituita da una wait: in questo caso il processo, una volta entrato nella sezione critica, non pi in grado di uscirne (situazione di blocco critico). Altro errore molto probabile lo scambio dei nomi di due semafori: anche in questo caso si arriva nella situazione di deadlock. All'aumentare del numero di semafori presenti nel programma ci si pu rendere conto di quanto sia facile commettere un errore. E necessario trovare un sistema pi semplice per la sincronizzazione tra processi.

9.1 Definizione di monitor Il monitor un costrutto sintattico costituito da un insieme di variabili, strutture dati e procedure che vengono raggruppate in un particolare modulo. Ogni processo pu invocare una procedura del monitor quando lo desidera, ma non ha la possibilit di manipolare direttamente le variabili e le strutture dati interne al monitor stesso. Esempio TYPE < nome del tipo > MONITOR < dichiarazione di variabili locali> procedure entry < nome> (...) begin ... end; . . procedure entry < nome> (...) begin ... end; procedure entry < nome> (...) begin ... end; . . procedure entry < nome> (...) begin
105

... end; begin < inizializzazione delle variabili locali > end;

Un monitor dunque costituito da tre parti: procedure entry procedure locali variabili locali Le procedure entry sono funzioni a cui i processi possono accedere, come gi detto, in ogni momento. Esse vengono svolte in mutua esclusione, le procedure e le variabili locali sono utilizzate dalle procedure entry, ma non sono visibili dall'esterno. Lo scopo del monitor quello di controllare l'assegnazione di una risorsa ai vari processi in modo tale da evitare conflitti. Tale assegnazione viene gestita secondo due livelli di controllo: il primo garantisce che un solo processo abbia accesso alla risorsa controllata; ci si ottiene garantendo la mutua esclusione delle procedure entry: un processo che faccia richiesta di una risorsa gi occupata viene sospeso. Il secondo livello di controllo gestisce l'ordine di accesso dei processi alla risorsa; ci pu essere ottenuto imponendo che il processo acceda alla risorsa solo se si verifica una certa condizione logica. Per controllare la sospensione e la riattivazione di un processo si utilizzano le variabili condizione, ad esempio var x,y: condition; Su queste variabili possono essere eseguite solo operazioni di wait e signal, ovvero: x.wait x.signal Esempio: monitor ProducerConsumer bf condition full, empty; bf integer count; provoca la sospensione del processo che la esegue; provoca la riattivazione di un processo, se ve ne sono, oppure non ha alcun effetto pratico in caso contrario.

procedure entry enter; begin if count = N then wait(full); enter_item; count := count + 1; if count = 1 then signal(empty); end; procedure entry remove; begin if count = 0 then wait(empty); remove_item; count := count - 1; if count = N-1 then signal(full); end; count := 0; end monitor; Nell'esempio riportato sono state dichiarate due variabili condizione empty, segnala che il buffer vuoto full segnala che il buffer pieno. Su di esse vengono implementate le operazioni di wait e signal. Vengono utilizzate due procedure entry, enter e remove: la enter (tipicamente eseguita dal produttore) controlla per prima cosa che il buffer non sia pieno, in tal caso esegue una wait sulla variabile condizione full provocando la sospensione del processo, in caso contrario un nuovo elemento viene inserito nel buffer, viene incrementato di uno il numero di celle occupate e viene effettuata una signal sulla variabile empty nel caso in cui una cella sia nuovamente piena. La procedura remove (tipicamente eseguita dal consumatore), dopo aver controllato che il buffer non sia vuoto, estrae un elemento dal buffer, decrementa di una unit il numero di elementi presenti nel buffer e, nel caso in cui si sia liberata una cella, esegue una signal sulla variabile condizione full. Come si pu notare le due procedure entry sono interdipendenti, cio, se un processo sospeso in una di esse, solo l'altra in grado di sbloccarlo. E importante che la signal sia l'ultima operazione eseguita da entrambe le procedure entry altrimenti possiamo trovare due processi attivi nel monitor.

9.2 Limiti del costrutto monitor

107

Il monitor, per quanto detto in precedenza, risulta essere uno strumento di analisi dei problemi molto efficace. Il linguaggio C non prevede nella sua sintassi un costrutto di questo tipo. L' unico modo per implementare il monitor nell'ambiente di programmazione che si sta utilizzando consiste nel tradurre tutto lo pseudocodice utilizzando funzioni e primitive note. Anche in questo caso un metodo intelligente che si pu seguire consiste nel definire le variabili locali, le operazioni di inizializzazione e le procedure entry in un header.

10 Sistemi distribuiti
Un sistema distribuito una combinazione di hardware, software ed elementi di una rete (network) in cui i componenti software operano su due o pi processori e comunicano tra di

loro tramite una rete. Tipicamente una rete si snoda attraverso un edificio o un campus e pi frequentemente collega aree geograficamente separate. Spesso in una rete, un computer viene detto nodo e la connessione tra computers un link. Nei protocolli TCP/IP, come vedremo in dettaglio, usato il termine host anzich nodo.

10.1 Networks:
Le reti vengono classificate in tre categorie a seconda della distanza geografica che i pacchetti di dati devono percorrere dalla sorgente alla destinazione: local area networks (LANS); wide area networks (WANS); metropolitan area networks (MANS). In particolare una rete WAN si compone di due o pi LANS, connesse da wide area links. Una tale rete detta a volte internet, mentre le reti locali possono essere chiamate subnetworks. Una LAN di solito interessa un singolo edificio o pochi edifici adiacenti: la massima distanza tra due suoi nodi (network diameter) minore di un miglio. I pacchetti di bits viaggiano su questa rete ad una velocit compresa tra i 4 Mbps (million bits per second) e 2 Gbps (billion bits per second), mentre i pi comuni tipi di collegamenti su wide area operano alla velocit di 64000 bits al secondo o meno. A differenza degli altri due tipi di rete, le LANS sono dunque caratterizzate da unalta capacit di dati e da un basso tasso di errore, dellordine di un bit errato su 109 bits trasmessi. I due tipi di LANS pi usati sono Ethernet e Token ring; inoltre la LAN multiaccesso (ogni nodo pu mandare un messaggio direttamente ad ogni altro nodo senza coinvolgerne un terzo) e pu inviare multicast messages (messaggi inviati a pi di una destinazione). La WAN, a volte chiamata anche long houl network, ricopre grandi distanze; i pacchetti di informazione viaggiano su di essa ad una velocit compresa tra i 9.6 Kbps ed i 45 Mbps. Gli errori di collegamento di una wide area sono dellordine di un bit errato su 105 bits trasmessi. A differenza delle LANS i dati devono passare da nodo a nodo fino a raggiungere la destinazione. Le MANS ricoprono unarea geografica intermedia tra le due precedenti ed in generale interconnettono reti LANS. La capacit di trasmissione maggiore rispetto alle LANS: la velocit compresa tra 56 Kbps e 100 Mbps. Ci sono diversi modi per connettere insieme pi reti: i repeaters (ripetitori) operano su layers (livelli) fisici, di solito copiano solo segnali elettrici (compreso il rumore) da un segmento di una rete al successivo e sono spesso usati con i cavi Ethernet; i bridges (ponti) spesso operano a livello dei data-link e copiano frames da una rete alla successiva; i routers non solo trasportano i pacchetti di informazione da una rete allaltra, ma stabiliscono anche il percorso che i dati devono seguire; i gateways sono entit che connettono due o pi reti. I repeaters sono di solito dei devices hardware, mentre bridges e routers possono essere implementati in hardware oppure software.

10.2 Protocolli
109

Data link process transport network

Data link process transport network

I computer in una rete usano protocolli ben definiti per comunicare. Un protocollo un insieme di regole e convenzioni tra i partecipanti alla comunicazione: eso specifica il formato e le modalit di trasmissione delle informazione sulla rete. In realt un protocollo fornisce una serie di servizi aggiuntivi quali, ad esempio, la correzione di errori, la bufferizzazione dei dati, ... Dal momento che i protocolli possono essere complessi, vengono progettati in livelli, per rendere pi facile la loro implementazione. E importante sottolineare che per ogni livello esistono dei protocolli.

phisical network Fig. 10.1 Modello semplificato di 4 livelli che connettono due sistemi Prima di addentrarci nei particolari dei protocolli TCP\IP , introduciamo alcune definizioni che ci saranno utili in seguito:

datagram: l'unit di base di informazione che passa attraverso l'internet TCP\IP. Un IP datagram sta ad una rete internet come un pacchetto hardware ad una rete fisica. connection-oriented: esiste una fase preliminare che consiste in una connessione (path) tra i due elaboratori; si instaura, cio un circuito virtuale tra mittente e destinatario in modo che i pacchetti vengano instradati tutti lungo lo stesso percorso,senza dover quindi specificare per ogni pacchetto la destinazione. Le connessioni di questo tipo sono vantaggiose poich il canale rimane aperto dall'inizio alla fine della comunicazione, permettendo un trasporto dati ad alte velocit. Associati a tali connessioni ci sono i QOS: qualit di servizio. connectionless: non prevede una fase di apertura di una connessione, ma i pacchetti o i datagram vengono trattati come entita`separate (non esiste un canale di comunicazione), ed ognuno di essi segue un percorso diverso. L'informazione viene frammentata ed assegnata al servizio di rete (si fa affidamento al buon funzionamento del sistema di comunicazione). Naturalmente, ogni pacchetto dovra contenere l'indirizzo del destinatario. Non per garantito che i pacchetti arrivino nello stesso ordine in cui sono stati spediti. Non esistono, in questo caso, QOS.

10.3 TCP/IP - The Internet Protocols


Sebbene la famiglia di protocolli pi conosciuti siano i TCP/IP, ne esistono altre (ad es. Xerox Networking Systems, IBMs Systems Network Architecture, IBMs Netbios, the OSI protocols, Unix-to-Unix Copy).

ICMP process TCP

User

IP interfaceUDP process

User Hardware

ARP

RARP

OSI Layers 5-7

OSI Layer 4

OSI Layer 3

OSI Layers 1-2

Fig. 10.2 Livelli nellambiente dei protocolli Internet Analizziamo ora i vari livelli di cui si compone la precedente figura: TCP Transmission Control Protocol. E un protocollo connection-oriented che fornisceun flusso di byte affidabile e bidirezionale per un processo utente. La maggior parte delle applicazioni Internet si avvale dei TCP. Dal momento che TCP usa IP, lintera famiglia di protocolli Internet spesso chiamata TCP/IP. UDP User Datagram Protocol. E un protocollo connectionless. A differenza dei TCP, che sono affidabili, non c garanzia che i datagram UDP raggiungano sempre la destinazione desiderata. ICMP Internet Control Message Protocol. Gestisce gli errori e controlla linformazione tra i gateway e gli host. Mentre i messaggi ICMP sono trasmessi usando i datagram IP, questi messaggi sono normalmente generati e processati dal software TCP/IP stesso e non dai processi utenti. IP Internet Protocol. Questo protocollo interfaccia lhardware con i TCP, UDP, ICMP. Si pu notare che i processi utenti non sono in contatto diretto con lIP layer, bens lo scambio di informazione avviene tramite i TCP e gli UDP. ARP Address Resolution Protocol. E il protocollo che converte lindirizzo Internet nellindirizzo hardware, ma non viene usato su tutte le reti. RARP Reverse Address Resolution Protocol. Compie loperazione inversa del precedente ARP ed analogamente presente solo su alcune reti.

111

10.4 Network layer IP


Il livello IP permette una comunicazione connectionless ed inaffidabile: connectionless perch ogni datagram indipendente dagli altri, inaffidabile perch non garantisce che linformazione venga ricevuta e non contenga errori. Ogni pacchetto di dati contiene gli indirizzi di sorgente e destinazione, cos che pu essere spedito in modo indipendente. LIP layer gestisce linstradamento dellinformazione attraverso la rete Internet, responsabile della fragmentation (vedere pi avanti) ed inoltre verifica la correttezza di 20 byte (contenenti, ad es., gli indirizzi sorgente e destinazione); se vengono rilevati degli errori, i byte vengono scartati e il protocollo di un livello superiore li rispedir.

10.5 Indirizzi Internet


Ogni famiglia di protocolli definisce la tipologia degli indirizzi che identificano le reti ed i computers. Un indirizzo Internet occupa 32 bit e contiene la codifica dellID ( = identificatore) della rete e quello dellhost (relativo allID della rete usata). Sulla rete TCP/IP ogni host deve essere identificabile univocamente tramite un indirizzo a 32 bit, che gli viene assegnato da unautorit centrale (NIC - Network Information Center). Un indirizzo Internet assume uno dei seguenti formati:
7 bits 24 bits

classe A

0 netid

hostid

14 bits

16 bits

classe B

1 0

netid

hostid

21 bits

8 bits

classe C

1 1 0

netid

hostid

28 bits

classe D

multicast address

classe E

reserved for future uses

Fig. 10.3 Formato degli indirizzi Internet La classe A usata quando un grande numero di host collegato ad una singola rete, mentre la classe C si riferisce a pi reti ma con pochi host. La NIC assegna alle organizzazioni interessate solo il tipo di indirizzo (classe A, B o C) e lID della rete, mentre lindirizzo individuale dellhost su quella rete viene stabilito dalle organizzazioni stesse.

Gli indirizzi Internet sono spesso scritti come numeri di quattro cifre decimali, separati da punti decimali. Ogni cifra decimale corrisponde ad un byte dellindirizzo a 32 bit. Ad es. il valore esadecimale 0x0102FF04 viene scritto come 1.2.255.4 e si riferisce ad un indirizzo della classe A con un network ID pari ad 1 ed un host ID uguale a 0x02FF04. Invece lindirizzo 128.3.0.5 appartiene alla classe B: infatti nel sistema binario 128 si traduce in 10000000, cio presenta le prime due cifre tipiche degli indirizzi della classe B (10, in binario). Analogamente si vede che 192.43.235.6 appartiene alla classe C. Ogni header di 20 byte riserva 32 bit per lindirizzo del source host e 32 per quello del destination host. Dal momento che lindirizzo Internet comprende il network ID e lhost ID, i gateway possono facilmente recuperare il solo network ID, per instradare i datagram senza la necessit di conoscere la locazione di ogni host della rete. E bene ricordare che un host pu connettersi ad una o pi reti; ne segue che pu aver pi indirizzi Internet, uno per ogni rete cui si connette. Quindi ciascun indirizzo Internet identifica un unico host, ma il singolo host non ha un solo indirizzo.

10.5.1 Subnet addresses: Ogni organizzazione con un indirizzo internet appartenente ad una qualsiasi classe pu suddividere lo spazio disponibile per gli indirizzi host nel modo desiderato, originando la le subnetworks (sottoreti). Per esmpio, se si vogliono assegnare i 16 bit disponibili per l'host ID (nel caso della classe B) a 150 host, si pu procedere nel seguente modo: si possono usare i primi 8 bit (high-order 8 bits) dell'host ID per individuare il network ID della sottorete; i restanti 8 bit possono indicare gli host individuali su ogni rete interna. In questo modo sufficiente, per l'instradamento, che il gateway conosca gli 8 bit del network ID della sottorete, anzich la locazione di tutti i 150 host.
14 bits 16 bits

netid

hostid

standard class B address

14 bits

8 bits

8 bits

netid

subnetid

hostid

subnetted class B address

Indirizzo Internet della classe B con sottorete

10.6 Trasmissione dati

encapsulation: l'aggiunta di alcuni byte di controllo che vengono anteposti all'informazione. Consideriamo, ad esempio, una applicazine TFTP, che usa protocolli UDP/IP, tra 2 sistemi connessi tramita Ethernet. Se il processo cliente deve trasferire 400 byte ad un processo server, il primo aggiunge 4 byte di controllo sull'informazione all'inizio del buffer di dati, prima che quest'ultimi passino al livello UDP, dove subiranno nuovamente l'encapsulation, fino a livello Ethernet.
113

data dati TFTP header data dati

TFTP message

UDP header

TFTP header

data

UDP message

IP header

UDP header

TFTP header

data

IP packet

Ethernet header

IP header

UDP header

TFTP header

data

Ethernet trailer

Ethernet frame

Fig. 10.4 Esempio di encapsulation di TFTP su UDP/ IP

multiplexing: un processo utente pu usare diversi protocolli contemporaneamente; inoltre pi processi utenti possono accedere nello stesso istante ai protocolli loro disponibili. demultiplexing: chi riceve l'informazione deve essere in grado di identificare il mittente. fragmentation: suddivisione in piccoli pacchetti di un flusso di dati di grandi dimensioni. Infatti la maggior parte dei livelli di una rete riesce a gestire pacchetti solo di una certa dimensione (MTU=Maximum Transmission Unit). Questa operazione anche detta, a volte, segmentation. Per esempio, se un gateway riceve un datagram troppo grande per poter essere trasmesso sulla rete successiva, il modulo IP lo suddivide in frammenti e spedisce ogni frammento come un pacchetto IP, duplicando l'indirizzo destinazione e quello sorgente in ognuno di essi, cos che il pacchetto IP pu essere spedito indipendentemente dagli altri. reassembly: l'inverso della fragmentation, in quanto i frammenti devono essere riassemblati per produrre una copia fedele del datagram originale4 prima di arrivare a destinazione. routing: indica l'instradamento dei dati.I modi possibili per spedire informazioni da un nodo ad un qualche host sono: 1. l'host di destinazione direttamente connesso al nodo mittente,oppure i nodi di interesse sono entrambi sulla stessa rete (Ethernet). In questi due casi la spedizione diretta e non necessaria nessuna scelta per l'instradamento. 2. la sorgente stabilisce la lista degli indirizzi dei nodi intermedi che il pacchetto deve attraversare (source routing). In tal modo il nodo corrente non deve prendere decisioni sulla rout che i dati devono percorrere. 3. il cammino tra una data sorgente e una data destinazione rimane invariato e pu essere conosciuto a priori dal nodo corrente, cos che quest'ultimo non deve fare

una scelta di routing. 4. il nodo corrente deve,questa volta, stabilire a quale host intermedio inviare l'informazione; pu cos capitare che due datagram consecutivi, spediti dalla stessa sorgente, arrivino ad una stessa destinazione tramite due percorsi diversi. Questo il tipico modo usato da un gateway sulla rete TCP/IP Internet.

controllo degli errori: il sistema opera correttamente se tutte le macchine funzionano bene e sono concordi sull'instradamento, ma ci non sempre si verifica. IP pu commettere errori nello scambio di dati quando, ad esempio, il computer di destinazione temporaneamente scollegato dalla rete o quando la rete troppo carica. Esistono due tipi di errori: i dati subiscono delle modifiche durante la trasmissione oppure vengono persi . Un modo per ovviare al primo problema consiste nell'includere un checksum (controllo derrore) da parte del mittente in modo che il ricevente possa verificare, con alta probabilit, che i dati siano rimasti invariati. In caso contrario viene richiesta al mandante la ritrasmissione. Questa tecnica viene di solito affiancata da una richiesta di acknowledgment (il ricevente avvisa il mittente della avvenuta ricezione, sia questa corretta o meno).In caso di acknowledgment negativo si procede alla ritrasmissione. Per porre rimedio alla perdita di informazione, in alcune reti, il mittente fa partire un timer al momento dell'invio,e, in caso si esaurisca l'intervallo di tempo stabilito (detto timeout), ritrasmette i dati. Tuttavia anche un acknowledgment positivo pu andare perso; quindi il mittente rispedisce il pacchetto, ma compito del ricevente ignorare i duplicati di messaggi precedentemente giunti a destinazione.

10.7 Numeri di porta


Come gi visto, pi utenti possono usare contemporaneamente i protocolli TCP ed UDP. Sono perci necessari dei metodi di identificazione dei dati associati ad ogni processo utente. Entrambi i TCP e gli UDP usano per l'identificazione dei numeri di porta interi su 16 bit. Quando un processo client vuole contattare un server (argomento che tratteremo in dettaglio pi avanti), il client deve poter identificare in qualche modo il server con cui vuole comunicare. Se il client conosce i 32 bit dell'indirizzo Internet dell'host su cui risiede il server, riesce a contattare l'host ed i protocolli TCP e UDP mettono a disposizione un gruppo di porte "well-known" per entrare in contatto con il server specifico. Ad es., ogni implementazione TCP/IP che supporta FTP (File Transfer Protocol), assegna a quest'ultimo il numero di porta 21, mentre al TFTP (Trivial File Transfer Protocol) assegnato il numero di porta UDP 69; il 25 invece il numero specifico per l'applicazione e-mail. Sottolineiamo poi che i numeri di porta TCP e UDP compresi tra 1 e 255 sono riservati, anche se alcuni sistemi operativi ne riservano ulteriori (di solito sino a 1023) per programmi privilegiati. Ipotizziamo ora che il client invii un messaggio al server FTP sulla porta 21 dell'host contattato. Come pu il server sapere dove spedire la risposta? Innanzi tutto quest'ultimo riesce a conoscere i 32 bit dell'indirizzo Internet del client prelevandolo dall'IP datagram, visto che i datagram contengono gli indirizzi della sorgente e della destinazione. Il processo client richiede inoltre al modulo TCP un numero di porta non ancora assegnato sul suo host locale. A sua volta il server pu conoscere questi 16 bit dall'header del TCP. Ci non genera ambiguit in quanto il numero di porta gi assegnato non pu essere riutilizzato per un altro processo fino a che non sia terminato il precedente servizio. Una connessione (o associazione) nella famiglia Internet specificata da 5 elementi: il protocollo (TCP o UDP); l'indirizzo dell'host locale (32 bit);
115

data transfer Operating

control process

control process

data transfer

stem

il numero di porta locale (16 bit); Operating l'indirizzo dell'host destinatario (32 bit); operating il numero di porta destinatario (16 bit). Ne un esempio: {tcp, 128.10.0.3, 1500, 128.10.0.7, 21}

system

10.8 Livelli di applicazione


FTP: File Transfer Protocol. E' un programma usato per trasferire file da un sistema allaltro. Offre molte facilitazioni ai fini del trasferimento stesso : 1. accesso interattivo. Sebbene FTP sia designato all'uso da parte di programmi, la maggior parte delle implementazioni forniscono un'interfaccia che permette di interagire facilmente con i server remoti. 2. specifica del formato. FTP permette al client di specificare il tipo ed il formato dei dati. Ad es., uno user pu specificare se un file contiene interi binari o caratteri, di tipo ASCII o EBCDIC. 3. controllo di autenticazione. I client, prima di richiedere il trasferimento dei file, devono inviare al server un nome di login ed una password per essere autorizzati alla comunicazione. L'accesso viene per negato nel caso in cui uno dei due non sia corretto. Una situazione tipica la seguente: uno user interattivo invoca un processo client FTPsul sistema locale, il quale a sua volta stabilisce una connessione con il server FTP sul sistema remoto usando TCP. FTP crea due connessioni tra i processi client e server, una per l'informazione di controllo (ad es. quali file trasferire,...), l'altra per il trasferimento dei dati.

client system

server system

client data connection

client control connection server control connection TCP/IP Internet

server data connection

Fig. 10.5 Esempio di client-server FTP Sia il client che il server creano un processo separato per gestire il trasferimento. In generale i processi e la connessione di controllo rimangono in vita fino a quando la sessione FTP rimane aperta. D'altra parte, i processi e le connessioni di trasferimento vengono creati dinamicamente quando necessario e FTP apre una nuova connessione per ogni spostamento di

users terminal

TELNET client operating system

TELNET server operating system

file. I comandi disponibili all'utente possono essere visualizzati tramite l'opzione help.

TFTP: Trivial File Transfer Protocol. E'un altro protocollo della famiglia TCP/IP per il trasferimento dei file, usato nelle applicazioni che non necessitano interazioni complesse tra client e server, pi semplice ed economico di FTP. Restringe il suo campo di applicabilit al solo trasferimento dei file, escludendo l'autenticazione, il listing delle directory, etc. Dal momento che pi restrittivo, TFTP occupa meno software di FTP. Diversamente da quest'ultimo inoltre, non usa TCP ma UDP o qualsiasi altro sistema non affidabile, che usi per un timeout e la ritrasmissione per garantire l'arrivo dell'informazione. Sebbene TFTP contenga il minimo necessario per lo spostamento dei dati, supporta comunque diversi tipi di file. TELNET: Remote Login. Fornisce una remote login facility, cio gli utenti possono accedere a tutti i comandi disponibili sul sistema remoto. Permette, inoltre, ad uno user interattivo su un sistema client di aprire una sessione di login su un sistema remoto. Una volta stabilita la connessione, TCP il client la user's keystrokes al processo server e ritorna l'autput dalla macchina remota al terminale di utente. Il servizio offerto chiamato trasparente,poich il terminale utente sembra "direttamente attaccato" alla macchina remota. TELNET offre tre servizi base: 1. definisce un network virtual terminal che fornisce interfacce standard, usando le quali il client non obbligato a conoscere i dettagli di ogni sistema remoto. 2. fornisce un insieme di opzioni standard che permette la comunicazione tra client e server. 3. tratta entrambi gli estremi della connessione in modo simmetrico: cos un programma arbitrario pu eventualmente diventare un client.

client reads for terminal

cliend sends to server server receives from client

server sends to pseudo terminal

TCP/IP Internet Fig. 10.6 Il cammino dei dati in un terminale TELNET remoto quando passano dal terminsle dello user al sistema operativo remoto. Come mostra la figura, quando uno user invoca TELNET, un programma applicativo sulla macchina dell'utente diventa il client. Quest'ultimo stabilisce una connessione TCP con il server, sulla quale si svolger la comunicazione. Quindi il client riceve la keystrokes dal terminale utente, la spedisce al server, e a sua volta accetta caratteri inviatigli dal server visualizzandoli sullo schermo dell'utente. In realt spesso il server pi complesso di quanto sia in figura, poich deve gestire pi connessioni concorrenti.
117

SMTP: Simple Mail Transfert Protocol. Fornisce un protocollo per lo scambio di posta elettronica (EMAIL) tra due sistemi, usando una connessione TCP. L'EMAIL offre un metodo veloce e conveniente per trasferire l'informazione. Nei casi precedenti, i protocolli di rete mandavano pacchetti direttamente alla destinazione, usando tecniche di timeout e ritrasmissione (nel caso di mancato acknowledment). Qui, invece, il sistema deve anche entrare in azione nell'eventualit di fallita connessione. Un mittente non vorrebbe n sospendere il proprio lavoro perch la macchina remota non disponibile, n vedersi interrotto il trasferimento per la sospensione temporanea della comunicazione. Per ovviare a tali inconvenienti, viene adottata la tecnica dello spooling. Quando un utente manda una mail, il sistema ne inserisce una copia sulla propria area privata di "immagazzinamento" (spool), detta anche mail queue area, precisando anche il mittente, la macchina di destinazione ed il momento di arrivo. Da qui inizia il trasferimento alla macchina remota come attivit in background, permettendo cos al mittente di continuare altre attivit. client (background transfer) users interface user reads mail server (to accept mail) TCP connection for incomming mail TCP connection for outputting mail

outgoing mailboxes mail spool for inco-ming mail area

user sends mail

Fig. 10.7 Componenti concettuali di un sistema di posta elettronica

Il processo di trasferimento mail in background diventa un client e, "trasformando" il nome della macchina di destinazione in un indirizzo IP, cerca di instaurare una connessione TCP con il server di destinazione. In caso di successo una copia del messaggio di origine viene inserita nella spool area (mailbox) del, sistema remoto; una volta ricevuta la mail il client elimina la propria copia dalla mail queue. Al contrario, se non si riescie a creare la connessione, il mittente memorizza il tempo impiegato nel tentativo di ritrasmettere. Se il software scopre che il messaggio non stato inviato entro un tempo prefissato (ad es. 3 giorni), la mail ritorna al mandante.

11 I Socket
I socket sono un tipo di IPC che permette la comunicazione tra processi su un singolo sistema o tra processi su sistemi diversi (ad esempio sulla rete Internet). La comunicazione all'interno di una rete presenta delle complicazioni non indifferenti, infatti il messaggio deve essere "fisicamente" trasportato al destinatario. Per poterlo fare, come gi detto, si devono usare dei protocolli di comunicazione: l'interazione tra questi ed i processi risulterebbe alquanto complessa poich sarebbe necessario conoscere dettagliatamente i formati da utilizzare per poter inviare il messaggio. Per evitare tutti i particolari architetturali e mantenere una forma di I/O il pi possibile simile a quella standard usata per i file, sono stati introdotti i socket. Essi possono essere visti come una generalizzazione del concetto di file, infatti vi si pu leggere e scrivere con altrettanta facilit. Come per i file i processi devono richiedere la creazione di un socket al sistema, il quale restituir un descrittore, con la differenza che un file descriptor viene "allacciato" ad un particolare file o dispositivo, mentre un socket viene creato senza essere correlato ad un indirizzo di destinazione (un'eventuale associazione potr essere effettuata successivamente). Abbiamo gi detto che una comunicazione pu avvenire con due modalit diverse: connection-oriented e connectionless. Vediamo come gestirle attraverso i socket. CONNECTION-ORIENTED Il server comincia l'esecuzione per primo: il client "parte" in un secondo tempo, cercando di creare la connessione. Server (protocollo connection-oriented) socket () bind () listen () accept () Client in attesa della connessione del client connessione stabilita connect () write ()
119

socket ()

read () richiesta del processo

dati (richiesta)

dati (risposta) write () read ()

Fig. 11.1 System call dei socket per un protocollo connection-oriented CONNECTIONLESS Per un protocollo connectionless, il client non deve stabilire una connessione con il server, ma deve solamente inviargli un datagram tramite la system call sendto, che richiede l'indirizzo della destinazione (il server) come parametro. Analogamente il server non deve accettare una connessione dal client, ma con la system call recvfrom aspetta l'arrivo dei dati da un qualche client. La recvfrom restituisce l'indirizzo di rete del processo client, insieme al datagram, cos che il server possa rispondere al processo giusto. Server (protocollo connectionless) socket () bind () recvfrom () in attesa della connessione del client Client socket ()

bind ()

dati (richiesta) richiesta del processo dati (risposta) sendto ()

sendto ()

recvfrom () Fig. 11.2 System call dei socket per un protocollo connectionless

11.1 Strutture dei socket


Molte system call richiedono come parametro il puntatore ad una struttura di indirizzo del socket. La definizione di tale struttura si trova nell'header <sys/socket.h>: struct sockaddr { u_short char }; sa_family: sa_data[14];

Questa generica struttura costituita da un campo iniziale di due byte in cui viene specificata la famiglia, seguito da 14 byte di informazione specifici della famiglia indicata. Per esempio la struttura sockaddr_in relativa al protocollo Internet avr i primi due byte contenenti il valore AF_INET seguiti da due byte indicanti il numero di porta e quattro byte di indirizzo. I restanti otto byte non vengono usati. In ambito UNIX invece,il campo sa_family conterr il valore AF_UNIX. Per la famiglia Internet, si possono trovare nell'header <netinet/in.h> le seguenti strutture: struct in_addr { u_long s_addr; }; struct sockaddr_in { short sin_family; /*AF_INET*/ u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; Nel dominio UNIX in <sys/un.h> definita la struttura: struct sockaddr_un { short sun_family; /* AF_UNIX*/ char sun_path[108]; }; Per gestire le strutture di indirizzo di dimensioni variabili, l'interfaccia di sistema passa sempre come parametro la loro dimensione, oltre al puntatore alla struttura. Anticipiamo che le system call della rete ricevono due argomenti: l'indirizzo della generica struttura sockaddr e la dimensione di quella dello specifico protocollo in uso. Il chiamante si deve solo procurare l'indirizzo dell'ultima struttura , convertendo il puntatore nel puntatore ad una generica sockaddr.

System call
11.2 Creazione
int socket (int family, int type, int protocol); Per usare tale system call occorre includere: <sys/types.h> e <sys/socket.h> Il parametro family pu assumere uno dei seguenti valori: AF_UNIX
121

AF_INET Type pu essere : SOCK_STREAM connection oriented SOCK_DGRAM connectionless SOCK_ROW solo per il super-user SOCK_SEQPACKET per i pacchetti sequenziali SOCK_RDM non ancora implementati Protocol viene tipicamente posto a 0. Il terzo parametro necessario in quanto la suddivisione in famiglie e tipi non sempre sufficiente a gestire tutti i casi che si possono presentare. Infatti una data classe di protocolli pu non supportare un certo tipo di servizio, sicch non tutte le combinazioni dei parametri family e type sono valide; oppure pi protocolli potrebbero fornire un certo servizio.

11.3 Ereditarieta e Terminazione


Come gi visto con i descrittori di file i processi generati ereditano una copia di tutti i socket aperti al momento della loro creazione. Il S.O. mantiene un contatore associato ad ogni socket, in tal modo pu sapere quanti processi possono accedervi. Tutti i processi hanno gli stessi diritti di accesso, pertanto un compito del programmatore gestire in modo oculato la risorsa. I processi che finiscono di utilizzare un socket effettueranno una chiamata di close sul descrittore. Se un processo termina per qualche motivo, il S.O. chiuder tutti i socket lasciati aperti. La chiamata close decrementa il contatore associato al descrittore e se questo raggiunge il valore zero il socket viene effettivamente distrutto, altrimenti viene lasciato aperto per quei processi che hanno ancora la possibilit di utilizzarlo. int close (int sock) ; sock descrittore del socket, ottenuto con la system call socket() .

Se il socket che viene chiuso di tipo orientato alla connessione (SOCK STREAM) ed stato specificato il parametro SO_LINGER (vedere pi avanti le opzioni sui socket) il sistema bloccher il processo sulla close finch non riuscir a trasmettere i dati rimanenti oppure finch non decider di non essere in grado di trasmettere (timeout). Se il parametro non stato specificato, la close verr eseguita in modo da permettere al processo di continuare al pi presto possibile.

11.4 Specifica di un indirizzo locale


Inizialmente un socket viene creato senza essere associato a nessun indirizzo, n locale n remoto. Per il protocollo internet questo significa che non stato specificato nessun numero di porta. L'associazione tra descrittore di socket e indirizzo si effettua tramite la chiamata bind. int bind (int sock, struct sockaddr *indirizzo, int lunghezza);

sock indirizzo socket lunghezza

il descrittore del socket una struttura dove specificato l'indirizzo locale a cui associare il la dimensione dell'indirizzo.

La generica struttura sockaddr costituita da un campo iniziale di due byte, in cui viene specificata la famiglia seguita da 14 byte di informazione specifici della famiglia indicata. Per esempio la struttura sockaddr_in relativa al protocollo internet avr i primi due byte contenenti il valore AF_INET seguiti da due byte indicanti il numero di porta e quattro byte di indirizzo. I restanti otto byte non vengono utilizzati. Poich la struttura deve essere di tipo sockaddr e le informazioni vengono per comodit inserite in una struttura specifica della famiglia di indirizzi utilizzata, il passaggio del parametro va effettuato tramite un casting, nel seguente modo: (struct sockaddr *) indirizzo Un tipico errore che pu verificarsi nell'uso di questa chiamata il tentativo di assegnare un numero di porta riservata o gi in uso da un altro processo. 11.4.1 Routine di conversione Per rendere compatibili le diverse architetture dei computers e i differenti protocolli di reti si usano le seguenti funzioni (presenti in <sys/types.h> e <netinet/in.h>): per convertire uno short (2 byte) dal network byte order al local host byte order: u_short ntohs (u_short netshort); per convertire un long (4 byte) dal network byte order al local host byte order: u_long ntohl (u_long netlong); per convertire un intero di 2 byte di un host local byte order in un intero di 2 byte in network byte order: u_short htons (u_short hostshort); per convertire un long int di un host local byte order in un long di un network byte order: u_long htonl (u_long hostlong); Esempi: hostshort = ntohs (netshort); hostlong = ntohl (netlong); netshort = htons (hostshort); netlong = htonl (hostlong); Si pu notare che: netshort = htons (ntohs (netshort) ); hostshprt = ntohs (htons (hostshort) ); 11.4.2 Routine di manipolazione di indirizzi IP Le routine per traslare dal formato decimale di un indirizzo a quello IP a 32 bit , contenute in
123

<sys/socket.h>, <netinet/in.h>, <arpa/inet.h>, sono: 1. unsigned long inet_addr (char *ptr); 2. char *inet_ntoa (struct in_addr inaddr); 3. unsigned long inet_network (char *ptr); La 1. fornisce il completo indirizzo IP a partire da una stringa ASCII contenente il numero espresso in formato decimale; la 2. restituisce la conversione inversa; la 3. forma la parte network dellindirizzo e pone degli zeri nella parte host.

11.5 Connessione di un socket ad un indirizzo remoto


Inizialmente il socket creato in uno stato non connesso, ci significa che non gli associata alcuna destinazione. Per mezzo della system call connect si pu associare permanentemente una destinazione ad un socket, ponendolo cos in uno stato connesso. Se si vuole utilizzare un protocollo di tipo connection oriented questa operazione indispensabile mentre utilizzando un protocollo di tipo connectionless un'operazione facoltativa che evita di specificare l'indirizzo ad ogni trasmissione. int connect (int sock, struct sockaddr *remaddr, int lunghezza); sock remaddr lunghezza descrittore del socket struttura contenente le informazioni associate all'indirizzo remoto specifica la lunghezza della struttura precedente

Questa system call non ritorna al chiamante fino ad avvenuta connessione, oppure restituisce un messaggio di errore. Il client non ha bisogno di conoscere lindirizzo locale prima di chiamare la connect. Il comportamento di questa funzione dipende dal tipo di protocollo associato al socket; in caso di socket di tipo stream tenta di costruire una connessione TCP col destinatario, in caso di socket di tipo datagram non fa altro che immagazzinare localmente l'indirizzo.

11.6 Come ottenere le informazioni relative ad un host


Per ottenere informazioni relative ad un host conoscendo il suo nome internet oppure il suo indirizzo, possibile utilizzare alcune funzioni di libreria. Queste implementano la consultazione di un server di database che fornisce le necessarie informazioni. 11.6.1 gethostbyname, gethostbyaddr Queste due funzioni permettono di ricavare una serie di informazioni relative ad un host, a partire rispettivamente dal nome e dall'indirizzo. struct hosten *gethostby name(char *nome); struct hosten *gethostbyaddr(char *addr, int lunghezza, int type); nome nome dell'host di cui si vogliono le informazioni

addr lunghezza type

indirizzo dell'host, ottenuto dal formato nnn.nnn tramite la funzione inet_addr dimensione dell'indirizzo tipo di indirizzo

Le funzioni restituiscono il puntatore alla struttura di tipo hostent che ha la seguente forma h_name nome ufficiale dell'host h_aliases lista di nomi alternativi. Tale lista terminata da uno zero h_addrtype tipo di indirizzo restituito h_lenght lunghezza dell'indirizzo h_addr_list lista di indirizzi di rete dell'host. Per visualizzare l'indirizzo cos, ottenuto nel classico formato nn.nn bisogna usare la funzione inet_ntoa 11.6.2 gethostname, sethostname gethostname(name,length); name indirizzo di un vettore di byte dove il nome dellhost deve essere inserito length intero che specifica la lunghezza del vettore name sethostname(name, length); name d lindirizzo di un vettore dove si trova lhost name length intero che indica la lunghezza dellarray

11.7 Opzioni sui socket


Il comportamento dei socket deve poter essere controllato dal sistema; per questo motivo possibile specificare dei parametri di utilizzo, ad esempio se il protocollo utilizza il timeout e la ritrasmissione si possono specificare i parametri in modo da ottimizzare il funzionamento dell'applicazione. Questa possibilit viene fornita dalla funzione setsockopt int setsockopt(int sock,int level,int option,char *optionval,int *optlen); sock level protocollo. interessati a option optionval optlen identificatore del socket indica a che livello di protocollo si deve manipolare l'opzione, per far questo si deve conoscere l'appropriato numero che identifica il Generalmente si usa SOL_SOCKET per indicare che si modificare l'opzione a livello del socket nome dell'opzione da modificare, la pi utilizzata SO_LINGER che permette di controllare il modo in cui avviene la chiusura del socket valore che si vuole assegnare all'opzione modificata puntatore ad un intero che contiene la lunghezza dell'opzione

La funzione setsockopt permette ad un progrmma applicativo di settare lopzione di un socket usando il set di valori ottenuti con la getsockopt. Il chiamante specifica il socket per il quale lopzione deve essere settata, lopzione da cambiare e un valore per lopzione,oltre che una locazione in cui porre le informazioni richieste.Il sistema operativo esamina le sue strutture interne per il socket e passa le informazioni al chiamante.
125

int getsockopt(int sock,int level,int option,char *optionnval,int *optlen); i parametri sono gli stessi della stesockopt,tranne: optionval indirizzo del buffer dove il sistema ripone il valore richiesto.

11.8 Listen
Per capire l'utilizzo della system call listen bisogna considerare il funzionamento di un'applicazione server. Un processo server fornisce un servizio a degli utenti; se il tempo necessario a fornire tale servizio non breve, pu accadere che avvengano altre richieste di servizio prima che venga esaudita quella in corso. Bisogna allora specificare se le richieste di connessione al socket possono essere messe in attesa o devono essere rifiutate. La system call listen permette al server di preparare la lista d'attesa sul socket, cio informa il sistema operativo che il protocollo pu accodare pi richieste sul socket. int listen (int sock, int lenqueue); sock identificatore del socket lenqueue lunghezza della coda; se essa viene superata, le richieste vengono rifiutate.

11.9 Accept
Vediamo ora come le connessioni vengono accettate da un server. Esso in generale deve poter accettare connessioni provenienti da qualunque indirizzo remoto; pertanto, effettuando la bind, in generale non si specifica un indirizzo preciso, ma si inserisce un valore particolare che indica la possibilit di accettare connessioni da chiunque. Fatto ci il server si deve mettere in attesa di una richiesta, ovvero esegue una accept; questa chiamata blocca il processo fino a che non arriva una richiesta. int accept (int sock, struct sockaddr *addr, int *addrlen); newsock sock addr lunghezza nuovo identificatore di socket identificatore del socket su cui si attende la connessione struttura che conterr l'indirizzo del richiedente lunghezza di addr.

All'arrivo della chiamata il sistema riempie la struttura addr con tutte le informazioni relative al richiedente e crea un nuovo identificatore di socket connesso all'indirizzo remoto. Il socket originale (che in grado di accettare tutte le connessioni) rimane aperto. Il server pu adesso esaudire la richiesta, scegliendo tra due approcci differenti: gestire le richieste in modo iterativo oppure in modo concorrente. Nel primo caso il server, dopo aver accettato la connessione, svolge tutte le operazioni che gli sono state richieste, quindi chiude il nuovo socket e si pone in attesa di una nuova richiesta. chiaro che, in caso di elaborazioni particolarmente lunghe, ci saranno probabilmente molti altri client in attesa di servizio; probabile quindi che si superi la lunghezza della coda, con conseguente rifiuto di molte richieste. In questo modo la disponibilit del servizio risulta

essere bassa. Per ovviare a questo inconveniente, meglio utilizzare un approccio di tipo concorrente. Il server crea un figlio per ogni richiesta in arrivo. Il figlio eredita i descrittori dei socket, chiude quello vecchio ed utilizza il nuovo, che gi stato connesso dal padre all'indirizzo remoto. Dopo aver espletato il servizio, il figlio termina e chiude il socket. Il padre invece, dopo aver generato il figlio, chiude il nuovo socket e si mette nuovamente in attesa di connessione. In questo tipo di approccio sembra che ci possano essere dei problemi in quanto pi processi utilizzano la stessa porta. Per capire il motivo per cui ci non comporti un problema, bisogna analizzare come vengono trattate le connessioni. In TCP una connessione viene identificata da una coppia di endpoint, non ha importanza quanti processi stiano utilizzando la porta locale, purch essi siano connessi a differenti destinazioni. Nel nostro caso abbiamo un processo attivo per ogni client pi un processo che attende le nuove connessioni. Quest'ultimo ha la possibilit di ricevere dati da qualsiasi client remoto, mentre i suoi figli utilizzano dei socket con uno specifico indirizzo di destinazione. Quando un segmento di dati giunge sulla porta, verr mandato al processo cui connesso il suo mittente; se non esiste alcun processo server collegato a questo indirizzo, i dati verranno mandati al padre. Poich il socket del padre non connesso ad alcun indirizzo, esso accetter esclusivamente nuove richieste di servizio, eventuali dati (non richieste di connessione) provenienti da client non connessi ad alcun processo figlio verranno scartati. Esempio di approccio concorrente int sockfd, newsockfd; if ( (sockfd = socket(...) ) < 0) perror (socket error ); if (bind (sockfd, ...) < 0) perror (bind error); if (listen (sockfd, 5) < 0) perror (listen error); for ( ; ; ) { newsockfd = accept (sockfd, ...); if (newsockfd < 0) perror (accept error); if (fork () == 0) close (sockfd); doit (newsockfd); /* richiesta del processo */ exit (0); } close (newsockfd); } Se invece si desidera un approccio iterativo, lo scenario il seguente: int sockfd, newsockfd; if ( (sockfd = socket(...) ) < 0) perror (socket error );
127

if (bind (sockfd, ...) < 0) perror (bind error); if (listen (sockfd, 5) < 0) perror (listen error); for ( ; ; ) { newsockfd = accept (sockfd, ...); if (newsockfd < 0) perror (accept error); doit (newsockfd); /* richiesta del processo */ close (newsockfd); }

12 Il modello client/server
Il termine client/server comunemente usato in due modi diversi. Il client/server computing si riferisce ad un uso delle reti contenenti due tipi di processori. Il primo tipo il cliente (di solito un PC oppure una workstation), che usato direttamente da una singola persona. Il secondo il server, che accessibile solamente, tramite la rete, ed tipicamente condiviso da un certo numero di client (e quindi da utenti). Il client/server model, invece, un modello di processi che costituiscono un sottoinsieme dei sistemi distribuiti. Ogni interazione caratterizzata da una relazione asimmetrica tra due processi software: uno, il client, avanza richieste per un servizio, l'altro, il server, svolge il servizio richiesto. Fatta la richiesta, il client rimane inattivo fino al completo svolgimento del servizio. Questo tipo di comunicazione viene detta half-duplex: dal client al server, in seguito dal server al client. In questo modo, i due processi possono scambiarsi i ruoli tra un'interazione e la successiva. Sono molte le applicazione che sfruttano questo modello. Prima di scendere nei particolari del modello client/server, specifichiamo la differenza tra un processo ed un thread. Un programma in esecuzione detto task o pi comunemente processo. Su un sistema multiprocessore (multiprocessing system), tutti i processori possono eseguire contemporaneamente diversi processi; invece nel caso di un singolo processore, in un determinato istante viene eseguito un solo processo, anche se il sistema operativo pu mantenere pi processi attivi, assegnando loro, a turno, il processore. Questa politica chiamata multitasking. Come i processi, i thread sono schedulati separatamente e possono procedere in modo indipendente gli uni dagli altri. Diversamente dai processi, i thread non richiedono al sistema operativo di acquisire troppi dati o spendere troppo tempo nel context switching: i dati possono essere condivisi tra i thread semplicemente tramite letture dalla memoria. Essi inoltre hanno program counter distinti, registri macchina e stack, ma tutto il resto condiviso da tutti i thread appartenenti allo stesso task. Processi
Program Counter

Thread

Program Counter Program Counter Program Counter Program Counter

Program Counter 129

I modelli client/server si suddividono in quattro categorie: single-threaded server; multi-threaded server; tiered server; peer-to-peer server.

12.1 Single-threaded server


E' il modello pi semplice. In esso il client inoltra una chiamata al server, che restituisce un qualche risultato. Il server non deve occuparsi dei conflitti di accesso ai dati poich c' un solo thread. Ci sono due casi generali di applicazione: - un client chiama una subroutine remota; - un server chiama una libreria non-thread-safe. Si usa la subroutine remota quando un client desidera ottenere dei dati, delle applicazioni, degli attributi di sistema oppure uno o pi sistemi remoti. Non necessario che il server sia gi in running al momento dell'arrivo della richiesta: infatti il client richiede raramente un servizio di questo tipo, quindi non indispensabile minimizzare il tempo di risposta. Nel secondo caso invece, il server svolge la maggior parte del suo lavoro chiamando una libreria che non thread-safe. Per far fronte alle richieste di pi client, sarebbe necessario mandare in esecuzione copie multiple ed identiche del server: infatti le applicazioni userebbero processi invece dei thread per ottenere esecuzioni concorrenti, a scapito di un inevitabile overhead. Un uso comune di questo modello si trova nei sistemi di gestione di database.

Client

Server

Fig. 12.1 Modello server single-threaded Esempio: Nel nostro esempio il processo process1 svolge le veci di un processo client e ad esso e associato il numero di porta 7500. Il processo process2 e invece un processo server con numero di porta 7600. La libreria mysock.h si trova nel capitolo 14. Process1:
#include <stdio.h> #include <errno.h> #include mysock.h #define #define #define #define LENSTR 20 MYPORT 7500 DESTPORT 7600 DESIND INADDR_ANY

int sockp, intdato; mex mess; void errore(char *); void errdone(char *); void done(); main() { if ((sockop = creatsock(SOCK_STREAM))<0) errore (1:errore creazione socket); if (connectaddr (sockp, DESIND,DESPORT) <0) errdone (1:Errore di connessione); printf (1: Codice: ); gets (mess.codice); printf (1: Nome: ); gets (mess.nome); sendmex (sockp, &mess); recvint (sockp, &intdato); printf (1: Ricevuto intero: %d\n), intdato); done (); } void errore (char *s) { perror (s); exit (1); } void errdone (char *s) { perror(s); done(); } void done () { if (closesock (sockp) <0 ) errore (1: Errore distruzione socket); printf (Distruzione avvenuta\n); exit(0); } 131

Process2:
#include <stdio.h> #include <errno.h> #include mysock.h #define LENSTR 20 #define MYPORT 7600 #define DESIND INADDR_ANY int sockp, newsock,sizeind; mex mess; struct sockaddr indirizzo; void errore(char *); void errdone(char *); void done(); main() {

if ((sockop = initsock(SOCK_STREAM, MYIND, MYPORT))<0) errore (2:errore inizializzazione socket); if (listensock (sockp,1) <0) errdone (1:Errore LISTEN); printf (2: Attesa messaggi...\n ); if ((newsock=acceptsock (sockp, &indirizzo,&sizeind)) <0) errdone (2: Errore ACCEPT); recvmex (newsock, &mess); printf (2: Ricevuto: %d\n); printf(2: Codice: %s\n,mess.codice); printf(2: Nome: %s\n,mess.nome); sendint (newsock, 10); closesock (newsock); done ();

void errore (char *s) { perror (s); exit (1); } void errdone (char *s) { perror(s); done(); } void done () { if (closesock (sockp) <0 ) errore (1: Errore distruzione socket); printf (Distruzione avvenuta\n); exit(0); }

12.2 Multi-threaded server

E' oggi il modello pi diffuso. Questo tipo di server tipicamente supporta operazioni multiple ed progettato per permettere accessi concorrenti (ognuno sul proprio thread) da parte di clienti diversi. Dal momento che i vari thread condividono i dati, il server deve usare dei semafori o altri meccanismi di controllo della concorrenza per evitare condizioni di conflitto. La gestione concorrente delle richieste permette inoltre di raggiungere un overlap del processore e dell'I/O, che generalmente riduce il tempo medio di attesa del servizio, durante il quale il client rimane bloccato.. Il server svolge il suo lavoro eseguendo istruzioni, system call ed utilizzando libreria vendor. Le risorse che usa sono locali al nodo su cui sta lavorando. Se il server multi-threaded progettato in modo opportuno, non dovrebbe essere necessario farne girare pi di una copia su un nodo, ed pi efficiente di processi multipli concorrenti. Per motivi di affidabilit e prestazioni, conveniente mandare in esecuzione copie dello stesso server su nodi differenti. Client Server Client

Fig. 12.2 Modello server multithreaded

Come esempio si puo` vedere la soluzione allesercitazione 4 (parte riguardante i socket) nel capitolo 14.

12.3 Tiered server


In questo modello, il client si rivolge ad un server che a sua volta demanda parte del servizio ad un altro server. In linea di principio possono essere coinvolti un numero qualsiasi di server aggiuntivi; in realt, se ne vengono coinvolti pi di due, il client deve attendere troppo tempo prima di ricevere la risposta. Infatti, ad ogni passo della catena, il chiamante rimane bloccato fino a che il server invocato non ha espletato la propria funzione; quindi, il tempo di risposta visto dal client dato dalla somma di tutte le chiamate. E bene notare che il server intermedio agisce sia da client che da server. Questo modello viene usato per evitare l'overhead su un singolo server, permettendo anche di riutilizzare server gi implementati.
133

Client

Server

Client

Fig. 12.3 Modello server tiered

Esempio: Lesempio che proponiamo una possibile soluzione allesonero concorrente di Maggio 96 la cui traccia riportata nel capitolo 14 insieme con unaltra possibile svolgimento del tema proposto. processo client
#include #include #include #include #include #include #include <sys/types.h> <sys/socket.h> <netdb.h> <netinet/in.h> <arpa/inet.h> <stdio.h> <errno.h>

#include <string.h> #include <fcntl.h> #define #define #define #define MAXCOD MAXNOME INDDEST PORTADEST 7 10 INADDR_ANY 7000

typedef struct Messaggio { char codice[MAXCOD]; char nome[MAXNOME]; int data; int numero; long prezzo; } msg; typedef struct Banca { char codcarta[MAXCOD]; char numcarta[MAXCOD]; long importo; } MEX; void iniziascontrino (); void scriviscontrino (char *, long, int); void finescontrino (long ); int dayofyear (int, int, int); int idsock, fscontrino; void main () { int tipo, scelta, caso; msg message; int quantita, totale = 0; int nprod = 0, TOT = 0, NPROD = 0; char codcliente[MAXCOD]; char codprod[MAXCOD]; char risp, codcassa[MAXCOD]; struct sockaddr_in rsock; int tentativi; MEX mess; int giorno, mese, anno, di; printf ("Codice cassa: "); gets(codcassa); printf ("Data odierna: "); scanf("%d%d%d",&giorno,&mese,&anno); message.data = dayofyear(giorno, mese, anno); getchar (); if ((idsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("Errore nella creazione del socket 1"); exit (1); } rsock.sin_addr.s_addr = htonl(INDDEST); rsock.sin_family = AF_INET; rsock.sin_port = htons(PORTADEST); if ((connect (idsock, (struct sockaddr *)&rsock, sizeof(rsock))) < 0) { perror ("Errore in connessione\n"); close (idsock); exit (1); } do { printf ("Codice cliente: tipo = 1; "); gets(codcliente);

135

totale = 0; nprod = 0; if ((send (idsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } iniziascontrino (); do { printf ("Codice prodotto: "); gets(codprod); printf ("Quantita: "); scanf ("%d", &quantita); getchar(); strcpy (message.codice, codprod); message.numero = quantita; if ((send (idsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } if ((recv (idsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore in receive"); close (idsock); exit (1); } scriviscontrino (message.nome, message.prezzo, quantita); totale = totale + (message.prezzo)*quantita; nprod = nprod + quantita; printf ("Altro prodotto (s/n)? "); scanf ("%c", &risp); getchar(); if ((send (idsock, &risp, sizeof(char), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } } while ((risp != 'n') && (risp != 'N')); finescontrino (totale); printf ("\nModalita di pagamento disponibili:\n"); printf ("\t1-> Bancomat\n"); printf ("\t2-> Contanti\n"); printf ("Scelta : "); scanf ("%d",&scelta); getchar(); switch (scelta) { case 1: printf ("Inserisci numero carta : "); gets (mess.numcarta); mess.importo = totale; tentativi = 1; while (tentativi < 3) { printf ("Inserisci codice carta : "); gets (mess.codcarta); tipo = 4; if ((send (idsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } if ((send (idsock, &mess, sizeof(MEX), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } if ((recv (idsock, &caso, sizeof(int), 0)) < 0) { perror ("Errore nella receive"); close (idsock); exit (1); } if (caso == 1) { printf ("\nPagamento effettuato\n");

break; } else if (caso == 0) { printf ("Pagamento non possibile\n"); printf ("Disponibilita non sufficiente\n"); break; } else tentativi++; } break; case 2: printf ("Pagamento effettuato\n"); break;

} TOT = TOT + totale; NPROD = NPROD + nprod;

tipo = 2; if ((send (idsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore nella send"); close (idsock); exit (1); } strcpy(message.codice, codcliente); message.numero = nprod; message.prezzo = totale; if ((send (idsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore in spedizione messaggio"); close (idsock); exit (2); } printf ("Altro cliente (s/n)? "); scanf ("%c", &risp); getchar (); } while ((risp != 'n') && (risp != 'N')); tipo = 3; if ((send (idsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore in spedizione messaggio"); close (idsock); exit (2); } strcpy(message.codice, codcassa); message.numero = NPROD; message.prezzo = TOT; if ((send (idsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore in spedizione messaggio"); close (idsock); exit (2); } tipo = 0; if ((send (idsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore in spedizione messaggio"); close (idsock); exit (2); } close (idsock); } void iniziascontrino () { if ((fscontrino=open("scontrino.txt",O_CREAT|O_WRONLY|O_TRUNC,0666)) < 0) { perror ("Errore in apertura file 'scontrino.txt'"); close (idsock); exit (1); } 137

} void scriviscontrino { write (fscontrino, write (fscontrino, write (fscontrino, } (char* nome, long prezzo, int quantita) nome, MAXCOD); &prezzo, sizeof(long)); &quantita, sizeof (int));

void finescontrino (long montante) { char nome[MAXCOD]; long prezzo; int quantita; close (fscontrino); if ((fscontrino = open ("scontrino.txt", O_RDONLY, 0666)) < 0) { perror ("Errore in apertura file 'scontrino.txt'"); close (idsock); exit (1); } printf ("\n\n"); while (read (fscontrino, nome, MAXCOD) > 0) { read (fscontrino, &prezzo, sizeof (long)); read (fscontrino, &quantita, sizeof (int)); printf ("%s\t\t%ld", nome, prezzo); if (quantita > 1) printf (" x %d\n", quantita); else printf ("\n"); } printf ("\nTOTALE: %ld lire\n\n", montante); close (fscontrino); } int dayofyear (int gg, int mm, int aa) { static char daytab[2][13] = { { 0,31,28,31,30,31,30,31,31,30,31,30,31 }, { 0,31,29,31,30,31,30,31,31,30,31,30,31 } }; int i, leap; leap = (((aa%4) == 0) && ((aa%100) != 0) || ((aa%400) == 0)); for (i = 1; i < mm; i++) gg += daytab[leap][i]; return (gg); }

processo server
#include #include #include #include #include #include #include #include #include #include #include #define #define #define #define #define #define <sys/types.h> <sys/socket.h> <sys/wait.h> <netdb.h> <netinet/in.h> <arpa/inet.h> <stdio.h> <errno.h> <string.h> <fcntl.h> <unistd.h> PORTA 7000 PORTADEST2 6500 PORTADEST 6000 MYIND INADDR_ANY INDDEST INADDR_ANY MAXCOD 7

#define CODA #define MAXNOME #define MAXPRODOTTI typedef struct Messaggio { char codice[MAXCOD]; char nome[MAXNOME]; int data; int numero; long prezzo; } msg; typedef struct Prezzi { char codice[MAXCOD]; char nome[MAXNOME]; long prezzo; } listino; typedef struct Cassa { char codice[MAXCOD]; long TOT; int NPROD; int data; } CASSA; typedef struct Cliente { char codice[MAXCOD]; long totale; int nprod; int data; } CLIENTE; typedef struct Banca { char codcarta[MAXCOD]; char numcarta[MAXCOD]; long importo; } MEX;

5 10 10000

void leggiprezzo (msg *, listino *); int fd1; void main() { int idsock, newsock, sock; int fd2, fd3; int i, j, tipo, caso; msg message; CLIENTE client; CASSA cassa; listino prezzi[MAXPRODOTTI]; int pid; struct sockaddr_in sockname, rsock; union wait status; char risp; MEX mess; if ((fd1 = open("listino.dat", { perror ("Errore in apertura exit (1); } if ((fd2 = open("Clienti.dat", { perror ("Errore in apertura exit (1); } O_RDONLY, 0666)) < 0) file"); O_CREAT|O_WRONLY|O_APPEND, 0666)) < 0) file 'Clienti.dat'");

139

if ((fd3 = open("Vendite.dat", O_CREAT|O_WRONLY|O_APPEND, 0666)) < 0) { perror ("Errore in apertura file 'Vendite.dat'"); exit (1); } for (i = 0; i < MAXPRODOTTI; i++) read(fd1, &prezzi[i], sizeof(listino)); if ((idsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("Errore in creazione socket"); exit (2); } sockname.sin_addr.s_addr = htonl((unsigned long) MYIND); sockname.sin_family = AF_INET; sockname.sin_port = htons(PORTA); if ((bind (idsock,(struct sockaddr *)&sockname, sizeof(sockname))) < 0) { perror ("Errore nella bind"); close (idsock); exit (2); } if ((listen (idsock, CODA)) < 0) { perror ("Errore nella listen"); close (idsock); exit (2); } while (1) { if ((newsock = accept (idsock, 0, 0)) < 0) { perror ("Errore nella accept"); close (idsock); exit (2); } if ((pid = fork ()) < 0) { perror ("Errore nella fork"); close (idsock); close (newsock); exit (2); } if (pid == 0) { close (idsock); if ((recv (newsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); } while (tipo != 0) { switch (tipo) { case 1: risp = 'x'; while ((risp != 'n') && (risp != 'N')) { if ((recv (newsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); } leggiprezzo (&message, prezzi); if ((send (newsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore nella send"); close (newsock); exit (2); } if ((recv (newsock, &risp, sizeof(char), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); }

} break; case 2: if ((recv (newsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); } strcpy(client.codice,message.codice); client.totale = message.prezzo; client.data = message.data; client.nprod = message.numero; printf ("Scrittura su base dati clienti...\n"); write (fd2, &client, sizeof(CLIENTE)); break; case 3: if ((recv (newsock, &message, sizeof(msg), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); } strcpy(cassa.codice,message.codice); cassa.TOT = message.prezzo; cassa.data = message.data; cassa.NPROD = message.numero; printf ("Scrittura su base dati casse...\n"); write (fd3, &cassa, sizeof(CASSA)); break; case 4: if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("Errore in creazione socket"); close (newsock); exit (2); } rsock.sin_addr.s_addr = htonl(INDDEST); rsock.sin_family = AF_INET; rsock.sin_port = htons (PORTADEST2); if ((connect(sock, (struct sockaddr *)&rsock, sizeof(rsock))) < 0) { perror ("Errore in connessione"); close (newsock); close (sock); exit (2);

} if ((recv (newsock, &mess, sizeof(MEX), 0)) < 0) { perror ("Errore nella receive"); close (newsock); close (sock); exit (2); } if ((send (sock, &mess, sizeof(MEX), 0)) < 0) { perror ("Errore nella send"); close (newsock); close (sock); exit (2); } if ((recv (sock, &caso, sizeof(int), 0)) < 0) { perror ("Errore nella receive"); close (newsock); close (sock); exit (2); } if ((send (newsock, &caso, sizeof(int), 0)) < 0) { perror ("Errore nella send"); close (newsock); close (sock); exit (2); } close (sock); 141

} close (newsock); exit (0);

break; } if ((recv (newsock, &tipo, sizeof(int), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); }

} }

} else { close (newsock); while (wait3 (&status, WNOHANG, NULL) > 0); }

void leggiprezzo (msg* dato, listino* prezzi) { int i; for (i = 0; i < MAXPRODOTTI; i++) { if (strcmp (dato->codice, prezzi[i].codice) == 0) { dato->prezzo = prezzi[i].prezzo; strcpy (dato->nome,prezzi[i].nome); } }

processo superuser
#include #include #include #include #define #define #define #define <stdio.h> <errno.h> <string.h> <fcntl.h> MAXCOD MAXCASSE MAXCLIENT CODA 7 100 100 5

typedef struct Cassa { char codice[MAXCOD]; long TOT; int NPROD; int data; } CASSA; typedef struct Cliente { char codice[MAXCOD]; long totale; int nprod; int data; } CLIENTE; int dayofyear(int, int, int); void main() { int fd1, fd2; int i, opt, cont; CLIENTE client; CASSA cassa; int trovato, prodotti = 0;

long spesa = 0; char codice[MAXCOD]; int giorno, mese, anno, data; if ((fd1 = open("Clienti.dat", { perror ("Errore in apertura exit (1); } if ((fd2 = open("Vendite.dat", { perror ("Errore in apertura exit (1); } O_CREAT|O_RDONLY, 0666)) < 0) file 'Clienti.dat'"); O_CREAT|O_RDONLY, 0666)) < 0) file 'Vendite.dat'");

printf ("Opzioni disponibili:\n"); printf ("\t1->\tControllo clienti\n"); printf ("\t2->\tControllo casse\n"); scanf ("%d", &opt); getchar (); switch (opt) { case 1: trovato = 0; printf ("Codice del cliente su cui effettuare il controllo: "); gets (codice); printf ("Data di partenza del controllo: "); scanf ("%d%d%d", &giorno, &mese, &anno); data = dayofyear( giorno, mese, anno); cont = read(fd1, &client, sizeof(CLIENTE)); while (cont != 0) { if (strcmp(client.codice,codice) == 0) { if (data <= client.data) { spesa += client.totale; prodotti += client.nprod; trovato++; } } cont = read(fd1, &client, sizeof(CLIENTE)); } if (trovato > 0) { printf ("Il cliente %s ",codice); printf ("ha acquistato %d prodotti ",prodotti); printf ("dal %d/%d/%d ad oggi\n", giorno, mese, anno); printf("spendendo in totale %ld lire in %d volte\n",spesa,trovato); } else { printf ("Il cliente %s ",codice); printf ("non ha mai acquistato in questo supermercato\n"); } break; case 2: trovato = 0; printf ("Codice della cassa su cui effettuare il controllo: "); gets (codice); printf ("Data di partenza del controllo: "); scanf ("%d%d%d", &giorno, &mese, &anno); data = dayofyear( giorno, mese, anno); cont = read(fd2, &cassa, sizeof(CASSA)); while (cont != 0) { if (strcmp(cassa.codice,codice) == 0) { if (data <= cassa.data) { spesa += cassa.TOT; prodotti += cassa.NPROD; trovato++; } } 143

} close (fd1); close (fd2); }

cont = read(fd2, &cassa, sizeof(CLIENTE)); } if (trovato > 0) { printf ("La cassa %s ",codice); printf ("ha venduto %d prodotti ",prodotti); printf ("dal %d/%d/%d ad oggi\n", giorno, mese, anno); printf("incassando in totale %ld lire in %d volte\n",spesa,trovato); } else { printf ("La cassa %s ",codice); printf ("non fa parte di questo supermercato\n"); } break;

int dayofyear (int gg, int mm, int aa) { static char daytab[2][13] = { { 0,31,28,31,30,31,30,31,31,30,31,30,31}, { 0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap = (((aa%4) == 0) && ((aa%100) == 0) || ((aa%400) == 0)); for (i = 0; i < mm; i++) gg += daytab[leap][i]; return gg; }

processo banca
#include #include #include #include #include #include #include #include #include #include #include #define #define #define #define #define <sys/types.h> <sys/socket.h> <sys/wait.h> <netdb.h> <netinet/in.h> <arpa/inet.h> <stdio.h> <errno.h> <string.h> <fcntl.h> <unistd.h> PORTA 6500 PORTADEST 7000 MYIND INADDR_ANY CODA 5 MAXCOD 7

typedef struct mex { char codcarta[MAXCOD]; char numcarta[MAXCOD]; long importo; } MEX; void main () { int fclienti, idsock, newsock, risp, fine; MEX mess, cliente; struct sockaddr_in sockname; if ((fclienti = open("Banca.dat", O_RDONLY, 0666)) < 0) { perror ("Errore in apertura file"); exit (1);

} if ((idsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror ("Errore in creazione socket"); exit (2); } sockname.sin_addr.s_addr = htonl((unsigned long) MYIND); sockname.sin_family = AF_INET; sockname.sin_port = htons(PORTA); if ((bind (idsock, (struct sockaddr *)&sockname, sizeof (sockname))) < 0) { perror ("Errore nella bind"); close (idsock); exit (2); } if ((listen (idsock,CODA)) < 0) { perror ("Errore nella listen"); close (idsock); exit (2); } while (1) { if ((newsock = accept(idsock, 0, 0)) < 0) { perror ("Errore nella accept"); close (idsock); exit (2); } if ((recv (newsock, &mess, sizeof(MEX), 0)) < 0) { perror ("Errore nella receive"); close (newsock); exit (2); } lseek (fclienti, 0, SEEK_SET); fine = read(fclienti, &cliente, sizeof(MEX)); while ((strcmp(mess.numcarta, cliente.numcarta) != 0) && (fine != 0)) fine = read(fclienti, &cliente, sizeof(MEX)); if (strcmp(mess.codcarta, cliente.codcarta) != 0) risp = -1; else if (mess.importo > cliente.importo) risp = 0; else risp = 1; if ((send (newsock, &risp, sizeof(int), 0)) < 0) { perror ("Errore nella send"); close (newsock); close (idsock); exit (2); } close (newsock); }

12.4 Peer-to-peer server


Il termine peer-to-peer assume vari significati a seconda del contesto in cui si trova. Nel nostro ambito indica che ogni server svolge anche la funzione di client. Ogni programma possiede thread indipendenti per assumere il ruolo di client e per svolgere le operazioni di server. Il client ed il server comunicano tra loro tramite i dati condivisi, eventualmente protetti da semafori. E' difficile pensare ad un campo di applicabilit per questo modello, anche perch comporta molti problemi di affidabilit, prestazioni e sicurezza, come ad esempio quello di localizzare i dati "reali".
145

Peer

Peer

Fig. 12.4 Modello server peer-to-peer

147

13 Daemon

Un daemon un processo che viene eseguito in background (cio senza un terminale associato o una shell di login) aspettando che si verifichi qualche evento oppure aspettando di svolgere un compito specifico in un periodo base. Ne un esempio la stampante in attesa di stampare un file. Un programma di login remoto che permette agli utenti di passare da un sistema ad un altro sulla rete, dovrebbe avere un processo daemon che aspetta la richiesta di login da parte di qualcuno che desidera attraversare la rete. I processi daemon possono essere attivati in diversi modi: 1. durante lo startup del sistema. Molti sistemi daemon vengono attivati dalla scritta di inizializzazione / etc / rc che eseguito dal comando / etc / init quando il sistema diventa multiuser. Quando un daemon ha inizio in questo modo gode dei privilegi di superuser. 2. periodicamente dal file di sistema / usr / lib / crontab . I daemon invocati in questo modo hanno dei privilegi di superuser, poich il programma cron stesso opera da superuser. 3. periodicamente dal file crontab di un utente. 4. tramite il comando at, che schedula un processo per unesecuzione successiva. 5. dal terminale di uno user, come processo in foreground o background (in genere quando viene

149

151

14 Esempi di programmazione in C
Nelle soluzioni delle esercitazioni da noi proposte sono stati usati degli header da noi creati per poter utilizzare in modo piu semplice le funzioni delle IPC. Qui di seguito vengono riportati i listati di questi file. Semafori.h initsem crea l'insieme di semafori, restituendo l'identificatore dell'insieme, richiede il numero di semafori dell'insieme; initvalsem anche questa funzione crea linsieme di semafori, ma a differenza della precedente questa richiede un parametro in pi in ingresso: il parametro per la generazione della chiave mediante la ftok; impostasem effettua linizializzaione di un semaforo, richiede il numero del semaforo e il valore che deve assumere; valsem restituisce il valore attuale del semaforo, richiede come parametro il numero del semaforo di cui si vuole sapere il valore; waitsem effettua la wait su un semaforo, richiede come parametro il numero del semaforo; signalsem effettua la signal su un semaforo, richiede come parametro il numero del semaforo; donesem elimina linsieme dei semafori. Tutte queste funzioni, tranne le prime due di creazione, richiedono come parametro anche lidentificatore dellinsieme di semafori.
#include #include #include #include #include int int int int int int int <sys/ipc.h> <sys/sem.h> <sys/types.h> <unistd.h> <errno.h>

initsem (int Numsem); initvalsem (int Numsem, int cod); impostasem (int idsem, int Numsem, int Valore); valsem (int idsem, int Numsem); waitsem (int idsem, int Numsem); signalsem (int idsem, int Numsem); donesem (int idsem);

int initsem (int Numsem) { int c; c = 101; while (((idsem = semget(ftok(mysem.h,c), Numsem, IPC_CREAT|IPC_EXCL| 0600)) < 0) && (c < 110)) c++; return (idsem); } int initvalsem (int Numsem, int cod) { idsem = semget(ftok(mysem.h, cod), Numsem, IPC_CREAT|0666); return (idsem); }

int impostasem (int idsem, int Numsem, int Valore) { union semun argomenti; argomenti.val = Valore; return (semctl(idsem, Numsem, SETVAL, argomenti)); } int valsem (int idsem, int Numsem) { int valore; union semun argomenti; valore = semctl (idsem, Numsem, GETVAL, argomenti)); return (valore); } int waitsem (int idsem, int Numsem) { struct sembuf operazione; operazione.sem_op = -1; operazione.sem_num = Numsem; operazione.sem_flg = SEM_UNDO; return (semop (idsem, &operazione, 1)); } int signalsem (int idsem, int Numsem) { struct sembuf operazione; operazione.sem_op = 1; operazione.sem_num = Numsem; operazione.sem_flg = SEM_UNDO; return (semop (idsem, &operazione, 1)); } int donesem (int idsem) { return (semctl (idsem, 0, IPC_RMID, NULL)); }

Messaggi.h Come per i semafori, anche per la gestione delle code di messaggi abbiamo creato un header che poi abbiamo utilizzato negli esempi. Questo file file contiene le seguenti funzioni: initmsg crea la coda di messaggi, restituisce lidentificatore della coda; initvalmsg come per i semafori, stata realizzata una versione della funzione di creazione della coda nella quale viene passata come parametro anche il valore da usare nella ftok per la creazione della chiave; inviastr funzione per la spedizione di un messaggio composto da una stringa di caratteri, richiede come parametri lidentificatore della coda e il testo da inviare; ricevistr funzione per la ricezione di un messaggio di tipo stringa, richiede come parametri lidentificatore della coda e una variabile di tipo char * nella quale memorizzare la stringa ricevuta; inviaint funzione che permette linvio di un messaggio composto da un numero intero, richiede come parametri lidentificatore della coda e il numero da inviare; riceviint funzione per ricevere un messaggio composto da un numero intero, i parametri richiesti sono lidentificatore della coda e una variabile nella quale memorizzare il valore ricevuto; donemsg funzione per la rimozione della coda di messaggi, richiede come parametro lidentificatore della coda.
#include <sys/ipc.h> #include <sys/msg.h> #include <sys/types.h> 153

#include <unistd.h> #include <errno.h> #include <string.h> #define #define #define #define STRINGA INTERO SIZESTR SIZEINT 1 2 256 sizeof(int)

typedef struct message1 { long type; char testo[SIZESTR]; } msgtxt; typedef struct message2 { long type; int dato; } msgint; int int int int int int int initmsg (); initvalmsg (int c); inviastr (int codamsg, char *testo); ricevistr (int codamsg, char *testo); inviaint (int codamsg, int i); riceviint (int codamsg, int *i); donemsg (int codamsg);

int initmsg () { int c, codamsg; c = 0; while (((codamsg = msgget(ftok(mymsg.h, c), IPC_EXCL)) < 0) && (c < 255)) c++; if (codamsg < 0) return (-1); return (codamsg); }; int initvalmsg (int c) { return(msgget(ftok(mymsg.h,c),0660|IPC_CREAT)); };

0600|IPC_CREAT|

int inviastr (int codamsg, char *testo) { int ritorno; msgtxt messaggio; messaggio.type = STRINGA; strcpy (messaggio.testo, testo); if ((ritorno = msgsnd(codamsg, (struct msgbuf *) (&messaggio), SIZESTR, IPC_NOWAIT)) < 0) return (-1); return (ritorno); } int ricevistr (int codamsg, char *testo) { msgtxt messaggio; if (msgrcv(codamsg, (struct msgbuf *)(&messaggio), SIZESTR, STRINGA, IPC_NOWAIT) < 0) return (-1); strcpy (testo, messaggio.testo); return (1); } int inviaint (int codamsg, int i)

{ int ritorno; msgint messaggio; messaggio.type = INTERO; messaggio.dato = i; if ((ritorno = msgsnd(codamsg, (struct msgbuf *) (&messaggio), SIZEINT, IPC_NOWAIT)) < 0) return (-1); return (ritorno); }; int riceviint (int codamsg, int *i) { msgint messaggio; if (msgrcv (codamsg, (struct msgbuf *)(&messaggio), SIZESTR, INTERO, IPC_NOWAIT) < 0) return (-1); *i = messaggio.dato; return (1); }; int donemsg (int codamsg) { if (msgctl (codamsg, IPC_RMID, NULL) < 0) return (-1); return (0); };

Memcond.h initmemc funzione per la creazione di unarea di memoria condivisa; richiede come parametro dingresso la dimensione del segmento che si vuole creare ne e restituisce lidentificatore; initvalmemc versione alternativa della funzione precedente in cui viene richiesto come parametro dingresso un valore da andare ad inserire nella funzione ftok; collegamemc funzione per collegare larea di memoria creata allarea dati del processo chiamante, richiede in ingresso lidentificatore della shared memory e restituisce lindirizzo della prima cella di memoria; scollegamemc funzione per scollegare larea di memoria condivisa dallarea dati del processo chiamante, richiede in ingresso lindirizzo di partenza del segmento condiviso, cio lindirizzo di collegamento. donememc funzione per deallocare larea di memoria creata, richiede come parametro lidentificatore del segmento.
#include <sys/ipc.h> #include <sys/types.h> #include <sys/shm.h> int initmemc (int size); int initvalmemc (int size, int c); char *collegamemc (int idmemc); int scollegamemc (char *indirizzo); int donememc (int idmemc); int initmemc (int size) { int c, ritorno; c = 0; while (((ritorno = shmget (ftok(memcond.h, c), size, IPC_CREAT|IPC_EXCL|0744)) < 0) && (c < 255)) c++; return (ritorno); 155

}; int initvalmem (int size, int c) { return( shmget (ftok(memcond.h, c), size, IPC_CREAT|0744)); }; char *collegamemc (int idmemc) { char *indirizzo; if ((indirizzo=shmat(idmemc, 0, 0744)) == (char *)-1) return (NULL); return (indirizzo); }; int scollegamemc (char *indirizzo) { return (shmdt (indirizzo)); }; int donememc (int idmemc) { return (shmctl (idmemc, IPC_RMID, NULL)); };

14.1 Esercitazione 1 System call per la gestione dei processi


Il sistema operativo e' l'"intermediario" tra l'utente e l'hardware dell'elaboratore: deve renderne agevole e conveniente l'utilizzo da partedell'utente e deve gestire al meglio le risorse hardware disponibili (CPU, memoria, periferici...). Il sistema operativo crea cioe' una macchina virtuale migliore, mascherando la complessita' dell'hardware e fornendo dei meccanismi di piu' facile impiego per la gestione delle risorse fisiche presenti. L'elemento del sistema operativo piu' vicino all'hardware e' il kernel, che fornisce le funzioni di base per l'utilizzo dell'elaboratore. Sopra al kernel vengono costruite altre "macchine virtuali" fino ad arrivare all'interfaccia utente (interprete dei comandi o "shell"). Nei confronti del sistema operativo Unix, il corso di Sidue si propone sia di fornire "operativita'" come "utente finale" (l'ambiente, i comandi, ...), sia di esaminare aspetti piu' propriamente "tecnici", da utente "sistemista" o programmatore di sistema (il compilatore C, le system call, la programmazione concorrente, ...). L'utente dell'ultimo tipo (l'utente "esperto" che si crea ad hoc i propri programmi applicativi) ha la necessita' di accedere in maniera completa a tutte le risorse che la macchina possiede: Unix permette questo accesso attraverso il meccanismo delle "chiamate al sistema operativo", le system call. processo: un processo e' un "programma in esecuzione". Unix e' un sistema operativo multiprogrammato: cosa significa ? In maniera approssimata, Unix mantiene in memoria piu' processi e (assumendo una "granularita' temporale" non troppo piccola...) li porta avanti in maniera contemporanea, parallela. (E' ovviamente una forzatura, perche', limitando il discorso a elaboratori monoprocessore, in ogni istante un solo processo e' "realmente" in esecuzione: realizzando pero' un opportuno meccanismo a "suddivisione di tempo", l'impressione "esterna" che si ricava e' quella di un elaboratore che porta avanti in parallelo piu' compiti, piu' "processi"...). Le system call per la gestione dei processi: fork, wait, signal, kill, exec, exit. I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono:

<unistd.h> <sys/wait.h> <sys/types.h> <signal.h> ESERCIZIO: programmazione in C concorrente Un processo padre inizializza alcune varibili numeriche e stringhe di caratteri, poi genera 5 figli che modificano le stesse variabili: - cosa succede alle variabili di ciascun processo ? - il processo padre apre alcuni file (per esempio un file di testo): qual e' il comportamento dei figli nei confronti di questi file ? - i figli "sostituiti" con system call exec e il loro rapporto col padre - il padre si pone in attesa della morte dei figli (non exec) a) tramite system call wait b) ogni figlio prima di morire manda un segnale al padre. Uno dei problemi principali e' legato all'effettivo ordine di esecuzione dei vari processi generati: non e' garantito alcun ordine ! (questo discorso opportunamente ampliato porta al trattamento delle tematiche sulla "sincronizzazione" di processi concorrenti). Soluzione: Questo programma e composto da un padre che apre il file "FILE .TXT" e inizializza le variabili: stringa = "stringa padre" (tipo stringa) dato = 10 (tipo intero) Inoltre genera 4 figli. Vediamo come i figli operano sulle variabili del padre e sul file: Listato file "eserc0.c":
#include #include #include #include #include <unistd.h> <sys/wait.h> <sys/types.h> <fcntl.h> <signal.h>

#define LRIGA 8 #define LSTR 20 void azione (); int k; main () { int pid1, pid2, pid3, pid4, pid5, file, dato, i; char stringa [LSTR]; union wait status; k = 0; file = open ("file.txt", O_RDWR, 0666); read (file, stringa, LRIGA); stringa [LRIGA-1] = '\0'; printf ("Padre: letto da file: %s\n", stringa); dato = 10; strcpy (stringa, "Stringa padre\n"); printf ("Padre: dato = %d, stringa = %s\n", dato, stringa); 157

signal (SIGUSR1, azione); pid1 = fork(); if (pid1 == 0) { printf ("Figlio1: dato = %d, stringa = %s\n", dato, stringa); kill (getppid (), SIGUSR1); exit (0); } pid2 = fork(); if (pid2 == 0) { read (file, stringa, LRIGA); stringa[LRIGA-1] = '\0'; printf ("Figlio2: letto da file: %s\n", stringa); kill (getppid (), SIGUSR1); exit (0); } pid3 = fork (); if (pid3 == 0) { execl ("figlio3","figlio3.c", (char *)0); printf ("Execl fallita"); exit (1); } pid4 = fork (); if (pid4 == 0) { read (file, stringa, LRIGA); stringa[LRIGA-1] = '\0'; printf ("Figlio4: letto fa file: %s\n", stringa); kill (getppid (), SIGUSR1); exit (0); } close (file); for (i = 1; i <= 5; i++) wait (&status); } printf ("Fine padre\n");

void azione () { k = k+1; printf ("Ricevuto segnale %d\n", k); signal (SIGUSR1, azione); }

Listato file "figlio3.c"


#include <unistd.h> main () { printf ("Inizio figlio 3\n"); printf ("Fine figlio 3\n"); }

Listato file "file.txt" PAROLA1 PAROLA2 PAROLA3 PAROLA4 ....

Risultato: $ Padre: letto da file: PAROLA1 Padre: dato = 10, stringa = stringa padre Figlio1: dato = 10, stringa = stringa padre Ricevuto segnale 1 Figlio2: letto da file: PAROLA2 Ricevuto segnale 2 Figlio4: letto da file: PAROLA3 Ricevuto segnale 3 Fine padre Inizio figlio 3 Fine figlio3 Da questo esempio si evince che i figli ereditano dal padre tutte le sue variabili, files compresi. Il processo padre comincia a leggere dal file e legge PAROLA1, successivamente ci legge il processo figlio2 e legge PAROLA2 e dopo il figlio4 che legge PAROLA3, dunque ogni volta che un processo legge dal file la posizione di lettura viene modificata per tutti i processi.

14.2 Esercitazione 2 Signal() e Kill()


Come gi intravisto nella Esercitazione 1 le primitive Signal() e Kill() permettono di scambiare alcune informazioni tra processi concorrenti. La prima permette di rendere un processo (o una serie, se questa utilizzata prima di una o pi primitive di fork() ) sensibile all'invio di un messaggio da un altro processo tramite la seconda primitiva. Quando questo processo riceve il messaggio esegue una funzione action specificata nella primitiva Signal(type_of_signal, action). In combinazione con la primitiva Pause() possibile ottenere una certa sincronizzazione dei processi concorrenti. Scrivete un programma C che utilizzi quanto detto tra un padre e due figli. I/O a basso livello Nel sistema operativo Unix, tutto l'input/output viene effettuato leggendo o scrivendo file, perch tutte le periferiche, compresi la tastiera e il video, sono considerati file all'interno del file system: la gestione della comunicazione tra un programma e le periferiche gestita da un'unica interfaccia omogenea per mezzo del concetto di file. descrittori di file Prima di poter utilizzare un file si deve informare il sistema delle operazioni che si vogliono effettuare: la fase di apertura del file in lettura, scrittura, ecc. Unix controlla che si abbiano i permessi per fare le operazioni di apertura richieste e, in caso affermativo, ritorna al programma un piccolo intero non negativo: il descrittore di file. Ogni volta che deve essere eseguito dell'input/output sul file, questo viene identificato attraverso il suo descrittore invece che col nome. Tutte le informazioni relative ad un file aperto vengono gestite dal sistema: il programma utente accede al file soltanto attraverso il descrittore. Nota: il concetto di descrittore di file (piccolo intero) molto simile (ma non uguale) al concetto di puntatore a file (definito tramite il tipo FILE *) usato nelle funzioni della libreria standard <stdio.h>.
159

Le operazioni di I/O che coinvolgono la tastiera e il video sono molto frequenti: quando la shell esegue un programma, vengono automaticamente aperti tre file aventi descrittori: 0 (standard input), 1 (standard output) e 2 (standard error). Un programma che legge dal descrittore 0 e scrive sui descrittori 1 e 2 esegue dell'I/O senza aver aperto esplicitamente alcun file. L'utente di un programma pu redirigere l'I/O di un programma o di un comando da e su file usando < , > e >&: $ nome_prog_eseguibile < file_input > file_output >& file_err Nell'esempio la shell modifica gli assegnamenti di default dei descrittori 0, 1 e 2, associandoli ai file indicati nella linea di comando. In generale un programma non sa n da dove effettivamente proviene il suo input (da tsatiera o da file) n dove realmente diretto il suo output (su video o su file) poich si limita a utilizzare i descrittori 0 per l'input, 1 e 2 per l'output. Esercizio: prendere confidenza con il concetto di redirezione dell'I/O tramite < e > sui comandi della shell (per esempio ls, cat, ... ) e anche su propri programmi che prevedono input e output. Le system call per la gestione di file a basso livello: open, creat, read, write, close I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono: <sys/stat.h> <sys/types.h> <fcntl.h> <unistd.h>

Per utilizzare file diversi dagli standard input, output ed error (di default oppure eventualmente rediretti), necessario aprirli esplicitamente tramite le due system call open e creat. int open(char *nome_file, int flags); int creat(char *nome_file, int perms); open tenta di aprire il file nome_file: ritorna il descrittore di file associato a nome_file. E' un errore tentare di aprire un file che non esiste. Per creare nuovi file o riscrivere quelli gi esistenti si utilizza creat, che restituisce anch'essa il descrittore associato a nome_file. Ambedue le system call restituiscono -1 in caso di errore. flags specifica come aprire il file: i valori pi frequenti sono: O_RDONLY apre solo in lettura O_WRONLY apre solo in scrittura O_RDWR apre sia in lettura che in scrittura perms specifica i permessi di accesso rwx per proprietario, gruppo, altri utenti. Si pu specificare con un numero ottale (nella forma: 0###). Le operazioni di lettura e scrittura su file avvengono per mezzo delle system call read e write: int read (int fd, char *buffer, int n);

int write

(int fd, char *buffer, int n);

fd il descrittore di file (ottenuto tramite una chiamata a open o creat) buffer un vettore di caratteri appartenente al programma, che conterr i dati provenienti da una operazione di lettura (read) o nel quale sono contenuti i dati che si vogliono scrivere su file (operazione di scrittura: write) n il numero di byte da trasferire Ogni chiamata a queste due system call restituisce il numero di byte effettivamente trasferiti (che pu essere inferiore a quello specificato); un valore di ritorno nullo significa che si raggiunta la fine del file, mentre un valore -1 lo si ha in caso di errore. Dopo aver utilizzato un file si pu liberare il descrittore, spezzando l'associazione filedescrittore di file, con la system call close: int close(int fd); fd il descrittore di file da liberare I descrittori di file sono risorse di sistema limitate: meglio chiudere i file, liberando i descrittori, se questi non sono usati. close restuisce -1 in caso di errore, 0 se l'operazione andata a buon fine. Per maggiori dettagli su queste system call consultare il manuale in linea (comando man). Esempio (parte di codice): #define BUFFER_SIZE 512

char buffer[BUFFER_SIZE]; int f1, f2; int n_read, n_write; ... if ((f1 = open("dati_lettura", O_RDONLY, 0)) = = -1) perror ("Non posso aprire in lettura il file dati_lettura"); ... if ((n_read = read (f1, buffer, BUFFER_SIZE)) != BUFFER_SIZE) perror ("Ho letto meno byte del previsto"); ... if ((f2 = creat("datafile.tmp", 0755)) = = -1) perror ("Non posso creare il file datafile.tmp con i permessi rwxr-xr-x"); ... if ((n_write = write(f2, buffer, BUFFER_SIZE) = = -1) perror ("Non riesco a scrivere sul file datafile.tmp"); ... if (close(f1)) perror ("Errore di chiusura dati_lettura"); ... if (close(f2))
161

perror ("Errore di chiusura datafile.tmp"); ESERCIZIO: programmazione concorrente in C. Un processo padre apre due file di testo in lettura tramite le funzioni fopen e open, esegue alcune letture poi genera (almeno) due figli i quali leggono anch'essi dai due file. Quali sono le differenze tra puntatori a file e descrittori di file ? Spiegare il comportamento dei vari processi. Prevedere il comportamento in caso di operazioni di scrittura. Trasmissione di dati fra processi usando le pipe Le signal sono utili per trattare eventi insoliti, eccezioni, errori (concetto di signal come di interrupt software) e, di fatto, non consentono la trasmissione di dati tra processi. Per questo ultimo scopo Unix prevede un costrutto di nome pipe. Una pipe un canale (unidirezionale) di comunicazione fra due (o pi) processi ed una generalizzazione del concetto Unix di file. Un processo pu spedire dati su una pipe tramite la system call write, e un altro processo pu ricevere i dati inviati usando la system call read dall'altro capo della pipe. A livello di comandi di shell si utilizzano pipe tramite il carattere '|': Esempio: $ cat /etc/passwd | grep sidue

I comandi cat e grep vengono eseguiti contemporaneamente: la shell genera due processi e crea una pipe di collegamento fra di loro per cui l'output del comando cat viene considerato come input per il comando grep. Il controllo di flusso su una pipe gestito automaticamente: se il processo cat produce dati sulla pipe troppo velocemente ( "riempie la pipe" ), la sua esecuzione viene sospesa e viene ripresa quando i dati sulla pipe, consumati dal processo grep, sono scesi ad un livello sufficiente ( "la pipe non pi piena" ). In un programma C una pipe creata attraverso la system call pipe ( includere il file <unistd.h> ) : int pipe(int filedes[2]); pipe restituisce una coppia di descrittori di file che identificano la pipe. In caso di errore pipe restituisce il valore -1. Se la chiamata alla system call non ritorna errore, filedes[2] , array di due interi, contiene i due descrittori di file che devono essere utilizzati per le operazioni di read e write sulla pipe. Si ha rispettivamente: filedes[0] filedes[1] descrittore di file aperto per la lettura dalla pipe (read) descrittore di file aperto per la scrittura sulla pipe (write)

Le pipe trattano i dati in modalit FIFO e i dati sono visti esclusivamente come sequenza di byte: per esempio si pu scrivere con una sola write un blocco di 512 byte di dato e poi effettuare 512 operazioni di read di un solo byte. La dimensione di una pipe, come gi accennato, non infinita: normalmente un'operazione di write su una pipe piena blocca il processo scrittore, cos come si blocca il processo lettore con una read da una pipe vuota.

Un processo che si crea una pipe e dopodich vi scrive e vi legge (perch non il viceversa ?) di utilit puramente didattica... La potenzialit delle pipe risulta evidente in un ambiente multi-processo, dove un processo padre e pi processi figli si scambiano dati e informazioni tramite una o pi pipe. ESERCIZI : 1) Si consideri un processo produttore di messaggi (p.e. stringhe di caratteri minuscoli) e un processo consumatore di messaggi (p.e. che converte in maiuscolo i messaggi). Realizzarlo con una struttura padre-figlio in cui il padre il produttore e il figlio consumatore. In che punto del codice va invocata la system call pipe (prima o dopo la fork) ? Perch ? (R. prima ...) Il processo figlio eredita dal padre i descrittori di file della pipe sia per la lettura che per la scrittura: cosa succede se anche il figlio-consumatore scrive sulla pipe? e se il padreproduttore legge dalla pipe ? Proporre una soluzione: cosa deve fare il produttore e cosa il consumatore. Considerare il caso in cui vi siano due produttori sulla stessa pipe oppure due consumatori (3 processi in totale). Quale dei due casi il pi complicato e perch ? 2) Si scriva un programma che crea due processi ambedue contemporaneamente produttore (di messaggi per l'altro processo) e consumatore (di messaggi provenienti dall'altro processo). Quante pipe si impiegano ? Quali sono gli errori da evitare per non cadere in una situazione di deadlock ? I segnali possono essere d'aiuto ? in che modo ? 3) Cosa succede se si chiude (close) un descrittore di file di una pipe in scrittura? e in lettura? Esempio Signal(), Kill(), Pause(), I/O basso livello : #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <fcntl.h> #define PERM 0666 #define MAX 25 void action(int sig) { signal (sig, action) ; } void main() { int pid, status i ; int fd ; char buf1[15]="sono_il_padre\n" ; char buf2[16]="sono_il_figlio\n" ; signal (SIGUSR1, action) ;
163

fd = creat("prova.txt", PERM) ; if ((pid=fork()) == 0 ) { pid = getppid() ; for ( ; ;) { write (fd, buf2, strlen(buf2)) ; kill (pid, SIGUSR1) ; } } else { for (i=0 ; i < MAX ; i++) { pause() ; write (fd, buf1, strlen(buf1)) ; } kill (pid, SIGUSR2) ; close (fd) ; exit (0) ; } } Esempio PIPE() : #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <fcntl.h> #define MAX 25 void main() { int pid, status, i ; int fd[2] ; char buf22[30]= "Padre : " ; char buf2[16] = "sono_il_figlio\n" ; pipe(fd) ; if ((pid=fork()) == 0) { close (fd[0]) ; for ( ; ;) { write (fd[1], buf2, strlen(buf2)) ; } } else { close (fd[1]) ; for (i=0 ; i<MAX ; i++) { read (fd[0], &(buf22[7]), strlen (buf2)) ; } kill (pid, SIGUSR1) ; for (i=0 ; read (fd[0], &(buf22[7]), strlen(buf2)) == 15 ; i++) ; printf (" C'erano ancora %d Messaggi nella pipe.\n",i) ; close (fd[0]) ; exit (0) ; } }

Soluzione:
Tramite le primitive di Signal() Kill() e Pause() si e` realizzato un esempio di sincronizzazione di processi. Listato file "eserc1.c"
#include<stdio.h> #include<signal.h> #include<sys/wait.h> void errore(char *s); void tratta(); main() { int pid1,pid2,padre; union wait status; signal(SIGUSR1,tratta); printf("sono il padre e creo due figli\n"); if((pid1=fork())<0) errore("errore generazione processi,\n"); if(pid1==0) { printf("sono il figlio 1,mi addormento e aspetto che mi svegli papa\n"); pause(); printf("sono figlio 1 e sono stato svegliato\n"); printf("fine figlio1\n"); exit(0); } if((pid2=fork())<0) errore("errore generazione processi\n"); if(pid2==0) { printf("sono il figlio 2 e sveglio papa\n"); padre=getppid(); kill(padre,SIGUSR1); printf("fine figlio 2\n"); exit(0); } printf("sono il papa,mi addormento e aspetto che mi svegli figlio2\n"); pause(); printf("sono papa, sono stato svegliato e sveglio figlio1\n"); kill(pid1,SIGUSR1); wait(&status); wait(&status); printf("fine padre\n");

void errore(char *s) { printf("%s\n",s); exit(1); } void tratta() { printf("segnale ricevuto\n"); }

165

Risultato: $ sono il padre e creo 2 figli sono il papa, mi addormento e aspetto che mi svegli figlio2 sono il figlio1, mi addormento e aspetto che mi svegli papa sono il figlio2 e sveglio papa fine figlio2 segnale ricevuto sono papa, sono stato svegliato e sveglio figlio1 segnale ricevuto sono figlio1 e sono stato svegliato fine figlio1 fine padre Programmazione concorrente in C Il processo padre apre i file "file1.txt" e "file2.txt" entrambi di tipo testo ed effettua su di essi delle letture. Inoltre genera due figli che a loro volta leggono dai due file. Vediamo con questo esempio come i 3 processi accedono ai file. Listato file "eserc2.c"
#include #include #include #include #include <sys/stat.h> <sys/types.h> <fcntl.h> <unistd.h> <sys/wait.h>

#define LPAROLA 8 main () { int file1, file2, pid1, pid2; char buffer [LPAROLA]; union wait status; file1 = open ("file1.txt", O_RDONLY, 0666); file2 = open ("file2.txt", O_RDONLY, 0666); read (file1, buffer, LPAROLA); buffer[LPAROLA-1] = '\0'; printf ("Padre legge da file1: %s\n", buffer); read (file2, buffer, LPAROLA); buffer[LPAROLA-1] = '\0'; printf ("Padre legge da file2: %s\n", buffer); pid1 = fork (); if (pid1 == 0) { read (file1, buffer, LPAROLA); buffer[LPAROLA-1] = '\0'; printf ("Figlio1 legge da file1: %s\n", buffer); read (file2, buffer, LPAROLA); buffer[LPAROLA-1] = '\0'; printf ("Figlio1 legge da file2: %s\n", buffer); exit (0); } pid2 = fork (); if (pid2 == 0) { read (file1, buffer, LPAROLA); buffer[LPAROLA-1] = '\0'; printf ("Figlio2 legge da file1: %s\n", buffer); read (file2, buffer, LPAROLA);

buffer[LPAROLA-1] = '\0'; printf ("Figlio2 legge da file2: %s\n", buffer); exit (0);

wait (&status); wait (&status); close (file1); close (file2);

Listato file "file1.txt" PAROLA1 PAROLA2 PAROLA3 PAROLA4 PAROLA5 ... Listato file "file2.txt" NUMERO1 NUMERO2 NUMERO3 NUMERO4 NUMERO5 ... Risultato: $ Padre legge da file1: PAROLA1 Padre legge da file2: NUMERO1 Figlio1 legge da file1: PAROLA2 Figlio1 legge da file2: NUMERO2 Figlio2 legge da file1: PAROLA3 Figlio2 legge da file2: NUMERO3 Osserviamo innanzi tutto che i figli ereditano i file dal padre e inoltre l'accesso ai file avviene dal punto in cui l'ha lasciato l'ultimo processo. In caso di operazioni di scrittura dunque tutti i processi dovrebbero scrivere in coda al file.

Esercizio pipe0: Questo e` un semplice esempio di produttore consumatore realizzato con una struttura padrefiglio che si scambiano messaggi con le PIPE. In questo caso il processo figlio produce numeri interi da 1 a 4 e li scrive sulla pipe, mentre il processo padre legge i numeri dalla pipe e li moltiplica per 2. Listato file "pipe0.c"
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> main () { int pid, i, fd[2], dato; pipe (fd); if ((pid = fork()) < 0) printf ("Errore fork\n"); if (pid == 0) { /* Codice Figlio produttore */ close (fd[0]); for (i = 1; i < 5; i++) { printf ("Produttore: %d\n", i); write (fd[1], &i, sizeof (int)); } 167

exit (0);

/* Codice Padre consumatore */ close (fd[1]); for (i = 1; i < 5; i++) { read(fd[0], &dato, sizeof (int)); printf ("Consumatore: %d\n", dato * 2); } close (fd[1]); }

Risultato: $ Produttore: 1 Consumatore: 2 Produttore: 2 Produttore: 3 Consumatore: 4 Produttore: 4 Consumatore: 6 Consumatore: 8 I processi figli ereditano dal padre i descrittori di file della pipe, dunque prima che i figli vengano creati il processo padre deve aver gia` inizializzato i descrittori. Le pipe sono degli strumenti di comunicazione unidirezionali (un processo scrive e un'altro riceve), dunque il processo che scrive deve chiudere la pipe in lettura e il processo che legge deve chiuderla in scrittura. Esercizio pipe1: Con questo programma si vuole realizzare una comunicazione bidirezionale con le pipe e non unidirezionale come nel programma precedente. Essendo le pipe canali unidirezionali bisogna impiegarne 2 (fn, fc). In questo esempio il processo padre alloca le risorse e genera due figli: figlio1 legge i numei interi dalla pipe fn e ne fa il quadrato, inoltre genera caratteri minuscoli (alla 'a' alla 'f') e li scrive sulla pipe fc figlio2 genera numeri interi (da 0 a 6) e li scrive sula pipe fn, inoltre legge caratteri minuscoli dalla pipe fc e li converte in caratteri maiuscoli. Ogni processo e` dunque sia produttore che consumatore. Listato file "pipe1.c"
#include #include #include #include #include <stdio.h> <fcntl.h> <unistd.h> <sys/wait.h> <errno.h>

main () { int fn[2], fc[2], pid1, pid2, num; char c, car, num1; union wait status; printf ("Numeri Caratteri\n"); if (((pipe(fn) < 0) || (pipe(fc) < 0))) { perror("Errore apertura PIPE"); exit(1);

} if ((pid1=fork())<0) { perror("Errore creazione processo 1"); exit(1); } if (pid1==0) { c='a'; close(fc[0]); close(fn[1]); write(fc[1],&c, sizeof(char)); write(fc[1],"\n", sizeof(char)); num=0; read(fn[0],&num1, sizeof(char)); num=atoi(&num1); while (num<=9) { c++; printf("%d %d\n",num, num*num); read(fn[0], &num1, sizeof(char)); num=atoi(&num1); write (fc[1], &c, sizeof(char)); num++; } exit(0); } if ((pid2 = fork()) < 0) { perror("Errore creazione processo 2"); exit(1); } if (pid2 ==0) { num=0; num1 = '0'+num; write(fn[1], &num1, sizeof(char)); read (fc[0], &c,sizeof(char)); while(num<=9) { printf(" %c read(fc[0], &c,sizeof(char)); num1='0'+num; write(fn[1], &num1, sizeof(char)); num++; } exit(0); } wait(&status); wait(&status);

%c\n",c,toupper(c));

Risultato: $ Numeri 0 0 1 1 b B 2 4
169

Caratteri a A

c C 3 9 d D 4 16 e E 5 25 f F 6 36 Le pipe sono dei canali di comunicazione con capienza limitata, dunque possono essere completamente piene o vuote. Rapresentano anche un mezzo di sincronizzazione tra processi, infatti se la pipe e` piena si blocca il processo che scrive, mentre se e` vuota si blocca il processo che legge. Per evitare una situazione di dealock non deve accadere che entrambe le pipe siano contemporaneamente piene o contemporaneamente vuote. Puo` risultare utile in questo caso adoperare degli allarmi con le signal in modo da evitare tempi di attesa indefiniti su una pipe vuota o piena.

14.3 Esercitazione 3 Semafori e memoria condivisa


System call per la gestione di semafori: semget, semctl, semop I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono: <sys/ipc.h> <sys/sem.h> <sys/types.h> <unistd.h>

Modello di accesso ad una Base Dati Esistono classi di processi detti Readers (lettori della basedati) e classi di processi detti Writers (scrittori della basedati). Queste classi di processo lavorano in modo diverso: ai lettori consentito lavorare sulla risorsa basedati in concorrenza, mentre gli scrittori devono lavorare in mutua esclusione. La mutua esclusione realizzata tramite semafori (quanti ne servono ?) Creare delle funzioni C che permettano di operare con comodit con i semafori; in altre parole creare un'interfaccia pi comoda e maneggevole alle system call, da inserire, assieme ad altre strutture dati e informazioni utili, in un file "semafori.h" da includere in tutti i programmi che richiedono l'utilizzo di semafori. Le funzioni potrebbero, per esempio, chiamarsi: - sem_init( ) - sem_wait( ) - sem_signal( ) - sem_release( ) Ci sono "parentele" con le system call Unix wait e signal? ESERCIZIO di programmazione concorrente in C: simulazione di Readers & Writers Un processo padre attende comandi da tastiera:

'r' crea un processo lettore che legge e poi termina 'w' crea un processo scrittore che scrive e poi termina 'x' termina (correttamente!) il programma La risorsa condivisa tra i vari processi sia un file di stringhe di caratteri di lunghezza costante. Il processo scrittore pu aggiungere una stringa di caratteri al file; il processo lettore pu leggere (magari in maniera casuale) uno dei record della basedati (= una delle stringhe di caratteri...). Le operazioni di lettura e scrittura devono "durare" un tempo arbitrario per ogni processo Reader o Writer (= introdurre un ritardo variabile del tipo: ritardo, lettura o scrittura, ritardo). Deve essere possibile seguire l'evoluzione della simulazione. Negli algoritmi che risolvono il problema dei Readers & Writers si pu scegliere se dare la precedenza ai lettori o agli scrittori: questo per cambia molto la struttura dell'algoritmo: il caso pi semplice dare la precedenza ai lettori (= se uno scrittore vuole scrivere deve aspettare che non ci siano lettori), anche se in realt questo pu introdurre altri problemi (p.e. la "starvation", o "morte per fame", dei processi scrittori... ) Realizzare il programma di simulazione dando la precedenza ai lettori. Modificare il programma per permettere al pi a X lettori contemporaneamente (che meccanismo utilizzare ?) Pensare a una soluzione per dare la precedenza agli scrittori. System call per la gestione di aree di memoria condivisa: shmget, shmat, shmdt, shmctl I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono: <sys/ipc.h> <sys/shm.h> <sys/types.h> <unistd.h> Le operazioni sulla memoria condivisa permettono a due o pi processi di condividere un segmento di memoria fisica, mentre normalmente le aree dati fra processi sono completamente separate. La memoria condivisa rappresenta quindi il meccanismo di comunicazione fra processi (IPC) pi efficiente. (Perch? ) La memoria condivisa non prevede per alcun meccanismo di sincronizzazione: i vari processi che fanno uso della stessa area di memoria condivisa utilizzeranno, in maniera opportuna, dei semafori. (Nota l'uso dei semafori per risolvere i diversi problemi di sincronizzazione nell'uso di una risorsa condivisa da parte di pi processi concorrenti). Creare delle funzioni C che permettano di operare con comodit con aree di memoria condivisa; in altre parole creare un'interfaccia pi comoda e maneggevole alle system call, da inserire, assieme ad altre strutture dati e informazioni utili, in un file "memcond.h" da includere in tutti i programmi che richiedono l'utilizzo di aree di memoria condivisa. ESERCIZIO di programmazione concorrente in C: Produttore & Consumatore Utilizzando i file "semafori.h" e "memcond.h" realizzare un programma che implementi una soluzione al problema del Produttore & Consumatore. Il processo padre sia il produttore, il processo figlio il consumatore, il buffer di passaggio dei dati tra produttore e consumatore, opportunamente protetto, sia realizzato tramite un'area di memoria condivisa. Ipotizzare inizialmente un buffer in grado di contenere una sola produzione. Estendere il buffer affinch contenga pi produzioni (buffer circolare FIFO di capacit MAX). La soluzione pensata sarebbe adatta nel caso di pi produttori e pi consumatori? (ipotizzando l'assoluta equivalenza fra produttori e fra consumatori, cio non importa chi produce e chi consumer il prodotto). ESERCIZIO di prog. concorrente in C: simulazione di Readers & Writers con memoria
171

condivisa. Nota R&W esercit. #4: la mutua esclusione sulla risorsa di tipo file viene gestita automaticamente dal s.o. UNIX. Un processo padre attende comandi da tastiera: 'r' crea un processo lettore che legge e poi termina 'w' crea un processo scrittore che scrive e poi termina 'x' termina (correttamente !) il programma La risorsa condivisa tra i vari processi sia una basedati costituita da un vettore di MAX stringhe di caratteri di lunghezza costante, mantenuto in memoria condivisa. Il processo scrittore (Writer) pu aggiornare una stringa di caratteri; il processo lettore (Reader) pu leggere (in maniera casuale) uno dei record della basedati (= una delle stringhe di caratteri). Le operazioni di lettura e scrittura devono "durare" un tempo arbitrario per ogni processo Reader o Writer (= introdurre un ritardo variabile del tipo: ritardo, lettura o scrittura, ritardo). Deve essere possibile seguire l'evoluzione della simulazione. Negli algoritmi che risolvono il problema dei Readers & Writers si pu scegliere se dare la precedenza ai lettori o agli scrittori: questo per cambia molto la struttura dell'algoritmo: il caso pi semplice dare la precedenza ai lettori (= se uno scrittore vuole scrivere deve aspettare che non ci siano lettori), anche se in realt questo pu introdurre altri problemi (p.e. la "starvation", o "morte per fame", dei processi scrittori... ). Realizzare il programma di simulazione dando la precedenza ai lettori. Modificare il programma per permettere al pi MAX lettori contemporaneamente (che meccanismo utilizzare ?) Pensare a una soluzione per dare la precedenza agli scrittori. ATTENZIONE: le code di messaggi, cos come i semafori e le aree di memoria condivise, sono risorse "fisiche" di sistema limitate, ed quindi particolarmente importante rilasciarle al termine del loro utilizzo. Cenno di soluzione al problema dei Readers & Writers con precedenza ai lettori. NR = 0 contatore del numero di lettori in fase di lettura ( una variabile condivisa) W, ME1, ME2 semafori inizializzati al valore 1 Algoritmo per i Readers sem_wait (ME1); NR = NR+1; if (NR = = 1) sem_wait (W); sem_signal (ME1); ... operazione di lettura ... sem_wait (ME1); NR = NR-1; if (NR = = 0)

/* il primo lettore esclude tutti gli scrittori */

/* l'ultimo lettore permette l'accesso agli

scrittori accodati */ sem_signal (W); sem_signal (ME1); Il semaforo ME1 serve a proteggere il contatore NR. Quando si avvia un Reader il contatore NR viene incrementato; se il Reader il primo lettore ad accedere alla basedati esclude l'accesso ai Writer (con sem_wait(W) ), consentendo per l'accesso in concorrenza ad altri Readers. Prima di terminare il Reader decrementa il contatore NR; nel caso sia l'ultimo lettore viene effettuata la sem_signal(W) che consentirebbe l'accesso degli eventuali Writers in attesa. Algoritmo per i Writers sem_wait (ME2); sem_wait (W); ... operazione di scrittura ... sem_signal (W); sem_signal (ME2); Il semaforo W serve per bloccare gli scrittori quando sulla basedati stanno operando uno o pi Readers. Il semaforo ME2 serve per evitare che ci possano essere pi Writers contemporaneamente attivi sulla basedati. Uno scrittore blocca innanzi tutto gli altri Writers (con sem_wait(ME2)), poi blocca i Readers (con sem_wait(W) ) e lavora in mutua esclusione sulla basedati. Quando lo scrittore termina, con la sem_signal(W) attiva gli eventuali Readers accodati durante l'operazione di scrittura (questo perch si data la precedenza ai Readers prima che ai Writers) e poi sblocca il semaforo ME2 (con sem_signal(ME2)) per attivare gli altri Writers. /* attiva i lettori giunti durante l'operazione di scrittura */ /* gli scrittori successivi al primo si accodano sul semaforo ME2 */ /* il primo scrittore si accoda sul semaforo W se ci sono dei lettori attivi */

Soluzione:
#include #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <fcntl.h> <ctype.h> <string.h> <sys/ipc.h> <sys/types.h> <unistd.h> <sys/wait.h> <errno.h> "semafori.h" "memcond.h"

#define PRIMO (int) 'a' #define MUTEX 0 173

char

*indirizzo;

void main() { int j, pid, mem_id, chiave; char buf[8]; /*char ch2[]="e";*/ int sem_group, val; union wait status; chiave = ftok("cond.c",'f'); mem_id = crea_area (chiave, 8, 0644|IPC_CREAT); val = 1; sem_group = sem_init(1,&val, "cond.c", 0644|IPC_CREAT); if ((pid=fork())==0) { /* SONO IL FIGLIO CONSUMATORE DI MESSAGGI*/ printf("\nSono il figlio\n"); indirizzo = collega (mem_id); sem_wait(sem_group, MUTEX, SEM_UNDO); for (j=0;j<8;j++) buf[j]=toupper(indirizzo[j]); buf[8]='\0'; printf("\n HO LETTO IL MESSAGGIO %s \n \n",buf); sem_signal(sem_group, MUTEX, SEM_UNDO); scollega (indirizzo); } else if (pid > 0) { /*SONO IL PADRE PRODUTTORE*/ printf ("\n Sono il padre\n"); indirizzo = collega(mem_id); sem_wait(sem_group, MUTEX, SEM_UNDO); for (j=0;j<8;j++) { buf[j]=(rand() % 26)+PRIMO; indirizzo[j] = buf[j]; } buf[8]='\0'; indirizzo[8] = buf[8]; printf("\n HO PRODOTTO IL MESSAGGIO %s \n ",buf); sem_signal(sem_group, MUTEX, SEM_UNDO); scollega (indirizzo); wait(&status); elimina_area (mem_id); sem_release (sem_group); } else { printf("\n Fork fallita\n"); exit(8); } }

Una possibile soluzione al problema dei lettori e scrittori puo` essere


#include #include #include #include #include #include #include #include #include #include <unistd.h> <sys/ipc.h> <errno.h> <fcntl.h> <sys/ipc.h> <string.h> <sys/types.h> <sys/signal.h> <sys/wait.h> "semafori.h"

#define #define #define #define #define #define

MAX_BUF 5 MAX 10 MUTEX1 0 W 1 MUTEX2 2 DIVIS 67108864

/*PROTOTIPI*/ int crea_lettore (int); int crea_scrittore (int); void lettura (char *); void scrittura (char *); int fd,pausa,nr,semafori,padre,contscri=0,cont=0,contlett=0; long int casuale; main() { union wait stato; char c; int i,val[]={1,1,1}; fd = creat("letscri.dat",O_RDWR); padre=getpid(); /* inizializza i semafori*/ semafori=sem_init (3,val,"es3file.c",0744|IPC_CREAT|IPC_EXCL); printf printf printf printf ("SCEGLI TRA : \n"); ("\nr--->Crea processo lettore che legge e poi termina\n"); ("\nw--->Crea processo scrittore che scrive e poi termina\n"); ("\nx--->termina programma \n");

c=getchar(); while (c!='x' && c!='X') { switch (c) { case 'r': ;case'R': { casuale=rand(); pausa=casuale/DIVIS; if (crea_lettore(semafori) == 0 ) contlett +=1; break; } case 'w': ;case'W': { casuale=rand(); pausa=casuale/DIVIS; if (crea_scrittore(semafori) == 0 ) contscri+=1; break; } default: { printf ("Carattere non valido \n"); break; } } printf ("Opzione :> "); c=getchar(); } 175

/*Il padre aspetta la terminazione dei suoi figli */ for (i=0;i < (contlett+contscri);i++); { wait (&stato); printf ("E' terminato un processo. \n"); } /*rilascio risorse*/ sem_release (semafori); close(fd); exit(0); } /*Questa procedura crea un lettore che accede alla base dati e legge una stringa.Ovviamente gestisce anche con i semafori la precedenza ai lettori*/ int crea_lettore (int semafori) { int lettore,pid; char stringa[MAX_BUF]; if ((lettore=fork())<0) { printf("FORK FALLITA\n"); return(1); } else if (lettore==0) { /* SONO IL FIGLIO LETTORE*/ sem_wait(semafori,MUTEX1,SEM_UNDO); nr++; if (nr==1) { /* IL PRIMO LETTORE DISABILITA GLI SCRITTORI */ sem_wait (semafori,W,SEM_UNDO); } sem_signal (semafori,MUTEX1,SEM_UNDO); lettura(stringa); /*LETTURA DELLA BASE DATI*/

pid=getpid(); printf ("Sono il lettore %d e ho letto la stringa %s \n",pid,stringa); sem_wait (semafori,MUTEX1,SEM_UNDO); nr--; if (nr==0) { /*SONO L'ULTIMO LETTORE E ABILITO GLI SCRITTORI*/ sem_signal (semafori,W,SEM_UNDO); } sem_signal (semafori,MUTEX1,SEM_UNDO); return(0);

} else return(2);

int crea_scrittore (int semafori)

int scrittore,pid,conta; char stw[MAX_BUF]; if ((scrittore=fork())<0) { printf("FORK FALLITA SULLO SCRITTORE \n"); return(1); } else if (scrittore==0) { /* SONO IL FIGLIO SCRITTORE */ sem_wait (semafori,MUTEX2,SEM_UNDO); sem_wait (semafori,W,SEM_UNDO); /* blocca la risorsa */ conta=contscri %10; stw[0]='s';stw[1]='c';stw[2]='r';stw[3]='i'; stw[4]=(conta +'0'); scrittura (stw);

pid= getpid(); printf ("Sono il processo scrittore con pid %d e ho scritto %s\n",pid,stw); sem_signal (semafori,W,SEM_UNDO); sem_signal (semafori,MUTEX2,SEM_UNDO); return(0); } else return(2); } /* Questa funzione legge dalla base dati comune */ void lettura (char *stringa) { int pid; pid =getpid(); printf ("Sono il lettore con pid %d e sto dormendo...\n",pid); sleep (pausa); read(fd,stringa,5); printf ("Sono il lettore con pid %d e ho letto:%s \n",pid,stringa); } /* PROCEDURA PER LA SCRITTURA SU BASE DATI*/ void scrittura(char *stw) { int pid; pid = getpid(); printf("Sono lo scrittore con pid %d e sto dormendo...\n",pid); sleep(pausa); write(fd,stw,5); printf("Sono lo scrittore con pid %d e ho scritto: %s \n",pid,stw); }

177

14.4 Esercitazione 4 Code di messaggi e socket


System call per la gestione di code di messaggi: msgget, msgctl, msgsnd, msgrcv Le code di messaggi trovano un tipico impiego nelle situazioni in cui vi (almeno) un processo server e pi processi client. I client richiedono specifici servizi, secondo le loro necessit, inviando opportuni messaggi ai server. I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono: <sys/ipc.h> <sys/tmsg.h> <sys/types.h> <unistd.h>

ESERCIZI: Scrivere 4 programmi per: - creare, nel caso non esista gi, una coda di messaggi - scrivere su una coda di messaggi diversi tipi di messaggi - leggere da una coda di messaggi uno specifico tipo di messaggi - rimuovere una coda di messaggi Utilizzare i programmi e verificarne il funzionamento con i comandi ipcs e ipcrm. E anche possibile testare i programmi fra gruppi diversi, in quanto le code di messaggi sono risorse di sistema non strettamente legate ai processi padre & figli di un utente (come invece accadeva per le pipe... ). ATTENZIONE: le code di messaggi, cos come i semafori e le aree di memoria condivise, sono risorse fisiche di sistema limitate, ed quindi particolarmente importante rilasciarle al termine del loro utilizzo. System call per la gestione dei socket: socket, bind, listen, accept, unlink, connect, send, recv, shutdown Il sistema UNIX supporta unampia variet di reti di comunicazione. Il sistema UNIX BSD ha introdotto le system call socket per fornire un insieme pi generale di operazioni per le comunicazioni in rete. I socket trovano un tipico impiego nelle situazioni in cui vi un processo server e pi processi client. Il server si mette in attesa di richieste di connessione da parte dei client; i client, una volta ottenuta la connessione con il server, possono richiedere e ottenere specifici servizi, secondo le loro necessit, inviando o leggendo dati dalla connessione con il server. I file di #include da incorporare nei sorgenti C per poter utilizzare le system call citate sono: <sys/socket.h> <sys/types.h> <unistd.h> ESERCIZI: Scrivere lo scheletro di : un generico processo server (funzionamento background) un generico processo client (funzionamento foreground) Client/Server Un sistema di prenotazione per voli aerei composto da:

un gestore delle prenotazioni dei voli per ogni volo, individuato da un codice, registra: citt di partenza, citt di destinazione, data del volo, numero di posti totale, numero di posti disponibili; per ogni prenotazione registra: codice del volo, data del volo, nome e cognome della persona che si prenota. pi terminali dagenzia ogni terminale pu richiedere al gestore: disponibilit dei posti, dati: codice del volo e data del volo; prenotazione di un posto, dati: codice del volo, data del volo, nome e cognome disdetta di una prenotazione, dati: codice del volo, data del volo, nome e cognome. rete di comunicazione tra terminali dagenzia e gestore delle prenotazioni Progettare e realizzare un programma di simulazione del problema esposto usando processi concorrenti su di un solo elaboratore. Traccia di soluzione La simulazione si pu realizzare tramite processi concorrenti comunicanti attraverso una risorsa coda di messaggi o meglio ancora tramite le primitive di SOCKET. Saranno presenti: un processo server, che simula il gestore delle prenotazioni, in grado di accedere al database che contiene le informazioni sui voli modificandone eventualmente il contenuto n processi client, ognuno univocamente identificabile, che simulano i terminali di agenzia: attraverso il server-gestore accedono alla basedati coda di messaggi che simula la rete di interconnessione tra terminali dagenzia e gestore delle prenotazioni oppure tramite Socket si utilizzer la reale rete TCP/IP a cui connessa la macchina (anche se in questo caso degenere usiamo il protocollo TCP/IP per chiamare da una macchina a se stessa). La basedati che mantiene le informazioni sui voli viene gestita interamente dal server-gestore e pu essere realizzata in molti modi diversi, mantenuta in memoria o su un file. Di primaria importanza che tutte le operazioni di aggiornamento della basedati devono essere di tipo atomico, per evitare problemi di inconsistenza della basedati: unoperazione di prenotazione di un certo posto implica una lettura della basedati seguita, se il posto disponibile, da una scrittura, che aggiorna la basedati (il posto ora impegnato); loperazione di lettura-scrittura deve essere indivisibile, ed compito del programmatore realizzare latomicit delloperazione di aggiornamento della basedati. La coda di messaggi utilizza la struttura dati: struct mex { long mtype; char mtext[MAXCHAR]; } in cui il primo campo serve ad identificare il numero di terminale a cui il messaggio associato e il secondo campo contiene il messaggio effettivo. Gestore delle prenotazioni - Server Il server, dopo aver creato la coda di messaggi, si mette in attesa di un messaggio in arrivo da un qualsiasi terminale. Nel momento in cui viene ricevuta una richiesta da uno dei client, il server provvede a memorizzare il numero di terminale dal quale la richiesta pervenuta, e inizia una conversazione esclusiva con esso: questo procedimento implica che il server esaudisce una richiesta alla volta, eliminando cos il problema delle eventuali inconsistenze nelle operazioni di aggiornamento della basedati se laccesso a questa avvenisse in maniera concorrente. Una volta eseguita la richiesta, il server restituisce al terminale un codice di controllo il cui
179

valore indica la riuscita o il fallimento delloperazione richiesta. I codici di controllo potrebbero essere: 1 prenotazione effettuata (ACK) 2 disponibilit di posti (ACK) 3 disdetta effettuata (ACK) 4 posti esauriti (ERR) 5 nominativo inesistente (ERR) 6 codice o data non validi (ERR) Terminale dagenzia - Client Le funzioni del terminale dagenzia - client sono: - richiesta del tipo di servizio da effettuare (richiesta numero posti, richiesta prenot., disdetta prenot.) - richiesta dei dati dellutente (codice volo, data volo, nome, cognome) - invio richiesta di servizio al server attraverso la coda dei messaggi - invio dei dati al server (ogni dato = un messaggio) - attesa del codice di controllo inviato dal server - gestore delle prenotazioni. Nota : i processi Client e Server possono essere visti pi in generale come degli automi a stati finiti attivi concorrentemente, in cui ogni messaggio ricevuto levento che permette il cambiamento di stato dellautoma. Lo stesso scambio di messaggi fra due processi pu essere generalizzato come implementazione di un ben preciso protocollo di comunicazione fra due entit.

Soluzione:
Programma sulle code di messaggi Il seguente programma gestisce le comunicazioni tra FORCH processi figli. Ogni figlio puo` comunicare con un fratello tramite il padre. Invia il messaggio e l'indice del fratello al padre, il quale, dopo opportuni controlli, trasmette il messaggio. Ogni processo figlio ha a disposizione un numero limitato di messaggi da inviare, dopodiche` termina
#include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> #include <errno.h> #define FORCH 5 #define #define #define #define #define MAX_MEX 31 NUM_MEX 9 MASTER 90 LIMITE 3 TRUE 1

main() { pid_t child,filosofi[FORCH]; union wait stato; unsigned long x; int msgqueue[FORCH],msg_satus=0; key_t msgkey; struct messaggio

{ char to_from; char mex[MAX_MEX]; } inv, rec; char std_mex[NUM_MEX][MAX_MEX]={"continua","dimmi", "calcola", "ritorna", "termina", "esegui", "stampa", "sospendi", "shut"}; struct unixmex { long mtype; char mtext[MAX_MEX]; } sysmex; /* struttura contenente il messaggio trasmesso*/ struct msqid_ds buf; /*********** child environment **********/ int whoami=-1; short myqueue = 0; /**************end child environment ***************/ /*********** father environment *********/ short childlive=0; short childmex[FORCH]; int cl=0; /*********** end father environment ***************/ for (x=0;x<FORCH;x++) { /* creazione code dei messaggi */ msgkey=ftok("5mess.c","a"+x); if ((msgqueue[x]=msgget(msgkey,0666|IPC_EXCL|IPC_CREAT)) == -1) { if (errno == EEXIST) if((msgqueue[x] = msgget(msgkey,0))==-1) { for (x--;x>=0;x--) if (msgctl(msgqueue[x],IPC_RMID,&buf)==-1) perror ("MSGQUEUE cannot be removed"); exit(1); } } } for(x=0;x<5;x++) { /* creazione processi */ child=fork(); if (child<0) perror("NO FORK"); if(child ==0) break; else filosofi[x]=child; } if (child==0) { /* CODICE FIGLIO */ printf("Io sono il bimbo %d.\n",x); whoami=x; /* inizializzazione numero figlio e sua coda */ myqueue=msgqueue[x]; srandom(whoami+1);/* inizializzazione generatore random */ while(TRUE) { x=(random()%FORCH); /*generazione casuale del destinatario*/ if (x==whoami) x=(x+1)%FORCH; /* e del messaggio */ inv.to_from=(char)x+'0'; /* creazione messaggio */ strcpy(inv.mex,std_mex[(int)(random()%NUM_MEX)]); sysmex.mtype=1; /* child manda mex di tipo 1 */ strcpy(sysmex.mtext,(char *)&inv); /* copia del messaggio nel 181

'sistema' */

buffer di

/* invio del messaggio */ msgsnd(myqueue,&sysmex,strlen(inv.mex)+1,0); for (cl=0;cl<MAX_MEX;sysmex.mtext[cl++]=0); /* ricezione risposta */ if((cl=msgrcv(myqueue,&sysmex,MAX_MEX+1,2,0))<=0) { perror("NO MESSAGE"); exit(1); } /* copia del messaggio ricevuto in un buffer */ strcpy(rec.mex,&sysmex.mtext[1]); rec.mex[cl-1]='\0'; rec.to_from=sysmex.mtext[0]; if (rec.to_from==MASTER) { printf ("Figlio %d: Il padre mi ha ordinato %s\n",whoami,rec.mex); if (!strcmp(rec.mex,"termina")) { printf ("Figlio %d termina\n",whoami); exit(0); } } else { printf ("Figlio %d riceve da %c %s\n",whoami,rec.to_from,rec.mex); } } } else { printf ("Io sono il padre. Io faccio il master\n"); childlive=FORCH; /* numero di figli attivi */ for (x=0;x<FORCH;childmex[x++]=0); /* finche` ci sono figli attivi */ while (childlive>0) { for (x=0;x<FORCH;x++) { /* se il figlio x e' vivo vedi se ha inviato messaggi */ if (filosofi[x]) { /* la receive non e` bloccante. Se non ho messaggio */ /* cl = 0 */ if ((cl=msgrcv(msgqueue[x],&sysmex,MAX_MEX+1,1,IPC_NOWAIT))==1) if (errno==ENOMSG) continue; else { childlive--; filosofi[x]=0; continue; } childmex[x]++; /* contatore dei messaggi */ strcpy(rec.mex,&sysmex.mtext[1]); rec.to_from=sysmex.mtext[0]; rec.mex[cl-1]='\0'; for (cl=0;cl<MAX_MEX+1;sysmex.mtext[cl++]=0); /* se il destinatario non e` attivo uccido il mittente */ if (!filosofi[rec.to_from-'0'])

{ sysmex.mtype=2; /* father manda mex di tipo 2 */ sprintf(sysmex.mtext,"%ctermina",MASTER); msgsnd(msgqueue[x],&sysmex,8,IPC_NOWAIT); childlive--; filosofi[x]=0; continue; } /*altrimenti invio il messaggio al destinatario indicandogli il mittente */ sysmex.mtype=2; strcpy(&sysmex.mtext[1],rec.mex); sysmex.mtext[0]=x+'0'; if (msgsnd(msgqueue[rec.to_from'0'],&sysmex,strlen(sysmex.mtext),IPC_NOWAIT)< 0) perror("NO SEND:"); /* se il figlio ha raggiunto il limite di messaggi lo uccido */ if (childmex[x]>LIMITE) { sysmex.mtype=2; /* father manda mex di tipo 2 */ sprintf(sysmex.mtext,"%ctermina",MASTER); msgsnd(msgqueue[x],&sysmex,8,IPC_NOWAIT); childlive--; filosofi[x]=0; } } } } /* quando tutti i figli sono terminati rimuovo le code */ for(x=0;x<FORCH;x++) if (msgctl(msgqueue[x],IPC_RMID,&buf)==-1) perror ("MSGQUEUE cannot be removed"); } }

Programmi sui socket processo Server


#include #include #include #include #include #include #include #include #include #include #define #define #define #define void void void void <stdio.h> <errno.h> <sys/wait.h> <sys/types.h> <sys/stat.h> <unistd.h> <fcntl.h> <string.h> <signal.h> "mytypes.h" PORTA 7000 PORTADEST 6000 MYIND INADDR_ANY CODA 5

errore (char *); errdone (char *); done (); prenota (mex *); 183

int idsock,fvolo,fpren; main() { int risp, pid, newsock, endf; volo recvolo; prenot recpren; mex mesg; union wait status; struct rusage *rusage; if ((fvolo = open ("volo.dat", O_RDWR, 0666)) < 0) errore ("SERVER: Errore apertura file volo.dat"); if ((fpren = open ("prenot.dat", O_RDWR, 0666)) < 0) errore ("SERVER: Errore apertura file prenot.dat"); if ((idsock=initsock(SOCK_STREAM,MYIND,PORTA))<0) errore("SERVER: errore inizializzazione socket"); signal (SIGKILL, done); if(listensock(idsock,CODA)<0) errore("SERVER:errore listen"); while(1) { if((newsock=acceptsock(idsock,0,0))<0) errore("SERVER:errore accept"); if((pid=fork())<0) perror("SERVER:errore nella fork"); else if(pid==0) { closesock(idsock); recvmexsock(newsock,&mesg); switch(mesg.mtype) { case 1: lseek (fvolo, 0, SEEK_SET); endf = readvolo (fvolo, &recvolo); while (((strcmp (recvolo.codice, mesg.codice) (recvolo.data != mesg.data)) && (endf endf = readvolo (fvolo, &recvolo); if (endf != 0) { lseek (fvolo, -sizeof (volo), SEEK_CUR); lockf (fvolo, F_LOCK, sizeof (volo)); sendintsock (newsock, recvolo.disp); if (recvolo.disp > 0) { recvmexsock (newsock, &mesg); /* mesg.mtype = 2 prenota */ if (mesg.mtype == 2 ) /* mesg.mtype = -1 non prenota */ { prenota (&mesg); sendintsock (newsock, -1); recvolo.disp --; writevolo (fvolo, &recvolo); } } lockf (fvolo, F_ULOCK, -sizeof (volo)); } else sendintsock (newsock, -6); break; case 2: lseek (fvolo, 0, SEEK_SET); endf = readvolo (fvolo, &recvolo); while (((strcmp (recvolo.codice, mesg.codice) (recvolo.data != mesg.data)) && (endf { /* lseek (fvolo, sizeof (volo), SEEK_CUR); endf = readvolo (fvolo, &recvolo); }

!= 0) || != 0))

!= 0) || != 0)) */

if (endf != 0) { if (recvolo.disp > 0) { lseek (fvolo, -sizeof (volo), SEEK_CUR); lockf (fvolo, F_LOCK, sizeof (volo)); recvolo.disp --; writevolo (fvolo, &recvolo); lockf (fvolo, F_ULOCK, -sizeof (volo)); prenota (&mesg); sendintsock (newsock, -1); } else sendintsock (newsock, -4); } else sendintsock (newsock, -6); break; case 3: lseek (fpren, 0, SEEK_SET); endf = readpren (fpren, &recpren); while (((strcmp (recpren.codice, mesg.codice) != 0) || (recpren.data != mesg.data) || (strcmp (recpren.nome, mesg.nome) != 0) || (recpren.cancellato == 1)) && (endf != 0)) endf = readpren (fpren, &recpren); if (endf != 0) { lseek (fpren, -sizeof (prenot), SEEK_CUR); recpren.cancellato = 1; writepren (fpren, &recpren); lseek (fvolo, 0, SEEK_SET); endf = readvolo (fvolo, &recvolo); while (((strcmp (recvolo.codice,mesg.codice) != 0) || (recvolo.data != mesg.data)) && (endf != 0)) endf = readvolo (fvolo, &recvolo); if (endf != 0) { lseek (fvolo, -sizeof (volo), SEEK_CUR); lockf (fvolo, F_LOCK, sizeof (volo)); recvolo.disp++; writevolo (fvolo, &recvolo); lockf (fvolo, F_ULOCK, -sizeof (volo)); } sendintsock (newsock, -3); } else sendintsock (newsock, -5); break ; } closesock (newsock); exit (0);

} closesock (newsock); while (wait3 (&status, WNOHANG, NULL) > 0); } }

void errore (char *s) { perror (s); exit (1); } void done () { 185

closesock (idsock); close (fvolo); close (fpren); exit (0); } void errdone (char *s) { perror (s); done (); } void prenota( mex *mesg) { int endf; prenot recpren; lseek(fpren,0,SEEK_SET); endf=readpren(fpren,&recpren); while((recpren.cancellato != 1) && (endf != 0)) { /* lseek(fpren,sizeof(prenot),SEEK_CUR); */ endf = readpren(fpren,&recpren); } strcpy(recpren.codice,mesg->codice); strcpy(recpren.nome,mesg->nome); recpren.data = mesg->data; recpren.cancellato = 0; if (endf != 0) lseek(fpren,-sizeof(prenot),SEEK_CUR); writepren(fpren,&recpren);

int writevolo (int fvolo, volo *rec) { return (write (fvolo, rec, sizeof (volo))); } int readvolo (int fvolo, volo *rec) { return (read (fvolo, rec, sizeof (volo))); } int writepren (int fpren, prenot *rec) { int rit; lockf (fpren, F_LOCK, SIZEPREN); /* lseek (fpren, -SIZEPREN, SEEK_CUR); */ rit = write (fpren, rec, SIZEPREN); lockf (fpren, F_ULOCK, -SIZEPREN); return (rit);

int readpren (int fpren, prenot *rec) { return (read (fpren, rec, SIZEPREN)); }

processo client
#include #include #include #include <stdio.h> <errno.h> <string.h> "mytypes.h"

#define MYPORTA 6000 #define INDDEST INADDR_ANY #define PORTADEST 7000 void errore (char *s); int idsock; void main() { char risp[20], cod[SIZECOD]; int giorno, mese, anno, disp, pid, i; mex mesg; do { if ((idsock = createsock (SOCK_STREAM)) < 0) errore ("CLIENT: Errore creazione socket"); if (connectaddr (idsock, INDDEST, PORTADEST) < 0) errore ("CLIENT: Errore di connessione"); printf ("1. Informazioni\n"); printf ("2. Prenotazioni\n"); printf ("3. Disdetta\n"); printf ("4. FINE\n"); gets (risp); i = atoi (risp); disp = 0; switch (i) { case 1: printf ("Codice volo: "); gets (mesg.codice); printf ("Data scelta: "); gets (risp); sscanf (risp,"%d/%d/%d", &giorno, &mese, &anno); mesg.data = day_of_year (giorno, mese, anno); mesg.mtype = 1; sendmexsock (idsock, &mesg); recvintsock (idsock, &disp); if (disp > 0) { printf ("Posti disponibili - %d\n",disp); printf ("Vuoi prenotare ? (s/n)"); gets (risp); if ((risp[0] == 's') || (risp[0] == 'S')) { mesg.mtype = 2; printf ("Nome: "); gets (mesg.nome); sendmexsock (idsock, &mesg); recvintsock (idsock, &disp); printf ("Prenotazione effettuata\n"); } else { mesg.mtype = -1; sendmexsock (idsock, &mesg); } } else if (disp == 0) printf("Posti esauriti\n"); else printf ("Codice o data non validi\n"); break ; case 2: printf ("Codice volo: "); gets (mesg.codice); printf ("Data scelta: "); gets (risp); sscanf (risp,"%d/%d/%d", &giorno, &mese, &anno); mesg.data = day_of_year (giorno, mese, anno); printf ("Nome: "); gets (mesg.nome); mesg.mtype = 2; sendmexsock (idsock, &mesg); 187

recvintsock (idsock, &disp); if (disp == -1) printf ("CLIENT: Prenotazione effettuata\n"); else if (disp == -4) printf ("CLIENT: Posti esauriti\n"); else printf ("CLIENT: Codice o data non validi\n"); break; case 3: printf ("Codice volo: "); gets (mesg.codice); printf ("Data scelta: "); gets (risp); sscanf (risp,"%d/%d/%d", &giorno, &mese, &anno); mesg.data = day_of_year (giorno, mese, anno); printf ("Nome: "); gets (mesg.nome); mesg.mtype = 3; sendmexsock (idsock, &mesg); recvintsock (idsock, &disp); if (disp == -3) printf ("CLIENT: Disdetta effettuata\n"); else if (disp == -5) printf ("CLIENT: Nominativo inesistente\n"); else printf ("CLIENT: Codice o data non validi\n"); break ; } if (closesock (idsock) < 0) perror ("CLIENT: Errore CLOSE"); } while (i != 4); exit (0);

void errore (char *s) { perror (s); exit (-1); } int day_of_year (int giorno, int mese, int anno) { static char daytab [2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap=(((anno % 4) == 0) && ((anno % 100) != 0) || ((anno % 400) == 0)); for (i = 1; i < mese; i++) giorno += daytab [leap][i]; return (giorno); }

Inseriamo ora due file header che contengono la definizione di tipi e funzioni che sono stati usati nei programmi precedenti:
#ifndef _MYTYPES_H #define _MYTYPES_H #include "mysock.h" #define MAX 50

#define SIZECOD 7 typedef struct Volo { char codice [SIZECOD]; char citpart [MAX]; char citdest [MAX]; int data; int postitot; int disp; } volo; #define SIZEPREN sizeof (prenot) typedef struct Prenotazioni { char codice [SIZECOD]; int data; char nome [MAX]; short cancellato; } prenot; typedef struct Pers { char nome [SIZESTR]; char cognome [SIZESTR]; char tel [20]; } pers; typedef struct Messaggio { int tipo; pers dato; } messaggio;

typedef struct Mex { short mtype; char codice [SIZECOD]; int data; char nome[SIZESTR]; } mex;

int sendmexsock (int sock, mex *mess) { return (send (sock, mess, sizeof (mex), 0)); } int recvmexsock (int sock, mex *mess) { return (recv (sock, mess, sizeof (mex), 0)); } #endif #ifndef _MYSOCK_H #define _MYSOCK_H #include #include #include #include #include 1

<sys/types.h> <sys/socket.h> <netdb.h> <netinet/in.h> <arpa/inet.h> 189

#include "mytypes.h" #define SIZESTR 50 int createsock (int tipo) { return (socket (AF_INET, tipo, 0)); } int donesock (int sock) { return (shutdown (sock, 1)); } int closesock (int sock) { return (close (sock)); } int autobindsock (int sock, int porta) { struct sockaddr_in sockname; struct hostent *hostinfo; char name[SIZESTR]; if (gethostname (name, SIZESTR) < 0) return (-1); if ((hostinfo = gethostbyname (name)) == NULL) return (-1); sockname.sin_addr = *((struct in_addr *)*(hostinfo -> h_addr_list)); sockname.sin_family = AF_INET; sockname.sin_port = htons (porta); return (bind (sock, (struct sockaddr *)&sockname, sizeof (sockname)));

int bindsock (int sock, double ind, int porta) { struct sockaddr_in sockname; sockname.sin_addr.s_addr = htonl((unsigned long)ind); sockname.sin_family = AF_INET; sockname.sin_port = htons (porta); return (bind (sock, (struct sockaddr *)&sockname, sizeof (sockname))); } int initsock (int tipo, double ind, int porta) { int sock; struct sockaddr_in sockname; if ((sock = socket (AF_INET, tipo, 0)) < 0 ) return (-1); if (bindsock (sock, ind, porta) < 0) return (-2); return (sock);

int listensock (int sock, int numcoda) { return (listen (sock, numcoda)); } int acceptsock (int sock, struct sockaddr *addr, int *sizeaddr) { return (accept (sock, (struct sockaddr *) addr, sizeaddr));

} int connectname (int sock, char * name, int porta) { struct hostent *hostinfo; struct sockaddr_in rsock; if ((hostinfo = gethostbyname (name)) == NULL) return (-1); rsock.sin_addr = *(struct in_addr *)*(hostinfo -> h_addr_list); rsock.sin_family = AF_INET; rsock.sin_port = htons (porta); return (connect (sock, (struct sockaddr *)&rsock, sizeof (rsock))); } int connectaddr (int sock, unsigned long int ind, int porta) { struct sockaddr_in rsock; rsock.sin_addr.s_addr = htonl (ind); rsock.sin_family = AF_INET; rsock.sin_port = htons (porta); return (connect (sock, (struct sockaddr *)&rsock, sizeof (rsock)));

int sendintsock (int sock, int mess) { return (send (sock, &mess, sizeof (int), 0)); } int recvintsock (int sock, int *mess) { return (recv (sock, mess, sizeof (int), 0)); } int sendlongintsock (int sock, long int mess) { return (send (sock, &mess, sizeof (long int), 0)); } int recvlongintsock (int sock, long int *mess) { return (recv (sock, mess, sizeof (long int), 0)); } int sendstrsock (int sock, char *mess) { return (send (sock, mess, SIZESTR, 0)); } int recvstrsock (int sock, char *mess) { return (recv (sock, mess, SIZESTR, 0)); } #endif

14.5 Esonero Maggio 96


Specifiche: Un supermercato e composto di varie casse per i pagamenti e di un server che contiene lo
191

stato del magazzino e il listino prezzi. Larchittettura harware prevede un PC per ogni cassa, una macchina UNIX per il server locale (pagamenti, listino), una macchina UNIX per la banca. Le casse sono collegate al server locale, questo e collegato alla banca. I collegamenti sono in rete Internet. Si scriva un programma che gestisca: 1. la determinazione del montante da pagare per una spesa: lettura di codice prodotto e quantita (simulare lettura da tastiera); richiesta al server locale del prezzo del prodotto; calcolo del totale. 2. il controllo delle vendite (in un processo a parte) calcolo del venduto per cassiera al giorno calcolo del venduto per cliente in un periodo dato. 3. un pagamento trmite Bancomat. Questo contempla: lettura di montante da pagare, numero carta e codice segreto (simulare da tastiera); inoltro della richiesta di pagamento al server locale; inoltro da parte di questo della richiesta di pagamento al server di banca; il server locale deve poter gestire piu richieste di pagamento contemporanee da varie casse verifica da parte del server di banca del codice segreto e della disponibilita del montante richiesto, addebitamento le eccezioni da gestire sono: codice segreto non valido (in tal caso la transazione e annullata), montante non disponibile valido (in tal caso la transazione e annullata) 4. modifica del punto3 con gestione di tre tentativi in caso di codice segreto non valido prima dellannullamento della transizione Soluzione: processo client
#include <fcntl.h> #include <signal.h> #include "mysock.h" void void void void void void void iniziascontrino (); scriviscontrino (char *nome, unsigned long int prezzo, int quantita); finescontrino (unsigned long int montante); aggiornadb (int codcliente, int codprod, int codoperat, int quantita); nomeprod (int codprod, char *nome); esci (); errore (char *s);

int sockserv, sockdb, fscontrino, allarme; void main () { int carta, codbanco, esito, codcliente, quantita, codprod, codoperat, prova, continua, risp; unsigned long int prezzo, ammontare; char nome [SIZESTR]; mexbanco messbanco; mexdb messdb; printf ("Codice operatore: "); scanf ("%d", &codoperat); continua = 1; while (continua == 1) { ammontare = 0; iniziascontrino (); printf ("Codice cliente: "); scanf ("%d", &codcliente); printf ("Inserimento lista prodotti (0 per terminare):\n");

printf ("Codice prodotto: "); scanf ("%d", &codprod); while (codprod != 0) /* Lettura prodotti comprati */ { printf ("Quantita': "); scanf ("%d", &quantita); if ((sockserv = createsock (SOCK_STREAM)) < 0) errore ("Errore: Creazione socket server"); if (connectaddr (sockserv, SERVIND, SERVPORT) < 0) perror ("Errore: Connessione fallita con SERVER"); else { sendintsock (sockserv, 1); /* Richiesta prezzo */ sendintsock (sockserv, codprod); recvlongintsock (sockserv, &prezzo); if (prezzo != 0) { ammontare = ammontare + (prezzo * quantita); aggiornadb (codcliente, codprod, codoperat, quantita); nomeprod (codprod, nome); scriviscontrino (nome, prezzo, quantita); } else perror ("Errore: Connessione fallita con DATA BASE"); closesock (sockserv); } printf ("Codice prodotto: "); scanf ("%d", &codprod); } finescontrino (ammontare); printf ("1) Pagamento con BANCOMNAT\n"); printf ("2) Pagamento CONTANTE\n"); scanf ("%d", &risp); if (risp == 1) /* Pagamento con bancomat */ { prova = 0; while (prova < 3) { printf ("Numero carta: "); scanf ("%d", &messbanco.numcarta); printf ("Codice segreto: "); scanf ("%d",&messbanco.codbanco); messbanco.ammontare = ammontare; if ((sockserv = createsock (SOCK_STREAM)) < 0) errore ("Errore: Creazione socket server"); if (connectaddr (sockserv, SERVIND, SERVPORT) < 0) perror ("Erroe: Connessione fallita con SERVER"); else { sendintsock (sockserv, 2); sendmexbanco (sockserv, &messbanco); recvintsock (sockserv, &esito); close (sockserv); if (esito == 0) { printf ("Pagamento avvenuto correttamente\n"); prova = 4; } else if (esito == -1) { printf ("Montante non disponibile\n"); prova = 4; } else if (esito == -3) { printf ("Collegamento fallito\n"); prova = 4; } else { prova ++; printf ("Codice errato\n"); esito = -4; } } 193

} }

} /* while (prova < 3) */ if (esito != 0) printf ("Pagamento in contanti\n"); } /* if (risp == 1) */ printf ("1) Ripeti\n"); printf ("2) Fine\n"); scanf ("%d", &continua);

void iniziascontrino () { if((fscontrino=open("scontrino.txt",O_CREAT|O_WRONLY|O_TRUNC,0666))<0) errore ("Errore: Apertura file SCONTRINO.TXT"); } void scriviscontrino (char *nome, unsigned long int prezzo, int quantita) { write (fscontrino, nome, SIZESTR); write (fscontrino, &prezzo, sizeof (long int)); write (fscontrino, &quantita, sizeof (int)); } void finescontrino (unsigned long int montante) { char nome[SIZESTR]; unsigned long int prezzo; int quantita; close (fscontrino); if ((fscontrino = open ("scontrino.txt", O_RDONLY, 0666)) < 0) errore ("Errore: Apertura file SCONTRINO.TXT"); printf ("\n\nSUPERMERCATI Sbiroli & Co\n"); printf ("c.so Tassoni 55 TORINO\n"); printf (" p.iva 02709550014\n\n"); while (read (fscontrino, nome, SIZESTR) > 0) { read (fscontrino, &prezzo, sizeof (long int)); read (fscontrino, &quantita, sizeof (int)); printf ("%s %ld", nome, prezzo); if (quantita > 1) printf (" x %d\n", quantita); else printf ("\n"); } printf ("\nTOTALE: %ld lire\n\n ", montante); printf ("Arrivederci e GRAZIE!\n\n\n"); close (fscontrino); } void aggiornadb (int codcliente, int codprod, int codoperat, int quantita) { mexdb messdb; messdb.codcliente = codcliente; messdb.codoperat = codoperat; messdb.codprod = codprod; messdb.quantita = quantita; if ((sockdb = createsock (SOCK_STREAM)) < 0) errore ("Errore: Creazione socket base dati"); if (connectaddr (sockdb, DBIND, DBPORT) < 0) perror ("Errore Aggiorna: Connessione con DATA BASE fallita"); else { sendintsock (sockdb, 2); sendmexdb (sockdb, &messdb);

} }

close (sockdb);

void nomeprod (int codprod, char *nome) { if ((sockdb = createsock (SOCK_STREAM)) < 0) errore ("Errore: Creazione socket base dati"); if (connectaddr (sockdb, DBIND, DBPORT) < 0) { perror ("Errore Nomeprod: Connessione con DATA BASE fallita"); strcpy (nome, "Prodotto"); } else { sendintsock (sockdb, 3); sendintsock (sockdb, codprod); recvstrsock (sockdb, nome); close (sockdb); }

void errore (char *s) { perror (s); exit (-1); } void esci () { allarme = 1; }

processo server
#include "mysock.h" #include <sys/wait.h> #include <signal.h> #define ATTESA 15 void esci (); void fine (); void errore (char *s); int allarme; void main () { union wait status; int codprod, oper, pid, sock, sockdb, sockbanca, newsock, esito; unsigned long int prezzo; mexbanco messbanco; signal (SIGKILL, fine); printf ("SERVER ATTIVO\n"); if ((sock = initsock (SOCK_STREAM, SERVIND, SERVPORT)) < 0) errore ("Errore: Socket ricevente non inizializzato"); listensock (sock, CODA); while (1) { if ((newsock = acceptsock (sock, NULL, NULL)) < 0) errore ("Errore: Accept"); if (( pid = fork ()) < 0) perror ("Errore: Creazione figlio fallita"); 195

if (pid == 0) /* Codice figlio */ { recvintsock (newsock, &oper); /* riceve identificatore dell' operazione */ switch (oper) /* da svolgere */ { case 1: recvintsock (newsock, &codprod); if ((sockdb = createsock (SOCK_STREAM)) < 0) errore ("Errore: Creazione socket DATA BASE"); if (connectaddr (sockdb, DBIND, DBPORT) < 0) { perror ("Errore: Connessione fallita con DATA BASE"); prezzo = 0; } else { sendintsock (sockdb, 1); /* Richiesta prezzo al data base */ sendintsock (sockdb, codprod); recvlongintsock (sockdb, &prezzo); /* Risposta prezzo */ close (sockdb); } sendlongintsock (newsock, prezzo); /* Invio prezzo al client */ break; case 2: /* Pagamento con bancomat */ recvmexbanco (newsock, &messbanco); signal (SIGALRM, esci); allarme = 0; if ((sockbanca = createsock (SOCK_STREAM)) < 0) errore ("Erroe: Creazione socket BANCA"); alarm (ATTESA); while((allarme == 0)&&(connectaddr(sockbanca,BANCIND,BANCPORT)<0)); if (allarme == 1) { perror ("ERRORE: Connessione fallita con BANCA"); esito = -3; } else { sendmexbanco (sockbanca, &messbanco); recvintsock (sockbanca, &esito); /* Esito pagamento */ close (sockbanca); } sendintsock (newsock, esito); break; } /*switch */ close (newsock); /* precesso figlio chiude il socket*/ exit (0); /* adoperato e termina*/ } /* if (pid = 0)*/ close (newsock); while (wait3 (&status, WNOHANG, NULL) > 0); } /* while (1) */ } void esci () { allarme = 1; } void fine () { printf ("SERVER FINE\n"); printf ("Progaramma terminato con KILL\n"); exit (0); }

void errore (char *s) { perror (s); exit (-1); }

processo database
#include #include #include #include "mysock.h" <sys/wait.h> <signal.h> <fcntl.h>

#include <stdio.h> long int determinaprezzo (int codprod); void aggiorna (int codcliente, int codoperat, int codprod, int quantita, int data); void nomeprodotto (int codprod, char *nome); void errore (char *s); void fine (); void main () { int oper, sock, newsock, codprod, pid, pid1, data, giorno, mese, anno; unsigned long int prezzo; char nome[SIZESTR]; mexdb messdb; union wait status; signal (SIGKILL, fine); printf ("Inserisci data (gg/mm/aaa): "); scanf ("%d/%d/%d", &giorno, &mese, &anno); data = giornoanno (giorno, mese, anno); if ((sock = initsock (SOCK_STREAM, DBIND, DBPORT)) < 0) errore ("Errore: Socket non inizializzato"); listensock (sock, CODA); printf ("BASE DATI ATTIVA\n"); if ((pid1 = fork ()) < 0) errore ("Errore: Creazione figlio fallita"); if (pid1 == 0) /* Figlio delle richieste */ { execl ("richiest", "richiest",(char *)0); errore ("Errore: In esecuzione processo richieste"); } while (1) { if ((newsock = acceptsock (sock, NULL, NULL)) < 0) errore ("Errore: Accept"); if ((pid = fork ()) < 0) perror ("Errore: Creazione figlio fallita"); if (pid == 0) { recvintsock (newsock, &oper); switch (oper) { case 1: /* Richiesta prezzo */ recvintsock (newsock, &codprod); prezzo = determinaprezzo (codprod); sendlongintsock (newsock, prezzo); break; case 2: /* Aggiorna data base */ recvmexdb (newsock, &messdb); aggiorna (messdb.codcliente, messdb.codoperat, messdb.codprod, messdb.quantita, data); break; case 3: /* Richiesta nome */ 197

recvintsock (newsock, &codprod); nomeprodotto (codprod, nome); sendstrsock (newsock, nome); break; } /* switch */ close (newsock);/* Processo figlio chiude il socket adoperato */ exit (0); /* e termina */ } /* if (pid == 0) */ close (newsock); while (wait3 (&status, WNOHANG, NULL) > 0); } /* while (1) */

long int determinaprezzo (int codprod) { int fprodotti, endf; recprod rprod; if ((fprodotti = open ("prodotti.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT"); lseek (fprodotti, 0, SEEK_SET); endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf == 0) /* Non trovato */ return (0); else /* Trovato */ return (rprod.prezzo);

void aggiorna (int codcliente, int codoperat, int codprod, int quantita, int data) { int fvenduto, fprodotti, endf; recvenduto rvenduto; recprod rprod; /* Aggiornamento file VENDUTO.DAT */ if ((fvenduto=open("venduto.dat",O_WRONLY|O_APPEND|O_CREAT, 0666)) < 0) errore ("Errore Aggiorna: Impossibile aprire file VENDUTO.DAT"); rvenduto.codcliente = codcliente; rvenduto.codoperat = codoperat; rvenduto.codprod = codprod; rvenduto.quantita = quantita; rvenduto.data = data; lseek (fvenduto, 0, SEEK_END); /* Sposta alla file file */ lockf (fvenduto, F_LOCK, sizeof (recvenduto)); write (fvenduto, &rvenduto, sizeof (recvenduto)); lockf (fvenduto, F_ULOCK, -sizeof (recvenduto)); close (fvenduto); /* Aggiornamento file PRODOTTI.DAT */ if ((fprodotti = open ("prodotti.dat", O_RDWR, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT"); lseek (fprodotti, 0, SEEK_SET); /* Sposta inizio file */ endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf != 0) { rprod.quantita = rprod.quantita - quantita; lseek (fprodotti, -sizeof (recprod), SEEK_CUR); lockf (fprodotti, F_LOCK, sizeof (recprod)); write (fprodotti, &rprod, sizeof (recprod));

lockf (fprodotti, F_ULOCK, -sizeof (recprod)); } close (fprodotti);

int giornoanno (int giorno, int mese, int anno) { static char daytab [2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap=(((anno % 4) == 0) && ((anno % 100) != 0) || ((anno % 400) == 0)); for (i = 1; i < mese; i++) giorno += daytab [leap][i]; return (giorno);

void fine () { printf ("BASE DATI FINE\n"); printf ("Programma terminato con KILL\n"); exit (0); } void nomeprodotto (int codprod, char *nome) { int fprodotti, endf; recprod rprod; if ((fprodotti = open ("prodotti.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT"); lseek (fprodotti, 0, SEEK_SET); endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf == 0) /* Non trovato */ strcpy (nome, "ARTIC"); else /* Trovato */ strcpy (nome, rprod.nome); } void errore (char *s) { perror (s); exit (-1); }

processo richiest
#include <stdio.h> #include <fcntl.h> #include "mytypes.h" int giornoanno (int giorno, int mese, int anno); int cercavendutoda (int idfile, int codoperat, int *quantita, int data); int cercavendutoa (int idfile, int codoperat, int * quantita, int datai, int dataf); void errore (char *s); void main () 199

unsigned int risp, codoper, codcliente, giorno, mese, anno, datai, dataf, npezzi, quantita; int fvenduto; printf ("Inizio Processo RICHIESTE\n"); do { printf ("1) Venduto per cassiera\n"); printf ("2) Veduto per cliente\n"); printf ("3) Fine\n"); scanf ("%d", &risp); switch (risp) { case 1: npezzi = 0; printf ("Codice operatore: "); scanf ("%d", &codoper); printf ("Inserisci data: "); scanf ("%d/%d/%d", &giorno, &mese, &anno); datai = giornoanno (giorno, mese, anno); if ((fvenduto = open ("venduto.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file VENDUTO.DAT"); lseek (fvenduto, 0, SEEK_SET); while (cercavendutoda(fvenduto,codoper,&quantita,datai) != 0) npezzi = npezzi + quantita; printf ("Numero pezzi venduti: %d\n", npezzi); break; case 2: npezzi = 0; printf ("Codice cliente: "); scanf ("%d", &codcliente); printf ("Data inizio: "); scanf ("%d/%d/%d", &giorno, &mese, &anno); datai = giornoanno (giorno, mese, anno); printf ("Data fine: "); scanf ("%d/%d/%d", &giorno, &mese, &anno); dataf = giornoanno (giorno, mese, anno); if ((fvenduto = open ("venduto.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file VENDUTO.DAT"); lseek (fvenduto, 0, SEEK_SET); while(cercavendutoa (fvenduto,codcl0iente,&quantita,datai,dataf) != 0) npezzi = npezzi +quantita; printf ("Numero pezzi venduti: %d\n", npezzi); break; } /* switch (risp) */ } while (risp != 3); printf ("Fine processo RICHIESTE\n"); exit (0);

} int giornoanno (int giorno, int mese, int anno) { static char daytab [2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap = (((anno % 4) == 0) && ((anno % 100) != 0) || ((anno % 400) == 0)); for (i = 1; i < mese; i++) giorno += daytab [leap][i]; return (giorno); } int cercavendutoda (int idfile, int codoperat, int *quantita, int data) { recvenduto rvenduto; int endf;

endf = read (idfile, &rvenduto, sizeof (recvenduto)); while ( (endf != 0) && ((rvenduto.codoperat != codoperat) || (rvenduto.data != data))) endf = read (idfile, &rvenduto, sizeof (recvenduto)); *quantita = rvenduto.quantita; return (endf); } int cercavendutoa (int idfile, int codcliente, int *quantita, int datai, int dataf) { recvenduto rvenduto; int endf; endf = read (idfile, &rvenduto, sizeof (recvenduto)); while ((endf != 0) && ((rvenduto.codcliente != codcliente) || (rvenduto.data < datai) || (rvenduto.data > dataf))) endf = read (idfile, &rvenduto, sizeof (recvenduto)); *quantita = rvenduto.quantita; return (endf); } void errore (char *s) { perror (s); exit (-1); }

processo banca
#include "mysock.h" #include <sys/wait.h> #include <signal.h> void errore (char *s); int pagamento (); void fine (); void main () { int sock, newsock, pid, esito; mexbanco messbanca; union wait status; signal (SIGKILL, fine); printf ("BANCA ATTIVA\n"); if ((sock = initsock (SOCK_STREAM, BANCIND, BANCPORT)) < 0) errore ("Errore: Socket non inizializzato"); listensock (sock, CODA); while (1) { if ((newsock = acceptsock (sock, NULL, NULL)) < 0) errore ("Errore: Accept"); if ((pid = fork ()) < 0) perror ("Errore: Creazione figlio fallita"); if (pid == 0) { recvmexbanco (newsock, &messbanca); esito = pagamento (); sendintsock (newsock, esito); close (newsock); exit (0); } close (newsock); while (wait3 (&status, WNOHANG, NULL) > 0); 201

void errore (char *s) { perror (s); exit (-1); } int pagamento () { int pagato; printf ("BANCA: Pagamento ? "); scanf ("%d", &pagato); return (pagato); } void fine () { printf ("BANCA.C FINE\n"); printf ("Programma terminato con KILL\n"); exit (0); }

mytypes.h
#define #define #define #define #define #define #define #define #define #define ATTESA MAXCOD CODA MAXSTR 15 10 5 20 0 7050 0 6050 0 8050

SERVIND SERVPORT DBIND DBPORT BANCIND BANCPORT

typedef struct MessBanco { int numcarta; int codbanco; unsigned long int ammontare; } mexbanco; typedef struct MessDB { int codcliente; int codprod; int codoperat; int quantita; } mexdb; typedef struct RecVenduto { int codprod; int codcliente; int codoperat; int quantita; int data; } recvenduto;

typedef struct RecProd { int codprod; unsigned long int prezzo; char nome [MAXSTR]; int quantita; } recprod; int sendmexbanco (int sock, mexbanco *mess) { } int recvmexbanco (int sock, mexbanco *mess) { return (recv (sock, mess, sizeof (mexbanco), 0)); } int sendmexdb (int sock, mexdb *mess) { return (send (sock, mess, sizeof (mexdb), 0)); } int recvmexdb (int sock, mexdb *mess) { return (recv (sock, mess, sizeof (mexdb), 0)); } return (send(sock, mess, sizeof (mexbanco), 0));

mysock.h
#ifndef _MYSOCK_H #define _MYSOCK_H #include #include #include #include #include #include 1

<sys/types.h> <sys/socket.h> <netdb.h> <netinet/in.h> <arpa/inet.h> "mytypes.h"

#define SIZESTR 50 int createsock (int tipo) { return (socket (AF_INET, tipo, 0)); } int donesock (int sock) { return (shutdown (sock, 1)); } int closesock (int sock) { return (close (sock)); } int autobindsock (int sock, int porta) { struct sockaddr_in sockname; struct hostent *hostinfo; char name[SIZESTR];

203

if (gethostname (name, SIZESTR) < 0) return (-1); if ((hostinfo = gethostbyname (name)) == NULL) return (-1); sockname.sin_addr = *((struct in_addr *)*(hostinfo -> h_addr_list)); sockname.sin_family = AF_INET; sockname.sin_port = htons (porta); return (bind (sock, (struct sockaddr *)&sockname, sizeof (sockname))); } int bindsock (int sock, double ind, int porta) { struct sockaddr_in sockname; sockname.sin_addr.s_addr = htonl((unsigned long)ind); sockname.sin_family = AF_INET; sockname.sin_port = htons (porta); return (bind (sock, (struct sockaddr *)&sockname, sizeof (sockname)));

int initsock (int tipo, double ind, int porta) { int sock; struct sockaddr_in sockname; if ((sock = socket (AF_INET, tipo, 0)) < 0 ) return (-1); if (bindsock (sock, ind, porta) < 0) return (-2); return (sock); } int listensock (int sock, int numcoda) { return (listen (sock, numcoda)); } int acceptsock (int sock, struct sockaddr *addr, int *sizeaddr) { return (accept (sock, (struct sockaddr *) addr, sizeaddr)); } int connectname (int sock, char * name, int porta) { struct hostent *hostinfo; struct sockaddr_in rsock; if ((hostinfo = gethostbyname (name)) == NULL) return (-1); rsock.sin_addr = *(struct in_addr *)*(hostinfo -> h_addr_list); rsock.sin_family = AF_INET; rsock.sin_port = htons (porta); return (connect (sock, (struct sockaddr *)&rsock, sizeof (rsock)));

int connectaddr (int sock, unsigned long int ind, int porta) { struct sockaddr_in rsock; rsock.sin_addr.s_addr = htonl (ind); rsock.sin_family = AF_INET; rsock.sin_port = htons (porta); return (connect (sock, (struct sockaddr *)&rsock, sizeof (rsock))); }

int sendintsock (int sock, int mess) { return (send (sock, &mess, sizeof (int), 0)); } int recvintsock (int sock, int *mess) { return (recv (sock, mess, sizeof (int), 0)); } int sendlongintsock (int sock, long int mess) { return (send (sock, &mess, sizeof (long int), 0)); } int recvlongintsock (int sock, long int *mess) { return (recv (sock, mess, sizeof (long int), 0)); } int sendstrsock (int sock, char *mess) { return (send (sock, mess, SIZESTR, 0)); } int recvstrsock (int sock, char *mess) { return (recv (sock, mess, SIZESTR, 0)); } #endif

205

15 Esempi di programmazione in C++

15.1 Esercitazione 1
Compilatori C++ in ambiente Unix Il gcc e' anche un compilatore C++ : i file sorgente scritti in C++ devono avere l'estensione ".C" (carattere punto seguito dal carattere C in maiuscolo). Il comando con cui richiamare il compilatore e' : g++. Compilatori C++ in ambiente Dos - Windows Nel laboratorio CCLINFO e' disponibile il compilatore C++ BorlandC 4.0. A richiesta e' disponibile su dischetto una versione demo, liberamente distribuibile, del compilatore Borland TurboC++ 3.0 sotto Dos; le istruzioni per utilizzare il dischetto sono: - copiare il contenuto del dischetto su hard disk: copy a:. destinazione - decomprimere il compilatore con il comando: pkunzip -d tclite.zip - spostarsi nella sottodirectory \bin e lanciare l'IDE: tc Esercizio: Manipolazione di Stringhe La classe che segue (string.h) manipola stringhe. 1-Completare la classe definendo il body delle funzioni (string.c). 2- Testare la classe con il main che segue (main.c). -------------------string.h // Classe string #ifndef __string #define __string class string { private: char data[80]; // contiene la stringa public: string (char *); // costruttore, copia s su dati tronca // se superati 79 caratteri utili void copy(char *s); // copia s su data usando strcpy, tronca // se superati 79 caratteri utili void append(char* s); // append s su data usando strcat , tronca // se superati 79 caratteri utili int lenght(); // rende il numero di char della stringa, senza \0 int compare(char* s) // confronto alfabetico di data e s, usando strcmp void toUpper(void) // trasforma data in maiuscolo void toLower(void) // trasforma data in minuscolo void read(FILE *fptr); // lettura da fptr, fino a \n , tronca // se superati 79 caratteri utili void write(FILE *fptr); // scrive data su fptr, non aggiunge \n };
207

#endif -------------------string.c #include string.h string::string(char* s=""){ // tbd } -------------------stringtest.c #include string.h void main(){ string s1, s2("pippo"); s1.read(stdin); s1.write(stdout); s1.copy("bof"); s1.append("davvero?"); s1.write(stdout); printf("lunghezza s1 %d \n", s1.lenght); s2.write(stdout); s2.toLower(); s2.write(stdout); s2.toUpper(); s2.write(stdout); printf("compare s1 s2 %d \n", s1.compare("pippo")); } 2-bis Provare il seguente main #include string.h void main(){ string s1, s2("pippo"); strcpy(s1.data, "pappa"); printf("%c", s2.data ); } 3- Modificare la classe mettendo la stringa sulla memoria dinamica (vedi string.h che segue), quindi togliendo il limite dei 79 caratteri e la troncatura. Notare che il client della classe (main.c in questo caso) NON viene modificato, grazie ai dati private (concetto di encapsulation o information hiding) -------------------string.h // Classe string #ifndef __string #define __string class string { private: char* data; // contiene la stringa public: string (char *); // costruttore, copia s su data void copy(char *s); // copia s su data usando strcpy void append(char* s); // append s su data usando strcat int lenght(); // rende il numero di char della stringa, senza \0 int compare(char* s) // confronto alfabetico di data e s, usando strcmp void toUpper(void) // trasforma data in maiuscolo

void toLower(void) // trasforma data in minuscolo void read(FILE *fptr); // lettura da fptr, fino a \n void write(FILE *fptr); // scrive data su fptr, non aggiunge \n }; #endif 4- Aggiungere alla classe costruttore, copy, append e compare con stringa come parametro. Cio' e' possibile grazie al function overload supportato dal c++ Notare che tutti ricevono delle reference come parametri. -------------------string.h // Classe string #ifndef __string #define __string class string { private: char* data; // contiene la stringa public: string (char *s); // costruttore, copia s su data string (string& s); // costruttore, copia s su data void copy(char *s); // copia s su data usando strcpy void copy(string& s); // copia s su data usando strcpy void append(char* s); // append s su data usando strcat void append(string& s); // append s su data usando strcat int lenght(); // rende il numero di char della stringa, senza \0 int compare(char* s) // confronto alfabetico di data e s, usando strcmp int compare(string& s) // confronto alfabetico di data e s, usando strcmp void toUpper(void) // trasforma data in maiuscolo void toLower(void) // trasforma data in minuscolo void read(FILE *fptr); // lettura da fptr, fino a \n void write(FILE *fptr); // scrive data su fptr, non aggiunge \n }; #endif 5- Ridefinire i member copy, append e compare come operatori =, +=, == , usando l'operator overload. Notare che i primi due ora rendono string& per poter concatenare operatori (vedi main.c che segue) -------------------string.h // Classe string #ifndef __string #define __string class string { private: char* data; // contiene la stringa public: string (char *s); // costruttore, copia s su data string (string& s); // costruttore, copia s su data string& operator=(char *s); // copia s su data usando strcpy string& operator=(string& s); // copia s su data usando strcpy string& operator+=(char* s); // append s su data usando strcat string& operator+=(string& s); // append s su data usando strcat int lenght(); // rende il numero di char della stringa, senza \0 int operator==(char* s) // confronto alfabetico di data e s, usando strcmp
209

int operator==(string& s) // confronto alfabetico di data e s, usando strcmp void toUpper(void) // trasforma data in maiuscolo void toLower(void) // trasforma data in minuscolo void read(FILE *fptr); // lettura da fptr, fino a \n void write(FILE *fptr); // scrive data su fptr, non aggiunge \n }; #endif -------------------stringtest.c #include string.h void main(){ string s1, s2("pippo"), s3(s2); s1.read(stdin); s1.write(stdout); s1 = "bof" ; s1 = s2 ; s1 += "davvero?"; s1 += s2; s1 = s2 += s3; // concatenazione di operatori s1.write(stdout); printf("lunghezza s1 %d \n", s1.lenght); s2.write(stdout); s2.toLower(); s2.write(stdout); s2.toUpper(); s2.write(stdout); printf("compare s1 s2 %d \n", s1 == s2); } Soluzione: punti 1 e 2
//Classe STRING #ifndef _STRINGA #define _STRINGA #include <string.h> #include <stdio.h> #define MAXSTR 79 class String { char data [MAXSTR+1]; public: String (char *); void copy (char *s); void append (char *s); int length (); int compare (char *s); void toUpper (void); void toLower (void); void read (FILE *fptr); void write (FILE *fptr); }; String::String (char *s = "")

{ }

strncpy (data, s, MAXSTR);

void String::copy (char *s) { strncpy (data, s, MAXSTR); } void String::append (char *s) { strncat (data, s, MAXSTR - strlen (data)); } int String::length () { return (strlen (data)); } int String::compare (char *s) { return (strcmp (data, s)); } void String::toUpper () { strupr (data); } void String::toLower () { strlwr (data); } void String::read (FILE *fptr) { if (fscanf (fptr,"%s", data) > MAXSTR) data[MAXSTR] = '\0'; } void String::write (FILE *fptr) { fprintf(fptr,"%s", data); } #endif

punto 3 :
//Classe STRING #ifndef _STRINGA #define _STRINGA #include <string.h> #include <stdio.h> #include <stdlib.h> #define MAX 255 class String { char *data; public: String (char *); void copy (char *s); void append (char *s); int length (); int compare (char *s); void toUpper (void); void toLower (void); void read (FILE *fptr); void write (FILE *fptr); void writeln (FILE * fptr); 211

}; String::String (char *s = "") { data = (char*) malloc (strlen (s)+1); strcpy (data, s); } void String::copy (char *s) { free (data); data = (char*) malloc (strlen (s)+1); strcpy (data, s); } void String::append (char *s) { char *help; help = (char *) malloc (strlen (data) + 1); strcpy (help, data); free (data); data = (char *) malloc (strlen (data) + strlen (s) + 1); strcpy (data, help); strcat (data, s); } int String::length () { return (strlen (data)); } int String::compare (char *s) { return (strcmp (data, s)); } void String::toUpper () { strupr (data); } void String::toLower () { strlwr (data); } void String::read (FILE *fptr) { char help [MAX]; fgets (help, MAX, fptr); help [strlen (help)-1] = '\0'; free (data); data = (char*) malloc (strlen (help)+1); strcpy (data, help); } void String::write (FILE *fptr) { fputs(data, fptr); } void String::writeln (FILE *fptr) { fputs(data, fptr); fputs ("\n", fptr); } #endif

punto 4 :

//Classe STRING

#ifndef _STRINGA #define _STRINGA #include <string.h> #include <stdio.h> #include <stdlib.h> #define MAX 255 class String { char *data; public: String (char *); String (String& s); void copy (char *s); void copy (String& s); void append (char *s); void append (String& s); int length (); int compare (char *s); int compare (String& s); char *getval (); void toUpper (void); void toLower (void); void read (FILE *fptr); void write (FILE *fptr); void writeln (FILE * fptr); }; String::String (char *s = "") { data = new char[strlen (s)+1]; strcpy (data, s); } String::String (String& s) { data = new char[strlen (s.getval ())+1]; strcpy (data, s.getval ()); } void String::copy (char *s) { delete data; data = new char[strlen (s)+1]; strcpy (data, s); } void String::copy (String& s) { copy (s.getval ()); } void String::append (char *s) { char *help; help = strcpy delete data = strcpy strcat } void String::append (String& s) { append (s.getval ()); } new char[strlen (data) + 1]; (help, data); data; new char[strlen (data) + strlen (s) + 1]; (data, help); (data, s);

213

int String::length () { return (strlen (data)); } int String::compare (char *s) { return (strcmp (data, s)); } int String::compare (String& s) { return (strcmp (data, s.getval ())); } char *String::getval () { return (data); } void String::toUpper () { strupr (data); } void String::toLower () { strlwr (data); } void String::read (FILE *fptr) { char help [MAX]; fgets (help, MAX, fptr); help [strlen (help)-1] = '\0'; delete data; data = new char[strlen (help)+1]; strcpy (data, help); } void String::write (FILE *fptr) { fputs(data, fptr); } void String::writeln (FILE *fptr) { fputs(data, fptr); fputs ("\n", fptr); } #endif

punto 5 :
//Classe STRING #ifndef _STRINGA #define _STRINGA #include <string.h> #include <stdio.h> #include <stdlib.h> #define MAX 255 class String { char *data; public: String (char *); String (String& s);

};

String& operator= (char *s); String& operator= (String& s); String& operator+= (char *s); String& operator+= (String& s); int length (); int operator== (char *s); int operator== (String& s); char *getval (); void toUpper (void); void toLower (void); void read (FILE *fptr); void write (FILE *fptr); void writeln (FILE * fptr);

String::String (char *s = "") { data = new char[strlen (s)+1]; strcpy (data, s); } String::String (String& s) { data = new char[strlen (s.getval ())+1]; strcpy (data, s.getval ()); } String& String::operator= (char *s) { delete data; data = new char[strlen (s)+1]; strcpy (data, s); return *this; } String& String::operator= (String& s) { delete data; data = new char[strlen (s.getval ())+1]; strcpy (data, s.getval ()); return *this; } String& String::operator+= (char *s) { char *help; help = strcpy delete data = strcpy strcat return new char[strlen (data) + 1]; (help, data); data; new char[strlen (data) + strlen (s) + 1]; (data, help); (data, s); *this;

String& String::operator+= (String& s) { char *help; help = strcpy delete data = strcpy strcat return new char[strlen (data) + 1]; (help, data); data; new char[strlen (data) + strlen (s.getval ()) + 1]; (data, help); (data, s.getval ()); *this;

} int String::length () { return (strlen (data));

215

} int String::operator== (char *s) { return (strcmp (data, s)); } int String::operator== (String& s) { return (strcmp (data, s.getval ())); } char *String::getval () { return (data); } void String::toUpper () { strupr (data); } void String::toLower () { strlwr (data); } void String::read (FILE *fptr) { char help [MAX]; fgets (help, MAX, fptr); help [strlen (help)-1] = '\0'; delete data; data = new char[strlen (help)+1]; strcpy (data, help); } void String::write (FILE *fptr) { fputs(data, fptr); } void String::writeln (FILE *fptr) { fputs(data, fptr); fputs ("\n", fptr); } #endif

15.2 Esercitazione 2 Lista


Sviluppare una classe per gestire liste bi-linkate di elementi generici. Un elemento generico di una lista bi-linkata puo' essere un'istanza (= oggetto) di una classe cell. Una lista bi-linkata puo' essere un'istanza di una classe list utilizzante oggetti di classe cell. Completare la dichiarazione della classe list con le definizioni delle funzioni. Puo' essere utile inserire in ogni funzione una stampa (cout << "nome funzione"; ) del nome delle funzione, in modo da esaminare la correttezza del flusso di esecuzione del programma (includere il file <iostream.h>). In seguito sono dati i file .h per le classi lista e cell e il main di prova. Sistemare gli include e definire i file .c

#include <iostream.h> //----------------------------- cell.h class cell { cell* NEXT; cell* PRIOR; void* ITEMPTR; public: cell( cell*, cell*, void*); ~cell(); friend class list; }; //----------------------------- list.h class list { cell* FIRST, *LAST; cell *CURRENT; int n_elem; // number of elements int is_empty() const; // true if empty list int has_only_one_item() const; // true if one item only void copy( list&); // copies the received list; target list is // supposed empty void free(); // deletes all cells of the list public: list(); list(list&); // copy constructor list& operator = (list&); ~list(); void putfirst(void* itemptr); // insert before first item void putlast(void * itemptr); // append after last item void* getfirst(); // extracts first item void* getlast(); // extracts last item void* first() const; // reads first item void* last() const; // reads last item void edit() const; // prints all items ostream& operator << (list& l); istream& operator >> (list& l); }; // --------------------------------------------- main.c int main(){ list l1,l2, l3; int one=1, two=2, three=3, four=4; l1.append(&one); l1.append(&two); l1.append(&three); l1.append(&four); l1.edit();
217

list l4 = l1; l1.edit(); l4.edit(); } Soluzione :


#ifndef __LISTA #define __LISTA #include <stdlib.h> #include <string.h> class Cell { Cell *Next; Cell *Prior; int *itemptr; public: Cell (Cell *, Cell *, int *); ~Cell (); int *getitemptr (); Cell *getnext (); friend class List; }; class List { Cell *First; Cell *Last; Cell *Current; int n_elem; int is_empty() const; int has_only_one_item() const; void copy (List &l); void free(); public: List (); List (List &); List &operator = (List &l); ~List (); Cell *getfirstcell () const; void putfirst(int *i); void putlast(int *i); void *getfirst (); void *getlast (); void *first () const; void *last () const; void edit () const; friend ostream &operator << (ostream& OS, List &l); }; Cell::Cell (Cell *n = NULL, Cell *p = NULL, int *i = NULL) { Next = n; Prior = p; itemptr = i; } Cell::~Cell () {} int *Cell::getitemptr () { return (itemptr); }

Cell *Cell::getnext () { return (Next); } List::List () { First = Last = Current = NULL; n_elem = 0; } List::List (List &l) { copy(l); } List::~List() { while (First != NULL) { Current = First; First = First -> Next; delete Current; } } int List::is_empty() const { return (n_elem == 0); } int List::has_only_one_item() const { return(n_elem == 1); } void List::copy (List &l) { Cell *cr, *prec; First = NULL; n_elem = l.n_elem; cr = l.First; while (cr != NULL) { Current = new Cell; Current -> itemptr = cr -> itemptr; Current -> Next = NULL; if (First == NULL ) { First = prec = Current; Current -> Prior = NULL; } else { Current -> Prior = prec; prec -> Next = Current; prec = Current; } cr = cr -> Next; } Last = Current; Current = First; } void List::free () { while (First != NULL) { Current = First; First = First -> Next; delete Current; } n_elem = 0; 219

Current = Last = NULL;

Cell *List::getfirstcell () const { return (First); } void List::putfirst (int *i) { Cell *help; help = new Cell; help -> itemptr = i; help -> Next = First; help -> Prior = NULL; if (First != NULL) First ->Prior = help; First = help; if (Last == NULL) Last = Current = First; n_elem++; } void List::putlast (int *i) { Cell *help; help = new Cell; help -> itemptr = i; help -> Prior = Last; help -> Next = NULL; if (Last != NULL) Last -> Next = help; Last = help; if (First == NULL) First = Current = Last; n_elem++;

void *List::getfirst() { void *dato; if (First == NULL) return(NULL); dato = First -> itemptr; if (First -> Next != NULL) { if (Current == First) Current = Current -> Next; First = First -> Next; delete First -> Prior; First -> Prior = NULL; } else { delete First; First = Last = Current = NULL; } n_elem --; return (dato); } void *List::getlast() { void *dato; if (Last == NULL) return(NULL); dato = Last -> itemptr; if (Last -> Prior != NULL) { if (Current == Last) Current = Current -> Prior; Last = Last -> Prior; delete Last -> Next; Last -> Next = NULL; }

else { delete Last; First = Last = Current = NULL; } n_elem --; return (dato); } void *List::first() const { if (First == NULL) return (NULL); return (First -> itemptr); } void *List::last() const { if (Last == NULL) return (NULL); return (Last -> itemptr); } List& List::operator = (List& l) { copy (l); return *this; } void List::edit () const { Cell *help; help = getfirstcell (); printf ("["); while (help != NULL) { printf ("%d", *(int *) (help->getitemptr ())); help = help -> getnext (); if (help != NULL) printf (", "); } printf ("]\n"); } ostream &operator << (ostream &OS, List &l) { Cell *help; help = l.getfirstcell (); OS << "["; while (help != NULL) { OS << *(int *) (help->getitemptr ()); help = help -> getnext (); if (help != NULL) cout << ", "; } OS << ']' << endl; return OS;

#endif

Per testarne il corretto funzionamento abbiamo implementato il seguente main :


#include #include #include #include <iostream.h> <stdio.h> <conio.h> "list1.hpp"

main () { List l1; List l4 = l1; 221

int one = 1, two = 2, three = 3, four = 4, five = 5; clrscr (); l1.putlast (&one); l1.putlast (&two); l1.putlast (&three); l1.putlast (&four); cout << l1; l4 = l1; l4.putlast (&five); cout << l1; cout << l4; printf ("EDIT l1 e l2\n"); l1.edit (); l4.edit ();

15.3 Esonero Giugno 96 Specifiche: Una carta telefonica internazionale prepagata funziona in questo modo. Ogni carta ha un valore in unit di comunicazione ed un codice di identificazione. Lutente che ha comprato la carta compone, da un qualsiasi telefono di qualsiasi paese europeo o nordamericano, un numeo verde con cui viene gratuitamente messo in contatto con un operatore. A questo punto comunica alloperatore il codice di identificazione della carta e il numero telefonico da chiamare. Loperatore verifica che la carta abbia disponibilit e inoltre la chiamata finch lutente non attacchi o la carta esaurisca la sua disponibilit. Alcuni clienti hanno un proprio numero di identificazione che permette loro di ottenere sconti sulla comunicazione in funzione delle unit consumate (es. sotto le 100 unit lunit dura un secondo, da 100 a 1000 2 secondi, da 1000 a 10000 3 secondi). La gestione della contabilit di tali carte richiede : - dato un identificatore di carta, verificare quante unit di comunicazione sono disponibili su di esso - dato un identificatore di carta, aggiornare le unit di comunicazione disponibili su di essa - dato un identificatore di cliente, associarlo ad una carta - dato un identificatore di cliente, sapere le carte a cui associato - dato un identificatore di cliente, calcolare la durata dellunit - leggere e scrivere le informazioni di cui sopra su file E richiesto di realizzare un programma C++ per la gestione della contabilit: 1. definire il context diagram OMT dellapplicazione 2. definire lobject model OMT dellapplicazione 3. definire linterfaccia (file.h) di tutte le classi dellapplicazione 4. definire il programma principale 5. definire limplementazione (file.c) delle clazzi dellapplicazione (almeno per una classe, in mancanza di tempo scegliere di implementare le classi ritenute pi importanti). E richiesto di utilizzare, per le strutture dati (set, liste, alberi, hash table, etc.) una libreria predefinita. Questa pu essere a scelta: la libreria MFC la libreria GNU

una libreria realizzata dallallievo Soluzione proposta : librerie utilizzate


// stack.hpp #ifndef _IOSTREAM_ #define _IOSTREAM_ #include <iostream.h> #endif #ifndef __STACK #define __STACK #include <string.h> class Elem { public: virtual Elem *crea () = 0; virtual void assegna (Elem *e) = 0; virtual void print (ostream &OS) = 0; }; typedef struct Nodo *ptrnodo; typedef struct Nodo { ptrnodo next; Elem *dato; } nodo;

class Stack { protected: ptrnodo testa; void copy (Stack &s); void free (); public: Stack (); Stack (Stack &s); void push (Elem *e); Elem *pop (); Stack &operator = (Stack &s); friend ostream &operator << (ostream &OS, Stack &s); ~Stack (); }; /* IMPLEMENTAZIONE STACK */ Stack::Stack () { testa = NULL; } void Stack::copy (Stack &s) { ptrnodo rec, scorri, hold; scorri = s.testa; while (scorri != NULL) { rec = new nodo; if (testa == NULL) testa = hold = rec; 223

rec -> dato = scorri -> dato -> crea (); rec -> dato -> assegna (scorri -> dato); rec -> next = NULL; scorri = scorri -> next; if (hold != rec) { hold ->next = rec; hold = rec; } } }

void Stack:: free () { ptrnodo rec; rec = testa; while (rec != NULL) { testa = testa -> next; delete rec; rec = testa; } } Stack::Stack (Stack &s) { copy (s); } void Stack::push (Elem *e) { ptrnodo rec; rec = new nodo; rec -> dato = e -> crea (); rec -> dato -> assegna (e); rec -> next = testa; testa = rec;

Elem *Stack::pop () { ptrnodo rec; Elem *dato; rec = testa; testa = testa -> next; dato = rec -> dato; delete rec; return (dato); } Stack &Stack::operator = (Stack &s) { free (); copy (s); return *this; } ostream &operator << (ostream &OS, Stack &s) { ptrnodo rec; rec = s.testa; OS << "["; while (rec != NULL) { rec -> dato -> print (OS); rec = rec -> next; if (rec != NULL) cout << ", "; }

OS << ']' << endl; return OS; } Stack::~Stack () { free (); } #endif

// lista.hpp #include "stack.hpp" class Lista : public Stack { protected: ptrnodo current; public: Lista (); Lista (Lista &l); void inizio (); void avanza (); }; Lista::Lista () { current = NULL; } Lista::Lista (Lista &l) { current = l.current; } void Lista::inizio () { current = testa; } void Lista::avanza () { current = current -> next; }

// classi.hpp #ifndef _STREAM_ #define _STREAM_ #include <fstream.h> #endif #include "lista.hpp" const unsigned MAXSTR = 50; class Carta: public Elem { protected: 225

int codcarta; int valore; friend class ListaCarta; public: Carta (int codice, int val); Carta (Carta &c); Elem *crea (); void assegna (Elem *e); void print (ostream &OS); int numunita (); int aggiornaunita (int consumo); friend ofstream &operator << (ofstream &FOS, Carta &c); friend ifstream &operator >> (ifstream &FIS, Carta &c); int getcodcarta (); }; class Utente: public Elem { protected: int codutente; char nome [MAXSTR]; unsigned int scattitot; int codcarta; friend class ListaUtente; public: Utente (int codice, char *n, unsigned int scatti, int carta); Utente (Utente &u); Elem *crea (); void assegna (Elem *e); void print (ostream &OS); void associacarta (Carta &c); void stampacarta (); void aggiornascatti (unsigned int consumo); int durataunita (); friend ofstream &operator << (ofstream &FOS, Utente &u); friend ifstream &operator >> (ifstream &FIS, Utente &u); }; class ListaCarta: public Lista { public: Carta *seekcarta (int codcarta); int numunita (int codcarta); int aggiornaunita (int codcarta, int consumo); }; class ListaUtente: public Lista { public: Utente *seekutente (int codutente); int associacarta (Carta &c, int codutente); void stampacarte (int codutente); int durataunita (int codutente); int seekcarta (int codcarta); };

/* FUNCTION MEMBER BODY CARTA */ Carta::Carta (int codice = 0, int val = 0) { codcarta = codice; valore = val; }

Carta::Carta (Carta& c) { codcarta = c.codcarta; valore = c.valore; } Elem *Carta::crea () { Carta *ptrCarta; ptrCarta = new Carta; return (ptrCarta); } void Carta::assegna (Elem *e) { Carta *ptrCarta; ptrCarta = (Carta *) e; codcarta = ptrCarta -> codcarta; valore = ptrCarta -> valore; } void Carta::print (ostream &OS) { OS << "(" << codcarta << ", " << valore << ")"; } int Carta::numunita () { return (valore); } int Carta::aggiornaunita (int consumo) { if ((valore - consumo) < 0) return (-1); else { valore = valore - consumo; return (0); } } int Carta::getcodcarta () { return (codcarta); } ofstream &operator << (ofstream &FOS, Carta &c) { FOS << c.codcarta << " " << c.valore << endl; return FOS; } ifstream &operator >> (ifstream &FIS, Carta &c) { FIS >> c.codcarta >> c.valore; return FIS; } /* FUNCTION MEMBER BODY UTENTE */ Utente::Utente (int codice = 0, char *n = NULL, unsigned int scatti = 5000, int carta = 0) 227

codutente = codice; strcpy (nome, n); scattitot = scatti; codcarta = carta;

Utente::Utente (Utente &u) { codutente = u.codutente; strcpy (nome, u.nome); scattitot = u.scattitot; codcarta = u.codcarta; } Elem *Utente::crea () { Utente *ptrUtente; ptrUtente = new Utente; return (ptrUtente); } void Utente::assegna (Elem *e) { Utente *ptrUtente; ptrUtente = (Utente *) e; codutente = ptrUtente -> codutente; strcpy (nome, ptrUtente -> nome); scattitot = ptrUtente -> scattitot; codcarta = ptrUtente -> codcarta; } void Utente::print (ostream &OS) { OS << "("; OS << nome; OS << "", ""; OS << scattitot; OS << ')'; OS << endl; } void Utente::associacarta (Carta &c) { codcarta = c.getcodcarta (); } void Utente::stampacarta () { cout << " " << codcarta; } void Utente::aggiornascatti (unsigned int consumo) { scattitot = scattitot + consumo; } int Utente::durataunita () { if (scattitot < 100) return (1); else if (scattitot < 1000) return (2);

else return (3); } ofstream &operator << (ofstream &FOS, Utente &u) { FOS << u.codutente << " " << u.nome << " " << u.scattitot << " " << u.codcarta << endl; return FOS; } ifstream &operator >> (ifstream &FIS, Utente &u) { FIS >>u.codutente >> u.nome >> u.scattitot >> u.codcarta; return FIS; } /* FUNCTION MEMBER BODY LISTACARTA */ Carta *ListaCarta:: seekcarta (int codcarta) { Carta *ptrCarta; ptrCarta = (Carta *) current ->dato; while ((ptrCarta != NULL) && (ptrCarta ->codcarta != codcarta)) { avanza (); ptrCarta = (Carta *) current -> dato; } return (ptrCarta);

int ListaCarta::numunita (int codcarta) { Carta* ptrCarta; inizio (); if ((ptrCarta = seekcarta (codcarta)) != NULL) return (ptrCarta->numunita ()); else return (-1); // codice non trovato } int ListaCarta::aggiornaunita (int codcarta, int consumo) { Carta * ptrCarta; inizio (); if ((ptrCarta = seekcarta (codcarta)) != NULL) return (ptrCarta ->aggiornaunita (consumo)); else return (-2); // codice non trovato } /* FUNCTION MEMBER BODY LISTAUTENTE */ Utente *ListaUtente::seekutente (int codutente) { Utente *ptrUtente; ptrUtente = (Utente *) current -> dato; while ((ptrUtente != NULL) && (ptrUtente -> codutente != codutente)) { avanza (); ptrUtente = (Utente *) current -> dato; } return (ptrUtente); 229

} int ListaUtente::associacarta (Carta &c, int codutente) { Utente *ptrUtente; inizio (); if ((ptrUtente = seekutente (codutente)) != NULL) { ptrUtente ->associacarta (c); return (0); } else return (-1); //codice non trovato } void ListaUtente::stampacarte (int codutente) { Utente *ptrUtente; inizio (); if ((ptrUtente = seekutente (codutente)) != NULL) { cout << "Elenco codice carte: " << endl; while (ptrUtente != NULL) { ptrUtente ->stampacarta (); avanza (); ptrUtente = seekutente (codutente); } cout << endl; } else cout << "Utente non esistente" << endl;

int ListaUtente::durataunita (int codutente) { Utente *ptrUtente; inizio (); if ((ptrUtente = seekutente (codutente)) != NULL) return (ptrUtente ->durataunita ()); else return (-1);

int ListaUtente::seekcarta (int codcarta) { Utente *ptrUtente; ptrUtente = (Utente *) current -> dato; while ((ptrUtente != NULL) && (ptrUtente -> codcarta != codcarta)) { avanza (); ptrUtente = (Utente *) current -> dato; } if (ptrUtente != NULL) return (ptrUtente -> codutente); else return (-1);

// cartetel.cpp #include #include #include #include <stdlib.h> <stdio.h> <conio.h> "classi.hpp"

void inizializza (); int leggicarta (); int leggiutente (); int leggiconsumo (); Carta *creacarta (); void fine (); int numcarte; ListaCarta lcarte; ListaUtente lutenti; main () { Carta *ptrCarta; Utente *ptrUtente; int risp, codcarta, codutente, consumo, nunita, durata; inizializza (); risp = 0; while (risp != 6) { clrscr; cout << "1) Unit disponibili\n"; cout << "2) Aggiornamento unit\n"; cout << "3) Associazione Cliente - Carta\n"; cout << "4) Stampa carte di Cliente\n"; cout << "5) Durata unit\n"; cout << "6) Fine" << endl; cin >> risp; switch (risp) { case 1: codcarta = leggicarta (); nunita = lcarte.numunita (codcarta); if (nunita != -1) cout << "Unit di comunicazioni disponibili: " << nunita << endl; else cout << "Carta non esistente" << endl; break; case 2: codcarta = leggicarta (); consumo = leggiconsumo (); lutenti.inizio (); if ((codutente = lutenti.seekcarta (codcarta)) != -1) { if (lcarte.aggiornaunita (codcarta, consumo) == -1) cout << "Scatti totali non sufficenti" << endl; else { lutenti.inizio (); while ((ptrUtente = lutenti.seekutente (codutente)) != NULL) { ptrUtente -> aggiornascatti (consumo); lutenti.avanza (); } cout << "Aggiornamento effettuato " << endl; } } else cout << "Carta non esistente" << endl; break; case 3: ptrCarta = creacarta (); codutente = leggiutente (); lutenti.inizio (); ptrUtente = lutenti.seekutente (codutente); 231

if (ptrUtente != NULL) { lcarte.push (ptrCarta); lutenti.push (ptrUtente); lutenti.associacarta (*ptrCarta, codutente); cout << "Associamento effettuato" << endl; } else { numcarte --; delete (ptrCarta); cout << "Cliente non esistente" << endl; } break; case 4: codutente = leggiutente (); lutenti.stampacarte (codutente); break; case 5: codutente = leggiutente (); if ((durata = lutenti.durataunita (codutente)) != -1) cout << "Durata di una unit: " << durata << "sec." << endl; else cout << "Utente non esistente" << endl; } cout << endl << endl; } fine (); } void inizializza () { ifstream fcarte, futenti; Carta *ptrCarta; Utente *ptrUtente; fcarte.open ("Carte.dat"); if (! fcarte) { perror("File CARTE.DAT non aperto\n"); exit (-1); } futenti.open ("Utenti.dat"); if (! futenti) { perror ("File UTENTE.DAT non aperto\n"); exit (-1); } fcarte >> numcarte; while (fcarte.eof() == 0) { ptrCarta = new Carta; fcarte >> *ptrCarta; lcarte.push (ptrCarta); } ptrCarta = (Carta *) lcarte.pop (); while (futenti.eof() == 0) { ptrUtente = new Utente ; futenti >> *ptrUtente; lutenti.push (ptrUtente); } ptrUtente = (Utente *) lutenti.pop (); fcarte.close (); futenti.close ();

int leggicarta () { int cod;

cout << "Codice carta: "; cin >> cod; return (cod);

int leggiutente () { int cod; cout << "Codice utente: "; cin >> cod; return (cod);

int leggiconsumo () { int scatti; cout << "Scatti consumati: "; cin >> scatti; return (scatti);

Carta * creacarta () { Carta *ptrCarta; numcarte ++; ptrCarta = new Carta (numcarte, 5000); return (ptrCarta);

void fine () { ofstream fcarte, futenti; Utente *ptrUtente; Carta *ptrCarta; fcarte.open ("Carte.dat"); futenti.open ("Utenti.dat"); fcarte << numcarte << endl; ptrCarta = (Carta *) lcarte.pop (); while (ptrCarta != NULL) { fcarte << *ptrCarta; ptrCarta = (Carta *) lcarte.pop (); } ptrUtente = (Utente *) lutenti.pop (); while (ptrUtente != NULL) { futenti << *ptrUtente; ptrUtente = (Utente *) lutenti.pop (); } fcarte.close (); futenti.close (); }

15.4 Classi di IPC


15.4.1 semafori : La seguente libreria contiene la classe per la gestione dei semafori. Con questa classe si cercato di rendere facile lutilizzo dei semafori senza penalizzare la qualit e il numero delle operazioni. Funzioni particolarmente complicate sono lallocazione dei semafori e le funzioni di controllo. Lallocazione avviene nel costruttore e lutente deve specificare il numero di
233

semafori e la modalit di allocazione tramite i parametri Num e mod del costruttore. Se nessun parametro specificato allora si intende che si vuole creare un solo semaforo in modalit esclusiva. Se viene specificato il parametro Num allora viene creato un insieme di Num semafori (da 0 a Num-1) in modalit esclusiva. Non detto per che la creazione esclusiva avvenga al primo tentativo, perci il costruttore prevede 10 tentativi. Come funzione di controllo sono previste le seguenti operazioni: 1. void impostasem (int Numsem, int Valore): imposta il semaforo specificato da Numsem al valore del parametro Valore 2. int valsem (int Numsem): restiruisce il valore del semaforo specificato da Numsem 3. void waitsem (int Numsem), signalsem (int Numsem): svolgono le funzioni di wait e signal tipiche dei semafori e sono riferiti al semaforo specificato dal parametro Numsem Linsieme dei semafori automaticamente deallocato dal distruttore.
# ifndef __MYSEM # define __MYSEM #include #include #include #include #include <sys/ipc.h> <sys/sem.h> <sys/types.h> <unistd.h> <errno.h>

class Semafori { union semun argomenti; struct sembuf operazione; int chiave; public: Semafori (int Num, int Valore, char *s); Semafori (int Num, char *s); int impostasem(int Numsem, int Valore); int valsem (int Numsem); int waitsem (int Numsem); int signalsem(int Numsem); ~Semafori (); FUNCTION MEMBER BODY SEMAFORI */

/*

Semafori::Semafori (int Num, int Valore, char *s) { int c,i; c = 101; while (((chiave = semget (ftok (s,c),Numsem, IPC_CREAT|IPC_EXCL|0600)) < 0 ) && (c < 110 )) c++; if (chiave < 0 ) { printf ("Inserisci un numero:"); scanf("%d/n", &c); if ((chiave = semget (ftok (s, c), 1, IPC_CREAT|IPC_EXCL|0600)) < 0) printf ("Impossibile creare SEMAFORI !/n"); } for (i=0; i<Num; i++) { argomenti.val = Valore; if (semctl (chiave, i,SETVAL, argomenti) < 0) return(-1); return(0); }

Semafori::Semafori (int Numsem=1, char *s) { int c; c = 101; while (((chiave = semget (ftok (s,c),Numsem, IPC_CREAT|IPC_EXCL|0600)) < 0 ) && (c < 110 )) c++; if (chiave < 0 ) { printf ("Inserisci un numero:"); scanf("%d/n", &c); if ((chiave = semget (ftok ("mysem.h, c), 1, IPC_CREAT|IPC_EXCL|0600)) < 0) printf ("Impossibile creare SEMAFORI !/n"); } } int Semafori::impostasem (int Numsem, int Valore) { argomenti.val = Valore; if (semctl (chiave, Numsem ,SETVAL, argomenti) < 0) return(-1); return(0); } int Semafori::valsem (int Numsem) { int valore; if ((valore = semctl (chiave, Numsem, GETVAL, argomenti)) < 0) return(-1); return (valore); (int Numsem) = -1; = Numsem; = SEM_UNDO;

int Semafori::waitsem { operazione.sem_op operazione.sem_num operazione.sem_flg

if (semop (chiave, &operazione, 1) < 0) return(-1); return(0); } int Semafori::signalsem { operazione.sem_op = operazione.sem_num = operazione.sem_flg = (int Numsem) 1; Numsem; SEM_UNDO;

if (semop (chiave, &operazione, 1) < 0) return(-1); return(0);

int Semafori::donesem () { if (semctl (chiave, 0, IPC_RMID, NULL) < 0) return(-1); return(0); }

235

Per capire a fondo il funzionamento consideriamo un semplice esempio. Il processo padre genera tre figli che devono essere in grado di usare esclusivamente una risorsa, quindi quando un processo la usa gli altri non la possono ottenere. In questo caso lutilizzo di una risorsa simulata da unattesa.
#include <ostream.h> #include <stdio.h> #include <sys/wait.h> #include "mysem.C" #define RIS 0 #define ATTESA 5 void errore (char *s); main () { Semafori S(1); union wait status; int f1, f2, f3; cout << "Inizio"<<endl; S.impostasem (RIS, 1); if ((f1 = fork ()) < 0) errore ("Errore creazione figlio 1"); if (f1 == 0) { cout << "Figlio 1 ATTIVO" << endl; S.waitsem (RIS); cout << "Figlio 1 usa la risorsa condivisa" << endl; sleep (ATTESA); cout << "Figlio 1 rilascia risorsa condivisa" << endl; S.signalsem (RIS); exit (0); } if ((f2 = fork ()) < 0) errore ("Errore creazione figlio 2"); if (f2 == 0) { cout << "Figlio 2 ATTIVO" << endl; S.waitsem (RIS); cout << "Figlio 2 usa la risorsa condivisa" << endl; sleep (ATTESA); cout << "Figlio 2 rilascia risorsa condivisa" << endl; S.signalsem (RIS); exit (0); } if ((f3 = fork ()) < 0) errore ("Errore creazione figlio 3"); if (f3 == 0) { cout << "Figlio 3 ATTIVO" << endl; S.waitsem (RIS);

cout << "Figlio 3 usa la risorsa condivisa" << endl; sleep (ATTESA); cout << "Figlio 3 rilascia risorsa condivisa" << endl; S.signalsem (RIS); exit (0); } wait (&status); wait (&status); wait (&status); } void errore (char *s) { cout << s <<endl; exit (-1); }

Uno dei possibili risultati che si possono ottenere dallesecuzione : $ Figlio1 ATTIVO Figlio1 usa la risorsa condivisa Figlio2 ATTIVO Figlio3 ATTIVO Figlio1 rilascia risorsa condivisa Figlio2 usa la risorsa condivisa Figlio2 rilascia risorsa condivisa Figlio3 usa la risorsa condivisa Figlio3 rilascia la risorsa condivisa Si vede da questo esempio che quando il figlio 1 usa la risorsa i figli 2 e 3 sono attivi, ma per usarla devono attendere che questa sia rilasciata.

15.4.2 code di messaggi : La classe che gestisce le code di messaggi sfrutta una classe che definisce il messaggio usando come attributi il tipo di messaggio (che sar usato in seguito in ricezione) e il messaggio vero e proprio, che indicizzato da un puntatore a void. Il messaggio pu essere creato passando come parametro al costruttore type e messaggio; esistono inoltre due costruttori che consentono di creare un messaggio passando solamente il tipo del messaggio oppure solo il messaggio (in questo caso il tipo settato a 0). La costruzione della coda di messaggio avviene nel costruttore che non ha bisogno di parametri (esiste anche una versione del costruttore che consente di passare come parametro la chiave per la ftok). Le funzioni implememtate sulla coda sono linvio e la ricezione di due tipi di messaggio (tipo stringa e tipo intero): le funzioni di spedizione utilizzano il dato da trasferire per costruire il messaggio corrispondente e restituiscono un valore che indica se tutto andato bene oppure ci sono stati errori e non stato possibile effettuare la spedizione. Le funzioni di ricezione creano invece un messaggio nel quale scrivono ci che ricevono e restituiscono il dato che hanno ricevuto. Leliminazione della coda avviene nel distruttore.
#ifndef _MYMSGQ_H #define _MYMSGQ_H #define STRINGA 1 #define INTERO 2 237

#define MAXSTR 255 class message { private: long type; void *messaggio; public: message (long, void *); message (long ); message (void *); ~message (); friend class msgqueue; }; class msgqueue { private: int idqueue; public: msgqueue (); msgqueue (int ); ~msgqueue (); int sndstr (char *); char *rcvstr (); int sndint (int ); int *rcvint (); }; #endif #include #include #include #include #include #include <sys/types.h> <sys/msg.h> <sys/ipc.h> <string.h> <iostream.h> "msgqueue.hpp"

#define FLAG 0600|IPC_CREAT message::message (long tipo, void *msg) { type = tipo; messaggio = msg; }; message::message (long tipo) { type = tipo; messaggio = NULL; }; message::message (void *msg) { type = 0; messaggio = msg; }; message::~message () { cout << "Distruttore del messaggio " << this << endl; }; msgqueue::msgqueue () { int c; while (((idqueue = msgget (ftok("msgqueue.hpp", c), FLAG|IPC_EXCL)) < 0) && (c < 255)) c++;

};

if (idqueue < 0) idqueue = -1;

msgqueue::msgqueue (int c) { idqueue = msgget (ftok("msgqueue.hpp", c), FLAG); }; msgqueue::~msgqueue () { msgctl (idqueue, IPC_RMID, NULL); }; int msgqueue::sndstr (char *stringa) { int ritorno, lunghezza; message nuovo (STRINGA, stringa); lunghezza = strlen (stringa); ritorno=msgsnd(idqueue,(struct msgbuf *)(&nuovo),lunghezza, IPC_NOWAIT); if (ritorno < 0) return (-1); else return (ritorno);

};

char *msgqueue::rcvstr () { message nuovo(STRINGA); int ritorno; ritorno=msgrcv(idqueue,(struct msgbuf*)(&nuovo),MAXSTR,STRINGA,IPC_NOWAIT); if (ritorno < 0) return (NULL); else return ((char *)nuovo.messaggio); }; int msgqueue::sndint (int dato) { message nuovo (INTERO, &dato); int ritorno, size; size = sizeof (int); ritorno = msgsnd (idqueue, (struct msgbuf *)(&nuovo), size, IPC_NOWAIT); if (ritorno < 0) return (-1); else return (ritorno); }; int *msgqueue::rcvint () { message nuovo (INTERO); int ritorno, size; size = sizeof (int); ritorno = msgrcv(idqueue,(struct msgbuf *)(&nuovo),size,INTERO,IPC_NOWAIT); if (ritorno < 0) return NULL; else return ((int *)nuovo.messaggio); };

15.4.3 memoria condivisa: Questa classe serve per gestire unarea di memoria che puo essere condivisa tra piu processi. I suoi attributi per poter fare cio sono un identificatore di area idmem, un intero che descrive la dimensione dellarea sizemem e lindirizzo di partenza dellarea startaddr che e un puntatore a carattere. La creazione dellarea avviene mediante il costruttore che richiede come parametro la dimensione dellarea che si vuole creare; esiste inoltre un secondo costruttore che richiede come parametro anche ls chiave per la ftok. Lallocazione e la deallocazione dellarea avvengono mediante le funzioni che non richiedono parametri in ingresso. Esistono poi le funzioni di lettura e scrittura leggi e scrivi che usano come parametro un pentatore a carattere. Esistono poi alcune funzioni di gestione che servono per cancellare tutto cio` che e` scritto in memoria, per sapere la dimensione dellarea e per vedere quanti byte sono ancora disponibili sullarea
239

#ifndef _MYMEMC_H #define _MYMEMC_H class memc { private: int idmem; int sizemem; char* startaddr; public: memc (int); memc (int, int); ~memc (); void collega (); int scollega (); char* leggi (); void scrivi (char *); void cancella (); int size (); int libera (); }; #endif #include #include #include #include #include <sys/types.h> <sys/ipc.h> <sys/shm.h> <string.h> "memc.hpp"

#define FLAGS IPC_CREAT|0600 extern "C" { int shmget (key_t, int, int); char *shmat (int, char *, int); int shmdt (char *); int shmctl (int, int, shmid_ds *); }; memc::memc (int s) { int c = 0; sizemem = s; while (((idmem=shmget(ftok("memc.hpp",c), sizemem, FLAGS|IPC_EXCL )) < 0) && (c < 255)) c++; startaddr = NULL; }; memc::memc (int s, int c) { sizemem = s; idmem = shmget (ftok ("memc.hpp", c), sizemem, FLAGS); startaddr = NULL; }; memc::~memc () { shmctl (idmem, IPC_RMID, NULL); }; void memc::collega () { if ((startaddr = shmat (idmem, 0, 0600)) == (char *)-1) startaddr = NULL; }; int memc::scollega ()

{ return (shmdt (startaddr)); }; char *memc::leggi () { return (startaddr); }; void memc::scrivi (char *s) { strcat (startaddr, s); }; void memc::cancella () { strcpy (startaddr, "\0"); }; int memc::size () { return (sizemem); }; int memc::libera () { int lunga, vuota; lunga = strlen (startaddr); vuota = sizemem - lunga; return (vuota); };

Il programma che segue e` stato implementato per analizzare il funzionamento delle classi per gestire la memoria condivisa e le code di messaggi.
#include #include #include #include #include #include <iostream.h> <unistd.h> <sys/types.h> <sys/wait.h> "memc.C" "msgqueue.C"

void main() { int pid, pid1, pid2, i; memc memory (1024); msgqueue coda; int piena; union wait status; char *stringa; int value, *dato; if ((pid1 = fork ()) < 0) { cout << "Errore nella fork" << endl; exit (1); } else if (pid1 == 0) // Codice del primo figlio { cout << "Sono il primo figlio" << endl; memory.collega(); memory.scrivi("Sono il primo figlio"); memory.scollega (); if ((value = coda.sndstr ("Sono il primo figlio")) < 0) cout << "Errore in spedizione" << endl; if ((dato = coda.rcvint ()) == NULL) cout << "Errore in ricezione" << endl; else cout << "Ho ricevuto " << &dato << endl; exit (0); } else // Codice del padre { if ((pid2 = fork ()) < 0) { cout << "Errore nella fork" << endl; 241

exit (1); } else if (pid2 == 0) // Codice del secondo figlio { cout << "Sono il secondo figlio" << endl; memory.collega(); do piena = memory.libera(); while (piena != memory.size()); stringa = memory.leggi(); cout << "Secondo figlio. Ho letto : " << stringa << endl; memory.scollega(); if ((value = coda.sndint(getpid())) < 0) cout << "Errore in spedizione" << endl; if ((stringa = coda.rcvstr()) == NULL) cout << "Errore in ricezione" << endl; else cout << "Ho ricevuto : "<< stringa << endl; exit (0); } else // Codice del padre { memory.collega (); for (i = 0; i < 2; i++) wait (&status); memory.cancella (); memory.scollega (); exit (0); } } }

15.4.4 socket: Con questa libreria possibile gestire facilmente lo scambio di qualsiasi tipo di oggetto o dato statico tra due processi sulla stessa macchina o su macchine diverse. Le macchine sono identificate da un indirizzo (es. cclinf.polito.it) e i processi da un numero di porta (es. 7000). La trasmissione di dati tra due processi realizzata dalla classe socket_base che contiene dati e metodi per linizializzazione, gestione e distruzione o deallocazione di socket UNIX. Descrizione dati : int porta contiene il numero di porta del processo int sock contiene lidentificatore del socket adoperato per la comunicazione del processo client int newsock : contiene lidentificatore del socket adoperato per la comunicazione del processo server struct sockaddr_in rsock : struttura formata dai campi : sin_addr contiene lindirizzo sin_family specifica la famiglia di appartenza del socket che in questo caso AF_INET. sin_port contiene il nnumero di porta Questo dato adoperato per effettuare le connessioni e contiene lindirizzo e il numero di porta del processo destinazione. struct hostent *hostinfo : puntatore al record che mantiene informazioni circa lhost struct in_addr *addp : usato per maneggiare gli indirizzi, ad es. per ricavare lindirizzo dalle informazioni contenute in hostinfo char hostname : contiene il nome dellhost int lung_coda : contiene la lunghezza della coda di attesa del socket, ovvero il

numero massimo di richieste accodabili. Nel nostro caso impostato a 10 struct sockaddr_in sockname : come rsock ma in questo caso usato per linizializzazione del socket e contiene i dati (indirizzo e numero di porta) del processo trasmittente t_flag_err flag_err : specifica se c stato errore durante lultima operazione eseguita. Pu assumere i seguenti valori : OK operazione effettuata correttamente ERROR operazione fallita Descrizione metodi : per buona norma cominciamo dal costruttore che permette di creare un socket e di inizializzarlo effettuando la bind e la listen se il processo il server. Il socket creato in modalit connection oriented ed appartiene alla famiglia AF_INET. Per poter inizializzare un socket bisogna specificare al costruttore il tipo di processo (se client o server) che intende usare tale socket. Inoltre bisogna specificare lindirizzo sotto forma di stringa e il numero di porta sotto forma di intero o stringa. Esempio : socket_base rtl1(client, cclinf.polito.it, 7654) ; oppure semplicemente socket_base rtl2(server) ; t_flag_err connect_f () : effettua loperazione di connect allindirizzo descritto in rsock. Restituisce : OK operazione eseguita correttamente ERROR operazione fallita void accept_f () : esegue loperazione di accept del server sul socket sock e pone in newsock lidentificatore del socket su cui il server pu comunicare con il processo richiedente del servizio void disconnect () : chiude la comunicazione su newsock int ricevi (unsigned char *oggetto, int dimensione) : permette di ricevere dal processo comunicante un qualunque dato con dimensione specificata da dimensione. Il parametro oggetto il puntatore al dato ricevuto. Restituisce : 0 operazione corretta -1 operazione fallita int spedisci (const unsigned char *oggetto, int dimensione) : permette di spedire a processo comunicante un qualunque dato con dimensione specificata da dimensione. Il parametro oggetto puntatore al dato da spedire. Restituisce : 0 operazione corretta -1 operazione fallita t_flag_err get_err () : restituisce il valore di flag_err. socket_base& operator< (char *) : permette di trasmettere al processo comunicante una stringa terminata da \0 socket_base& operator> (char *) : permette di ricevere dal processo comunicante una stringa terminata da \0 socket_base () : distruttore che chiude il socket sock
#ifndef SOCKET_BASE_H #define SOCKET_BASE_H #include <stdlib.h> #include "definiz.h" #include "tipi.h" #include <unistd.h> 243

#include "/usr/include/sys/types.h" #include "/usr/include/sys/un.h" #include "/usr/include/netdb.h" #include "/usr/include/netinet/in.h" #include "/usr/include/arpa/inet.h" #include "/usr/include/sys/socket.h" #include "/usr/include/sys/wait.h" //enum t_cli_serv {client_server}; class socket_base { protected: int porta; int sock,newsock; struct sockaddr_in rsock; struct hostent *hostinfo; struct in_addr *addp; char hostname[DIM_HOST_NAME]; int lung_coda; struct sockaddr_in sockname; t_flag_err flag_err; void init_connection(); void init_socket(); public: socket_base(t_cli_serv cli_serv,char * arg1,int port); socket_base(t_cli_serv cli_serv,char * arg1,char * port); socket_base(t_cli_serv cli_serv); ~socket_base(); t_flag_err connect_f(); void accept_f(); void disconnect(); inline int spedisci(const unsigned char * oggetto_punt,int dimension);

inline int ricevi(unsigned char * oggetto_punt,int dimension); socket_base& operator <(char *);//invio sulla rete di una // stringa terminata da \0 socket_base& operator >(char *);//lettura dalla rete di una // stringa terminata da \0 t_flag_err get_err(); // funzione per rivelare se ci sono // stati errori in fase di connessione }; #endif #include "socket_base.h" socket_base::socket_base(t_cli_serv cli_serv,char * arg1,int arg2) { strcpy(hostname,arg1); /* utilizzare parametri di default*/ cout<<hostname<<endl; porta=arg2; if (cli_serv==client) { }/*fine if*/ else if (cli_serv==server) { lung_coda=10; init_socket();

}/*fine else if */ }/*fine costruttore*/ socket_base::socket_base(t_cli_serv cli_serv,char * arg1,char * arg2) { strcpy(hostname,arg1); /* utilizzare parametri di default*/ porta=atoi(arg2); if (cli_serv==client) { }/*fine if*/ else if (cli_serv==server) { lung_coda=10; init_socket(); }/*fine else if */ }/*fine costruttore*/ socket_base::socket_base(t_cli_serv cli_serv) { if (cli_serv==client) { strcpy(hostname,"cclinf.polito.it"); porta=NUM_PORTA; }/*fine if*/ else if (cli_serv==server) { lung_coda=10; porta=NUM_PORTA; init_socket(); }/*fine else if*/ }/*fine costruttore*/ void socket_base::init_connection () { memset ((char *)&rsock,0,sizeof(rsock)); if ((hostinfo=gethostbyname(hostname))==NULL) { cerr<<"CLIENT:non collego l'host :"; flag_err=ERROR; //exit(1); }/*fine cond_error*/ newsock=socket(AF_INET,SOCK_STREAM,0); if(newsock==-1) { cerr<<"CLIENT:Socket non creato:"; flag_err=ERROR; exit(1); }/*fine err*/ addp=(struct in_addr *)*(hostinfo->h_addr_list); rsock.sin_addr=*addp; rsock.sin_family=AF_INET; rsock.sin_port=htons(porta); }/*fine init_connection*/ void socket_base::init_socket() { flag_err=OK; memset ((char *)&sockname,0,sizeof(sockname)); if ((sock=socket(AF_INET,SOCK_STREAM,0))==-1) { cerr<<"SERVER:inizializzazione fallita:"; flag_err=ERROR; 245

exit (1); }/*fine if */ sockname.sin_family=AF_INET; sockname.sin_addr.s_addr=INADDR_ANY; sockname.sin_port=htons(porta); if ((bind(sock,(struct sockaddr *)&sockname,sizeof(sockname)))==-1) { close (sock); cerr<<"SERVER:bind fallita:"; flag_err=ERROR; exit(1); }/*fine if*/ listen (sock,lung_coda); }/*fine init_socket*/ void socket_base::accept_f() { newsock=accept(sock,0,0); }/*fine accept*/ t_flag_err socket_base::connect_f() { flag_err=ERROR; init_connection(); if (connect(newsock,(struct sockaddr *)(&rsock),sizeof(rsock))==-1) { cerr<<"CLIENT:non riesco a connettere l'host:"; flag_err=ERROR; }/*fine connect*/ return flag_err; }/*fine connect*/ void socket_base::disconnect() { close (newsock); }/*fine disconnect*/ socket_base::~socket_base() { close (sock); }/*fine distruttore*/ socket_base& socket_base::operator <(char *str){ write(newsock,str,strlen(str)+1); return(*this); } socket_base& socket_base::operator>(char *str){ int i; read(newsock,str,1); for (i=1;str[i-1]!='\0';i++) read(newsock,&str[i],1); return(*this); } int socket_base::ricevi(unsigned char * oggetto_punt,int dimension) { return read (newsock,oggetto_punt,dimension); } int socket_base::spedisci(const unsigned char * oggetto_punt,int dimension) { return write(newsock,oggetto_punt,dimension); }

t_flag_err socket_base::get_err() { return flag_err; }

15.4.5 Esonero concorrente Maggio 96 in C++ Modificando la versione della soluzione dellesonero del 18/5/96 riportata nel capitolo 14 e adattandola a questa libreria otteniamo i seguenti programmi in C++ concorrente processo client
#include #include #include #include <iostream.h> <fcntl.h> "socket_base.cpp" "mytypes.hpp"

void void void void void void void

iniziascontrino (); scriviscontrino (char *nome, unsigned long int prezzo, int quantita); finescontrino (unsigned long int montante); aggiornadb (int codcliente, int codprod, int codoperat, int quantita); nomeprod (int codprod, char *nome); esci (); errore (char *s);

int fscontrino; Intero intdato; Long longdato; socket_base sockserv (client, "", SERVPORT); socket_base sockdb (client, "", DBPORT); void main () { int carta, codbanco, esito, codcliente, quantita, codprod, codoperat, prova, continua, risp; unsigned long int prezzo, ammontare; char nome [MAXSTR]; MexBanco Messbanco; cout <<"Codice operatore: "; cin >> codoperat; continua = 1; while (continua == 1) { ammontare = 0; iniziascontrino (); cout << "Codice cliente: "; cin >> codcliente; cout << "Inserimento lista prodotti (0 per terminare):\n"; cout << "Codice prodotto: "; cin >> codprod; while (codprod != 0) /* Lettura prodotti comprati */ { cout << "Quantita': "; cin >> quantita; sockserv.connect_f(); intdato.set(1); sockserv.spedisci ((const unsigned char *) &intdato, sizeof 247

(Intero)); sockserv.spedisci ((const unsigned char *) &codprod, sizeof (int)); sockserv.ricevi ((unsigned char *)&prezzo, sizeof (Lint)); cout << "PROVA4\n"; cout << "Prezzo " << prezzo << endl; if (prezzo != 0) { ammontare = ammontare + (prezzo * quantita); cout << "PROVA 5\n"; aggiornadb (codcliente, codprod, codoperat, quantita); nomeprod (codprod, nome); cout << "PROVA 6\n"; scriviscontrino (nome, prezzo, quantita); } else cerr << "Errore: Connessione fallita con DATA BASE"; sockserv.disconnect (); cout << "Codice prodotto: "; cin >> codprod; } finescontrino (ammontare); cout << "1) Pagamento con BANCOMNAT\n"; cout << "2) Pagamento CONTANTE\n"; cin >> risp; if (risp == 1) /* Pagamento con bancomat */ { prova = 0; while (prova < 3) { Messbanco.read (ammontare); sockserv.connect_f (); intdato.set (2); sockserv.spedisci ((const unsigned char *) &intdato, sizeof sockserv.spedisci ((const unsigned char *) &Messbanco, sizeof sockserv.ricevi ((unsigned char *) &esito, sizeof (int)); sockserv.disconnect (); if (esito == 0) { cout << "Pagamento avvenuto correttamente\n"; prova = 4; } else if (esito == -1) { cout << "Montante non disponibile\n"; prova = 4; } else if (esito == -3) { cout << "Collegamento fallito\n"; prova = 4; } else { prova ++; cout << "Codice errato\n"; esito = -4; } } /* while (prova < 3) */ if (esito != 0) cout << "Pagamento in contanti\n"; } /* if (risp == 1) */ cout << "1) Ripeti\n"; cout << "2) Fine\n"; cin >> continua;

(Intero)); (MexBanco));

void iniziascontrino () { if ((fscontrino = open ("scontrino.txt", O_CREAT | O_WRONLY |O_TRUNC, 0666)) < 0) errore ("Errore: Apertura file SCONTRINO.TXT"); } void scriviscontrino (char *nome, unsigned long int prezzo, int quantita) { write (fscontrino, nome, MAXSTR); write (fscontrino, &prezzo, sizeof (long int)); write (fscontrino, &quantita, sizeof (int)); } void finescontrino (unsigned long int montante) { char nome[MAXSTR]; unsigned long int prezzo; int quantita; close (fscontrino); if ((fscontrino = open ("scontrino.txt", O_RDONLY, 0666)) < 0) errore ("Errore: Apertura file SCONTRINO.TXT"); cout << "\n\nSUPERMERCATI Sbiroli & Co\n"; cout << "c.so Tassoni 55 TORINO\n"; cout << " p.iva 02709550014\n\n"; while (read (fscontrino, nome, MAXSTR) > 0) { read (fscontrino, &prezzo, sizeof (long int)); read (fscontrino, &quantita, sizeof (int)); cout << nome << " " << prezzo << endl; if (quantita > 1) cout << " x " << quantita << endl; else cout << "\n"; } cout << "\nTOTALE: "<< montante <<" lire\n\n "; cout << "Arrivederci e GRAZIE!\n\n\n"; close (fscontrino); } void aggiornadb (int codc, int codp, int codop, int quant) { Mexdb Messdb (codc, codp, codop, quant); sockdb.connect_f (); intdato.set (2); sockdb.spedisci ((const unsigned char *) &intdato, sizeof (Intero)); sockdb.spedisci ((const unsigned char *) &Messdb, sizeof (Mexdb)); sockdb.disconnect (); } void nomeprod (int codprod, char *nome) { Stringa strdato; if (sockdb.connect_f () == ERROR) strcpy (nome, "Prodotto"); else { intdato.set (3); sockdb.spedisci ((const unsigned char *) &intdato, 249

sizeof (Intero)); sockdb.spedisci ((const unsigned char *) &codprod, sizeof (int)); sockdb.ricevi ((unsigned char *) &strdato, sizeof (Stringa)); sockdb.disconnect (); strcpy (nome, strdato.get ()); } }

void errore (char *s) { cerr << s; exit (-1); }

processo server
#include #include #include #include #include <iostream.h> "socket_base.cpp" "mytypes.hpp" <sys/wait.h> <signal.h>

#define ATTESA 15 void esci (int cod); int allarme; void main () { union wait status; int codprod, oper, pid, esito; Lint prezzo; Intero intdato; Long longdato; socket_base sock (server, "", SERVPORT); socket_base sockdb (client, "", DBPORT); socket_base sockbanca (client, "", BANCPORT); MexBanco Messbanco; cout << "SERVER ATTIVO\n"; while (1) { sock.accept_f (); if (( pid = fork ()) < 0) cerr << "Errore: Creazione figlio fallita"; if (pid == 0) /* Codice figlio */ { sock.ricevi ((unsigned char *)& intdato, sizeof (Intero)); /* riceve identificatore dell' operazione */ switch (intdato.get ()) /* da svolgere */ { case 1: sock.ricevi ((unsigned char *) &codprod, sizeof (int)); if (sockdb.connect_f () == ERROR) { prezzo = 0; cout << "Prezzo " << prezzo<< endl; } else

{ (Intero)); (int));

cout << "Invio al database" << endl; intdato.set (1); sockdb.spedisci ((const unsigned char *) &intdato, sizeof sockdb.spedisci ((const unsigned char *) &codprod, sizeof sockdb.ricevi ((unsigned char *) &prezzo, sizeof (Lint)); sockdb.disconnect ();

} (Lint));

sock.spedisci ((const unsigned char *) &prezzo, sizeof break; case 2: /* Pagamento con bancomat */ sock.ricevi ((unsigned char *) &Messbanco, sizeof

(MexBanco));

signal (SIGALRM, esci); allarme = 0; alarm (ATTESA); while ((allarme == 0) && (sockbanca.connect_f () == ERROR)); if (allarme == 1) { cerr << "ERRORE: Connessione fallita con BANCA"; esito = -3; } else { sockbanca.spedisci ((const unsigned char *)& Messbanco, sizeof (MexBanco)); sockbanca.ricevi ((unsigned char *) &esito, sizeof (int)); sockbanca.disconnect (); } sock.spedisci ((const unsigned char *) &esito, sizeof (int)); break; } /*switch */ sock.disconnect (); exit (0); } /* if (pid = 0)*/ sock.disconnect (); while (wait3 (&status, WNOHANG, NULL) > 0); } /* while (1) */ }

void esci (int code) { allarme = 1; } processo database #include #include #include #include #include <iostream.h> "socket_base.cpp" "mytypes.hpp" <sys/wait.h> <fcntl.h>

#include <stdio.h> long int determinaprezzo (int codprod); void aggiorna (Mexdb Messdb, int data); int giornoanno (int giorno, int mese, int anno); 251

void nomeprodotto (int codprod, char *nome); void errore (char *s); void main () { int codprod, oper, pid, pid1, data, giorno, mese, anno; char nome[MAXSTR]; Lint prezzo; socket_base sock (server, "", DBPORT); Mexdb Messdb; Intero intdato; Stringa strdato; union wait status; cout << "Inserisci data (gg mm aaa): "; cin >> giorno >> mese >> anno; data = giornoanno (giorno, mese, anno); cout << "BASE DATI ATTIVA\n"; if ((pid1 = fork ()) < 0) errore ("Errore: Creazione figlio fallita"); if (pid1 == 0) /* Figlio delle richieste */ { execl ("richiest", "richiest",(char *)0); errore ("Errore: In esecuzione processo richieste"); } while (1) { sock.accept_f (); if ((pid = fork ()) < 0) perror ("Errore: Creazione figlio fallita"); if (pid == 0) { sock.ricevi ((unsigned char *) &intdato, sizeof (Intero)); oper = intdato.get (); switch (oper) { case 1: /* Richiesta prezzo */ sock.ricevi ((unsigned char * ) &codprod,sizeof (int));; prezzo = determinaprezzo (codprod); sock.spedisci ((const unsigned char *) &prezzo, sizeof (Lint)); break; case 2: /* Aggiorna data base */ sock.ricevi ((unsigned char *) &Messdb, sizeof (Mexdb)); aggiorna (Messdb, data); break; case 3: /* Richiesta nome */ sock.ricevi((unsigned char *) &codprod, sizeof (int)); nomeprodotto (codprod, nome); strdato.set (nome); sock.spedisci ((const unsigned char *) &strdato, sizeof (Stringa)); break; } /* switch */ sock.disconnect (); exit (0); } /* if (pid == 0) */ sock.disconnect (); while (wait3 (&status, WNOHANG, NULL) > 0); } /* while (1) */ } long int determinaprezzo (int codprod) { int fprodotti, endf; recprod rprod; if ((fprodotti = open ("prodotti.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT");

lseek (fprodotti, 0, SEEK_SET); endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf == 0) /* Non trovato */ return (0); else /* Trovato */ return (rprod.prezzo); } void aggiorna (Mexdb Messdb, int data) { int fvenduto, fprodotti, endf; recvenduto rvenduto; recprod rprod; /* Aggiornamento file VENDUTO.DAT */ if ((fvenduto = open ("venduto.dat", O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) errore ("Errore Aggiorna: Impossibile aprire file VENDUTO.DAT"); rvenduto = Messdb.retvenduto (data); lseek (fvenduto, 0, SEEK_END); /*Sposta alla fine file*/ lockf (fvenduto, F_LOCK, sizeof (recvenduto)); write (fvenduto, &rvenduto, sizeof (recvenduto)); lockf (fvenduto, F_ULOCK, -sizeof (recvenduto)); close (fvenduto); /* Aggiornamento file PRODOTTI.DAT */ if ((fprodotti = open ("prodotti.dat", O_RDWR, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT"); lseek (fprodotti, 0, SEEK_SET); /* Sposta inizio file */ endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != rvenduto.codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf != 0) { rprod.quantita = rprod.quantita - rvenduto.quantita; lseek (fprodotti, -sizeof (recprod), SEEK_CUR); lockf (fprodotti, F_LOCK, sizeof (recprod)); write (fprodotti, &rprod, sizeof (recprod)); lockf (fprodotti, F_ULOCK, -sizeof (recprod)); } close (fprodotti); } int giornoanno (int giorno, int mese, int anno) { static char daytab [2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap = (((anno % 4) == 0) && ((anno % 100) != 0) || ((anno % 400) == 0)); for (i = 1; i < mese; i++) giorno += daytab [leap][i]; return (giorno); } void nomeprodotto (int codprod, char *nome) { int fprodotti, endf; 253

recprod rprod; if ((fprodotti = open ("prodotti.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file PRODOTTI.DAT"); lseek (fprodotti, 0, SEEK_SET); endf = read (fprodotti, &rprod, sizeof (recprod)); while ((endf != 0) && (rprod.codprod != codprod)) endf = read (fprodotti, &rprod, sizeof (recprod)); if (endf == 0) /* Non trovato */ strcpy (nome, "ARTIC"); else /* Trovato */ strcpy (nome, rprod.nome); } void errore (char *s) { cerr << s; exit (-1); }

processo richiest
#include <stdio.h> #include <fcntl.h> #include "mytypes.hpp" int giornoanno (int giorno, int mese, int anno); int cercavendutoda (int idfile, int codoperat, int *quantita, int data); int cercavendutoa (int idfile, int codoperat, int * quantita, int datai, int dataf); void errore (char *s); void main () { int risp, codoper, codcliente, giorno, mese, anno, datai, dataf, npezzi, quantita; int fvenduto; cout << "Inizio Processo RICHIESTE\n"; do { cout << "1) Venduto per cassiera\n"; cout << "2) Veduto per cliente\n"; cout << "3) Fine\n"; cin >> risp; switch (risp) { case 1: npezzi = 0; cout << "Codice operatore: "; cin >> codoper; cout << "Inserisci data: (gg mm aa)"; cin >> giorno >> mese >> anno; datai = giornoanno (giorno, mese, anno); if ((fvenduto = open ("venduto.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file VENDUTO.DAT"); lseek (fvenduto, 0, SEEK_SET); while (cercavendutoda (fvenduto, codoper, &quantita, datai) != npezzi = npezzi + quantita; cout << "Numero pezzi venduti: " << npezzi << endl; break; case 2: npezzi = 0;

0)

dataf) != 0)

cout << "Codice cliente: "; cin >> codcliente; cout << "Data inizio (gg mm aa): "; cin >> giorno >> mese >> anno; datai = giornoanno (giorno, mese, anno); cout << "Data fine (gg mm aa): "; cin >> giorno >> mese >> anno; dataf = giornoanno (giorno, mese, anno); if ((fvenduto = open ("venduto.dat", O_RDONLY, 0666)) < 0) errore ("Errore: Impossibile aprire file VENDUTO.DAT"); lseek (fvenduto, 0, SEEK_SET); while (cercavendutoa (fvenduto, codcliente, &quantita, datai,

npezzi = npezzi +quantita; cout << "Numero pezzi venduti: " << npezzi << endl; break; } /* switch (risp) */ } while (risp != 3); cout << "Fine processo RICHIESTE\n"; exit (0);

int giornoanno (int giorno, int mese, int anno) { static char daytab [2][13] = { {0,31,28,31,30,31,30,31,31,30,31,30,31}, {0,31,29,31,30,31,30,31,31,30,31,30,31} }; int i, leap; leap = (((anno % 4) == 0) && ((anno % 100) != 0) || ((anno % 400) == 0)); for (i = 1; i < mese; i++) giorno += daytab [leap][i]; return (giorno); } int cercavendutoda (int idfile, int codoperat, int *quantita, int data) { recvenduto rvenduto; int endf; endf = read (idfile, &rvenduto, sizeof (recvenduto)); while ( (endf != 0) && ((rvenduto.codoperat != codoperat) || (rvenduto.data != data))) endf = read (idfile, &rvenduto, sizeof (recvenduto)); *quantita = rvenduto.quantita; return (endf); } int cercavendutoa (int idfile, int codcliente, int *quantita, int datai, int dataf) { recvenduto rvenduto; int endf; endf = read (idfile, &rvenduto, sizeof (recvenduto)); while ((endf != 0) && ((rvenduto.codcliente != codcliente) || (rvenduto.data < datai) || (rvenduto.data > dataf))) endf = read (idfile, &rvenduto, sizeof (recvenduto)); *quantita = rvenduto.quantita; return (endf); }

255

void errore (char *s) { cerr << s; exit (-1); }

processo banca
#include #include #include #include <iostream.h> "socket_base.cpp" "mytypes.hpp" <sys/wait.h>

int pagamento (); void fine (); void main () { int pid; MexBanco Messbanca; union wait status; Intero esito; socket_base sock (server, "", BANCPORT); cout << "BANCA ATTIVA\n"; while (1) { sock.accept_f(); if ((pid = fork ()) < 0) cerr << "Errore: Creazione figlio fallita"; if (pid == 0) { sock.ricevi ((unsigned char *) &Messbanca, sizeof (MexBanco)); esito = pagamento (); sock.spedisci ((const unsigned char *) &esito, sizeof (int)); sock.disconnect(); exit (0); } sock.disconnect(); while (wait3 (&status, WNOHANG, NULL) > 0); } }

int pagamento () { int pagato; cout << "BANCA: Pagamento ? "; cin >> pagato; return (pagato); }

257

16 Esempi di programmazione in Assembler


Procedura Acapo
PUBLIC ACAPO CSEG ACAPO SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG PROC FAR

;insert code here PUSH PUSH MOV MOV INT MOV INT POP POP RET ACAPO ENDP CSEG ENDS END AX DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX AX ; stampa un CR ; stampa un LF

Listing:
PUBLIC 0000 0000 ACAPO

CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG ACAPO ;insert code here PROC FAR

0000 50 0001 52 0002 B4 02 0004 B2 0D 0006 CD 21 0008 B2 0A 000A CD 21

PUSH AX PUSH DX MOV MOV INT MOV INT AH, 2 DL, 0DH 21H DL, 0AH 21H ; stampa un CR ; stampa un LF

000C 5A 000D 58 000E CB 000F 000F

POP POP RET ACAPO ENDP CSEG ENDS END

DX AX

Segments and Groups: Name CSEG . . . . . . . . . . . . . . Symbols: Name ACAPO . . . . . . . . . . . . . Type Value Attr CSEG Global Length = 000F Length Align 000F Combine Class 'CODE'

PARA PUBLIC

F PROC 0000

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT acapo @VERSION . . . . . . . . . . . . TEXT 510 32 Source Lines 32 Total Lines 8 Symbols 47662 + 163677 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Input
; procedura di lettura e conversione di un numero ; il numero letto e decodificato viene lasciato in DX DSEG SEGMENT PARA PUBLIC 'DATA'

ERR_MESS DB 'Numero troppo grande', 0DH, 0AH, '$' DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG INPUT ;ALREADY SET BY DOS LOADER

PUBLIC

INPUT PROC FAR PUSH PUSH LAB0: XOR LAB1: AX BX DX, DX MOV BX, 10 259

MOV INT CMP JE CMP JB CMP JA SUB XCHG MOV MUL CMP JNE MOV ADD JC JMP I_ERR: MOV INT JMP FINE: POP POP RET INPUT CSEG ENDS END

AH, 1 21H AL, 13 FINE AL, '0' LAB1 AL, '9' LAB1 AL, '0' AX, BX BH, 0 DX DX, 0 I_ERR DX, AX DX, BX I_ERR LAB1

; legge un carattere ; e` un CR ? ; se si` fine ; se no controlla che sia un numero

; OK: togli codifica ASCII ; moltiplica per 10

; somma la cifra letta

LEA DX, ERR_MESS AH, 9 21H LAB0 BX AX ;RETURN ENDP

Listing:
TITLE INPUT ; procedura di lettura e conversione di un numero ; il numero letto e decodificato viene lasciato in DX 0000 DSEG SEGMENT PARA PUBLIC 'DATA'

0000 4E 75 6D 65 72 6F ERR_MESS DB 'Numero troppo grande', 0DH, 0AH, ' $' 20 74 72 6F 70 70 6F 20 67 72 61 6E 64 65 0D 0A 24 0017 0000 LOADER PUBLIC 0000 0000 50 0001 53 0002 33 D2 0004 BB 000A INPUT DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG ;ALREADY SET BY DOS

INPUT PROC FAR PUSH AX PUSH BX LAB0: XOR LAB1: MOV DX, DX BX, 10

0007 B4 01 0009 CD 21 000B 3C 0D 000D 74 26 000F 3C 30 0011 72 F1 0013 3C 39 0015 77 ED 0017 2C 30 0019 93 001A B7 00 001C F7 E2 001E 83 FA 00 0021 75 08 0023 8B D0 0025 03 D3 0027 72 02 0029 EB D9 002B 8D 16 0000 R 002F B4 09 0031 CD 21 0033 EB CD 0035 5B 0036 58 0037 CB 0038 0038 FINE:

MOV INT CMP JE CMP JB CMP JA SUB XCHG MOV MUL CMP JNE MOV ADD JC JMP I_ERR: LEA MOV INT JMP POP POP RET INPUT ENDP CSEG ENDS END

AH, 1 21H AL, 13 FINE AL, '0' LAB1 AL, '9' LAB1 AL, '0' AX, BX BH, 0 DX DX, 0 I_ERR DX, AX DX, BX I_ERR LAB1 DX, ERR_MESS AH, 9 21H LAB0 BX AX

; legge un carattere ; e` un CR ? ; se si` fine ; se no controlla che sia un numero

; OK: togli codifica ASCII ; moltiplica per 10

; somma la cifra letta

;RETURN

Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr DSEG CSEG Length Align 0038 0017 Combine Class 'CODE' 'DATA'

PARA PUBLIC PARA PUBLIC

ERR_MESS . . . . . . . . . . . . FINE . . . . . . . . . . . . . .

L BYTE0000 0035

L NEAR

INPUT . . . . . . . . . . . . . F PROC 0000 I_ERR . . . . . . . . . . . . . L NEAR LAB0 . . . . . . . . . . . . . . LAB1 . . . . . . . . . . . . . . L NEAR L NEAR

CSEG Global Length = 0038 002B CSEG 0002 0004 CSEG CSEG

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT input @VERSION . . . . . . . . . . . . TEXT 510 53 Source Lines 53 Total Lines 261

15 Symbols 47660 + 163679 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Output
; procedura di conversione ed output di un numero ; il numero da scrivere viene letto da DX DSEG CBUF DSEG CSEG SEGMENT PARA PUBLIC 'DATA' DB 20 DUP(0) ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,DS:DSEG OUTPUT PROC FAR DI AX BX DX DI, DI AX, DX DX, 0 BX, 10 DIV BX DL, '0' CBUF[DI], DL DI DX, 0 AX, 0 CICLO DI DL, CBUF[DI] AH, 2 21H DI, 0 LAB DL, 13 AH, 2 21H DL, 10 21H DX BX AX DI ; stampa un CR ; stampa un LF

PUBLIC OUTPUT PUSH PUSH PUSH PUSH XOR MOV CONV: MOV MOV CICLO: ADD MOV INC MOV CMP JNE LAB: DEC MOV MOV INT CMP JNE MOV MOV INT MOV INT POP POP POP POP

RET OUTPUT CSEG ENDS END

ENDP

;RETURN

Listing:
TITLE OUTPUT ; procedura di conversione ed output di un numero ; il numero da scrivere viene letto da DX 0000 0000 0014[ 00 ] 0014 0000 DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,DS:DSEG PUBLIC 0000 0000 0001 0002 0003 0004 0006 0008 000B 000E 0010 0013 0017 0018 001B 001E 0020 0021 0025 0027 0029 002C 002E 0030 0032 0034 0036 57 50 53 52 33 FF 8B C2 BA 0000 BB 000A F7 F3 80 C2 30 88 95 0000 R 47 BA 0000 3D 0000 75 EE 4F 8A 95 0000 R B4 02 CD 21 83 FF 00 75 F2 B2 0D B4 02 CD 21 B2 0A CD 21 OUTPUT PUSH PUSH PUSH PUSH XOR MOV CONV: MOV MOV CICLO: DIV ADD MOV INC MOV CMP JNE LAB: DEC MOV MOV INT CMP JNE MOV MOV INT MOV INT POP POP POP OUTPUT PROC FAR DI AX BX DX DI, DI AX, DX DX, 0 BX, 10 BX DL, '0' CBUF[DI], DL DI DX, 0 AX, 0 CICLO DI DL, CBUF[DI] AH, 2 21H DI, 0 LAB DL, 13 AH, 2 21H DL, 10 21H DX BX AX 263 ; stampa un CR ; stampa un LF DSEG SEGMENT PARA PUBLIC 'DATA' CBUF DB 20 DUP(0)

0038 5A 0039 5B 003A 58

003B 5F 003C CB 003D 003D

POP RET

DI ;RETURN

OUTPUT ENDP CSEG ENDS END

Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr DSEG Length = 0014 000E CSEG 0008 CSEG 0020 CSEG CSEG Global Length = 003D Length Align 003D 0014 Combine Class 'CODE' 'DATA'

PARA PUBLIC PARA PUBLIC

CBUF . . . . . . . . . . . . . . L BYTE0000 CICLO . . . . . . . . . . . . . L NEAR CONV . . . . . . . . . . . . . . L NEAR LAB . . . . . . . . . . . . . . OUTPUT . . . . . . . . . . . . . L NEAR

F PROC 0000

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT output @VERSION . . . . . . . . . . . . TEXT 510 57 Source Lines 57 Total Lines 14 Symbols 47504 + 163835 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Anagr
; esame dell'11 settembre 1989 EXTRN SORT:FAR, CMPSTR:FAR PUBLIC PAR1, PAR2 DSEG BUFF PAR1 PAR2 PAR3 DSEG CSEG SEGMENT PARA PUBLIC 'DATA' DB 'SALUTE$ANCORA$GRANO$MAMMA$ARNESE$POZZO$RAMPONE$ARGANO$RAGNO$' DB 'PIOGGIA$$' DB 20 DUP (0) DB 20 DUP (0) DB 20 DUP (0) ENDS SEGMENT PARA PUBLIC 'CODE'

ASSUME CS:CSEG,SS:STACK ENTPT PROC PUSH XOR PUSH FAR DS AX,AX AX

;ALREADY SET BY DOS LOADER ;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code MOV MOV LAB1: INT CMP JE CMP JB CMP JA MOV INC JMP END1: DEC MOV LEA CALL CALL XOR LAB2: MOV CICLO: MOV MOV INC INC CMP JNE DEC DEC MOV LEA CALL CALL CMP JE MOV LEA INC INT CALL

BX, 1 ; acquisizione da tastiera della parola da ; cercare AH, 1 21H AL, 13 END1 AL, 'A' LAB1 AL, 'Z' LAB1 PAR1[BX], AL BX SHORT LAB1 BX PAR1, BL DI, PAR1 SORT ACAPO SI, SI BX, 1 ; carico una parola in PAR2 e PAR3 MOV AL, BUFF[SI] PAR2[BX], AL PAR3[BX], AL BX SI AL, '$' SHORT CICLO BX BX PAR2, BL DI, PAR2 SORT CMPSTR DX, 0 SHORT LAB3 AH, 9 DX, PAR3 DX 21H ACAPO ; ordina la parola da cercare ; e` un CR ? ; controllo che sia una lettera ; maiuscola

; ordino la parola in PAR2

; confronto PAR1 e PAR2 ; se sono diverse salto ; visualizzo la parola

265

LAB3: MOV CMP JNE RET ENTPT ENDP

AL, BUFF[SI] AL, '$' LAB2

; ho finito BUFF ?

; ; procedura per andare a capo ; ACAPO PROC PUSH PUSH MOV MOV INT MOV INT POP POP RET ACAPO ENDP CSEG ENDS ;256 WORD STACK AREA AX DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX AX ; stampa un CR ; stampa un LF

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

Listing:
TITLE ANAGR EXTRN SORT:FAR, CMPSTR:FAR PUBLIC PAR1, PAR2 0000 DSEG SEGMENT PARA PUBLIC 'DATA'

0000 53 41 4C 55 54 45 BUFF DB 'SALUTE$ANCORA$GRANO$MAMMA$ARNESE$POZZO$RAMPONE$ARGANO$RAGNO$' 24 41 4E 43 4F 52 41 24 47 52 41 4E 4F 24 4D 41 4D 4D 41 24 41 52 4E 45 53 45 24 50 4F 5A 5A 4F 24 52 41 4D 50 4F 4E 45 24 41 52 47 41 4E 4F 24 52 41 47 4E 4F 24 003C 50 49 4F 47 47 49 DB 'PIOGGIA$$' 41 24 24

0045 0014[ 00 ] 0059 0014[ 00 ] 006D 0014[ 00 ]

PAR1

DB 20 DUP (0)

PAR2

DB 20 DUP (0)

PAR3

DB 20 DUP (0)

0081 0000 LOADER 0000 0000 1E HAVE 0001 33 C0 VECTOR SO THE 0003 50 BACK TO DOS 0004 B8 ---- R 0007 8E D8 JUST DID

DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS ENTPT PROC FAR PUSH DS XOR AX,AX ;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD ; FAR RETURN WILL GO

PUSH AX MOV

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER WHAT I

; my code

0009 BB 0001 000C B4 01 000E CD 21 0010 3C 0D 0012 74 0F 0014 3C 41 0016 72 F6 0018 3C 5A 001A 77 F2 001C 88 87 0045 R 0020 43 0021 EB EB 0023 4B 0024 88 1E 0045 R 0028 8D 3E 0045 R 002C 9A 0000 ---- E 0031 E8 0079 R 0034 33 F6 0036 BB 0001 0039 8A 84 0000 R

MOV MOV LAB1: INT CMP JE END1 CMP JB CMP JA MOV INC JMP END1: DEC MOV

BX, 1 AH, 1 21H AL, 13

; acquisizione da tastiera della parola da cercare

; e` un CR ?

AL, 'A' ; controllo che sia una lettera maiuscola LAB1 AL, 'Z' LAB1 PAR1[BX], AL BX SHORT LAB1 BX PAR1, BL ; ordina la parola da cercare

LEA DI, PAR1 CALL SORT CALL ACAPO XOR LAB2: MOV CICLO: MOV SI, SI BX, 1 AL, BUFF[SI]

; carico una parola in PAR2 e PAR3

267

003D 88 87 0059 R 0041 88 87 006D R 0045 43 0046 46 0047 3C 24 0049 75 EE 004B 4B 004C 4B 004D 88 1E 0059 R 0051 8D 3E 0059 R 0055 9A 0000 ---- E 005A 9A 0000 ---- E 005F 83 FA 00 0062 74 0C 0064 B4 09 0066 8D 16 006D R 006A 42 006B CD 21 006D E8 0079 R 0070 8A 84 0000 R 0074 3C 24 0076 75 BE 0078 CB 0079

MOV MOV INC INC CMP JNE DEC DEC MOV LEA CALL

PAR2[BX], AL PAR3[BX], AL BX SI AL, '$' SHORT CICLO BX BX PAR2, BL DI, PAR2 SORT

; ordino la parola in PAR2

CALL CMPSTR CMP JE MOV LEA INC INT CALL LAB3: MOV CMP JNE RET ENTPT ENDP ; ; procedura per andare a capo ; DX, 0 SHORT LAB3 AH, 9 DX, PAR3 DX 21H ACAPO AL, BUFF[SI] AL, '$' LAB2

; confronto PAR1 e PAR2 ; se sono diverse salto ; visualizzo la parola

; ho finito BUFF ?

0079 0079 50 007A 52 007B B4 02 007D B2 0D 007F CD 21 0081 B2 0A 0083 CD 21 0085 5A 0086 58 0087 C3 0088 0088 0000 0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

ACAPO PROC PUSH AX PUSH DX MOV MOV INT MOV INT POP POP RET ACAPO ENDP CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA AH, 2 DL, 0DH 21H DL, 0AH 21H DX AX ; stampa un CR ; stampa un LF

STACK ENDS

END Segments and Groups: Name Length Align

ENTPT

Combine Class

CSEG . . . . . . . . . . . . . . 0088 DSEG . . . . . . . . . . . . . . 0081 STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . BUFF . . . . . . . . . . . . . . Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value N PROC

Attr 0079 CSEG Length = 000F

L BYTE0000

DSEG CSEG External

CICLO . . . . . . . . . . . . . L NEAR 0039 CMPSTR . . . . . . . . . . . . . L FAR 0000 END1 . . . . . . . . . . . . . . L NEAR ENTPT . . . . . . . . . . . . . F PROC 0000 LAB1 . . . . . . . . . . . . . . LAB2 . . . . . . . . . . . . . . LAB3 . . . . . . . . . . . . . . PAR1 . . . . . . . . . . . . . . PAR2 . . . . . . . . . . . . . . PAR3 . . . . . . . . . . . . . . SORT . . . . . . . . . . . . . . L NEAR L NEAR L NEAR L BYTE0045 L BYTE0059 L BYTE006D L FAR 0000

0023 CSEG CSEG Length = 0079 000E 0036 0070 CSEG CSEG CSEG

DSEG Global Length = 0014 DSEG Global Length = 0014 DSEG Length = 0014 External

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT anagr @VERSION . . . . . . . . . . . . TEXT 510 121 Source Lines 121 Total Lines 23 Symbols 47470 + 159774 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Atoi
DSEG DSEG CSEG SEGMENT PARA PUBLIC 'DATA' ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX ;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS 269

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code XOR MOV INPUT: INT CMP JE CMP JB CMP JA SUB XCHG MOV MUL MOV ADD MOV JMP FINE: RET ENTPT ENDP CSEG ENDS

;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

CX, CX BX, 10 MOV AH, 1 ; legge un carattere 21H AL, 13 ; e` un CR ? FINE ; se si` fine AL, '0' ; se no controlla che sia un numero INPUT AL, '9' INPUT AL, '0' ; OK: togli codifica ASCII AX, BX BH, 0 CX ; moltiplica per 10 CX, AX CX, BX ; somma la cifra letta BX, 10 INPUT

;RETURN TO DOS

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

;256 WORD STACK AREA

Listing:
TITLE ATOI

0000 0000 0000 LOADER 0000 0000 1E HAVE 0001 33 C0 VECTOR SO THE 0003 50 BACK TO DOS 0004 B8 ---- R

DSEG SEGMENT PARA PUBLIC 'DATA' DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS ENTPT PROC FAR PUSH DS XOR AX,AX ;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD ; FAR RETURN WILL GO ;SET UP ADDRESSABILITY

PUSH AX MOV AX,DSEG

TO 0007 8E D8 WHAT I JUST DID

MOV DS,AX ASSUME DS:DSEG ; my code

; THE DATA SEGMENT ;TELL ASSEMBLER

0009 33 C9 000B BB 000A 000E B4 01 0010 CD 21 0012 3C 0D 0014 74 18 0016 3C 30 0018 72 F4 001A 3C 39 001C 77 F0 001E 2C 30 0020 93 0021 B7 00 0023 F7 E1 0025 8B C8 0027 03 CB 0029 BB 000A 002C EB E0 002E 002E CB 002F 002F 0000 0000 0040[ 53 54 41 43 4B 20 20 20 0200

XOR MOV INPUT: MOV INT CMP JE CMP JB CMP JA SUB XCHG MOV MUL MOV ADD MOV JMP FINE: RET ENTPT ENDP CSEG ENDS

CX, CX BX, 10 AH, 1 21H AL, 13 FINE AL, '0' INPUT AL, '9' INPUT AL, '0' AX, BX BH, 0 CX CX, AX CX, BX BX, 10 INPUT

; legge un carattere ; e` un CR ? ; se si` fine ; se no controlla che sia un numero

; OK: togli codifica ASCII ; moltiplica per 10 ; somma la cifra letta

;RETURN TO DOS

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA

STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 002F DSEG . . . . . . . . . . . . . . 0000 STACK . . . . . . . . . . . . . 0200 Symbols: Name Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr CSEG Length = 002F 002E 000E CSEG CSEG

ENTPT . . . . . . . . . . . . . F PROC 0000 FINE . . . . . . . . . . . . . . L NEAR

INPUT . . . . . . . . . . . . . L NEAR

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT atoi @VERSION . . . . . . . . . . . . TEXT 510 271

53 Source Lines 53 Total Lines 13 Symbols 47630 + 161661 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Itoa
DSEG BUFF DSEG CSEG SEGMENT PARA PUBLIC 'DATA' DB 20 DUP(0) ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX ;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code MOV XOR CONV: MOV MOV CICLO: ADD MOV INC MOV CMP JNE OUTPUT: MOV MOV INT CMP JNE RET ENTPT ENDP CSEG ENDS CX, 234 DI, DI AX, CX BL, 10 DIV BL AH, '0' BUFF[DI], AH DI AH, 0 AL, 0 CICLO DEC DI DL, BUFF[DI] AH, 2 21H DI, 0 OUTPUT

;RETURN TO DOS

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

;256 WORD STACK AREA

Listing:
TITLE ITOA 0000 0000 0014[ 00 ] 0014 0000 LOADER 0000 0000 1E HAVE 0001 33 C0 THE 0003 50 DOS 0004 B8 ---- R 0007 8E D8 DID ; my code 0009 B9 00EA 000C 000E 0010 0012 0014 0017 001B 001C 001E 0020 0022 0023 0027 0029 002B 002E 33 FF 8B C1 B3 0A F6 F3 80 C4 30 88 A5 0000 R 47 B4 00 3C 00 75 F0 4F 8A 95 0000 R B4 02 CD 21 83 FF 00 75 F2 MOV XOR CONV: MOV MOV CICLO: DIV ADD MOV INC MOV CMP JNE OUTPUT: MOV MOV INT CMP JNE RET ENTPT ENDP CSEG ENDS CX, 234 DI, DI AX, CX BL, 10 BL AH, '0' BUFF[DI], AH DI AH, 0 AL, 0 CICLO DEC DI DL, BUFF[DI] AH, 2 21H DI, 0 OUTPUT ;RETURN TO DOS ENTPT PROC FAR PUSH DS XOR AX,AX ;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD VECTOR SO ; FAR RETURN WILL GO BACK TO DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS DSEG SEGMENT PARA PUBLIC 'DATA' BUFF DB 20 DUP(0)

PUSH AX MOV

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER WHAT I JUST

0030 CB 0031 0031 0000

STACK SEGMENT PARA STACK 'STACK' 273

0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

DB

64 DUP("STACK ")

;256 WORD STACK AREA

STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 0031 DSEG . . . . . . . . . . . . . . 0014 STACK . . . . . . . . . . . . . 0200 Symbols: Name BUFF . . . . . . . . . . . . . . Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr DSEG Length = 0014 0012 000E CSEG CSEG

L BYTE0000

CICLO . . . . . . . . . . . . . L NEAR CONV . . . . . . . . . . . . . . L NEAR ENTPT . . . . . . . . . . . . . F PROC 0000 OUTPUT . . . . . . . . . . . . .

CSEG Length = 0031 0022 CSEG

L NEAR

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT itoa @VERSION . . . . . . . . . . . . TEXT 510 53 Source Lines 53 Total Lines 15 Symbols 47478 + 163861 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Bubble
; ; ; ; ; ; Procedura di ordinamento di un vettore con l'algoritmo di bubble-sort (SCANLON pg. 178). Il vettore e` localizzato nel Data Segment, a partire dall'offset contenuto in DI, ed e` composto di byte. Il primo elemento contiene la sua lunghezza (num. elementi + 1 ), che si suppone minore di 256. SEGMENT PARA PUBLIC 'DATA'

DSEG

SAVE_CNT DW ? START_ADDR DW ? DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG ;ALREADY SET BY DOS LOADER

PUBLIC SORT

SORT

PROC FAR PUSH AX PUSH BX PUSH CX CL, [DI] CH, 0 CX SAVE_CNT, CX DI START_ADDR, DI BX, 1 CX, SAVE_CNT DI, START_ADDR AL, [DI] [DI+1], AL CONT [DI+1], AL [DI], AL BX, BX DI NEXT BX, 0 INIT CX BX AX ;RETURN

MOV MOV DEC MOV INC MOV INIT: MOV MOV MOV NEXT: MOV CMP JAE XCHG MOV SUB CONT: INC LOOP CMP JE POP POP POP SORT CSEG RET ENDP ENDS END

Listing:
; Procedura di ordinamento di un vettore con l' algoritmo di ; bubble-sort (SCANLON pg. 178). ; Il vettore e` localizzato nel Data Segment, a partire dall'offset ; contenuto in DI, ed e` composto di byte. Il p rimo elemento contiene la sua ; lunghezza (num. elementi + 1 ), che si suppon e minore di 256. 0000 0000 0000 0002 0000 0004 0000 LOADER PUBLIC SORT DSEG SEGMENT PARA PUBLIC 'DATA' SAVE_CNT DW ? START_ADDR DW ? DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG ;ALREADY SET BY DOS

275

0000 0000 50 0001 53 0002 51 0003 8A 0D 0005 B5 00 0007 49 0008 89 0E 0000 R 000C 47 000D 89 3E 0002 R 0011 BB 0001 0014 8B 0E 0000 R 0018 8B 3E 0002 R 001C 8A 05 001E 38 45 01 0021 73 07 0023 86 45 01 0026 88 05 0028 2B DB 002A 47 002B E2 EF 002D 83 FB 00 0030 74 DF 0032 59 0033 5B 0034 58 0035 CB 0036 0036

SORT PROC FAR PUSH AX PUSH BX PUSH CX MOV MOV DEC MOV INC MOV INIT: MOV MOV MOV NEXT: MOV CMP JAE XCHG MOV SUB CONT: INC LOOP CMP JE POP POP POP RET SORT ENDP CSEG ENDS END CL, [DI] CH, 0 CX SAVE_CNT, CX DI START_ADDR, DI BX, 1 CX, SAVE_CNT DI, START_ADDR AL, [DI] [DI+1], AL CONT [DI+1], AL [DI], AL BX, BX DI NEXT BX, 0 INIT CX BX AX ;RETURN

Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr 002A 0011 001C CSEG CSEG CSEG Length Align 0036 0004 Combine Class 'CODE' 'DATA'

PARA PUBLIC PARA PUBLIC

CONT . . . . . . . . . . . . . . L NEAR INIT . . . . . . . . . . . . . . L NEAR

NEXT . . . . . . . . . . . . . . L NEAR

SAVE_CNT . . . . . . . . . . . . L WORD 0000 DSEG SORT . . . . . . . . . . . . . . F PROC 0000 CSEG Global Length = 0036 START_ADDR . . . . . . . . . . . L WORD 0002 DSEG @CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT bubble @VERSION . . . . . . . . . . . . TEXT 510 51 Source Lines 51 Total Lines

15 Symbols 47542 + 163797 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Buff
; ; ; ; ; ; gestisce un buffer circolare DIM-1 numeri interi. L'utente puo`: 1 - inserire un elemento con 2 - togliere un elemento con 3 - uscire dal programma con in grado di contenere fino a il comando '+'<numero><CR> il comando '-' il comando 'E'

EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR DIM DSEG EQU 5 ; dimensione (in numero di elementi) del buffer

SEGMENT PARA PUBLIC 'DATA'

BUFF DW DIM DUP(0) B_IN DW 0 B_OUT DW 0 FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' PROMPT DB '>>$' DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX ;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code RD_CMD: LEA DX, PROMPT MOV AH, 9 INT 21H MOV INT CMP JE CMP JE CMP JE JMP AH, 1 21H AL, '+' CMD_I AL, '-' CMD_E AL, 'E' FINE SHORT RD_CMD

; stampa il prompt

; legge il comando ; insert ; extract ; end

277

CMD_I: CALL CALL JMP CMD_E: CALL CMP JE CALL JMP FINE: RET ENTPT ENDP

CALL ACAPO

INPUT

ENQUEUE RD_CMD CALL ACAPO

DEQUEUE DX, 0FFFFH RD_CMD OUTPUT RD_CMD ;RETURN TO DOS ; errore

;*********************************************************** ; inserisce l'intero presente in DX nel buffer ;*********************************************************** ENQUEUE PUSH PUSH PUSH PUSH MOV CMP JB PROC AX BX DX SI SI, B_IN SI, B_OUT COMPT

; se B_IN<B_OUT salta ; testa se il buffer e` pieno

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, 2*DIM-2 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV BX, B_OUT SUB BX, B_IN DEC BX DEC BX JE FULL ESTRAI: INC INC CMP JNE MOV E_LAB: MOV JMP FULL: LEA

; testa se il buffer e` pieno

MOV BUFF[SI], DX SI SI SI, 2*DIM E_LAB SI, 0 B_IN, SI E_FINE DX, FULL_MESS

MOV INT E_FINE: POP POP POP RET ENQUEUE

AH, 9 21H POP DX BX AX SI

ENDP

;*********************************************************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;*********************************************************** DEQUEUE PUSH PUSH MOV CMP JE MOV INC INC CMP JNE MOV D_LAB: JMP EMPTY: MOV INT MOV JMP D_FINE: POP RET DEQUEUE CSEG ENDS ;256 WORD STACK AREA ENDP PROC AX SI SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI SI SI, 2*DIM D_LAB SI, 0 MOV B_OUT, SI D_FINE LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI ; testa se il buffer e` vuoto

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

Listing:
TITLE BUFF 279

; gestisce un buffer circolare in grado di contenere fino a DIM-1 numeri interi. ; L'utente puo`: ; 1 - inserire un elemento con il comando '+'<numero><CR> ; 2 - togliere un elemento con il comando '-' ; 3 - uscire dal programma con il comando 'E' EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR = 0005 0000 0000 0005[ 0000 ] 000A 0000 000C 0000 B_IN DW 0 B_OUT DW 0 DIM EQU 5 ; dimensione (in numero di elementi) del buffer

DSEG SEGMENT PARA PUBLIC 'DATA' BUFF DW DIM DUP(0)

000E 42 55 46 46 45 52 FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' 20 50 49 45 4E 4F 0D 0A 24 001D 42 55 46 46 45 52 EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' 20 56 55 4F 54 4F 0D 0A 24 002C 3E 3E 24 PROMPT DB '>>$' 002F 0000 LOADER 0000 0000 1E HAVE 0001 33 C0 VECTOR SO THE 0003 50 BACK TO DOS 0004 B8 ---- R 0007 8E D8 WHAT I JUST DID ; my code 0009 8D 16 002C R 000D B4 09 000F CD 21 0011 B4 01 0013 CD 21 0015 3C 2B 0017 74 0A 0019 3C 2D 001B 74 15 RD_CMD: LEA DX, PROMPT MOV AH, 9 INT 21H MOV INT CMP JE CMP JE AH, 1 21H AL, '+' CMD_I AL, '-' CMD_E ; stampa il prompt ENTPT PROC FAR PUSH DS XOR AX,AX ;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD ; FAR RETURN WILL GO DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS

PUSH AX MOV

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER

; legge il comando ; insert ; extract

001D 3C 45 001F 74 25 0021 EB E6

CMP JE JMP

AL, 'E' FINE

; end

SHORT RD_CMD

0023 9A 0000 ---- E 0028 9A 0000 ---- E 002D E8 0047 R 0030 EB D7 0032 9A 0000 ---- E 0037 E8 0093 R 003A 83 FA FF 003D 74 CA 003F 9A 0000 ---- E 0044 EB C3 0046 CB 0047

CMD_I: CALL INPUT CALL ACAPO CALL ENQUEUE JMP CMD_E: RD_CMD CALL ACAPO

CALL DEQUEUE CMP JE DX, 0FFFFH RD_CMD ; errore

CALL OUTPUT JMP FINE: RET ENTPT ENDP RD_CMD ;RETURN TO DOS

;************************************************************* ************** ; inserisce l'intero presente in DX nel buffer ;************************************************************* ************** 0047 0047 50 0048 53 0049 52 004A 56 004B 8B 36 000A R 004F 3B 36 000C R 0053 72 10 ENQUEUE PUSH PUSH PUSH PUSH MOV CMP JB PROC AX BX DX SI SI, B_IN SI, B_OUT COMPT

; se B_IN<B_OUT salta

0055 8B 1E 000A R 0059 2B 1E 000C R 005D 83 FB 08 0060 74 24 0062 EB 0D 90

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, 2*DIM-2 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV

; testa se il buffer e` pieno

0065 8B 1E 000C R pieno

BX, B_OUT

; testa se il buffer e`

281

0069 2B 1E 000A R 006D 4B 006E 4B 006F 74 15 0071 89 94 0000 R 0075 46 0076 46 0077 83 FE 0A 007A 75 03 007C BE 0000 007F 89 36 000A R 0083 EB 09 90

SUB DEC DEC JE ESTRAI: INC INC CMP JNE MOV E_LAB: MOV JMP

BX, B_IN BX BX FULL MOV BUFF[SI], DX SI SI SI, 2*DIM E_LAB SI, 0 B_IN, SI E_FINE

0086 8D 16 000E R 008A B4 09 008C CD 21 008E 008F 0090 0091 5E 5A 5B 58

FULL: LEA MOV INT E_FINE: POP POP POP RET ENQUEUE

DX, FULL_MESS AH, 9 21H POP DX BX AX SI

0092 C3 0093

ENDP

;************************************************************* ************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;************************************************************* ************** 0093 0093 50 0094 56 0095 8B 36 000C R 0099 3B 36 000A R 009D 74 15 009F 8B 94 0000 R 00A3 46 00A4 46 00A5 83 FE 0A 00A8 75 03 00AA BE 0000 00AD 89 36 000C R 00B1 EB 0F 90 DEQUEUE PROC

PUSH AX PUSH SI MOV CMP JE MOV INC INC CMP JNE MOV D_LAB:MOV JMP SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI SI SI, 2*DIM D_LAB SI, 0 B_OUT, SI D_FINE ; testa se il buffer e` vuoto

00B4 8D 16 001D R 00B8 B4 09 00BA CD 21 00BC BA FFFF 00BF EB 01 90 00C2 5E 00C3 58 00C4 C3 00C5

EMPTY: MOV INT MOV JMP D_FINE: POP RET DEQUEUE

LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI

ENDP

00C5 0000 0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA

STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 00C5 DSEG . . . . . . . . . . . . . . 002F STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr External DSEG Length = 0005 DSEG DSEG 0032 CSEG CSEG 0065 CSEG 0093 CSEG CSEG 00B4 CSEG DSEG 0047 CSEG Length = 004C 283 CSEG Length = 0032

L FAR 0000 0000 000A 000C

BUFF . . . . . . . . . . . . . . L WORD B_IN . . . . . . . . . . . . . . L WORD B_OUT . . . . . . . . . . . . . L WORD

CMD_E . . . . . . . . . . . . . L NEAR CMD_I . . . . . . . . . . . . . L NEAR 0023 COMPT . . . . . . . . . . . . . L NEAR DEQUEUE . . . . . . . . . . . . N PROC DIM . . . . . . . . . . . . . . NUMBER 0005 D_FINE . . . . . . . . . . . . . L NEAR 00C2 D_LAB . . . . . . . . . . . . . L NEAR 00AD EMPTY . . . . . . . . . . . . . EMPT_MESS . . . . . . . . . . . ENQUEUE . . . . . . . . . . . . L NEAR L BYTE001D N PROC

ENTPT . . . . . . . . . . . . . ESTRAI . . . . . . . . . . . . . E_FINE . . . . . . . . . . . . . E_LAB . . . . . . . . . . . . .

F PROC 0000 L NEAR L NEAR L NEAR

CSEG 0071 008E 007F

Length = 0047 CSEG CSEG CSEG CSEG CSEG DSEG External External DSEG 0009 CSEG

FINE . . . . . . . . . . . . . . L NEAR 0046 FULL . . . . . . . . . . . . . . L NEAR 0086 FULL_MESS . . . . . . . . . . . L BYTE000E INPUT . . . . . . . . . . . . . L FAR 0000 OUTPUT . . . . . . . . . . . . . PROMPT . . . . . . . . . . . . . RD_CMD . . . . . . . . . . . . . L FAR 0000 L BYTE002C L NEAR

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT buff @VERSION . . . . . . . . . . . . TEXT 510

199 Source Lines 199 Total Lines 35 Symbols 47476 + 155671 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Buffcirc
; ; ; ; ; ; ; gestisce un buffer circolare composto di MAX elementi, ognuno corrispondente ad un record di DIM_REC byte. Nella prima word viene scritto un intero su 16 bit. L'utente puo`: 1 - inserire un elemento con il comando '+'<numero><CR> 2 - togliere un elemento con il comando '-'<CR> 3 - uscire dal programma con il comando 'E'

EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR MAX EQU 10 DIM_REC EQU 2 CONST1 DSEG EQU ; dimensione (in # elementi) del buffer ; dimensione (in byte) del singolo elemento (MAX-1)*DIM_REC ; costante per il test su buffer pieno

SEGMENT PARA PUBLIC 'DATA'

BUFF DW MAX*DIM_REC DUP(0) B_IN DW 0 B_OUT DW 0 FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' PROMPT DB '>>$' DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE'

ASSUME CS:CSEG,SS:STACK ENTPT PROC PUSH XOR PUSH FAR DS AX,AX AX

;ALREADY SET BY DOS LOADER

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code RD_CMD: LEA DX, PROMPT MOV AH, 9 INT 21H MOV INT CMP JE CMP JE CMP JE JMP CMD_I: CALL CALL JMP CMD_E: CALL CMP JE CALL JMP FINE: RET ENTPT ENDP AH, 1 21H AL, '+' CMD_I AL, '-' CMD_E AL, 'E' FINE SHORT RD_CMD CALL ACAPO INSERT RD_CMD CALL ACAPO INPUT

; stampa il prompt

; legge il comando ; insert ; extract ; end

EXTRACT DX, 0FFFFH RD_CMD OUTPUT RD_CMD ;RETURN TO DOS ; errore

;*********************************************************** ; inserisce l'intero presente in DX nel buffer ;*********************************************************** INSERT PUSH PUSH PUSH PUSH PROC AX BX DX SI 285

MOV CMP JB

SI, B_IN SI, B_OUT COMPT

; se B_IN<B_OUT salta ; testa se il buffer e` pieno

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, CONST1 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV BX, B_OUT SUB BX, B_IN SUB BX, DIM_REC JE FULL ESTRAI: ADD CMP JNE MOV E_LAB: MOV JMP FULL: LEA MOV INT E_FINE: POP POP POP RET INSERT ENDP

; testa se il buffer e` pieno

MOV BUFF[SI], DX SI, DIM_REC SI, MAX*DIM_REC E_LAB SI, 0 B_IN, SI E_FINE DX, FULL_MESS AH, 9 21H POP DX BX AX SI

;*********************************************************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;*********************************************************** EXTRACT PUSH PUSH MOV CMP JE MOV ADD CMP JNE MOV D_LAB: JMP PROC AX SI SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI, DIM_REC SI, MAX*DIM_REC D_LAB SI, 0 MOV B_OUT, SI D_FINE ; testa se il buffer e` vuoto

EMPTY: MOV INT MOV JMP D_FINE: POP RET EXTRACT CSEG ENDS

LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI

ENDP

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

;256 WORD STACK AREA

Listing:
TITLE BUFFCIRC ; gestisce un buffer circolare composto di MAX elementi, ognuno ; corrispondente ad un record di DIM_REC byte. Nella prima word viene ; scritto un intero su 16 bit. ; L'utente puo`: ; 1 - inserire un elemento con il comando '+'<numero><CR> ; 2 - togliere un elemento con il comando '-'<CR> ; 3 - uscire dal programma con il comando 'E' EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR = 000A = 0002 = 0012 buffer pieno 0000 0000 0014[ 0000 ] 0028 0000 002A 0000 B_IN DW 0 B_OUT DW 0 MAX EQU 10 DIM_REC EQU 2 CONST1 EQU ; dimensione (in # elementi) del buffer ; dimensione (in byte) del singolo elemento (MAX-1)*DIM_REC ;costante per il test su

DSEG SEGMENT PARA PUBLIC 'DATA' BUFF DW MAX*DIM_REC DUP(0)

002C 42 55 46 46 45 52 FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' 20 50 49 45 4E 4F 0D 0A 24 003B 42 55 46 46 45 52 EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' 20 56 55 4F 54 4F 0D 0A 24 004A 3E 3E 24 PROMPT DB '>>$' 004D 0000 DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS 287

LOADER 0000 0000 1E HAVE 0001 33 C0 VECTOR SO THE 0003 50 BACK TO DOS 0004 B8 ---- R 0007 8E D8 WHAT I JUST DID ; my code 0009 8D 16 004A R 000D B4 09 000F CD 21 0011 B4 01 0013 CD 21 0015 3C 2B 0017 74 0A 0019 3C 2D 001B 74 15 001D 3C 45 001F 74 25 0021 EB E6 RD_CMD: LEA DX, PROMPT MOV AH, 9 INT MOV INT CMP JE CMP JE CMP JE JMP 21H AH, 1 21H AL, '+' CMD_I AL, '-' CMD_E AL, 'E' FINE ; legge il comando ; insert ; extract ; end ; stampa il prompt ENTPT PROC FAR PUSH DS XOR AX,AX ;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD ; FAR RETURN WILL GO

PUSH AX MOV

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER

SHORT RD_CMD

0023 9A 0000 ---- E 0028 9A 0000 ---- E 002D E8 0047 R 0030 EB D7 0032 9A 0000 ---- E 0037 E8 0095 R 003A 83 FA FF 003D 74 CA 003F 9A 0000 ---- E 0044 EB C3 0046 CB 0047

CMD_I: CALL INPUT CALL ACAPO CALL INSERT JMP CMD_E: RD_CMD CALL ACAPO

CALL EXTRACT CMP JE DX, 0FFFFH RD_CMD ; errore

CALL OUTPUT JMP FINE: RET ENTPT ENDP RD_CMD ;RETURN TO DOS

;************************************************************* ************** ; inserisce l'intero presente in DX nel buffer ;************************************************************* ************** 0047 0047 50 0048 53 0049 52 004A 56 004B 8B 36 0028 R 004F 3B 36 002A R 0053 72 10 0055 8B 1E 0028 R 0059 2B 1E 002A R 005D 83 FB 12 0060 74 26 0062 EB 0E 90 INSERTPROC PUSH AX PUSH BX PUSH DX PUSH SI MOV CMP JB SI, B_IN SI, B_OUT COMPT

; se B_IN<B_OUT salta ; testa se il buffer e` pieno

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, CONST1 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV SUB SUB JE ESTRAI: ADD CMP JNE MOV E_LAB: MOV JMP

0065 8B 1E 002A R pieno 0069 2B 1E 0028 R 006D 83 EB 02 0070 74 16 0072 0076 0079 007C 007E 0081 0085 89 94 0000 R 83 C6 02 83 FE 14 75 03 BE 0000 89 36 0028 R EB 09 90

BX, B_OUT

; testa se il buffer e`

BX, B_IN BX, DIM_REC FULL MOV BUFF[SI], DX SI, DIM_REC SI, MAX*DIM_REC E_LAB SI, 0 B_IN, SI E_FINE

0088 8D 16 002C R 008C B4 09 008E CD 21 0090 0091 0092 0093 5E 5A 5B 58

FULL: LEA MOV INT E_FINE: POP POP POP RET INSERTENDP

DX, FULL_MESS AH, 9 21H POP DX BX AX SI

0094 C3 0095

289

;************************************************************* ************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;************************************************************* ************** 0095 0095 50 0096 56 0097 8B 36 002A R 009B 3B 36 0028 R 009F 74 16 00A1 8B 94 0000 R 00A5 83 C6 02 00A8 83 FE 14 00AB 75 03 00AD BE 0000 00B0 89 36 002A R 00B4 EB 0F 90 00B7 8D 16 003B R 00BB B4 09 00BD CD 21 00BF BA FFFF 00C2 EB 01 90 00C5 5E 00C6 58 00C7 C3 00C8 EXTRACT PROC

PUSH AX PUSH SI MOV CMP JE MOV ADD CMP JNE MOV D_LAB:MOV JMP EMPTY: MOV INT MOV JMP D_FINE: POP RET EXTRACT ENDP SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI, DIM_REC SI, MAX*DIM_REC D_LAB SI, 0 B_OUT, SI D_FINE LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI ; testa se il buffer e` vuoto

00C8 0000 0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA

STACK ENDS END ENTPT

Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Length Align 00C8 004D Combine Class 'CODE' 'DATA'

PARA PUBLIC PARA PUBLIC

STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . Type

PARA STACK 'STACK'

Value

Attr External DSEG Length = 0014 DSEG DSEG 0032 CSEG CSEG 0065 CSEG 0012 0002 CSEG CSEG 00B7 CSEG DSEG Length = 0047 CSEG 0095 CSEG Length = 0033 CSEG CSEG CSEG CSEG DSEG External CSEG Length = 004E

L FAR 0000 0000 0028 002A

BUFF . . . . . . . . . . . . . . L WORD B_IN . . . . . . . . . . . . . . L WORD B_OUT . . . . . . . . . . . . . L WORD

CMD_E . . . . . . . . . . . . . L NEAR CMD_I . . . . . . . . . . . . . L NEAR 0023 COMPT . . . . . . . . . . . . . L NEAR CONST1 . . . . . . . . . . . . . NUMBER DIM_REC . . . . . . . . . . . . NUMBER D_FINE . . . . . . . . . . . . . L NEAR 00C5 D_LAB . . . . . . . . . . . . . L NEAR 00B0 EMPTY . . . . . . . . . . . . . L NEAR EMPT_MESS . . . . . . . . . . . L BYTE003B ENTPT . . . . . . . . . . . . . F PROC 0000 CSEG ESTRAI . . . . . . . . . . . . . L NEAR 0072 EXTRACT . . . . . . . . . . . . N PROC E_FINE . . . . . . . . . . . . . L NEAR 0090 E_LAB . . . . . . . . . . . . . L NEAR 0081 FINE . . . . . . . . . . . . . . L NEAR 0046 FULL . . . . . . . . . . . . . . L NEAR 0088 FULL_MESS . . . . . . . . . . . L BYTE002C INPUT . . . . . . . . . . . . . L FAR 0000 INSERT . . . . . . . . . . . . . N PROC MAX . . . . . . . . . . . . . . OUTPUT . . . . . . . . . . . . . PROMPT . . . . . . . . . . . . . RD_CMD . . . . . . . . . . . . . NUMBER 0047 000A

L FAR 0000 L BYTE004A L NEAR DSEG 0009

External

CSEG

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT buffcirc @VERSION . . . . . . . . . . . . TEXT 510

197 Source Lines 197 Total Lines 37 Symbols 47450 + 155697 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Compstr
; Procedura per il confronto di due stringhe, contenute 291

; ; ; ; ;

nelle due variabili esterne PAR1 e PAR2. Si suppone che il primo elemento sia costituito dalla lunghezza (su 8 bit) della stringa stessa. Ritorna in DX il risultato: 0 se sono diverse, 1 se sono uguali.

EXTRN PAR1:BYTE, PAR2:BYTE CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG CMPSTR PROC FAR AX CX DI SI DI, PAR1 SI, PAR2 AL, [DI] AL, [SI] DIFF CL, AL CH, 0 CX DI SI AL, [DI] AL, [SI] DIFF LAB DX, 1 SHORT FINE DX, 0 SI DI CX AX ENDP ;RETURN ;ALREADY SET BY DOS LOADER

PUBLIC CMPSTR PUSH PUSH PUSH PUSH INIT: LEA LEA MOV CMP JNE MOV MOV DEC LAB: INC INC MOV CMP JNE LOOP EQUAL: MOV JMP DIFF: MOV FINE: POP POP POP POP RET CMPSTR CSEG ENDS END

Listing:
; Procedura per il confronto di due stringhe, c ontenute nelle due ; variabili esterne PAR1 e PAR2. Si suppone che il primo elemento sia ; costituito dalla lunghezza (su 8 bit) della s tringa stessa. ; Ritorna in DX il risultato: 0 se sono diverse , 1 se sono uguali.

EXTRN PAR1:BYTE, PAR2:BYTE 0000 CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG ;ALREADY SET BY DOS LOADER PUBLIC 0000 0000 0001 0002 0003 50 51 57 56 CMPSTR PUSH PUSH PUSH PUSH INIT: LEA LEA CMPSTR PROC FAR AX CX DI SI DI, PAR1 SI, PAR2 AL, [DI] AL, [SI] DIFF CL, AL CH, 0 CX DI SI AL, [DI] AL, [SI] DIFF LAB DX, 1 SHORT FINE DX, 0 SI DI CX AX ;RETURN

0004 8D 3E 0000 E 0008 8D 36 0000 E 000C 8A 05 000E 3A 04 0010 75 14 0012 8A C8 0014 B5 00 0016 49 0017 47 0018 46 0019 8A 05 001B 3A 04 001D 75 07 001F E2 F6 0021 BA 0001 0024 EB 03 0026 BA 0000 0029 5E 002A 5F 002B 59 002C 58 002D CB 002E 002E

MOV CMP JNE MOV MOV DEC LAB: INC INC MOV CMP JNE LOOP EQUAL: MOV JMP DIFF: FINE: MOV POP POP POP POP RET

CMPSTR ENDP CSEG ENDS END

Segments and Groups: Name CSEG . . . . . . . . . . . . . . Symbols: Name CMPSTR . . . . . . . . . . . . . DIFF . . . . . . . . . . . . . . EQUAL . . . . . . . . . . . . . FINE . . . . . . . . . . . . . . Type Value Attr CSEG Global Length = 002E CSEG 0021 CSEG 293 CSEG Length Align 002E Combine Class 'CODE'

PARA PUBLIC

F PROC 0000 L NEAR 0026

L NEAR L NEAR 0029

INIT . . . . . . . . . . . . . . LAB . . . . . . . . . . . . . . PAR1 . . . . . . . . . . . . . . PAR2 . . . . . . . . . . . . . .

L NEAR L NEAR V BYTE V BYTE

0004 0017 0000 0000

CSEG CSEG External External

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT cmpstr @VERSION . . . . . . . . . . . . TEXT 510 47 Source Lines 47 Total Lines 15 Symbols 47580 + 163759 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Cmpstr1
; ; ; ; ; ; ; Procedura per il confronto di due stringhe, contenute nelle due variabili esterne PAR1 e PAR2. Si suppone che il primo elemento sia costituito dalla lunghezza (su 8 bit) della stringa stessa. Ritorna in DX il risultato: 0 se sono diverse, 1 se sono uguali. La procedura usa le istruzioni su strighe.

EXTRN PAR1:BYTE, PAR2:BYTE CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG CMPSTR PROC FAR AX CX DI SI DI, PAR1 SI, PAR2 ;ALREADY SET BY DOS LOADER

PUBLIC CMPSTR PUSH PUSH PUSH PUSH INIT: LEA LEA

MOV CL, [DI] CMP CL, [SI] JNE DIFF MOV CH, 0 DEC CX PUSH ES MOV AX, DS MOV ES, AX REPE CMPSB POP ES JNE DIFF EQUAL: MOV DX, 1 JMP SHORT FINE

DIFF: MOV FINE: POP POP POP POP RET CMPSTR CSEG ENDS END

DX, 0 SI DI CX AX ;RETURN ENDP

Listing:
; Procedura per il confronto di due stringhe, c ontenute nelle due ; variabili esterne PAR1 e PAR2. Si suppone che il primo elemento sia ; costituito dalla lunghezza (su 8 bit) della s tringa stessa. ; Ritorna in DX il risultato: 0 se sono diverse , 1 se sono uguali. ; La procedura usa le istruzioni su strighe. EXTRN PAR1:BYTE, PAR2:BYTE 0000 CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG ;ALREADY SET BY DOS LOADER PUBLIC 0000 0000 0001 0002 0003 50 51 57 56 CMPSTR PUSH PUSH PUSH PUSH INIT: LEA LEA CMPSTR PROC FAR AX CX DI SI DI, PAR1 SI, PAR2

0004 8D 3E 0000 E 0008 8D 36 0000 E 000C 8A 0D 000E 3A 0C 0010 75 12 0012 B5 00 0014 49 0015 06 0016 8C D8 0018 8E C0 001A F3/ A6 001C 07 001D 75 05 001F BA 0001 0022 EB 03 0024 BA 0000 0027 5E 0028 5F 0029 59 002A 58

MOV CL, [DI] CMP CL, [SI] JNE DIFF MOV CH, 0 DEC CX PUSH ES MOV AX, DS MOV ES, AX REPE CMPSB POP ES JNE DIFF EQUAL: MOV DX, 1 JMP SHORT FINE DIFF: FINE: MOV POP POP POP POP DX, 0 SI DI CX AX 295

002B CB 002C 002C

RET CMPSTR ENDP CSEG ENDS END

;RETURN

Segments and Groups: Name CSEG . . . . . . . . . . . . . . Symbols: Name CMPSTR . . . . . . . . . . . . . DIFF . . . . . . . . . . . . . . EQUAL . . . . . . . . . . . . . FINE . . . . . . . . . . . . . . INIT . . . . . . . . . . . . . . PAR1 . . . . . . . . . . . . . . PAR2 . . . . . . . . . . . . . . Type Value Attr CSEG Global Length = 002C CSEG 001F CSEG CSEG External External CSEG Length Align 002C Combine Class 'CODE'

PARA PUBLIC

F PROC 0000 L NEAR 0024

L NEAR L NEAR L NEAR V BYTE V BYTE 0027 0004 0000 0000

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT cmpstr1 @VERSION . . . . . . . . . . . . TEXT 510 47 Source Lines 47 Total Lines 14 Symbols 47572 + 163767 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Diago1
; ; ; ; calcola le somme dei valori sulle diagonali diritte della matrice il cui offset e` passato in BX e la cui dimensione e` in AX, e le scrive nel vettore il cui offset e` scritto in SI. SEGMENT PARA PUBLIC 'DATA' DW DW DW DW ENDS DIAGO1 0 0 0 0

DSEG ENNE SUML SUMH MATP DSEG

PUBLIC

CSEG

SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG PROC AX BX CX DX SI DI ENNE, AX MATP, BX FAR

;ALREADY SET BY DOS LOADER

DIAGO1 PUSH PUSH PUSH PUSH PUSH PUSH MOV MOV MOV MOV MOV SHL DEC DEC LABD0: MOV LABD1: ADD CMP JNE MOV CALL MOV ADD ADC CONT: INC CMP JNE MOV INC CMP JNE MOV MOV SHL SHL ADD MOV INC INC MOV MOV MOV MOV LOOP MOV MOV CALL MOV MOV POP

SUMH, 0 SUML, 0 CX, AX CX, 1 CX CX MOV AX, 0 ; indice riga DX, 0 ; indice colonna MOV DI, AX ; e` sulla diagonale corrente? DI, DX DI, CX CONT BX, MATP CONVERT ; si` BX, [DI] SUML, BX SUMH, 0 DX DX, ENNE LABD1 DX, 0 AX AX, ENNE LABD1 BX, SUML DI, CX DI, 1 DI, 1 DI, SI [DI], BX DI DI BX, SUMH [DI], BX SUMH, 0 SUML, 0 LABD0 AX, 0 DX, 0 CONVERT BX, [DI] [SI], BX DI 297 ; elemento 0,0 ; vai alla casella successiva

; fine della diagonale

POP POP POP POP POP RET DIAGO1

SI DX CX BX AX ENDP

; Calcola in DI l'offset dell'elemento di MAT le cui ; coordinate sono contenute in AX, DX all'interno della ; matrice il cui offset sta in BX CONVERT PROC PUSH PUSH PUSH MOV MOV MUL ADD SHL ADD POP POP POP RET CONVERT ENDP AX CX DX CX, DX DI, BX ENNE AX, CX AX, 1 DI, AX DX CX AX

CSEG

ENDS END

Listing:
; calcola le somme dei valori sulle diagonali d iritte della ; matrice il cui offset e` passato in BX e la c ui dimensione e` in AX, e ; le scrive nel vettore il cui offset e` scritt o in SI. 0000 0000 0002 0004 0006 0008 0000 0000 0000 0000 DSEG SEGMENT PARA PUBLIC 'DATA' ENNE SUML SUMH MATP DW DW DW DW 0 0 0 0

DSEG ENDS

PUBLIC 0000 LOADER 0000 0000 0001 0002 0003 0004 0005 50 53 51 52 56 57 DIAGO1 PUSH PUSH PUSH PUSH PUSH PUSH MOV MOV MOV

DIAGO1

CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG ;ALREADY SET BY DOS PROC FAR AX BX CX DX SI DI ENNE, AX MATP, BX

0006 A3 0000 R 0009 89 1E 0006 R 000D C7 06 0004 R 0000 0013 C7 06 0002 R 0000 0019 8B C8 001B D1 E1 001D 49 001E 49 001F B8 0000 0022 BA 0000 0025 8B F8 0027 03 FA 0029 3B F9 002B 75 12 002D 8B 1E 0006 R 0031 E8 0088 R 0034 8B 1D 0036 01 1E 0002 R 003A 83 16 0004 R 00 003F 42 0040 3B 16 0000 R 0044 75 DF 0046 BA 0000 0049 40 004A 3B 06 0000 R 004E 75 D5 0050 8B 1E 0002 R 0054 8B F9 0056 D1 E7 0058 D1 E7 005A 03 FE 005C 89 1D 005E 47 005F 47 0060 8B 1E 0004 R 0064 89 1D 0066 C7 06 0004 R 0000 006C C7 06 0002 R 0000 0072 E2 AB 0074 B8 0000 0077 BA 0000 007A E8 0088 R

MOV SUMH, 0 SUML, 0 MOV CX, AX SHL CX, 1 DEC CX DEC CX LABD0:MOV AX, 0 MOV DX, 0 LABD1:MOV DI, AX ADD DI, DX CMP DI, CX JNE CONT MOV BX, MATP CALL CONVERT MOV BX, [DI] ADD SUML, BX ADC SUMH, 0 CONT: INC CMP JNE MOV INC CMP JNE DX DX, ENNE LABD1 DX, 0 AX AX, ENNE LABD1

; indice riga ; indice colonna ; e` sulla diagonale corrente ?

; si`

; vai alla casella successiva

MOV

MOV BX, SUML MOV DI, CX SHL DI, 1 SHL DI, 1 ADD DI, SI MOV [DI], BX INC DI INC DI MOV BX, SUMH MOV [DI], BX SUMH, 0 MOV SUML, 0 LOOP LABD0 MOV AX, 0 MOV DX, 0 CALL CONVERT

; fine della diagonale

; elemento 0,0

299

007D 8B 1D 007F 89 1C 0081 0082 0083 0084 0085 0086 5F 5E 5A 59 5B 58

MOV MOV POP POP POP POP POP POP RET DIAGO1

BX, [DI] [SI], BX DI SI DX CX BX AX ENDP

0087 CB 0088

; Calcola in DI l'offset dell'elemento di MAT l e cui coordinate sono ; contenute in AX, DX all'interno della matrice il cui offset sta in BX 0088 0088 50 0089 51 008A 52 008B 8B CA 008D 8B FB 008F F7 26 0000 R 0093 03 C1 0095 D1 E0 0097 03 F8 0099 5A 009A 59 009B 58 009C C3 009D CONVERT PROC PUSH AX PUSH CX PUSH DX MOV MOV MUL ADD SHL ADD POP POP POP RET CONVERT ENDP CX, DX DI, BX ENNE AX, CX AX, 1 DI, AX DX CX AX

009D

CSEG ENDS END

Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr Length Align 009D 0008 Combine Class 'CODE' 'DATA'

PARA PUBLIC PARA PUBLIC

CONT . . . . . . . . . . . . . . L NEAR 003F CONVERT . . . . . . . . . . . . N PROC DIAGO1 . . . . . . . . . . . . . F PROC 0000 0000 001F 0025 0006 0004 0002

CSEG 0088 CSEG Length = 0015 CSEG Global Length = 0088 DSEG CSEG CSEG DSEG DSEG DSEG

ENNE . . . . . . . . . . . . . . L WORD LABD0 . . . . . . . . . . . . . L NEAR LABD1 . . . . . . . . . . . . . L NEAR MATP . . . . . . . . . . . . . . L WORD SUMH . . . . . . . . . . . . . . L WORD SUML . . . . . . . . . . . . . . L WORD

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT diago1 @VERSION . . . . . . . . . . . . TEXT 510 123 Source Lines 123 Total Lines 18 Symbols 47580 + 161711 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Diagonal
;*********************************************************** ; main di DIAGO1.ASM ;*********************************************************** EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR, DIAGO1:FAR MAX DSEG EQU 100

SEGMENT PARA PUBLIC 'DATA' MAX*MAX DUP (0) 2*MAX-1 DUP (0) 2*MAX-1 DUP (0) 0

MAT DW VETT1 DD VETT2 DD N DSEG DW ENDS

CSEG

SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX AX,DSEG

;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH MOV

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO 301

MOV DS,AX ASSUME DS:DSEG LEA CALL MOV LEA LEA LEA CALL DI, MAT GETMAT N, AX BX, MAT SI, VETT1 DI, VETT2 DIAGO1

; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV CX, N SHL CX, 1 DEC CX LEA DI, VETT1 LAB1: MOV DX, [DI] CALL OUTPUT CALL ACAPO INC DI INC DI INC DI INC DI LOOP LAB1 ; CALL ACAPO

; MOV CX, N ; INC CX ; LEA DI, VETT2 ;LAB2: MOV DX, [DI] ; CALL OUTPUT ; CALL ACAPO ; INC DI ; INC DI ; LOOP LAB2 RET ENTPT ENDP ; acquisisce una matrice: legge il numero di righe e di ; colonne, li mette in AX e DX, e poi legge gli elementi, ; memorizzandoli a partire dall'indirizzo contenuto in DI GETMAT PUSH CALL PUSH MOV CALL CALL PUSH MOV CALL MUL MOV PROC CX INPUT ; numero righe DX AX, DX ACAPO INPUT ; numero colonne DX CX, DX ACAPO CX ; calcolo numero elementi

CX, AX

LABGET: CALL INPUT CALL ACAPO MOV [DI], DX INC DI INC DI LOOP LABGET POP POP POP RET GETMAT CSEG ENDS ;256 WORD STACK AREA ENDP DX AX CX ; ripristino numero colonne ; ripristino numero righe

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

Listing:
;*************************** ; main di DIAGO1.ASM ;*************************** EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR, DIAGO 1:FAR = 0064 0000 0000 2710[ 0000 ] 4E20 00C7[ 00000000 ] 513C 00C7[ 00000000 ] 5458 0000 545A N DW 0 VETT2 DD 2*MAX-1 DUP (0) VETT1 DD 2*MAX-1 DUP (0) MAX EQU 100

DSEG SEGMENT PARA PUBLIC 'DATA' MAT DW MAX*MAX DUP (0)

DSEG ENDS

0000 LOADER

CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS

303

0000 0000 1E HAVE 0001 33 C0 THE 0003 50 DOS 0004 B8 ---- R 0007 8E D8 DID 0009 8D 3E 0000 R 000D E8 0042 R 0010 A3 5458 R 0013 0017 001B 001F 8D 1E 0000 R 8D 36 4E20 R 8D 3E 513C R 9A 0000 ---- E

ENTPT PROC FAR PUSH DS XOR AX,AX

;ENTRY POINT FROM DOS ;SET UP THE STACK TO ; THE DOUBLE WORD VECTOR SO ; FAR RETURN WILL GO BACK TO

PUSH AX MOV

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER WHAT I JUST LEA DI, MAT CALL GETMAT MOV LEA LEA LEA CALL N, AX BX, MAT SI, VETT1 DI, VETT2 DIAGO1 CX, N CX, 1 CX DI, VETT1 DX, [DI] OUTPUT ACAPO DI DI DI DI LAB1

0024 8B 0E 5458 R 0028 D1 E1 002A 49 002B 8D 3E 4E20 R 002F 8B 15 0031 9A 0000 ---- E 0036 9A 0000 ---- E 003B 47 003C 47 003D 47 003E 47 003F E2 EE

MOV SHL DEC LEA LAB1: MOV CALL CALL INC INC INC INC LOOP ;

CALL ACAPO CX, N CX DI, VETT2 DX, [DI] OUTPUT ACAPO DI DI LAB2

; MOV ; INC ; LEA ;LAB2: MOV ; CALL ; CALL ; INC ; INC ; LOOP 0041 CB 0042 RET ENTPT ENDP

; acquisisce una matrice: legge il numero di righe e di colonne, li ; mette in AX e DX, e poi legge gli elementi, memorizzandoli a partire ; dall'indirizzo contenuto in DI 0042 0042 51 0043 9A 0000 ---- E 0048 52 GETMAT PROC

PUSH CX CALL INPUT ; numero righe PUSH DX

0049 8B C2 004B 9A 0000 ---- E 0050 9A 0000 ---- E 0055 52 0056 8B CA 0058 9A 0000 ---- E 005D F7 E1 005F 8B C8 0061 9A 0000 ---- E 0066 9A 0000 ---- E 006B 89 15 006D 47 006E 47 006F E2 F0 0071 5A 0072 58 0073 59 0074 C3 0075 0075 0000 0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

MOV

AX, DX

CALL ACAPO CALL INPUT ; numero colonne PUSH DX MOV CX, DX CALL ACAPO MUL CX ; calcolo numero elementi

MOV CX, AX LABGET: CALL INPUT CALL ACAPO MOV [DI], DX INC DI INC DI LOOP LABGET POP POP POP RET GETMAT CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WO RD STACK AREA ENDP DX AX CX ; ripristino numero colonne ; ripristino numero righe

STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 0075 DSEG . . . . . . . . . . . . . . 545A STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . DIAGO1 . . . . . . . . . . . . . Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr External External

L FAR 0000 L FAR 0000

ENTPT . . . . . . . . . . . . . F PROC 0000 GETMAT . . . . . . . . . . . . .

CSEG Length = 0042 0042 CSEG Length = 0033 305

N PROC

INPUT . . . . . . . . . . . . . L FAR 0000 LAB1 . . . . . . . . . . . . . . L NEAR 002F LABGET . . . . . . . . . . . . . L NEAR MAT . . . . . . . . . . . . . . MAX . . . . . . . . . . . . . . N ............... OUTPUT . . . . . . . . . . . . . L WORD NUMBER L WORD 0000 0064 5458

External CSEG 0061 CSEG DSEG Length = 2710 DSEG External DSEG Length = 00C7 DSEG Length = 00C7

L FAR 0000 4E20 513C

VETT1 . . . . . . . . . . . . . L DWORD VETT2 . . . . . . . . . . . . . L DWORD

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT diagonal @VERSION . . . . . . . . . . . . TEXT 510 122 Source Lines 122 Total Lines 23 Symbols 47526 + 159718 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Equus
EXTRN DIM DSEG SCACC A B DSEG CSEG INPUT:FAR, OUTPUT:FAR EQU 5

SEGMENT PARA PUBLIC 'DATA' DB DW DW ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK DIM*DIM DUP (0) 2,1,-1,-2,-2,-1,1,2 1,2,2,1,-1,-2,-2,-1

;*********************************************************** ; ; MAIN ; ; ;*********************************************************** ENTPT PROC PUSH XOR PUSH MOV MOV ASSUME FAR DS AX,AX AX AX,DSEG DS,AX DS:DSEG

MOV MOV PUSH PUSH MOV PUSH

SCACC[0], 1 AX, 0 AX AX AX, 2 AX

CALL MUOVI ADD CMP JNE MOV MOV MOV MOV MOV CALL INC DEC JNE CALL MOV LOOP RET ENDP SP, 6 AX, 1 FINE SI, 0 CX, DIM*DIM DX, 0 AX, 10 DL, SCACC[SI] OUTPUT SI AX AVANTI ACAPO AX, 10 LOOP1 ; visualizza la ; soluzione trovata

LOOP1:

FINE: ENTPT

; return

;*********************************************************** ; ; MUOVI ; ; Esegue una mossa del cavallo a partire dalla locazione ; passata come parametro. Controlla se la nuova mossa porta ; in una casella libera ed appartenente alla scacchiera. In ; caso affermativo la occupa e richiama se stessa, ; diversamente prova una nuova mossa. ; Se nessuna delle mosse possibili da` esito positivo ; ritorna uno 0, altrimenti ritorna 1. ; ;*********************************************************** MUOVI PROC PUSH MOV SUB PUSH PUSH PUSH PUSH PUSH MOV CMP BP BP, SP SP, 2 BX CX DX SI DI CX, [BP+4] CX, DIM*DIM+1 ; mossa

307

JB CONT MOV AX, 1 JMP SHORT MFINE CONT: MLOOP: MOV MOV MOV ADD JS ADD JS CMP JAE CMP JAE MOV MOV MUL MOV ADD CMP JNE MOV INC PUSH PUSH PUSH CALL ADD CMP JNE DEC MOV

; ultima mossa

GOOD:

DI, 0 ; DI contiene i BX, [BP+6] ; BX contiene x SI, [BP+8] ; SI contiene y BX, A[DI] ; calcola newx NOGOOD SI, B[DI] ; calcola newy NOGOOD BX, DIM NOGOOD SI, DIM NOGOOD [BP-2], BX ; calcola gli offset in SCACC AX, DIM BX BX, AX BX, SI SCACC[BX], 0 ; casella libera ? NOGOOD SCACC[BX], CL ; se si`, occupa la casella CX SI [BP-2] CX MUOVI SP, 6 AX, 0 MFINE CX ; nessuna soluzione possibile SCACC[BX], 0 ; rilascia la casella DI DI DI, 16 MLOOP AX, 0 DI SI DX CX BX SP, 2 SP, BP BP ; tenta un'altra mossa

NOGOOD: INC INC CMP JNE MOV MFINE: POP POP POP POP POP ADD MOV POP MUOVI CSEG STACK STACK RET ENDP ENDS

SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ENDS END ENTPT

; stack area

Listing:
TITLE EQUUS EXTRN INPUT:FAR, OUTPUT:FAR = 0005 0000 0000 0019[ 00 ] 0019 0002 0001 FFFF FFFE FFFE FFFF 0001 0002 0029 0001 0002 0002 0001 FFFF FFFE FFFE FFFF 0039 0000 A B DW DW 2,1,-1,-2,-2,-1,1,2 1,2,2,1,-1,-2,-2,-1 DIM EQU 5

DSEG SEGMENT PARA PUBLIC 'DATA' SCACC DB DIM*DIM DUP (0)

DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;********************************************** ; ; MAIN ; ; ;**********************************************

0000 0000 1E 0001 33 C0 0003 50 0004 B8 ---- R 0007 8E D8 0009 C6 06 0000 R 01 000E 0011 0012 0013 0016 B8 0000 50 50 B8 0002 50

ENTPT PROC FAR PUSH DS XOR AX,AX PUSH AX MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG MOV SCACC[0], 1

MOV AX, 0 PUSH AX PUSH AX MOV AX, 2 PUSH AX CALL MUOVI ADD SP, 6

0017 E8 0044 R 001A 83 C4 06 001D 3D 0001 0020 75 21 0022 BE 0000 0025 B9 0019 0028 BA 0000

CMP AX, 1 JNE FINE MOV MOV MOV SI, 0 ; visualizza la CX, DIM*DIM ; soluzione trovata DX, 0 309

002B 002E 0032 0037 0038 0039

B8 000A 8A 94 0000 R 9A 0000 ---- E 46 48 75 00

MOV AX, 10 LOOP1: MOV DL, SCACC[SI] CALL OUTPUT INC SI DEC AX JNE AVANTI CALL ACAPO MOV AX, 10 LOOP LOOP1 FINE: RET ENTPT ENDP ; return

003B E8 0000 U 003E B8 000A 0041 E2 EB 0043 CB 0044

;********************************************** ; ; MUOVI ; ; Esegue una mossa del cavallo a partire dalla locazione passata ; come parametro. Controlla se la nuova mossa p orta in una casella ; libera ed appartenente alla scacchiera. In ca so affermativo la ; occupa e richiama se stessa, diversamente pro va una nuova mossa. ; Se nessuna delle mosse possibili da` esito po sitivo ritorna uno 0, ; altrimenti ritorna 1. ; ;********************************************** 0044 0044 55 0045 8B EC 0047 83 EC 02 004A 004B 004C 004D 004E 53 51 52 56 57 MUOVI PROC PUSH BP MOV BP, SP SUB PUSH PUSH PUSH PUSH PUSH SP, 2 BX CX DX SI DI

004F 8B 4E 04 0052 83 F9 1A 0055 72 05 0057 B8 0001 005A EB 57 005C 005F 0062 0065 0069 006B BF 0000 8B 5E 06 8B 76 08 03 9D 0019 R 78 3E 03 B5 0029 R

MOV CX, [BP+4] ; mossa CMP CX, DIM*DIM+1 JB CONT MOV AX, 1 ; ultima mossa JMP SHORT MFINE CONT: MOV DI, 0 ; DI contiene i MLOOP: MOV BX, [BP+6] ; BX contiene x MOV SI, [BP+8] ; SI contiene y ADD BX, A[DI] ; calcola la newx JS NOGOOD ADD SI, B[DI] ; calcola newy

006F 78 38 0071 83 FB 05 0074 73 33 0076 83 FE 05 0079 73 2E 007B 89 5E FE 007E B8 0005 0081 F7 E3 0083 8B D8 0085 03 DE 0087 80 BF 0000 R 00 008C 75 1B 008E 88 8F 0000 R 0092 41 0093 56 0094 FF 76 FE 0097 51 0098 E8 0044 R 009B 83 C4 06 009E 3D 0000 00A1 75 10 00A3 49 00A4 C6 87 0000 R 00 00A9 47 00AA 47 00AB 83 FF 10 00AE 75 AF 00B0 B8 0000 00B3 00B4 00B5 00B6 00B7 5F 5E 5A 59 5B

JS NOGOOD CMP BX, DIM JAE NOGOOD CMP SI, DIM JAE NOGOOD MOV [BP-2], BX ; calcola gli offset in SCACC MOV AX, DIM MUL BX MOV BX, AX ADD BX, SI CMP SCACC[BX], 0 ; casella libera ? JNE NOGOOD GOOD: MOV SCACC[BX], CL ; se si`, occupa la casella INC CX PUSH SI PUSH [BP-2] PUSH CX CALL MUOVI ADD SP, 6 CMP AX, 0 JNE MFINE DEC CX ; nessuna soluzione possibile MOV SCACC[BX], 0 ; rilascia la casella NOGOOD: INC DI ; tenta un'altra mossa INC DI CMP DI, 16 JNE MLOOP MOV AX, 0

MFINE: POP DI POP SI POP DX POP CX POP BX ADD SP, 2

00B8 83 C4 02 00BB 8B E5 00BD 5D 00BE C3 00BF 00BF 0000 0000 0040[ 53 54 41 43 4B 20 20 20 ] 0200

MOV SP, BP POP BP RET MUOVI ENDP CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ; stack area

STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class 311

CSEG . . . . . . . . . . . . . . 00BF DSEG . . . . . . . . . . . . . . 0039 STACK . . . . . . . . . . . . . 0200 Symbols: Name A ............... B ............... Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr 0019 0029 005C 0005 CSEG Length = 0044 0043 008E CSEG CSEG External 002E CSEG CSEG 005F CSEG 0044 CSEG Length = 007B 00A9 CSEG External DSEG DSEG CSEG

L WORD L WORD

CONT . . . . . . . . . . . . . . L NEAR DIM . . . . . . . . . . . . . . NUMBER

ENTPT . . . . . . . . . . . . . F PROC 0000 FINE . . . . . . . . . . . . . . L NEAR

GOOD . . . . . . . . . . . . . . L NEAR INPUT . . . . . . . . . . . . . L FAR 0000 LOOP1 . . . . . . . . . . . . . L NEAR

MFINE . . . . . . . . . . . . . L NEAR 00B3 MLOOP . . . . . . . . . . . . . L NEAR MUOVI . . . . . . . . . . . . . N PROC NOGOOD . . . . . . . . . . . . . OUTPUT . . . . . . . . . . . . . L NEAR L FAR 0000

SCACC . . . . . . . . . . . . . L BYTE0000

DSEG Length = 0019

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT equus @VERSION . . . . . . . . . . . . TEXT 510

155 Source Lines 155 Total Lines 25 Symbols 47468 + 155679 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Interp
_TEXT SEGMENT ASSUME PUBLIC _interp PROC WORD PUBLIC 'CODE' CS:_TEXT _interp

PUSH MOV SUB PUSH PUSH PUSH PUSH PUSH MOV MOV XOR LAB: CMP JE JL ADD JMP

BP BP, SP SP, 2 BX CX DX DI SI BX, [BP+4] AX, [BP+6] SI, SI AX, [BX+SI] FOUND ; il valore cercato e` nella tabella LAB1 SI, 4 SHORT LAB MOV FINE AX, DX, DX, DX CX, CX, CX CX, AX, AX, [BX+SI+2] ; scrivo il risultato ; puntatore alla tabella ; valore x

FOUND: JMP LAB1: SUB MOV SUB IMUL MOV SUB IDIV MOV ADD FINE: MOV MOV XOR POP POP POP POP POP ADD POP

[BX+SI-4] ; x-x1 [BX+SI+2] [BX+SI-2] ; f2-f1 ; (f2-f1) * (x-x1) [BX+SI] [BX+SI-4] ; x2-x1 ; (f2-f1) * (x-x1) / (x2-x1) [BX+SI-2] CX ; f1 + (f2-f1) * (x-x1) / (x2-x1) ; scrivi il risultato ; valore di ritorno

BX, [BP+8] [BX], AX AX, AX SI DI DX CX BX SP, 2 BP

RET _interp ENDP _TEXT ENDS END

Listing:
TITLE INTERP 0000 _TEXT SEGMENT ASSUME PUBLIC 0000 _interp PROC 313 WORD PUBLIC 'CODE' CS:_TEXT _interp

0000 55 0001 8B EC 0003 83 EC 02 0006 53 0007 51 0008 52 0009 57 000A 56 000B 8B 5E 04 000E 8B 46 06 0011 33 F6 0013 0015 0017 0019 001C 3B 00 74 07 7C 0B 83 C6 04 EB F5 LAB:

PUSH BP MOV BP, SP SUB SP, 2 PUSH PUSH PUSH PUSH PUSH MOV MOV XOR CMP JE JL ADD JMP BX CX DX DI SI BX, [BP+4] AX, [BP+6] SI, SI AX, [BX+SI] FOUND LAB1 SI, 4 SHORT LAB MOV FINE AX, [BX+SI-4] DX, [BX+SI+2] DX, [BX+SI-2] DX CX, [BX+SI] CX, [BX+SI-4] CX CX, [BX+SI-2] AX, CX BX, [BP+8] [BX], AX AX, AX SI DI DX CX BX SP, 2 BP ; x - x1 ; f2 - f1 ; (f2 - f1) * (x - x1) ; x2 - x1 ; (f2 - f1) * (x - x1) / (x2 - x1) ; f1 + (f2 - f1) * (x - x1) / (x2 - x1) ; scrivi il risultato ; valore di ritorno ; il valore cercato e` nella tabella ; puntatore alla tabella ; valore x

001E 8B 40 02 0021 EB 18 90 0024 2B 40 FC 0027 8B 50 02 002A 2B 50 FE 002D F7 EA 002F 8B 08 0031 2B 48 FC 0034 F7 F9 0036 8B 48 FE 0039 03 C1 003B 8B 5E 08 003E 89 07 0040 33 C0 0042 0043 0044 0045 0046 5E 5F 5A 59 5B

FOUND: JMP LAB1: SUB MOV SUB IMUL MOV SUB IDIV MOV ADD FINE: MOV MOV XOR POP POP POP POP POP ADD POP RET _interp ENDP _TEXT ENDS END

AX, [BX+SI+2] ; scrivo il risultato

0047 83 C4 02 004A 5D 004B C3 004C 004C

Segments and Groups: Name Length Align Combine Class 'CODE'

_TEXT . . . . . . . . . . . . . 004C

WORD PUBLIC

Symbols: Name Type Value Attr _TEXT 001E _TEXT _TEXT _TEXT

FINE . . . . . . . . . . . . . . L NEAR 003B FOUND . . . . . . . . . . . . . L NEAR LAB . . . . . . . . . . . . . . LAB1 . . . . . . . . . . . . . . L NEAR L NEAR 0013 0024

@CPU . . . . . . . . . . . . . . TEXT @FILENAME . . . . . . . . . . . @VERSION . . . . . . . . . . . . _INTERP . . . . . . . . . . . . 61 Source Lines 61 Total Lines 12 Symbols

0101h TEXT interp1 TEXT 510 N PROC

0000

_TEXT Global Length = 004C

47496 + 163843 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Media
EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR, MEDIA:FAR MAX MAX2 DSEG EQU EQU 10000 50

SEGMENT PARA PUBLIC 'DATA' MAX DUP(0) 0 0

VETT DW KAPPA DW ENNE DW DSEG CSEG ENDS

SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX

ENTPT PROC PUSH XOR PUSH

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG CALL CALL MOV MOV CALL CALL MOV LEA LAB1: CALL INPUT ; numero elementi vettore (n) ACAPO CX, DX ENNE, DX INPUT ; numero elementi su cui fare la media (k) ACAPO KAPPA, DX DI, VETT INPUT ; caricamento vettore

315

CALL MOV INC INC LOOP LEA MOV MOV CALL LEA LAB2: MOV CALL CALL INC INC LOOP RET ENTPT ENDP

ACAPO [DI], DX DI DI LAB1 BX, VETT CX, ENNE AX, KAPPA MEDIA DI, VETT DX, [DI] OUTPUT ACAPO DI DI LAB2 ; visualizzazione vettore

; acquisisce una matrice: legge il numero di righe e di ; colonne, li mette in AX e DX, e poi legge gli elementi, ; memorizzandoli a partire dall'indirizzo contenuto in DI GETMAT PUSH CALL PUSH MOV CALL CALL PUSH MOV CALL MUL PROC CX INPUT ; numero righe DX AX, DX ACAPO INPUT ; numero colonne DX CX, DX ACAPO CX ; calcolo numero elementi

MOV CX, AX LABGET: CALL INPUT CALL ACAPO MOV [DI], DX INC DI INC DI LOOP LABGET POP POP POP RET GETMAT ENDP DX AX CX ; ripristino numero colonne ; ripristino numero righe

CSEG

ENDS ;256 WORD STACK AREA

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

Listing:
TITLE MEDIA EXTRN INPUT:FAR, OUTPUT:FAR, ACAPO:FAR, MEDIA :FAR = 2710 = 0032 0000 0000 2710[ 0000 ] 4E20 0000 4E22 0000 4E24 KAPPA DW ENNE DW DSEG ENDS 0 0 MAX EQU MAX2 EQU 10000 50

DSEG SEGMENT PARA PUBLIC 'DATA' VETT DW MAX DUP(0)

0000

CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ENTPT PROC PUSH XOR PUSH MOV FAR DS AX,AX AX

0000 0000 1E 0001 33 C0 0003 50 0004 B8 ---- R 0007 8E D8 0009 000E 0013 0015 0019 001E 0023 0027 002B 0030 0035 0037 0038 0039 9A 0000 ---- E 9A 0000 ---- E 8B CA 89 16 4E22 R 9A 0000 ---- E 9A 0000 ---- E 89 16 4E20 R 8D 3E 0000 R 9A 0000 ---- E 9A 0000 ---- E 89 15 47 47 E2 F0

AX,DSEG MOV DS,AX ASSUME DS:DSEG CALL CALL MOV MOV CALL CALL MOV INPUT ACAPO CX, DX ENNE, DX INPUT ACAPO KAPPA, DX DI, VETT INPUT ACAPO [DI], DX DI DI LAB1 BX, VETT CX, ENNE AX, KAPPA 317 ; numero elementi vettore (n)

; numero elementi su cui fare la media (k)

LEA LAB1: CALL CALL MOV INC INC LOOP LEA MOV MOV

; caricamento vettore

003B 8D 1E 0000 R 003F 8B 0E 4E22 R 0043 A1 4E20 R

0046 9A 0000 ---- E 004B 8D 3E 0000 R 004F 8B 15 0051 9A 0000 ---- E 0056 9A 0000 ---- E 005B 47 005C 47 005D E2 F0 005F CB 0060

CALL MEDIA LEA LAB2: MOV CALL CALL INC INC LOOP RET ENTPT ENDP DI, VETT DX, [DI] OUTPUT ACAPO DI DI LAB2 ; visualizzazione vettore

; acquisisce una matrice: legge il numero di righe e di colonne, li ; mette in AX e DX, e poi legge gli elementi, memorizzandoli a partire ; dall'indirizzo contenuto in DI 0060 0060 51 0061 9A 0000 ---- E 0066 52 0067 8B C2 0069 9A 0000 ---- E 006E 9A 0000 ---- E 0073 52 0074 8B CA 0076 9A 0000 ---- E 007B F7 E1 007D 8B C8 007F 9A 0000 ---- E 0084 9A 0000 ---- E 0089 89 15 008B 47 008C 47 008D E2 F0 008F 5A 0090 58 0091 59 0092 C3 0093 0093 0000 0000 0040[ GETMAT PROC

PUSH CX CALL INPUT ; numero righe PUSH DX MOV AX, DX CALL ACAPO CALL INPUT ; numero colonne PUSH DX MOV CX, DX CALL ACAPO MUL CX ; calcolo numero elementi

MOV CX, AX LABGET: CALL INPUT CALL ACAPO MOV [DI], DX INC DI INC DI LOOP LABGET POP POP POP RET GETMAT CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WO ENDP DX AX CX ; ripristino numero colonne ; ripristino numero righe

RD STACK AREA 53 54 41 43 4B 20 20 20 ] 0200 STACK ENDS END ENTPT

Segments and Groups: Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 0093 DSEG . . . . . . . . . . . . . . 4E24 STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr External

L FAR 0000

ENNE . . . . . . . . . . . . . . L WORD ENTPT . . . . . . . . . . . . . F PROC 0000 GETMAT . . . . . . . . . . . . .

4E22 DSEG CSEG Length = 0060 0060 External 4E20 DSEG CSEG CSEG 007F CSEG CSEG Length = 0033

N PROC

INPUT . . . . . . . . . . . . . L FAR 0000 KAPPA . . . . . . . . . . . . . L WORD

LAB1 . . . . . . . . . . . . . . L NEAR 002B LAB2 . . . . . . . . . . . . . . L NEAR 004F LABGET . . . . . . . . . . . . . L NEAR MAX . . . . . . . . . . . . . . NUMBER MAX2 . . . . . . . . . . . . . . NUMBER MEDIA . . . . . . . . . . . . . L FAR 0000 OUTPUT . . . . . . . . . . . . . VETT . . . . . . . . . . . . . . 2710 0032

External External DSEG Length = 2710

L FAR 0000 L WORD 0000

@CPU . . . . . . . . . . . . . . TEXT 0101h @FILENAME . . . . . . . . . . . TEXT media @VERSION . . . . . . . . . . . . TEXT 510 113 Source Lines 113 Total Lines 24 Symbols 47546 + 161745 Bytes symbol space free 0 Warning Errors 0 Severe Errors

Procedura Lista
; gestione di una lista ; accetta i seguenti comandi: 319

; ; ; ;

I<num><CR> C<NUM><CR> L F

inserzione nella lista cancella un valore dalla lista stampa contenuto della lista fine

DSEG

SEGMENT PARA PUBLIC 'DATA'

CBUF DB 20 DUP(0) HEAD DW 0 F_HEAD DW 0 MEM_MESS DB 'Memoria dinamica esaurita', 0DH, 0AH, '$' ERR_MESS DB 'Numero troppo grande', 0DH, 0AH, '$' DEL_MESS DB 'Numero elementi cancellati', 0DH, 0AH, '$' PROMPT DB '>>$' DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK FAR DS AX,AX AX ;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG ; my code MOV HEAD, 0

; testa lista ; stampa il prompt

RD_CMD: LEA DX, PROMPT MOV AH, 9 INT 21H MOV INT CMP JE ; ; CMP JE CMP JE CMP JE PUSH MOV MOV INT MOV INT POP JMP CMD_I: AH, 1 21H AL, 'I' CMD_I AL, 'C' CMD_C AL, 'V' CMD_V AL, 'E' FINE DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX

; legge il comando ; insert ; delete ; visualizza ; end

; stampa un CR ; stampa un LF

SHORT RD_CMD CALL INPUT

PUSH MOV MOV INT MOV INT POP CALL JMP CMD_V: MOV MOV INT MOV INT POP CALL JMP ;CMD_C: ; ; ; ; ; ; ; ; ; ; ; ; ; ; PUSH MOV MOV INT MOV INT POP CALL LEA MOV INT MOV CALL JMP

DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX INSERT RD_CMD PUSH DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX DISPLAY RD_CMD CALL INPUT

; stampa un CR ; stampa un LF

; stampa un CR ; stampa un LF

DX AH, 2 DL, 0DH 21H DL, 0AH 21H DX DELETE DX, DEL_MESS AH, 9 21H DX, CX OUTPUT RD_CMD

; stampa un CR ; stampa un LF

; cancellalo

; stampa numero elementi cancellati

FINE: RET ENTPT ENDP

;RETURN TO DOS

;*********************************************************** ; ; inserisce l'elemento in DX nel primo slot della lista ; ;*********************************************************** INSERT PROC AX

PUSH PUSH BX MOV

AH, 48H 321

MOV INT JC MOV MOV MOV MOV MOV

BX, 1 21H IN_FINE BX, AX [BX], DX DX, HEAD [BX]+2, DX HEAD, AX ES ; errore in allocazione ; campo dato ; aggiorna HEAD

IN_FINE: POP POP BX POP AX RET INSERT ENDP

;*********************************************************** ; ; stampa il contenuto della lista ; ;*********************************************************** DISPLAY PUSH PUSH PUSH MOV D_LOOP: JE MOV CALL MOV JMP D_FINE: POP POP RET DISPLAY PROC BX DX ES BX, HEAD CMP BX, 0 D_FINE DX, [BX] OUTPUT BX, [BX]+2 SHORT D_LOOP POP DX BX ENDP ES

;*********************************************************** ; procedura di lettura e conversione di un numero ; il numero letto e decodificato viene lasciato in DX ;*********************************************************** INPUT PROC PUSH PUSH LAB0: XOR LAB1: MOV INT CMP AX BX DX, DX MOV BX, 10 AH, 1 ; legge un carattere 21H AL, 13 ; e` un CR ?

JE CMP JB CMP JA SUB XCHG MOV MUL CMP JNE MOV ADD JC JMP JMP I_ERR: MOV INT JMP I_FINE: POP RET INPUT ENDP

I_FINE AL, '0' LAB1 AL, '9' LAB1 AL, '0' AX, BX BH, 0 DX DX, 0 I_ERR DX, AX DX, BX I_ERR LAB1 LAB0

; se si` fine ; se no controlla che sia un numero

; OK: togli codifica ASCII ; moltiplica per 10

; somma la cifra letta

LEA DX, ERR_MESS AH, 9 21H LAB0 POP AX BX ;RETURN

;*********************************************************** ; procedura di conversione ed output di un numero ; il numero da scrivere viene letto da DX ;*********************************************************** OUTPUT PUSH PUSH PUSH PUSH XOR MOV CONV: MOV MOV CICLO: ADD MOV INC MOV CMP JNE LAB: DEC MOV MOV INT CMP JNE MOV PROC DI AX BX DX DI, DI AX, DX DX, 0 BX, 10 DIV BX DL, '0' CBUF[DI], DL DI DX, 0 AX, 0 CICLO DI DL, CBUF[DI] AH, 2 21H DI, 0 LAB DL, 13 ; stampa un CR 323

MOV INT MOV INT POP POP POP POP RET OUTPUT CSEG ENDS

AH, 2 21H DL, 10 21H DX BX AX DI ENDP

; stampa un LF

;RETURN

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

;256 WORD STACK AREA

Procedura Region

;*********************************************************** ; ; ; Compito del 25 settembre 1989 ; ;*********************************************************** MAX DIM EQU EQU 10000 ; dimensione buffer circolare 11 ; dimensione matrice

EXTRN INPUT:FAR, OUTPUT:FAR DSEG SEGMENT PARA PUBLIC 'DATA' DB DB DB DB DB DB DB DB DB DB DB 255,255,255,255,255,255,255,255,255,255,255 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255 255,255,255, 0,255,255,255,255,255, 0,255 255, 0,255, 0,255, 0, 0, 0,255, 0,255 255, 0,255, 0,255,255,255, 0,255, 0,255 255, 0,255, 0,255, 0,255, 0,255, 0,255 255, 0,255, 0,255, 0,255, 0,255, 0,255 255, 0,255, 0,255, 0,255,255,255, 0,255 255, 0,255, 0,255, 0, 0, 0, 0, 0,255 255,255,255,255,255,255,255,255,255,255,255

MATRIX

BUFF DW MAX DUP(0) B_IN DW 0 B_OUT DW 0 CBUF DB 20 DUP(0) ERR_MESS DB 'Numero troppo grande', 0DH, 0AH, '$' FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' DSEG CSEG ENDS SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREADY SET BY DOS LOADER

ENTPT PROC PUSH XOR PUSH

FAR DS AX,AX AX

;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO THE ; FAR RETURN WILL GO BACK TO DOS ;SET UP ADDRESSABILITY TO ; THE DATA SEGMENT ;TELL ASSEMBLER WHAT I JUST DID

MOV AX,DSEG MOV DS,AX ASSUME DS:DSEG MOV LOOP0: MOV LOOP1: AX, 1 MOV SI, 0 CX, DIM*DIM INC SI

; contatore numero regioni ; indice corrente in MATRIX

; cerca se esistono altre regioni da ; esplorare CMP MATRIX[SI], 0 LOOPNE LOOP1 JCXZ EEFINE MATRIX[SI], AL CALL EXPAND DEQUEUE SI, DX SI, 0FFFFh LOOP2 ; marca il primo elemento

MOV LOOP2: CALL MOV CMP JNE INC JMP EEFINE: DEC CALL

AX ; passa a un'altra regione SHORT LOOP0 MOV DX, AX DX OUTPUT ; visualizza il numero di regioni

RET ENTPT ENDP

;RETURN TO DOS

;*********************************************************** ; ; EXPAND ; Trova in SI il puntatore ad una casella di MATRIX; per ; ognuna delle 4 caselle a questa adiacenti deve controllare ; se sono nella matrice e se sono libere. In caso ; affermativo le occupa, scrivendovi il valore contenuto nel ; registro AL, e mette il relativo puntatore nel buffer. ; Nota che, essendo i bordi della matrice tutti settati a 1, ; il primo controllo coincide con il secondo, e viene ; saltato. ; ;*********************************************************** EXPAND PUSH PUSH MOV ADD CMP JNE MOV MOV PROC DI DX DI, SI DI, DIM MATRIX[DI], 0 LAB1 MATRIX[DI], AL DX, DI 325

CALL LAB1: MOV DEC CMP JNE MOV MOV CALL LAB2: MOV SUB CMP JNE MOV MOV CALL LAB3: MOV INC CMP JNE MOV MOV CALL LAB4: POP POP RET EXPAND

ENQUEUE DI, SI DI MATRIX[DI], 0 LAB2 MATRIX[DI], AL DX, DI ENQUEUE DI, SI DI, DIM MATRIX[DI], 0 LAB3 MATRIX[DI], AL DX, DI ENQUEUE DI, SI DI MATRIX[DI], 0 LAB4 MATRIX[DI], AL DX, DI ENQUEUE DX DI

ENDP

;*********************************************************** ; inserisce l'intero presente in DX nel buffer ;*********************************************************** ENQUEUE PUSH PUSH PUSH PUSH MOV CMP JB PROC AX BX DX SI SI, B_IN SI, B_OUT COMPT

; se B_IN<B_OUT salta ; testa se il buffer e` pieno

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, (MAX-1)*2 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV BX, B_OUT SUB BX, B_IN DEC BX DEC BX JE FULL

; testa se il buffer e` pieno

ESTRAI: INC INC CMP JNE MOV E_LAB: MOV JMP FULL: LEA MOV INT E_FINE: POP POP POP RET ENQUEUE

MOV BUFF[SI], DX SI SI SI, MAX*2 E_LAB SI, 0 B_IN, SI E_FINE DX, FULL_MESS AH, 9 21H POP DX BX AX SI

ENDP

;*********************************************************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;*********************************************************** DEQUEUE PUSH PUSH MOV CMP JE MOV INC INC CMP JNE MOV D_LAB: JMP EMPTY: MOV INT MOV JMP D_FINE: POP RET DEQUEUE CSEG ENDS ENDP PROC AX SI SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI SI SI, MAX*2 D_LAB SI, 0 MOV B_OUT, SI D_FINE LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI ; testa se il buffer e` vuoto

327

STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT

;256 WORD STACK AREA

Listing:
;********************************************** ; ; ; REGION

; ;**********************************************

= 2710 = 000B

MAX DIM

EQU EQU

10000 11

; dimensione buffer circolare ; dimensione matrice

EXTRN INPUT:FAR, OUTPUT:FAR 0000 0000 FF FF FF FF FF FF FF FF FF FF FF 000B FF 00 00 00 00 00 00 00 00 00 FF 0016 FF 00 00 00 00 00 00 00 00 00 FF 0021 FF FF FF 00 FF FF FF FF FF 00 FF 002C FF 00 FF 00 FF 00 00 00 FF 00 FF 0037 FF 00 FF 00 FF FF FF 00 FF 00 FF 0042 FF 00 FF 00 FF 00 FF 00 FF 00 FF 004D FF 00 FF 00 FF 00 FF 00 FF 00 FF 0058 FF 00 FF 00 FF 00 FF FF FF 00 FF 0063 FF 00 FF 00 FF 00 00 00 00 00 FF 006E FF FF FF FF FF FF FF FF FF FF FF DB DB DB DB DB DB DB DB DSEG SEGMENT PARA PUBLIC 'DATA' MATRIX DB 55, 255, 255, 255, 255 255, 255, 255, 255, 255, 255, 2

DB 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255 255, 255, 255, 0, 255, 255, 2 55, 255, 255, 0, 255 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255 255, 0, 255, 0, 255, 255, 2 55, 0, 255, 0, 255 255, 0, 255, 0, 255, 0, 2 55, 0, 255, 0, 255 255, 0, 255, 0, 255, 0, 2 55, 0, 255, 0, 255 255, 0, 255, 0, 255, 0, 2 55, 255, 255, 0, 255 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 255 DB 255, 255, 255, 255, 255, 255, 2 55, 255, 255, 255, 255

0079 2710[ 0000 ] 4E99 0000 4E9B 0000 4E9D 0014[ 00 ]

BUFF

DW MAX DUP(0)

B_IN DW 0 B_OUT DW 0 CBUF DB 20 DUP(0)

4EB1 4E 75 6D 65 72 6F ERR_MESS DB 'Numero troppo grande', 0DH, 0AH, '$' 20 74 72 6F 70 70 6F 20 67 72 61 6E 64 65 0D 0A 24 4EC8 42 55 46 46 45 52 FULL_MESS DB 'BUFFER PIENO', 0DH, 0AH, '$' 20 50 49 45 4E 4F 0D 0A 24 4ED7 42 55 46 46 45 52 EMPT_MESS DB 'BUFFER VUOTO', 0DH, 0AH, '$' 20 56 55 4F 54 4F 0D 0A 24 4EE6 0000 DSEG ENDS CSEG SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG,SS:STACK ;ALREAD Y SET BY DOS LOADER ENTPT PROC FAR PUSH DS XOR AX,AX PUSH AX MOV ;ENTRY POINT FROM DOS ;SET UP THE STACK TO HAVE ; THE DOUBLE WORD VECTOR SO ; FAR RETURN WILL GO BACK TO

0000 0000 1E 0001 33 C0 THE 0003 50 DOS 0004 B8 ---- R 0007 8E D8 DID 0009 B8 0001 000C BE 0000 000F B9 0079 0012 46 esplorare 0013 80 BC 0000 R 00 0018 E0 F8 001A E3 14 001C 0020 0023 0026 0028 002B 88 84 0000 R E8 0039 R E8 00DC R 8B F2 83 FE FF 75 F3

AX,DSEG ;SET UP ADDRESSABILITY TO MOV DS,AX ; THE DATA SEGMENT ASSUME DS:DSEG ;TELL ASSEMBLER WHAT I JUST MOV AX, 1 ; contatore numero regioni

LOOP0: MOV MOV LOOP1: INC CMP

SI, 0 ; indice corrente in MATRIX CX, DIM*DIM SI MATRIX[SI], 0 ; cerca se esistono altre regioni da

LOOPNE LOOP1 JCXZ EEFINE MOV MATRIX[SI], AL LOOP2: CALL EXPAND CALL DEQUEUE MOV SI, DX CMP SI, 0FFFFh JNE LOOP2 INC JMP ; marca il primo elemento

002D 40 002E EB DC

AX ; passa a un'altra regione SHORT LOOP0

329

0030 8B D0 0032 4A 0033 9A 0000 ---- E

EEFINE: MOV DX, AX DEC DX CALL OUTPUT

; visualizza il numero di regioni

0038 CB 0039

RET TO DOS ENTPT ENDP

;RETURN

;********************************************** ; ; EXPAND ; Trova in SI il puntatore ad una casella di MATRIX; per ognuna delle 4 ; caselle a questa adiacenti deve controllare se sono nella matrice e se ; sono libere. In caso affermativo le occupa, scrivendovi il valore ; contenuto nel registro AL, e mette il relativo puntatore nel buffer. ; Nota che, essendo i bordi della matrice tutti settati a 1, il primo ; controllo coincide con il secondo, e viene saltato. ; ;********************************************** 0039 0039 57 003A 52 003B 8B FE 003D 83 C7 0B 0040 80 BD 0000 R 00 0045 75 09 0047 88 85 0000 R 004B 8B D7 004D E8 008E R 0050 8B FE 0052 4F 0053 80 BD 0000 R 00 0058 75 09 005A 88 85 0000 R 005E 8B D7 0060 E8 008E R 0063 8B FE 0065 83 EF 0B 0068 80 BD 0000 R 00 006D 75 09 006F 88 85 0000 R 0073 8B D7 0075 E8 008E R 0078 8B FE 007A 47 007B 80 BD 0000 R 00 0080 75 09 0082 88 85 0000 R 0086 8B D7 EXPAND PROC

PUSH DI PUSH DX MOV ADD CMP JNE MOV MOV CALL LAB1: MOV DEC CMP JNE MOV MOV CALL LAB2: MOV SUB CMP JNE MOV MOV CALL LAB3: MOV INC CMP JNE MOV MOV DI, SI DI, DIM MATRIX[DI], 0 LAB1 MATRIX[DI], AL DX, DI ENQUEUE DI, SI DI MATRIX[DI], 0 LAB2 MATRIX[DI], AL DX, DI ENQUEUE DI, SI DI, DIM MATRIX[DI], 0 LAB3 MATRIX[DI], AL DX, DI ENQUEUE DI, SI DI MATRIX[DI], 0 LAB4 MATRIX[DI], AL DX, DI

0088 E8 008E R 008B 5A 008C 5F 008D C3 008E

CALL ENQUEUE LAB4: POP POP RET EXPAND ENDP DX DI

;********************************************** ; inserisce l'intero presente in DX nel buffer ;********************************************** 008E 008E 008F 0090 0091 50 53 52 56 ENQUEUE PUSH PUSH PUSH PUSH MOV CMP JB PROC AX BX DX SI SI, B_IN SI, B_OUT COMPT

0092 8B 36 4E99 R 0096 3B 36 4E9B R 009A 72 11 009C 8B 1E 4E99 R 00A0 2B 1E 4E9B R 00A4 81 FB 4E1E 00A8 74 25 00AA EB 0D 90

; se B_IN<B_OUT salta ; testa se il buffer e` pieno

; B_OUT <= B_IN MOV BX, B_IN SUB BX, B_OUT CMP BX, (MAX-1)*2 JE FULL JMP ESTRAI ; B_IN < B_OUT COMPT: MOV BX, B_OUT SUB BX, B_IN DEC BX DEC BX JE FULL ESTRAI: INC INC CMP JNE MOV E_LAB: MOV JMP MOV BUFF[SI], DX SI SI SI, MAX*2 E_LAB SI, 0 B_IN, SI E_FINE

00AD 8B 1E 4E9B R 00B1 2B 1E 4E99 R 00B5 4B 00B6 4B 00B7 74 16 00B9 89 94 0079 R 00BD 46 00BE 46 00BF 81 FE 4E20 00C3 75 03 00C5 BE 0000 00C8 89 36 4E99 R 00CC EB 09 90

; testa se il buffer e` pieno

00CF 8D 16 4EC8 R 00D3 B4 09 00D5 CD 21 00D7 5E

FULL: LEA MOV INT E_FINE:

DX, FULL_MESS AH, 9 21H POP SI 331

00D8 5A 00D9 5B 00DA 58 00DB C3 00DC

POP POP POP RET ENQUEUE

DX BX AX

ENDP

;********************************************** ; estrae un numero dal buffer e lo mette in DX ; ritorna il valore FFFFh se il buffer e` vuoto ;********************************************** 00DC 00DC 50 00DD 56 00DE 8B 36 4E9B R 00E2 3B 36 4E99 R 00E6 74 16 00E8 8B 94 0079 R 00EC 46 00ED 46 00EE 81 FE 4E20 00F2 75 03 00F4 BE 0000 00F7 89 36 4E9B R 00FB EB 0F 90 00FE 0102 0104 0106 0109 8D 16 4ED7 R B4 09 CD 21 BA FFFF EB 01 90 DEQUEUE PROC

PUSH AX PUSH SI MOV CMP JE MOV INC INC CMP JNE MOV D_LAB:MOV JMP EMPTY: MOV INT MOV JMP D_FINE: POP RET DEQUEUE ENDP SI, B_OUT SI, B_IN EMPTY DX, BUFF[SI] SI SI SI, MAX*2 D_LAB SI, 0 B_OUT, SI D_FINE LEA DX, EMPT_MESS AH, 9 21H DX, 0FFFFH D_FINE POP AX SI ; testa se il buffer e` vuoto

010C 5E 010D 58 010E C3 010F

010F 0000 0000 0040[

CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WO RD STACK AREA

53 54 41 43 4B 20 20 20 ] 0200 STACK ENDS END Segments and Groups: Name Length Align Combine Class ENTPT

CSEG . . . . . . . . . . . . . . 010F DSEG . . . . . . . . . . . . . . 4EE6 STACK . . . . . . . . . . . . . 0200 Symbols: Name Type

PARA PUBLIC 'CODE' PARA PUBLIC 'DATA' PARA STACK 'STACK'

Value

Attr 0079 4E99 4E9B DSEG Length = 2710 DSEG DSEG

BUFF . . . . . . . . . . . . . . L WORD B_IN . . . . . . . . . . . . . . L WORD B_OUT . . . . . . . . . . . . . L WORD

CBUF . . . . . . . . . . . . . . L BYTE4E9D DSEG Length = 0014 COMPT . . . . . . . . . . . . . L NEAR 00AD CSEG DEQUEUE . . . . . . . . . . . . N PROC DIM . . . . . . . . . . . . . . NUMBER 000B D_FINE . . . . . . . . . . . . . L NEAR 010C D_LAB . . . . . . . . . . . . . L NEAR 00F7 EEFINE . . . . . . . . . . . . . L NEAR 0030 EMPTY . . . . . . . . . . . . . L NEAR EMPT_MESS . . . . . . . . . . . L BYTE4ED7 ENQUEUE . . . . . . . . . . . . N PROC ENTPT . . . . . . . . . . . . . F PROC 0000 CSEG ERR_MESS . . . . . . . . . . . . L BYTE4EB1 ESTRAI . . . . . . . . . . . . . L NEAR 00B9 EXPAND . . . . . . . . . . . . . N PROC E_FINE . . . . . . . . . . . . . L NEAR 00D7 E_LAB . . . . . . . . . . . . . L NEAR 00C8 FULL . . . . . . . . . . . . . . L NEAR 00CF FULL_MESS . . . . . . . . . . . L BYTE4EC8 INPUT . . . . . . . . . . . . . L FAR 0000 LAB1 . . . . . . . . . . . . . . LAB2 . . . . . . . . . . . . . . LAB3 . . . . . . . . . . . . . . LAB4 . . . . . . . . . . . . . . LOOP0 . . . . . . . . . . . . . LOOP1 . . . . . . . . . . . . . LOOP2 . . . . . . . . . . . . . L NEAR L NEAR L NEAR L NEAR L NEAR L NEAR L NEAR 0050 0063 0078 008B 000C 0012 0020 00DC CSEG CSEG CSEG 00FE CSEG DSEG 008E CSEG Length = 004E Length = 0039 DSEG CSEG 0039 CSEG Length = 0055 CSEG CSEG CSEG DSEG External CSEG CSEG CSEG CSEG CSEG CSEG CSEG DSEG External CSEG Length = 0033

MATRIX . . . . . . . . . . . . . L BYTE0000 MAX . . . . . . . . . . . . . . NUMBER 2710 OUTPUT . . . . . . . . . . . . . L FAR 0000

@CPU . . . . . . . . . . . . . . TEXT 0101h 333

_Microsoft (R) Macro Assembler Version 5.10 Symbols-2 @FILENAME . . . . . . . . . . . @VERSION . . . . . . . . . . . . 253 Source Lines 253 Total Lines 42 Symbols 47462 + 151590 Bytes symbol space free 0 Warning Errors 0 Severe Errors TEXT region TEXT 510

2/25/97 10:17:21

Soluzione di un problema: ricerca di una sottomatrice Si scriva una procedura assembler per la ricerca di una sottomatrice MAT2 allinterno di una matrice MAT1. Le due matrici sono memorizzate per righe, sono composte di interi con segno ed hanno dimensione contenute in memoria alle locazioni X1, Y1 e X2, Y2 rispettivamente. La matrice MAT1 composta da un numero massimo di 100 righe e 100 colonne, mentre la matrice MAT2 composta da un numero massimo di 10 righe e 10 colonne. In caso di successo la procedura deve visualizzare le coordinate dellelemento nellangolo in alto a sinistra della sottomatrice trovata; diversamente deve visualizzare un oppportuno messaggio. Nel caso la sottomatrice da cercare sia presente pi volte allinterno della matrice MAT1 sufficiente fornire le coordinate di una delle sottomatrici corrispondenti. Soluzione :
Libreria utilizzata : Mylib PUBLIC Acapo, CancLn, ReadByte, PrintByte, ReadSByte, PrintSByte PUBLIC Readword, PrintWord CR EQU 13 .186 .MODEL large .STACK .DATA TabDiv DW Riga DB Ritorno DB .CODE

10000, 1000, 100, 10, 1, 0 13, 79 DUP (' '), 13, '$' 13, 10, '$'

; Procedura che va a capo Acapo PROC FAR PUSH dx PUSH ax LEA MOV INT POP dx, Ritorno ah, 9 21h ax

POP RET

dx

Acapo ENDP

; Cancella la linea correntee si riposiziona all'inizio ; della stessa riga CancLn PROC FAR PUSH ax PUSH dx LEA MOV INT POP POP RET dx, Riga ah, 9 21h dx ax

CancLn ENDP

; Procedura che legge un Unsigned Byte e lo mette ; nell'indirizzo passato nello Stack. In caso di errore ; CF=1. ReadByte PROC FAR PUSH MOV PUSH PUSH PUSH MOV XOR MOV INT bp bp, sp bx dx di

; Salva registri usati nello stack

bh, 10 ; Usato per le moltiplicazioni di byte per 10 dx, dx ; DX = 0 ah, 1 ; Lettura carattere in AL 21h

while1 : CMP JE CMP JB CMP JA SUB XCHG MUL ADD CMP JA MOV MOV INT al, CR endwhile1 al, '0' trabocco al, '9' trabocco al, '0' al, dl bh ax, dx ax, 255 trabocco dl, al ah, 1 21h

; Errore se il numero letto non ; una cifra numerica ; in AL il numero letto ; AX = AL * BH ; AX = AX + DX ; DL contiene il byte finora letto ; Lettura carattere in AL

JMP while1

335

endwhile1: MOV MOV MOV CLC JMP fine trabocco: STC fine: POP di POP dx POP bx POP bp RET ReadByte ENDP ; Ripristino registri ; Condizione di errore CF = 1 di ,[bp+6] BYTE PTR [di], dl ; Risultato nel parametro di ; ritorno al, dl ; CL = 0 Risultato corretto

; Procedura che stampa l'Unsigned Byte passato nello stack PrintByte PROC FAR PUSH MOV SUB PUSH PUSH PUSH PUSH bp bp, sp sp, 2 bp bx dx ax

; Alloca BYTE locale

MOV BYTE PTR [bp],0 ; Fin' ora trovati solo 0 MOV al, BYTE PTR [bp+6] ; In AL il parametro di ingresso LEA ADD si, TabDiv si, 4 ; Inizializzo il peso della cifra (100)

Repeat1: XOR XOR MOV DIV MOV CMP JE CMP JE CMP JE dx, dx ah, ah bx, WORD PTR [si] bx ; AX = AX / 10 ^Peso della cifra dh, dl ; Resto in DH (DL verr modificato) BYTE PTR [bp], 1 Then1 WORD PTR [si], 1 ; Stampa sempre l'ultima cifra ; (per il valore 0) Then1 al, 0 ; Non stampa le cifre 0 in ; testa al numero endif1

Then1:

MOV ADD MOV MOV INT

BYTE PTR [bp], 1 al, '0' dl, al ah, 2 21h

; Non ci sono altri 0 in testa ; AL il carattere da stampare ; Stampa AL

endif1: ADD MOV si, 2 al, dh ; SI l'indirizzo del prossimo ; valore per cui dividere il resto ; Resto in al

CMP WORD PTR [si], 0 JNE Repeat1 POP POP POP POP POP RET ax dx bx sp bp ; Ripristina i registri ; Dealloca BYTE locale

PrintByte ENDP

; Legge un Signed Byte mettendolo nell'indirizzo specificato ; come parametro nello stack. In caso di errore CF = 1 ReadSByte PROC FAR PUSH MOV SUB PUSH PUSH PUSH PUSH PUSH MOV XOR MOV INT CMP JE bp bp, sp sp, 2 bp bx dx di ax bh, 10 dx, dx ah, 1 21h al, '-' else2

; Alloca una word locale ; Salva sp nella posizione (senza ; variabili locali) ; Salva registri usati nello stack

; Usato per le moltiplicazioni di byte ; per 10 ; DX = 0 ; Lettura carattere in AL

then2: MOV JMP else2: MOV INT endif2: while2: 337 BYTE PTR [bp], 1 ; Numero negativo 21h ; Lettura di un carattere (AH = 1) BYTE PTR [bp], 0 endif2 ; Numero positivo

CMP JE CMP JB CMP JA SUB XCHG MUL ADD

al, CR endwhile2 al, '0' trabocco2 al, '9' trabocco2 al, '0' al, dl bh ax, dx

; Esci se letto CR ; Errore se il numero letto non ; una cifra numerica

; in AL il numero letto ; AX = AL * 10 ; AX = AX + DX

CMP JE

BYTE PTR [bp], 1 Else3

Then3: MOV JMP di, 127 endif3 ; Caso positivo

Else3: MOV di, 128 ; Caso negativo

endif3: CMP JA MOV MOV INT JMP ax, di trabocco2 dl, al ah, 1 21h while2

; DL contiene il byte finora letto ; Lettura carattere in AL

endwhile2: CMP JE BYTE PTR [bp], 0 endif4 ; Valore negativo

Then4: NEG dl endif4: MOV MOV MOV CLC JMP di, [bp+6] BYTE PTR [di], l al, dl fine2

; Risultato nel parametro di ; ritorno (per indirizzo) ; CL = 0 Risultato corretto

trabocco2: STC fine2: POP POP POP POP POP POP RET ax di dx bx sp bp

; Condizione di errore CF = 1

; Ripristino registri

ReadSByte ENDP

; Procedura che stampa il Signed Byte passato nello stack PrintSByte PROC FAR PUSH MOV PUSH PUSH MOV CMP JGE bp bp, sp ax dx

; Salva registri

dh, BYTE PTR [bp+6] ; In DH il parametro di ingresso dh, 0 endif5

then5: MOV MOV INT NEG endif5: MOV XOR al, dh ah,ah ; In AL il valore assoluto dl, '-' ah,2 21h dh ; Stampa il segno se minore di 0

PUSH ax CALL PrintByte POP ax POP POP POP RET dx ax bp ; Ripristina registri

PrintSByte ENDP

PrintWord

PROC

FAR

PUSH bp MOV bp, sp PUSH si PUSH di PUSH bx PUSH dx MOV LEA MOV bh, 1 si, TabDiv ax, [bp+6] ; Se BH = 1 non cifre significative ; In AX il parametro d'ingresso

repeat2: XOR DIV MOV dx, dx WORD PTR [si] di, dx ; DX = 0 ; AX = AX / si. Resto in DX ; Sposta resto in DI 339

CMP JE CMP JNE CMP JE

WORD PTR [si], 1 then6 ax, 0 then6 bh, 1 endif6

; L'ultima cifra deve essere ; sempre stampata

; Se uno 0 non significativo non ; deve essere stampato

then6: XOR bh, bh ADD al, '0' MOV MOV INT endif6: MOV ADD JNE ax, di si, 2 ; In AX il resto dl, al ah, 2 21h ; bh = 0 ; In AL il codice ASCII del ; carattere da stampare ; Stampa la cifra

CMP WORD PTR [si], 0 repeat2

fine3: POP POP POP POP POP RET PrintWord ReadWord dx bx di si bp ENDP PROC FAR

PUSH bp MOV bp, sp PUSH PUSH PUSH PUSH PUSH PUSH MOV XOR XOR MOV INT di dx bx ax cx si bx, 10 di, di cx, cx ah, 1 21h ; BX ; DI = ; ; CX ; = 10 per le moltiplicazioni 0 per memorizzare il numero totale letto = 0 per memorizzare l'ultima cifra letta

; Lettura nuova cifra in AL

while3: CMP JE CMP JB al, CR endwhile3 al, '0' errore ; Se CR fine inserimento ; Errore se il numero letto non ; una cifra numerica

CMP JA SUB MOV MOV MUL CMP JNE ADD JC MOV MOV INT JMP

al, '9' errore al, '0' cl, al ax, di bx dx, 0 errore ax, cx errore di, ax ah, 1 21h while3

; ; In AX ; ; Se il ; ; ; Se il ;

In CL il numero letto il numero letto fino ad ora AX = AX * 10 risultato non st in 16 bit allora errore AX = AX + bl risultato non st in 16 bit allora errore

; Lettura nuova cifra in AL

endwhile3: CLC MOV MOV MOV JMP si, [bp+6] [si], di ax, di fine4 ; CF = 0 risultato corretto ; Risultato nel parametro di uscita

errore: STC fine4: POP POP POP POP POP POP POP RET si cx ax bx dx di bp ; Ripristino registri

ReadWord ENDP

END Programma principale: matrici.asm ; Ricerca di una sottomatrice MAT2 in una matrice MAT1 EXTRN ReadByte: FAR, PrintByte: FAR, ReadSByte: FAR, CancLn: FAR, Acapo: FAR EXTRN PrintWord: FAR, ReadWord: FAR, PrintSByte: FAR MAX1 EQU 100 MAX2 EQU 10 PosizE MACRO RIG, COL PUSH dx PUSH ax LEA MOV dx, PosElem ah, 9 341

INT

21h

MOV al, RIG CALL PrintByte MOV MOV INT dl, '.' ah, 2 21h

MOV al, COL CALL PrintByte MOV MOV INT POP POP ENDM .186 .MODEL medium .STACK .DATA PosElem DB InsElem DB InsRiga DB InsCol DB InsMat1 DB InsMat2 DB Separa DB Trov DB NOTrov DB MATR1 X1 Y1 MATR2 X2 Y2 Deb .CODE MOV MOV LEA MOV INT PUSH PUSH PUSH PUSH CALL POP POP POP POP LEA MOV DB DB DB DB DB DB dl, ':' ah, 2 21h ax dx

"Elem ", '$' "Elementi matrice:", 13, 10, '$' "Righe: ", '$' "Colonne: ", '$' "Inserimento MAT1:", 13, 10, '$' "Inserimento MAT2:", 13, 10, '$' " : ", '$' "Trovata alle coordinate ", '$' "Sottomatrice non trovata !", 13, 10, '$' MAX1*MAX1 DUP (0) 0 0 MAX2*MAX2 DUP (0) 0 0

DB "Debugger", 13, 10, '$' ax, @DATA DS, ax dx, InsMat1 ah, 9 21h MAX1 OFFSET MATR1 OFFSET X1 OFFSET Y1 ReadMat ax ax ax ax dx, InsMat2 ah, 9 ; .STARTUP

; Inserimento MAT1

INT PUSH PUSH PUSH PUSH CALL POP POP POP POP

21h MAX2 OFFSET MATR2 OFFSET X2 OFFSET Y2 ReadMat ax ax ax ax

; Inserimento MAT2

XOR MOV SUB INC MOV XOR for1:

ah, al, al, al cx,

ah Y1 Y2 ax

; ; ; CX = Y1 - Y2 + 1 ; ; BX = numero di riga

bx, bx

PUSH XOR MOV SUB INC MOV XOR for2: LEA ADD ADD PUSH PUSH CALL POP POP JNC INC

cx ah, al, al, al cx,

; ah X1 X2 ax ; ; CX = X1 - X2 + 1 ; ; ; SI = numero di colonna

si, si

ax, MATR1 ax, bx ax, si ax OFFSET MATR2 CompMat ax ax Trovato si

; Confronta le sottomatrici

LOOP for2 POP XOR MOV ADD cx ah, ah al, X1 bx, ax ; Ripristina il valore di cx del ciclo ; precedente ; Sposta alla riga successiva

LOOP for1 NonTrovato: POP LEA MOV INT JMP Trovato: 343 cx ; Svuota lo stack di CX del ciclo FOR1 dx, NoTrov ah, 9 21h FineMain

LEA MOV INT CALL

dx, Trov ah, 9 21h Acapo ; Stampa il numero di riga trovato

MOV ax,si CALL PrintByte LEA MOV INT MOV DIV CALL CALL dx, Separa ah, 9 21h ax, bx X1 PrintByte Acapo

; In AL il numero di colonna ; Stampa il numero di colonna trovato

FineMain: MOV MOV INT ; ; ; ; ; al, 0 ah, 4ch 21h ; .EXIT

Effettua il confronto tra le sottomatrici speficicate. Parametri: 1) [bp+4] = &MAT2 2) [bp+6] = &Sottomatrice di MAT1 Se trovata allora CF = 0 altrimenti CF = 1.

CompMat PROC NEAR

PUSH bp MOV bp, sp PUSH PUSH PUSH PUSH PUSH MOV MOV CLD MOV MOV ax cx si di dx ax, DS ES, ax ; Salva registri

si, [bp+4] di, [bp+6]

; Carica i puntatori alle righe delle ; matrici

XOR ah, ah MOV al, Y2 MOV cx, ax for3: PUSH cx XOR ah, ah MOV al, X2 MOV cx, ax

; In CX il numero di iterazioni ; Salva CX nello stack ; In CX il numero di iterazioni del

; nuovo ciclo REPE CMPSB CMP cx, 0 JNE Diversi XOR MOV SUB ADD ah, al, al, di, ah X1 X2 ax ; Confronto della riga ; Se trovata una disuguaglianza ; esci da ciclo ; ; ; Passa alla riga successiva ; Ripristina CX dallo stack

POP cx LOOP for3

Uguali: CLC ; Se le matrici sono uguali allora CF = 0 JMP EndComp Diversi: POP cx ; Svuota lo stack del valore di CX del ciclo FOR3 STC ; Se le matrici sono divese allora CF = 1 EndComp: POP POP POP POP POP POP RET ENDP ;---------------------------------------------------------; Effettua la lettura di una matrice leggendo il numero di ; righe, di colonne e la sequenza degli elementi (byte con ; segno) ; Parametri: ; 1) [bp+4] = &Righe ; 2) [bp+6] = &Colonne ; 3) [bp+8] = &Matrice ; 4) [bp+10] = Dimensione massima ReadMat PROC NEAR dx di si cx ax bp

; Ripristina registri

PUSH MOV SUB PUSH PUSHA LEA

bp bp, sp sp, 2 bp

; 1 word di memoria locale ; Salva il valore di ritorno di SP

dx, InsRiga

repeat10: CALL CancLn MOV ah, 9 INT 21h CALL ReadByte 345 ; Cancella la linea corrente ; Stampa messaggio d'inserimento righe

MOV

cl, al

; In CL il numero di righe ; Ripeti in caso d'errore ; Confronto con il valore massimo

JC repeat10 CMP cl, [bp+10] JA repeat10 MOV MOV di, [bp+4] [di], al

; Numero righe nel parametro di ritorno ; in memoria con passaggio parametri per ; indirizzo

CALL Acapo LEA dx, InsCol

repeat11: CALL CancLn MOV INT ah, 9 21h ; Cancella la linea corrente ; Stampa messaggio d'inserimento colonne

CALL ReadByte MOV ch, al JC repeat11 CMP ch, [bp+10] JA repeat11 MOV MOV MOV

; In CH il numero di colonne ; Ripeti in caso d'errore ; Confronto con il valore massimo

di, [bp+6] ; Numero colonne nel parametro di ritorno [di], al ; in memoria con passaggio parametri per ; indirizzo [bp-2], al ; Numero colonne in memoria locale

CALL Acapo LEA MOV INT XOR MOV MUL MOV dx, InsElem ah, 9 ; Stampa messaggio d'inserimento elementi 21h ah, ah al, ch cl cx,ax ; ; ; CX = Righe * Colonne ; In CL il numero di elementi (<= 200)

XOR si, si for4: MOV MOV DIV MOV ax, si bl, BYTE PTR [bp-2] bl ; AL = numero righa, AH = numero colonna bx, ax ; BX come AX

repeat12: CALL CancLn PosizE bh, bl CALL ReadSByte JC repeat12 MOV bx, [bp+8] ; Memorizza l'elemento letto nella ADD bx, si ; matrice passata come parametro ; nello stack ; Stampa la posizione dell'elemento ; Legge elemento della matrice

MOV ADD

[bx], al si, 1

CALL Acapo LOOP for4

POPA POP sp POP bp RET ENDP

END

347

Bibliografia
UNIX un sistema operativo multiutente, guida all'uso e alla programmazione concorrente Alessandra Demaria, Chiara Regale, Franco Sbiroli, Federico Stirano Febbraio 1997

[1] R.W.~Stevens: UNIX Network Programming, Englewood Cliffs,Prentice Hall, 1990. [2] H.W.Lockhart Jr: OSF DCE Guide to Developing Distibuted Applications,New York, McGraw Hill, 1994. [3] D.E.~Comer: Internetworking with TCP/IP:Principles, Protocols and Architecture, Englewood Cliffs,Prentice Hall, 1988. [4] P.Ancilotti,M.Boari: Principi e Tecniche di Programmazione Concorrente, Torino, UTET Libreria, 1987. [5] A.S.Tanenbaum: Modern Operating System, Englewood Cliffs,Prentice Hall, 1992.

[6]S.R.Bourne UNIX System V, Milano,Addison--Wesley, 1990. [7] J.A.Lowell: UNIX Shell Programming, New York,Wiley \& Sons, 2nd edition, 1990.

349

Potrebbero piacerti anche