• Non ci sono risultati.

ESERCIZI DI PROGRAMMAZIONE C IN AMBIENTE UNIX

N/A
N/A
Protected

Academic year: 2021

Condividi "ESERCIZI DI PROGRAMMAZIONE C IN AMBIENTE UNIX"

Copied!
18
0
0

Testo completo

(1)

Introduzione

In questa dispensa sono stati raccolti alcuni esercizi di programmazione C in ambiente Unix, tratti dagli esami di Sistemi Operativi svoltisi al Politecnico di Torino negli anni scorsi.

Si è cercato di scegliere esercizi che privilegiassero l’utilizzo delle system calls Unix, a discapito di quelli che, invece, fossero più basati sul corretto utilizzo della programmazione in C, utilizzando notevoli porzioni di testo tramite funzioni e istruzioni già viste nel corso di Fondamenti di Informatica.

ESERCIZI DI PROGRAMMAZIONE C IN AMBIENTE UNIX

(2)

Funzioni di Libreria C di Unix

!"Duplicazione di un processo chiamante

Figlio=fork()

Int figlio

La funzione fork() genera un processo figlio avente le stesse caratteristiche del padre, è cioè una funzione che stabilisce relazioni di tipo gerarchico. Il figlio eredita tutte le caratteristiche del padre, pur restandone distinto e potendo evolversi indipendentemente.

Il risultato delle funzione fork() è il descrittore del figlio, pertanto, se tale valore è pari a 0, vuol dire che il processo figlio è stato creato correttamente (in valore minore di 0 vuole dire che la fork ha fallito l’esecuzione) e, utilizzando la seguente sequenza di istruzioni

If(figlio==0) { …..

…..

}

posso specificare una sequenza di istruzioni che deve eseguire solo il figlio Nello stesso modo, posso indicare il processo padre come segue:

If(figlio==0) {

…..

…..

} else { ….

….

}

Ho ovviamente supposto che la fork abbia fornito risultato positivo, altrimenti la else non funziona correttamente, comprendendo fra le parentesi graffe anche l’erronea esecuzione della fork (figlio <0)

Dopo la fork() persistono due processi: il padre ed il figlio. Si ricorda che, qualora debba eseguire due fork, perché, ad esempio, il padre genera 2 figli, non posso eseguire le due fork una dopo l’altra, altrimenti perdo il descrittore del processo padre che ha generato al fork.

!"Apertura di files già presenti su memoria di massa

Fd=open(nome,flags) Int fd, flags

Char *nome

nome rappresenta il nome del file, flags rappresenta la modalità di apertura dello stesso, cioè R(read), W(write) e R/W(read write) e fd rappresenta il descrittore del file. Si ricorda che, come è prassi nel linguaggio C, il valore restituito dalla funzione open in fd vale 0 se il file viene aperto correttamente.

Codice relativo al figlio

Codice relativo al padre

(3)

!"Creazione di un file

fd=creat(nome,perm) int fd, perm

char *nome

perm rappresenta le modalità di protezione con cui viene creato un file. Normalmente il parametro formale perm viene passato tramite un numero intero, definito nella sezione

#define PERM ….. Affinché il programma associ correttamente a tale intero bisogna includere la libreria standard <fcntl.h> Si ricorda che, come è prassi nel linguaggio C, il valore restituito dalla funzione in fd vale 0 se il file viene creato correttamente.

!"Lettura di un file

letto=read(fd,buf,n) int fd, n, letto

char *buf

fd rappresenta il descrittore del file (ovviamente già aperto o, eventualmente, creato), n è il numero di byte da leggere, mentre buf rappresenta l’area di memoria su cui depositare il file. Se indichiamo al posto di fd il numero intero 0, vuol dire che la funzione legge dallo standard I/O, cioè dalla tastiera. Si ricorda che, come è prassi nel linguaggio C, il valore restituito dalla funzione rappresenta il numero di caratteri letti (quindi deve essere pari ad n).

!"Chiusura di un file

fine=close(fd) int fd, fine

Tale funzione restituisce valore 0 se il file, identificato da fd, viene chiuso correttamente.

Qualora restituisca un valore <0 vuol dire che il file era già stato chiuso.

!"Scrittura di un file

scritto=write(fd,buf,n) int fd, n, scritto

char *buf

tale funzione è ovviamente la duale di quella di lettura, preleva n dati da buf e li scrive sul file descritto da fd. Lo standard I/O, cioè il video o la stampante, viene indicato con il numero intero 1. Si ricorda che, come è prassi nel linguaggio C, il valore restituito dalla funzione rappresenta il numero di caratteri scritti (quindi deve essere pari ad n).

!"Comunicazione fra processi: la pipe

ret=pipe(canale) int ret, canale[2]

(4)

L’uso delle pipe consente di sincronizzare due processi in relazione gerarchica fra loro, creando un meccanismo di comunicazione accessibile in lettura da una parte e in scrittura dall’altra. La sincronizzazione è garantita dal fatto che la pipe si blocca in lettura se è vuota, mentre si blocca in scrittura se è piena. Canale[0] identifica che la pipe è aperta in lettura, quindi un processo può leggere dalla pipe. Canale[1] identifica che la pipe è aperta in scrittura, quindi un processo può scrivere sulla pipe. In altre parole canale[0] e canale[1]

rappresentano 2 file, sincronizzati dalla pipe, di cui uno accessibile solo in lettura, l’altro solo in scrittura.

!"Segnali di sincronizzazione

signal (tiposegnale,func) pause()

La funzione signal predispone il sistema alla ricezione di un segnale di tipo tiposegnale, fornendone la risposta tramite la funzione func. Qualora arrivi un segnale, il processo risponde tramite la func. I segnali di Unix sono contenuti nella libreria standard <signal.h>. La funzione pause() sospende il processo in attesa dell’arrivo di un segnale.

kill (identificatore,tiposegnale)

La funzione kill consente ad un processo di inviare al processo identificato da identificatore un segnale di tipo tiposegnale . Si ricorda l’uso delle funzioni di libreria identificatore=getpid(), con cui un processo può conoscere il suo descrittore e la funzione identificatore=getppid(), con cui un processo può conoscere il descrittore del processo padre.

!"Uscita da un processo

void=exit(status) Int status

Il processo termina e comunica attraverso il parametro status la sua terminazione (0 tutto OK, -1 terminazione erronea)

La terminazione di un processo può servire per comunicare il parametro ad un altro processo che è in attesa della stessa.

!"Attesa della terminazione di un processo

fd=wait(&status) Int status, fd

Nel parametro fd viene restituito il descrittore del file che è terminato, poiché ha comunicato al processo chiamante il suo stato di terminazione (status)

La funzione wait è sospensiva e predispone il processo chiamante all’attesa della terminazione di un altro processo. Quest’ultimo, tramite la funzione exit(status), può comunicare la sua terminazione e sbloccare il processo chiamante.

(5)

Esercizio 1: Si scriva un programma C per ambiente Unix tale che venga emulata una struttura di processi organizzati master-slave; si supponga di dover gestire 3 slave.

Ogni slave deve mandare un segnale di tipo SIGUSR1 al master; tale messaggio può essere mandato solo previa autorizzazione del master mediante un segnale di tipo SIGUSR2. Quando il master ha ricevuto tutti e tre i messaggi, uccide gli slave con un messaggio di tipo SIGKILL.

La logica del programma è la seguente:

a) Ogni slave deve inviare la master un segnale di tipo sigusr1(sincronizzazione)

b) Tale messaggio può essere inviato solo dopo che il master ha autorizzato lo slave tramite un segnale di tipo sigusr2 (sincronizzazione)

c) Quando il master ha ricevuto tutti e tre i messaggi, uccide (cioè fa terminare ) i tre processi slave (da lui generati !!!), tramite un segnale di di tipo sigkill

#include <stdio.h>

#include <signal.h>

void func1(void);

void func2(void);

void func1() \*predisposizione di risposta al segnale inviato dallo slave*\

{

printf(“ho ricevuto un messaggio dallo slave \n”);

}

void func2() \*predisposizione di risposta al segnale inviato dal master*\

{

printf(“ho ricevuto un messaggio dal master \n”);

}

main()

Int pid1, pid2, pid3 {

pid1=fork(); \* il processo chiamante genera il primo slave *\

If(pid1==0) \* se primo slave *\

{

signal (sigusr2,func2); \*se arriva un segnale di tipo sigusr2, rispondo con *\

\* func2 *\

pause (); \*attendo un segnale*\

kill (getppid(),sigusr1); \*invio un segnale sigusr2 al padre *\

pause() \*attendo un segnale*\

} else

{

pid2=fork(); \* il processo chiamante genera il secondo slave *\

If(pid2==0) \* se secondo slave *\

{

signal (sigusr2,func2);

pause ();

kill (getppid(),sigusr1);

pause()

(6)

}

else

{

pid3=fork() \* genero il terzo slave *\

If(pid3==0) \* se terzo slave*\

{

signal (sigusr2,func2);

pause ();

kill (getppid(),sigusr1);

pause()

}

else \*processo padre (master)*\

{

signal (sigusr1,func1) \*predispongo la risposta al sigusr1*\

kill (pid1,sigusr2) \*invio sigusr2 al primo slave*\

pause() \*resto in attesa di una risposta*\

signal (sigusr1,func1) \*predispongo la risposta al sigusr1*\

kill (pid2,sigusr2) \*invio sigusr2 al secondo slave*\

pause() \*resto in attesa di una risposta*\

signal (sigusr1,func1) \*predispongo la risposta al sigusr1*\

kill (pid3,sigusr2) \*invio sigusr2 al terzo slave*\

pause() \*resto in attesa di una risposta*\

kill (pid1, sigkill); \*uccido il primo slave*\

kill (pid2, sigkill); \*uccido il secondo slave*\

kill (pid3, sigkill); \*uccido il terzo slave*\

Exit(0); \*termino regolarmente*\

} }

} }

(7)

Esercizio 2

La logica del programma è la seguente:

a) Il processo 1 deve leggere 5 caratteri da tastiera

b) Deve mettere (scrivere) tali caratteri nella mailbox (mail2.txt) c) Deve avvisare il processo 2 tramite un segnale di tipo sigusr1 Il processo 2 deve

a) Leggere la posta

b) Invertire l’ordine dei caratteri

c) Inserire la stringa invertita nella mailbox mail1.txt

#include <signal.h>

#include <stdio.h>

#include <fcntl.h>

#define PERM 0766 void func (void) void func() {

printf(“ho inserito un messaggio nella mailbox\n”);

}

main()

int fd1,fd2,pid,buf1[5],buf2[5],i,j,status;

fd1=creat(“mail1.txt”,PERM); \* creo i file su cui depositare i dati*\

fd2=creat(“mail2.txt”,PERM);

Pid=fork(); \*genero il figlio*\

If(pid==0) \*il processo figlio*\

{

read(0,buf1,5); \*legge 5 caratteri da tastiera (0) e li deposita sul *\

\* buf1*\

write (fd2,buf1,5); \*scrive tali caratteri sul file mail2.txt *\

kill(getppid(),sigusr1); \*invio un segnale di sincronizzazione la padre *\

close(fd2) \* chiudo il file mail2.txt *\

wait(&status)); \*il processo resta in attesa della terminazione *\

\* del secondo*\

}

(8)

Else \* padre*\

{

signal (sigusr1,func); \*predispongo la risposta al segnale*\

pause(); \*resto in attesa di un segnale*\

fd2=open (“mail2.txt”,o_rdonly);

\*apro il file mail2.txt in sola lettura*\

read (fd2,buf1,5); \* leggo il contenuto e lo deposito su buf1*\

j=4;

for(i=0,i<5,i++)

{

buf2[j]=buf1[i];

\* inverto i caratteri inserendoli in buf2*\

j=j-1;

}

close(fd2); \* chiudo il flie*\

write (fd1,buf2,5); \* scrivo sulla mail1.txt*\

close (fd1); \* chiudo il file mail1.txt *\

exit(0); \*esco e comunico la terminazione regolare (0) *\

}

(9)

Esercizio 3: Si scriva un programma C per ambiente Unix tale che venga realizzata una struttura padre-figlio. Il processo padre, ogni 5 secondi, manda un segnale di tipo sigusr1 al figlio e poi si sospende in attesa di una risposta dal figlio stesso.

Il figlio, ricevuto il segnale, legge un carattere da tastiera. Se il carattere è ‘q’ o ‘Q’ il figlio invierà al recesso padre un messaggio di tipo sigkill e poi terminerà, altrimenti, manda al padre un messaggio di tipo sigusr2 che lo sblocca.

La logica del programma è la seguente:

il padre: ogni 5 secondi invia un segnale di tipo sigusr2 e attende una risposta dal figlio il figlio:

a) Resta in attesa del segnale;

b) legge da tastiera un carattere;

c) se il carattere è q o Q uccide il padre e poi termina, altrimenti gli invia un segnale che lo sblocca

#include <signal.h>

#include <stdio.h>

#define timeout 5 void func1 (void)

void func1() {

printf(“ho ricevuto un messaggio\n”);

}

main() {

int pid, status,buf[1]

status =1;

pid=fork(); \* genero il figlio*\

if(pid !=0) \* processo padre*\

{

do {

kill (pid=0, sigusr1);

sleep (timeout);

wait (&status);

}

while (status=1)

} else {

signal(sigusr1,func1);

pause();

read (0,buf,1);

if((buf==”q”)//(buf==”Q”)) {

kill(getppid,sigkill);

exit(-1);

} else

(10)

{

exit(0);

}

(11)

Esercizio 4

#define PERM 0600

#include<fcntl.h>

main() {

int I, j, pid, canale[2], tab[10], status, fd

pipe (canale); \* genero la pipe *\

pid=fork(); \* genero il processo figlio *\

if(pid!=0) \* processo padre*\

{

close (canale[0]); \* chiudo la pipe in lettura*\

i=3;

for (j=0,j<10,j++) \* genero la tabellina del 3*\

{

tab[j]=i;

i=i+3;

}

write(canale[1],tab,10]; \* scrivo sulla pipe *\

wait(&status); \* attendo la terminazione del figlio*\

}

else \* processo figlio *\

{

close(canale[1]); \* chiudo la pipe in scrittura*\

fd=creat(‘tab_3.txt’,PERM); \*creo il file *\

read (canale[0],tab,10); \* leggo la pipe *\

write (fd,tab,10); \*scrivo sul file *\

close(fd); \* chiudo il file *\

exit(0); \* comunico la terminazione *\

} }

(12)

Esercizio 5

main() {

int I, j, pid, canale1[2], canale2[2], tab[10], somma [1], status, valore,

pipe (canale1); \* genero la prima pipe *\

pipe (canale2); \* genero la seconda pipe *\

pid=fork(); \* genero il processo figlio *\

if(pid>0) \* processo padre*\

{

for (i=0;i<10,i++)

{

valore =rand(); \*genero il valore casuale*\

tab[i]=valore; \*scrivo il valore su tab[i] *\

}

close (canale1[0]); \* chiudo la pipe1 in lettura*\

close (canale2[1]); \* chiudo la pipe2 in scrittura*\

write(canale1[1],tab,10); \* scrivo sulla pipe 1 *\

wait(&status); \* attendo la terminazione del figlio*\

read(canale2[0],somma,1); \* leggo dalla pipe 2 la somma*\

exit(0);

}

else \*processo figlio *\

{

close (canale1[1]); \* chiudo la pipe1 in scrittura*\

close (canale2[0]); \* chiudo la pipe2 in lettura*\

somma[0]=0; \*inizializzo la somma*\

read(canale1[0],tab,10); \* leggo dalla pipe 1 la tabella*\

for (j=0;j<10,j++) \*genero la somma*\

{

somma[0]=somma[0]+tab[j];

}

write (canale2[1],somma,1]); \*scrivo la somma sulla pipe2*\

exit(0); \* comunico la terminazione*\

}

(13)

Esercizio 6

Si scriva un programma C per ambiente Unix in cui il padre crea due processi figli. Il primo figlio legge 5 caratteri da tastiera e li passa al secondo, che, al termine, invia la fratello un messaggio di avvenuta ricezione. Il padre aspetta la terminazione dei figli e segnala l’ordine con cui terminano.

#include <signal.h>

#include <stdio.h>

void func1 (void)

void func1() {

printf(“ho ricevuto un messaggio\n”);

}

main() {

int figlio1, figlio2, figlio, canale[2], tab[5], status

pipe (canale1); \* genero la pipe *\

figlio1=fork(); \* genero il primo processo figlio *\

if(figlio1<0)

{

printf(“fork fallita\n”); \*segnalo l’errore sulla fork*\

exit(-1);

}

else if(figlio1==0) \*primo figlio*\

{

close(canale[0]); \* chiudo la pipe1 in lettura*\

read(0,tab,5); \*leggo 5 caratteri da tastiera (0) e li *\

\*deposito su tab*\

write (canale[0];),buf1,5); \*scrivo sulla pipe*\

signal(sigusr1,func1); \*predispongo la ricezione del *\

\*segnale*\

pause(); \*resto in attesa*\

exit(0); \*esco regolarmente*\

} else

{ \*codice del padre*\

figlio2=fork(); \* genero il secondo processo figlio *\

if(figlio2<0)

{

printf(“fork fallita\n”); \*segnalo l’errore sulla fork*\

exit(-1);

}

else if (figlio2==0) \*secondo figlio*\

{

close(canale[1]); \* chiudo la pipe1 in scrittura*\

read(canale[0],tab,5); \*leggo 5 caratteri dalla pipe e li *\

\*deposito su tab*\

kill(figlio1,sigusr1); \*invio il segnale al primo figlio*\

(14)

exit(0);

}

else \* padre*\

{

figlio=wait(&status); \*aspetto la terminazione*\

if(figlio==figlio1)

{ \ha terminato il primo figlio*\

printf(“ha terminato il primo figlio\n”);

else if (figlio==figlio2) printf(“ha terminato il secondo figlio\n”);

wait(&status);

printf(ha terminato anche l’altro figlio\n”);

}

} }

(15)

Esercizio 7

La logica del programma è la seguente:

a) il padre legge 2 numeri da tastiera

b) passa il primo numero al figlio che calcola il fattoriale.

c) Il figlio restituisce il risultato al padre, che gli passa il secondo numero

d) Mentre il figlio calcola il fattoriale il padre scrive il primo risultato sul file fattorial.txt, creato con permessi 0666.

e) Il figlio passa il secondo risultato al padre che lo scrive sul file fattorial.txt.

#define PERM 0666

#include <stdio.h>

main() {

int figlio, canale1[2], canale2[2], num1[1], num2[1], fat1[1], fat2[1], fd, i

pipe (canale1); \* genero la prima pipe *\

pipe (canale2); \* genero la seconda pipe *\

figlio=fork(); \* genero il primo processo figlio *\

if(figlio<0)

{

printf(“fork fallita\n”); \*segnalo l’errore sulla fork*\

exit(-1);

}

else if(figlio>0) \processo padre*\

{

close (canale1[0]); \* chiudo la pipe1 in lettura*\

close (canale2[1]); \* chiudo la pipe2 in scrittura*\

read (0,num1,1) \leggo da tastiera il primo numero*\

read (0,num2,1) \leggo da tastiera il secondo numero*\

write(canale1[1],num1,1); \* scrivo sulla pipe 1 il primo numero*\

read(canale2[0],fat1,1); \* leggo dalla pipe 2 il primo risultato*\

write(canale1[1],num2,1); \* scrivo sulla pipe 1 il secondo numero*\

fd=creat (‘fattorial.txt’,PERM); \creo il file su cui depositare i dati*\

write(fd,fat1,1); \*scrivo il primo risultato *\

read(canale2[0],fat2,1); \* leggo dalla pipe 2 il secondo risultato*\

write(fd,fat2,1); \*scrivo il secondo risultato *\

close(fd) \*chiudo il file aperto*\

exit(0) \*termino regolarmente*\

}

else \*figlio*\

{

close (canale2[0]); \* chiudo la pipe2 in lettura*\

close (canale1[1]); \* chiudo la pipe1 in scrittura*\

(16)

read (canale1[0],num1,1) \leggo da pipe1 il primo numero*\

fat1[0]=num1[0] \*calcolo il primo fattoriale *\

for(i=1,i<num1[0], i++)

{

fat1[0]=fat1[0]*(num1[0]-i);

}

write(canale2[1],fat1,1); \* scrivo sulla pipe 2 il primo fattoriale*\

read (canale1[0],num2,1) \leggo da pipe1 il secondo numero*\

fat2[0]=num2[0] \*calcolo il secondo fattoriale *\

for(i=1,i<num2[0], i++)

{

fat2[0]=fat2[0]*(num2[0]-i);

}

write(canale2[1],fat2,1); \* scrivo sulla pipe 2 il secondo fattoriale*\

exit(0) \*termino regolarmente*\

} }

(17)

Esercizio 8

#include <stdio.h>

const K 6 const X 0.5 int fatt(int )

int potenza (int,int)

main() {

int figlio1, figlio2, canale1[2], canale2[2], somma1[3], somma2[3], i, j, somma[1]

pipe (canale1); \* genero la prima pipe *\

pipe (canale2); \* genero la seconda pipe *\

figlio1=fork(); \* genero il primo processo figlio *\

if(figlio1<0)

{

printf(“fork fallita\n”); \*segnalo l’errore sulla fork*\

exit(-1);

}

else if(figlio1==0) \primo figlio*\

{

close(canale1[0]); \*chiudo pipe1 in lettura*\

j=1

for(i=0, i<(K/2), i++) \*calcolo i primi tre termini della somma*\

{

somma1[i]=(potenza(x,(2*j-1)))/fatt((2*j-1));

write(canale1[1],somma1,1); \*scrivo sulla pipe1*\

j=j+2

}

exit(0);

}

else \*processo padre\*

{

figlio2=fork(); \*genero il secondo figlio*\

if(figlio2<0)

{

printf(“fork fallita\n”); \*segnalo l’errore sulla fork*\

(18)

exit(-1);

}

else if(figlio 2==0) \*secondo figlio *\

{

close(canale2[0]); \*chiudo pipe2 in lettura*\

j=2

for(i=0, i<(K/2), i++)

\*calcolo i secondi tre termini della somma*\

{

somma2[i]=-(potenza(x,(2*j-1)))/fatt((2*j-1));

write(canale2[1],somma2,1); \*scrivo sulla pipe2*\

j=j+2

}

exit(0);

}

else \*padre*\

{

close(canale1[1]); \*chiudo pipe1 in scrittura*\

close(canale2[1]); \*chiudo pipe2 in scrittura*\

read (canale1[1],somma1,3);

read (canale2[1],somma2,3);

somma =0;

for(i=0,i<3,i++) somma[0] =somma[0] +somma1[i];

for(i=0,i<3,i++) somma[0] =somma[0] +somma2[i];

write(1,somma,1);

exit(0);

} }

int fatt(int n) {

if(n==0) fatt=1;

else fatt=n*fatt(n-1);

return fatt

}

int potenza(int base, int esponente) {

if(esponente==0) potenza=1;

else potenza=base*potenza(base, esponente-1);

return potenza

}

Riferimenti

Documenti correlati

– file filename: stampa il tipo di file di filename – cat filename: mostra il contenuto di filename – pwd: stampa la directory corrente?. – exit (logout): esegue

If you want to use the shell variable every time you interact with the C shell, use a text editor to add the command line to the .cshrc file.. (See &#34;Editors&#34; on page 39 for

• la User Area viene mantenuta, tranne le informazioni legate al codice del processo (ad esempio, le funzioni di gestione dei segnali, che dopo l’exec non sono più

» se il processo che termina ha figli in esecuzione, il processo init adotta i figli dopo la terminazione del padre (nella process structure di ogni figlio al pid del processo

che scrive in ciascun elemento del vettore contatori (anch’esso di dimensione N) quanti elementi del vettore valori sono divisibili per il numero primo presente nella

conta_divisibili (int quadrati[], int valori[], int n_valori, int contatori[]) che scrive in ciascun elemento del vettore contatori (anch’esso di dimensione N) quanti elementi

che scrive in ciascun elemento del vettore contatori (anch’esso di dimensione N) quanti elementi del vettore valori sono divisibili per il numero primo presente nella

conta_divisibili (int quadrati[], int valori[], int n_valori, int contatori[]) che scrive in ciascun elemento del vettore contatori (anch’esso di dimensione N) quanti elementi