UNIX
un sistema operativo multiutente Guida all'uso e alla programmazione concorrente
Febbraio 1997
PREMESSA
EDITING
3.1 VI
3.1.1 MODO TESTO 3.1.2 MODO COMANDI
PROGRAMMAZIONE
4.1 VARIABILI
4.1.1 VARIABILI FILE
3
IL SISTEMA
5.1 IDENTIFICATORI
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
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
11
11.1 11.2 11.3 11.4
I SOCKET
STRUTTURE DEI SOCKET CREAZIONE EREDITARIETA E TERMINAZIONE SPECIFICA DI UN INDIRIZZO LOCALE
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
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
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
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
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
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
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
semaforo.h
client_server.h
pippo.h minni.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.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:
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.
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.
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:
. .. /
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.
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
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
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 (@).
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
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
< 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.);
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
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.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.
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.
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.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.
Posizione Puntatore Posizione Puntatore corrente Puntatore alli-node corrente corrente alli-node all i-node Id n
Id 1
Id n
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).
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.
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.
#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' : ");
} }
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); }
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
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); } }
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");
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;
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.
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);
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
{ }
/* 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);
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
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);
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);
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
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);
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);
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
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 */
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
/* 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
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.
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
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.
User
IP interfaceUDP process
User Hardware
ARP
RARP
OSI Layer 4
OSI Layer 3
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
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
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
14 bits
8 bits
8 bits
netid
subnetid
hostid
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
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
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.
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
client system
server system
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
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.
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
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 ()
dati (richiesta)
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 ()
sendto ()
recvfrom () Fig. 11.2 System call dei socket per un protocollo connectionless
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.
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.
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.
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.
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
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
I modelli client/server si suddividono in quattro categorie: single-threaded server; multi-threaded server; tiered server; peer-to-peer server.
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); }
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
Come esempio si puo` vedere la soluzione allesercitazione 4 (parte riguardante i socket) nel capitolo 14.
Client
Server
Client
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;
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
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
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); }
Peer
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)); };
<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); }
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.
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
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");
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);
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.
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)
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"
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); } }
/*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 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
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"); } }
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);
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
#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
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); }
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));
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
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
#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.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 = "")
{ }
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;
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
#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
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
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
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
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
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);
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 ();
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 (); }
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;
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;
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++;
};
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); }
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"
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 ()); } }
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
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
;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
PUSH AX PUSH DX MOV MOV INT MOV INT AH, 2 DL, 0DH 21H DL, 0AH 21H ; stampa un CR ; stampa un LF
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
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
;RETURN
Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr DSEG CSEG Length Align 0038 0017 Combine Class 'CODE' 'DATA'
ERR_MESS . . . . . . . . . . . . FINE . . . . . . . . . . . . . .
L BYTE0000 0035
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
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)
POP RET
DI ;RETURN
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'
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'
;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
265
; 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'
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
; 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]
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
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
; 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
ENTPT
Combine Class
CSEG . . . . . . . . . . . . . . 0088 DSEG . . . . . . . . . . . . . . 0081 STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . BUFF . . . . . . . . . . . . . . Type
Value N PROC
L BYTE0000
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
;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
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
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
;RETURN TO DOS
STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA
Value
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
;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
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
DB
64 DUP("STACK ")
CSEG . . . . . . . . . . . . . . 0031 DSEG . . . . . . . . . . . . . . 0014 STACK . . . . . . . . . . . . . 0200 Symbols: Name BUFF . . . . . . . . . . . . . . Type
Value
L BYTE0000
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'
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
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
;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
277
CMD_I: CALL CALL JMP CMD_E: CALL CMP JE CALL JMP FINE: RET ENTPT ENDP
CALL ACAPO
INPUT
;*********************************************************** ; 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
; 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
MOV BUFF[SI], DX SI SI SI, 2*DIM E_LAB SI, 0 B_IN, SI E_FINE DX, FULL_MESS
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
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
CMP JE JMP
; 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 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
; 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
; 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
FULL: LEA MOV INT E_FINE: POP POP POP RET ENQUEUE
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
ENDP
CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA
CSEG . . . . . . . . . . . . . . 00C5 DSEG . . . . . . . . . . . . . . 002F STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . Type
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
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
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
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
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'
;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
;*********************************************************** ; inserisce l'intero presente in DX nel buffer ;*********************************************************** INSERT PUSH PUSH PUSH PUSH PROC AX BX DX SI 285
MOV CMP JB
; 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
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
ENDP
STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT
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
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 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
; 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
FULL: LEA MOV INT E_FINE: POP POP POP RET INSERTENDP
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
CSEG ENDS STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") ;256 WORD STACK AREA
Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Length Align 00C8 004D Combine Class 'CODE' 'DATA'
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
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
External
CSEG
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
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
@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
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
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
;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
@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
PUBLIC
CSEG
SEGMENT PARA PUBLIC 'CODE' ASSUME CS:CSEG, DS:DSEG PROC AX BX CX DX SI DI ENNE, AX MATP, BX FAR
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
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
; si`
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
; elemento 0,0
299
MOV MOV POP POP POP POP POP POP RET DIAGO1
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
Segments and Groups: Name CSEG . . . . . . . . . . . . . . DSEG . . . . . . . . . . . . . . Symbols: Name Type Value Attr Length Align 009D 0008 Combine Class 'CODE' 'DATA'
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
CSEG
;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
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 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
;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
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
CSEG . . . . . . . . . . . . . . 0075 DSEG . . . . . . . . . . . . . . 545A STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . DIAGO1 . . . . . . . . . . . . . Type
Value
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
@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
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
; 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 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
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
;********************************************** ; ; 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
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
CSEG . . . . . . . . . . . . . . 00BF DSEG . . . . . . . . . . . . . . 0039 STACK . . . . . . . . . . . . . 0200 Symbols: Name A ............... B ............... Type
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
MFINE . . . . . . . . . . . . . L NEAR 00B3 MLOOP . . . . . . . . . . . . . L NEAR MUOVI . . . . . . . . . . . . . N PROC NOGOOD . . . . . . . . . . . . . OUTPUT . . . . . . . . . . . . . L NEAR L FAR 0000
SCACC . . . . . . . . . . . . . L BYTE0000
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
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
_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
0000
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
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
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
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)
LEA LAB1: CALL CALL MOV INC INC LOOP LEA MOV MOV
; caricamento vettore
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
CSEG . . . . . . . . . . . . . . 0093 DSEG . . . . . . . . . . . . . . 4E24 STACK . . . . . . . . . . . . . 0200 Symbols: Name ACAPO . . . . . . . . . . . . . Type
Value
Attr External
L FAR 0000
4E22 DSEG CSEG Length = 0060 0060 External 4E20 DSEG CSEG CSEG 007F CSEG CSEG Length = 0033
N PROC
LAB1 . . . . . . . . . . . . . . L NEAR 002B LAB2 . . . . . . . . . . . . . . L NEAR 004F LABGET . . . . . . . . . . . . . L NEAR MAX . . . . . . . . . . . . . . NUMBER MAX2 . . . . . . . . . . . . . . NUMBER MEDIA . . . . . . . . . . . . . L FAR 0000 OUTPUT . . . . . . . . . . . . . VETT . . . . . . . . . . . . . . 2710 0032
@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
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
;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
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
; stampa un CR ; stampa un LF
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
;RETURN TO DOS
;*********************************************************** ; ; inserisce l'elemento in DX nel primo slot della lista ; ;*********************************************************** INSERT PROC AX
BX, 1 21H IN_FINE BX, AX [BX], DX DX, HEAD [BX]+2, DX HEAD, AX ES ; errore in allocazione ; campo dato ; aggiorna HEAD
;*********************************************************** ; ; 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
;*********************************************************** ; 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
; stampa un LF
;RETURN
STACK SEGMENT PARA STACK 'STACK' DB 64 DUP("STACK ") STACK ENDS END ENTPT
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
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
; 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
;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
; 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 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
Listing:
;********************************************** ; ; ; REGION
; ;**********************************************
= 2710 = 000B
MAX DIM
EQU EQU
10000 11
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
BUFF
DW MAX 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
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
329
0038 CB 0039
;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
;********************************************** ; 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
; 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
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
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
Value
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
_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
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
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:
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
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
al, CR endwhile2 al, '0' trabocco2 al, '9' trabocco2 al, '0' al, dl bh ax, dx
; in AL il numero letto ; AX = AL * 10 ; AX = AX + DX
CMP JE
endif3: CMP JA MOV MOV INT JMP ax, di trabocco2 dl, al ah, 1 21h while2
Then4: NEG dl endif4: MOV MOV MOV CLC JMP di, [bp+6] BYTE PTR [di], l al, dl fine2
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
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
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
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
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
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
; Inserimento MAT2
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
; ah X1 X2 ax ; ; CX = X1 - X2 + 1 ; ; ; SI = numero di colonna
si, 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
MOV ax,si CALL PrintByte LEA MOV INT MOV DIV CALL CALL dx, Separa ah, 9 21h ax, bx X1 PrintByte Acapo
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.
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
XOR ah, ah MOV al, Y2 MOV cx, ax for3: PUSH cx XOR ah, ah MOV al, X2 MOV cx, ax
; 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
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
bp bp, sp sp, 2 bp
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
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
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
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
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