Extra: Brute force multi-core

Una volta realizzato il cracker sequenziale, se non siete ancora appagati 😅, potete provare a realizzarne uno che “riempia” gli N core disponibili a tale scopo il programma deve, intuitivamente:

  • fare N fork in modo da utilizzare tutti gli N core
  • fare una wait e poi di nuovo una fork (un core si è liberato e viene subito riutilizzato)
  • prima di uscire fare tutte le wait necessarie ad attendere eventuali processi figli ancora in esecuzione

Provate a confrontare la soluzione sequenziale con quella multi-core utilizzando time che vi dice il tempo di esecuzione complessivo.

$ time ./cracker
...
PIN trovato: Yeah!!
real	0m2.438s
user	0m0.503s
sys	0m0.431s

$ time ./crackerMultiCore
...
PIN trovato: Yeah!!
real	0m0.733s
user	0m0.504s
sys	0m0.801s
...
$

Notare lo speedup di circa 4 volte nella versione multicore. Notare inoltre che alcuni processi figlio continuano anche dopo la terminazione in quanto sono in esecuzione concorrente (parallela) con il processo genitore.

Provare da soli prima di guardare la soluzione!

Le parti nuove sono evidenziate nel sorgente:

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

#define PINLEN "5"         // lunghezza PIN come stringa per snprintf
#define CHECK "./checkPIN" // il programma da eseguire
#define NCORES 8           // Numero di core

/* 
 * Funzione che fa il check dello status ed esce quando
 * la exit ha ritornato zero.
 * Viene riutilizzata in diversi punti del main
 */
void check(int status) {
    if (WIFEXITED(status) && WEXITSTATUS(status)==0) {
        printf("PIN trovato: Yeah!!\n");
        exit(EXIT_SUCCESS);
    }
}

int main(int argc) {
    int pid, status;
    int pin;                     // il pin da provare come numero intero
    int maxbuf = atoi(PINLEN)+1; // PINLEN+1, convertito in intero
    char pinstring[maxbuf];      // il buffer per la stringa contenente il pin
                                 // (il byte in più è per il terminatore di stringa)
    int activecores = 1;         // numero di core attivi

    /* il processo genitore sta in questo loop */
    for (pin=0;;pin++) {
        /* 
         * converte pin in pinstring mettendo un padding di zeri
         * fino a raggiungere PINLEN (5 nel nostro caso) 
         */
        if (snprintf(pinstring, maxbuf, "%0"PINLEN"d", pin) >= maxbuf) {
            /*
             * superata la PINLEN ==> nessun pin trovato, quindi esce
             * notare che snprintf ritorna >= maxbuf quando ha troncato la stringa
             * per farla stare nel buffer (vedere la manpage)
             */
            exit(EXIT_FAILURE); 
        }
        
        pid = fork(); // Crea il processo figlio
        if ( pid < 0 ) { 
            perror("errore fork");
            exit(EXIT_FAILURE); 
        }
        if (pid == 0) { 
            /* 
             * processo figlio, esegue il check lanciando CHECK 
             * con pinstring come argv[1]
             */
            execl(CHECK, CHECK, pinstring, NULL); 
            /* qui non deve arrivarci mai */
            perror("Errore exec!");
            exit(EXIT_FAILURE);
        } 
        /* 
         * solo il genitore continua: ha fatto la fork 
         * incrementa i numero di core occupati
         */
        activecores++; 
        /* 
         * Se i core sono pieni attende un figlio:
         * a regime appena uno finisce ne lancia un altro
         */
        if (activecores==NCORES) {
            if((pid=wait(&status)) >= 0) {
                activecores--; // il figlio ha liberato un core
                check(status); // controlla se ha trovato il PIN
            }
        }
    }
    /* attende eventuali figli ancora in esecuzione */
    while((pid=wait(&status)) >= 0) {
        check(status);
    }
}