Gestione Processi in C
La gestione dei processi è una parte fondamentale della programmazione di sistema in C. I processi sono entità indipendenti che eseguono istruzioni in parallelo o in modo concorrente, consentendo di sfruttare appieno le capacità del sistema operativo e del processore. In questa guida, esploreremo come creare, gestire e terminare processi in C, utilizzando le principali funzioni di sistema come fork
, exec
, e wait
.
Creazione di Processi
In C, la creazione di un nuovo processo è generalmente gestita attraverso la funzione fork()
, che crea un processo figlio duplicando il processo padre.
Funzione fork()
pid_t fork(void);
fork()
: Crea un nuovo processo duplicando il processo chiamante. Il nuovo processo è chiamato processo figlio, mentre il processo originale è il processo padre.
Esempio di Creazione di un Processo con fork()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
perror("Errore nella creazione del processo");
return 1;
} else if (pid == 0) {
printf("Processo figlio, PID: %d\n", getpid());
} else {
printf("Processo padre, PID: %d, processo figlio PID: %d\n", getpid(), pid);
}
return 0;
}
Uscita Possibile:
Processo padre, PID: 12345, processo figlio PID: 12346
Processo figlio, PID: 12346
In questo esempio, fork()
restituisce 0
nel processo figlio e il PID del processo figlio nel processo padre. Il processo padre e il processo figlio continuano l’esecuzione del codice dopo la chiamata a fork()
.
Esecuzione di un Nuovo Programma
Una volta creato un processo figlio, è possibile sostituire il suo codice eseguibile con un nuovo programma utilizzando una delle funzioni della famiglia exec
.
Funzione execl()
int execl(const char *path, const char *arg, ...);
execl()
: Esegue il programma specificato dal percorsopath
, sostituendo il processo chiamante. Gli argomenti sono passati come parametri alla funzione.
Esempio di Uso di execl()
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Processo figlio
execl("/bin/ls", "ls", "-l", (char *)NULL);
perror("Errore nell'esecuzione di execl");
} else if (pid > 0) {
// Processo padre
printf("Processo padre, PID: %d\n", getpid());
}
return 0;
}
In questo esempio, il processo figlio esegue il comando ls -l
, sostituendo il suo codice con il nuovo programma.
Sincronizzazione dei Processi
I processi padre e figlio possono essere sincronizzati utilizzando la funzione wait()
, che permette al processo padre di aspettare che un processo figlio termini.
Funzione wait()
pid_t wait(int *status);
wait()
: Attende la terminazione di un processo figlio e restituisce il PID del processo figlio terminato. Il codice di uscita del processo figlio è memorizzato instatus
.
Esempio di Uso di wait()
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Processo figlio
printf("Processo figlio in esecuzione...\n");
sleep(2); // Simula un lavoro
printf("Processo figlio terminato.\n");
} else if (pid > 0) {
// Processo padre
int status;
wait(&status);
printf("Processo figlio terminato con codice: %d\n", WEXITSTATUS(status));
}
return 0;
}
Uscita:
Processo figlio in esecuzione...
Processo figlio terminato.
Processo figlio terminato con codice: 0
In questo esempio, il processo padre aspetta che il processo figlio termini prima di continuare l’esecuzione.
Comunicazione tra Processi
La comunicazione tra processi può essere realizzata utilizzando meccanismi come le pipe, che permettono di trasferire dati tra processi padre e figlio.
Creazione di una Pipe
int pipe(int pipefd[2]);
pipe()
: Crea una pipe, che è rappresentata da due file descriptor:pipefd[0]
per la lettura epipefd[1]
per la scrittura.
Esempio di Comunicazione tra Processi con una Pipe
#include <stdio.h>
#include <unistd.h>
int main() {
int pipefd[2];
char buffer[30];
if (pipe(pipefd) == -1) {
perror("Errore nella creazione della pipe");
return 1;
}
pid_t pid = fork();
if (pid == 0) {
// Processo figlio
close(pipefd[1]); // Chiude il lato di scrittura
read(pipefd[0], buffer, sizeof(buffer));
printf("Processo figlio ha letto: %s\n", buffer);
close(pipefd[0]);
} else if (pid > 0) {
// Processo padre
close(pipefd[0]); // Chiude il lato di lettura
write(pipefd[1], "Ciao dal processo padre", 24);
close(pipefd[1]);
}
return 0;
}
Uscita:
Processo figlio ha letto: Ciao dal processo padre
In questo esempio, il processo padre scrive un messaggio nella pipe, e il processo figlio legge il messaggio dalla pipe.
Terminazione dei Processi
Un processo può essere terminato utilizzando la funzione exit()
, che chiude il processo e restituisce un codice di uscita al sistema operativo.
Funzione exit()
void exit(int status);
exit()
: Termina il processo e restituiscestatus
come codice di uscita al sistema operativo.
Esempio di Uso di exit()
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Il programma sta terminando...\n");
exit(0);
}
Uscita:
Il programma sta terminando...
In questo esempio, il programma termina con un codice di uscita di 0
, indicando una terminazione riuscita.
Conclusioni
La gestione dei processi in C è una parte essenziale della programmazione di sistema, che consente di creare, sincronizzare e comunicare tra processi indipendenti. Conoscere le funzioni di sistema come fork
, exec
, wait
e pipe
è fondamentale per sviluppare applicazioni che sfruttano il parallelismo e la concorrenza. Con pratica ed esperienza, sarai in grado di costruire applicazioni robuste e efficienti che sfruttano appieno le capacità del sistema operativo.