🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Gestione Processi in C

Codegrind Team•Aug 23 2024

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 percorso path, 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 in status.

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 e pipefd[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 restituisce status 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.