Sistemi di Calcolo

Corso di Laurea in Ingegneria Informatica e Automatica - A.A. 2017-2018

HomePage | Avvisi | Diario lezioni | Programma | Materiale didattico | Esami | Forum | Login

Esempio: clonazione immagine memoria con fork, EXIT_FAILURE/EXIT_SUCCESS


fork-clone.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {

    int x = 23;
    printf("[genitore] x = %d, &x = %p \n", x, &x);

    pid_t pid = fork(); // l'immagine di memoria viene clonata

    if (pid == -1) {
        perror("errore nella fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // processo figlio

        printf("[figlio] x = %d, &x = %p (prima)\n", x, &x);
        x = 17;
        printf("[figlio] x = %d, &x = %p (dopo)\n", x, &x);
        _exit(EXIT_SUCCESS);
    }

    // eseguo codice solo nel padre
    int status;
    pid_t figlio = wait(&status);

    if (figlio == -1) {
        perror("errore nella wait");
        exit(EXIT_FAILURE);
    }

    printf("[genitore] x = %d, &x = %p \n", x, &x);
    return EXIT_SUCCESS;
}


Appronfondimento: exit versus _exit


La funzione POSIX exit svolge le seguenti azioni:
La funzione POSIX _exit svolge le seguenti azioni:

Esempio: esecuzione multipla di funzione registrata con atexit
exit-mess-atexit
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

void bye(void) {
    printf("[pid=%d] That was all, folks\n", getpid());
}

int main() {

    int res = atexit(bye);  // quando verra' invocata exit
                            // verra' eseguita la funzione bye
    if (res != 0) { // man atexit: The atexit() function returns the value 0 if successful; otherwise it returns a nonzero value.
        perror("errore atexit");
        exit(EXIT_FAILURE);
    }

    printf("[pid=%d] genitore\n", getpid());

    pid_t pid = fork(); // l'immagine di memoria viene clonata

    if (pid == -1) {
        perror("errore nella fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // processo figlio

        printf("[pid=%d] figlio\n", getpid());
        _exit(EXIT_SUCCESS); // provare a sostituire con exit
    }

    // eseguo codice solo nel padre
    pid_t figlio = wait(NULL);

    if (figlio == -1) {
        perror("errore nella wait");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS; // eseguita indirettamente: exit(EXIT_SUCCESS)
}


Esempio: flushing ripetuto dei buffer
exit-mess-buffer.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {

    printf("AAAA"); // printf usa gli stream stdio e flush del buffer potrebbe non essere immediato se la stringa non contiene il carattere newline

    pid_t pid = fork(); // l'immagine di memoria viene clonata

    if (pid == -1) {
        perror("errore nella fork");
        exit(EXIT_FAILURE);
    }

    // fflush(stdout); // provare a decommentare, forza il flushing dei buffer stdio

    if (pid == 0) { // processo figlio

        printf("BBBB"); // printf usa gli stream stdio e flush del buffer potrebbe non essere immediato se la stringa non contiene il carattere newline
        exit(EXIT_SUCCESS); // provare a sostituire con _exit: exit fa flush, _exit non lo fa
    }

    // eseguo codice solo nel padre
    pid_t figlio = wait(NULL);

    if (figlio == -1) {
        perror("errore nella wait");
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS; // eseguita: exit(EXIT_SUCCESS)
                    // exit fa sempre flush dei buffer   
}


Esempio: pattern fork+exec+wait, EXIT_FAILURE/EXIT_SUCCESS, execvp, WEXITSTATUS

Versione 1
Implementazione rudimentale di una shell: esegue il comando ricevuto come argomento

myshell.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

// eseguo il comando ricevuto come argomento
// e attendo terminazione del nuovo processo lanciato
// Uso: myshell <nome-programma>
int main(int argc, char * argv[]) {

    if (argc < 2) {
        printf("Usage: %s <nome-programma>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    printf("Lancio esecuzione comando: %s\n", argv[1]);
    pid_t pid = fork();

    if (pid == -1) {
        perror("errore nella fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // processo figlio

        char * myargv[] = { argv[1], NULL };
        int res = execvp(myargv[0], myargv);    // execvp vs execv:
                                    // - execv: richiede il percorso all'eseguibuile
                                    // - execvp: richiede il percorso all'eseguibuile oppure il nome di un comando disponibile in PATH

        // se sto eseguendo questo codice allora res == -1
        perror("errore nella execvp");
        _exit(EXIT_FAILURE);
    }

    // eseguo codice solo nel padre
    int status;
    pid_t figlio = wait(&status);

    if (figlio == -1) {
        perror("errore nella wait");
        exit(EXIT_FAILURE);
    }

    printf("Fine processo eseguito (status=%d, figlio=%d)\n", WEXITSTATUS(status), figlio);
    return EXIT_SUCCESS;
}


Versione 2
Implementazione rudimentale di una shell: esegue il comando ricevuto come argomento passando, se necessario, anche i suoi argomenti

myshell-2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

// eseguo il comando ricevuto come argomento
// e attendo terminazione del nuovo processo lanciato
int main(int argc, char * argv[]) {

    if (argc < 2) {
        printf("Usage: %s <nome-programma> [<arg1>] [<arg2>] [<arg3>] ...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    printf("Lancio esecuzione comando:");
    int i;
    for (i = 1; i < argc; i++) printf(" %s", argv[i]);
    printf("\n");

    pid_t pid = fork();

    if (pid == -1) {
        perror("errore nella fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) { // processo figlio

        char * myargv[argc]; // argc-1 argomenti + NULL
        int k;
        for (k = 1; k < argc; k++)
            myargv[k-1] = argv[k];
        myargv[argc -