PDA

View Full Version : [C]Eseguire comandi tramite processi


dennis8787
29-09-2014, 17:47
Ciao a tutti,
il mio non è proprio un dubbio ma una richiesta di conferma a quello che ho scritto. Se ho fatto bene o meno.
Posto subito il codice:

void run_command_in_parallel_mode(struct list* cmd){
int i; //Contatore del ciclo for
pid_t pid; // Process identifier del processo generato con fork()
struct node* tmp;
tmp = cmd -> header;
char filename[50];

for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
sprintf(filename, "out.%d", i+1);
freopen( filename, "w", stdout); // Reindirizza stdout nel file destinazione
system(tmp -> command);
tmp = tmp -> next;
}else{
printf("\nProcesso padre con PID %d e PPID %d\n", getpid(), getppid());
}
}
freopen( "CON", "w", stdout ); // Ripristina stdout
}

Questa funzione prende in ingresso una lista i cui nodi contengono comandi da eseguire. Ogni processo deve eseguire un comando, e l'output di ogni comando deve essere scritto su un file di testo.
Ora, funzionare funziona, ma non sono convinto al 100% che vada bene.
Io mi faccio un for che cicla per tutta la lunghezza della lista, ad ogni ciclo faccio una biforcazione del processo con fork() e, se pid==0 vuol dire che quello è il figlio, quindi apro il file di output ed eseguo il comando con la funzione system(). Ma funziona proprio così? nel senso, è vero che i comandi vengono eseguiti in "parallelo" ognuno con un processo diverso? Oppure devo usare una funzione tipo exec per differenziare il programma che il processo esegue da quello che esegue il padre?

sottovento
30-09-2014, 08:22
if(pid==0){ // Il processo figlio è stato generato
printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
sprintf(filename, "out.%d", i+1);
freopen( filename, "w", stdout); // Reindirizza stdout nel file destinazione
system(tmp -> command);
tmp = tmp -> next;
}


Nel momento che esegui la fork(), crei un nuovo processo. Il nuovo processo ha una copia dello stato del processo iniziale, vale a dire che le variabili hanno lo stesso valore, ma sono copie, non sono le stesse.

Pertanto la

tmp = tmp->next;

andra' a cambiare il valore della copia forkata, e non e' proprio quello che vuoi.
C'e' un altro problema simile, aspetto a dirtelo perche' magari vuoi darci un'occhiata da solo

dennis8787
30-09-2014, 11:54
Nel momento che esegui la fork(), crei un nuovo processo. Il nuovo processo ha una copia dello stato del processo iniziale, vale a dire che le variabili hanno lo stesso valore, ma sono copie, non sono le stesse.

Pertanto la

tmp = tmp->next;

andra' a cambiare il valore della copia forkata, e non e' proprio quello che vuoi.
C'e' un altro problema simile, aspetto a dirtelo perche' magari vuoi darci un'occhiata da solo
Può essere che l'altro problema riguardi l'esecuzione di
freopen( "CON", "w", stdout ); // Ripristina stdout
che viene eseguita prima che i processi sdoppiato terminano l'esecuzione?
Per fare quello che vorrei io, come dovrei farlo?
Potrei, ad esempio, scrivere i comandi su un file .sh e poi farlo eseguire dal processo, quindi dentro l'if, con il comando exec?

sottovento
30-09-2014, 14:13
Può essere che l'altro problema riguardi l'esecuzione di
freopen( "CON", "w", stdout ); // Ripristina stdout
che viene eseguita prima che i processi sdoppiato terminano l'esecuzione?

Esattamente!


Per fare quello che vorrei io, come dovrei farlo?
Potrei, ad esempio, scrivere i comandi su un file .sh e poi farlo eseguire dal processo, quindi dentro l'if, con il comando exec?

Si, potresti, cosi' potresti ridirigere l'output di ogni singola esecuzione in un file separato, che mi sembrava fosse la tua idea.

In verita' il tuo algoritmo originale non sembra ottenere un gran vantaggio dal multiprocessing, poiche' la console e' una risorsa condivisa e quindi dovresti pensare a sincronizzare i processi fra di loro, in modo che accedano a questa risorsa solo uno alla volta, perdendo ogni vantaggio. A questo punto, tanto varrebbe evitare di fare la fork() ed eseguire un solo comando alla volta.

dennis8787
30-09-2014, 14:31
Esattamente!



Si, potresti, cosi' potresti ridirigere l'output di ogni singola esecuzione in un file separato, che mi sembrava fosse la tua idea.

In verita' il tuo algoritmo originale non sembra ottenere un gran vantaggio dal multiprocessing, poiche' la console e' una risorsa condivisa e quindi dovresti pensare a sincronizzare i processi fra di loro, in modo che accedano a questa risorsa solo uno alla volta, perdendo ogni vantaggio. A questo punto, tanto varrebbe evitare di fare la fork() ed eseguire un solo comando alla volta.

Quello che hai detto nell'ultima parte e' esattamente quello che richiede il testo, cioè far decidere all'utente di eseguire i comandi che ha inserito o sequenzialmente (cosa banale che ho già implementato) o parallelamente (cosa di cui avevo il dubbio nel codice che ho postato).
Per quanto riguarda, quindi, la parte di esecuzione parallela, allora vado per la scrittura e l'esecuzione di un file .sh dal processo.
Semmai provo a buttar giù qualcosa e lo posto

dennis8787
30-09-2014, 19:34
Esattamente!



Si, potresti, cosi' potresti ridirigere l'output di ogni singola esecuzione in un file separato, che mi sembrava fosse la tua idea.

In verita' il tuo algoritmo originale non sembra ottenere un gran vantaggio dal multiprocessing, poiche' la console e' una risorsa condivisa e quindi dovresti pensare a sincronizzare i processi fra di loro, in modo che accedano a questa risorsa solo uno alla volta, perdendo ogni vantaggio. A questo punto, tanto varrebbe evitare di fare la fork() ed eseguire un solo comando alla volta.

Ho provato a modificare la funzione così:
void run_command_in_parallel_mode(struct list* cmd){
int i; //Contatore del ciclo for
pid_t pid; // Process identifier del processo generato con fork()
FILE* fd;
struct node* tmp;
tmp = cmd -> header;
//char filename[50];

for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
//printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
fd = fopen("prova.sh", "w+e");
fprintf(fd, "#!/bin/bash\n");
chmod("prova.sh", S_IRWXU);
fprintf(fd, "%s > out.%d", tmp->command, i);
/*sprintf(filename, "out.%d", i+1);
freopen( filename, "w", stdout); // Reindirizza stdout nel file destinazione
system(tmp -> command);*/
fclose(fd);
execv("./prova.sh", NULL);
printf("Questa non viene stampata!\n");
tmp = tmp -> next;
//fclose(fd);
}else{
//printf("\nProcesso padre con PID %d e PPID %d\n", getpid(), getppid());
}
}
//freopen( "CON", "w", stdout ); // Ripristina stdout
}

mi crea però problemi la chiusura, infatti se inserisco più di 1 comando, il primo viene tranquillamente eseguito, mentre gli altri no..non capisco dove metterla la chiusura del file..

sottovento
30-09-2014, 21:37
tmp = tmp -> next; <-- Ancora qui? :D



L'avanzamento deve essere fatto nel processo padre.
Altra cosa: una volta che il processo figlio ha finito il suo lavoro, non deve riprendere a fare il ciclo, bensi' deve morire. UCCIDILO! (una exit() e' sufficiente)

dennis8787
30-09-2014, 21:54
L'avanzamento deve essere fatto nel processo padre.
Altra cosa: una volta che il processo figlio ha finito il suo lavoro, non deve riprendere a fare il ciclo, bensi' deve morire. UCCIDILO! (una exit() e' sufficiente)

OK. Ho messo tmp=tmp->next; nell'else dell'if, cioè viene eseguito dal padre. Come ultima istruzione dell'if, processo figlio, ho messo una exit(0);
Quello però che ancora non mi è chiaro è quando devo fare la chiusura del file aperto. Anch'essa deve essere fatta dal padre, e quindi deve essere messa nel ramo else dell'if, dopo l'incremento?

sottovento
01-10-2014, 08:16
Quello però che ancora non mi è chiaro è quando devo fare la chiusura del file aperto. Anch'essa deve essere fatta dal padre, e quindi deve essere messa nel ramo else dell'if, dopo l'incremento?
Da questo spezzone di codice:

FILE* fd;
struct node* tmp;
tmp = cmd -> header;
//char filename[50];

for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
//printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
fd = fopen("prova.sh", "w+e");

puoi notare che il file e' stato aperto dal figlio. Non farti ingannare, il padre non ha nessun file aperto!

dennis8787
01-10-2014, 09:53
Da questo spezzone di codice:

FILE* fd;
struct node* tmp;
tmp = cmd -> header;
//char filename[50];

for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
//printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
fd = fopen("prova.sh", "w+e");

puoi notare che il file e' stato aperto dal figlio. Non farti ingannare, il padre non ha nessun file aperto!

e infatti la mia prima idea era di mettere l'fclose(fd) dentro al figlio, appena dopo l'istruzione execv e prima di exit(0), ma non funziona...

sottovento
01-10-2014, 10:03
e infatti la mia prima idea era di mettere l'fclose(fd) dentro al figlio, appena dopo l'istruzione execv e prima di exit(0), ma non funziona...

Cosa vuol dire non funziona? Ti sei accorto che il file e' rimasto aperto? Come? Oppure hai un crash?
Ad ogni modo, tutti i figli cercheranno di aprire lo stesso file, visto che il nome e' fisso. Immagino non sia quello che vuoi fare....

dennis8787
01-10-2014, 10:12
Cosa vuol dire non funziona? Ti sei accorto che il file e' rimasto aperto? Come? Oppure hai un crash?
Ad ogni modo, tutti i figli cercheranno di aprire lo stesso file, visto che il nome e' fisso. Immagino non sia quello che vuoi fare....

Non funziona nel senso che non fa quello che mi aspetto. Quando lo lancio in esecuzione crea il file "prova.sh" ma non lo esegue e i file di output out. non vengono creati. Se apro il file "prova.sh" dentro trovo l'intestazione e sotto la riga, ad esempio, "date > out.0", dove date è il primo comando che inserisco da terminale.
Quello che vorrei è che ogni processo crea il file "prova.sh" con dentro il comando da eseguire, e lo esegue. Ogni processo deve creare, e poi eseguire, il proprio file .sh.

EDIT: Quello che ottengo da questo codice
for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
/*fd = fopen("prova.sh", "w");
fprintf(fd, "#!/bin/bash\n");
chmod("prova.sh", S_IRWXU);*/
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
//printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
fd = fopen("prova.sh", "w+");
fprintf(fd, "#!/bin/bash\n");
int d = chmod("prova.sh", S_IRWXU);
if(d==0){
printf("Permessi di esecuzione dati!\n");
}else{
printf("Permessi di esecuzione non dati!\n");
}
fprintf(fd, "%s > out.%d", tmp->command, i);
/*sprintf(filename, "out.%d", i+1);
freopen( filename, "w", stdout); // Reindirizza stdout nel file destinazione
system(tmp -> command);*/
//fclose(fd);
execv("./prova.sh", NULL);
//tmp = tmp -> next;
fclose(fd);
exit(0);

non è che ogni processo apre il file, scrive dentro quello che deve scrivere, lo esegue e poi lo chiude? In questo modo il processo dopo può aprirlo, scrivere, eseguirlo chiudere e così per tutti i processi..

sottovento
01-10-2014, 10:42
Intendevo che il file "prova.sh", essendo unico ed essendo creato e scritto contemporaneamente da tutti i processi, ti puo' dare problemi.
Potresti decidere di usare un file diverso per ogni processo.

Una volta corretto questo potresti fare un giro di debug o magari cominciare a stampare un po' di valori, per vedere come vanno le cose. Probabilmente potresti partire da una lista con un solo valore, cosi' da creare un solo processo e vedere se lui funziona.

Un'altra cosa: scusa l'ignoranza ma per ridirigere l'output ho sempre usato la dup(), i.e. qualcosa del tipo


inFile = open(filename_in, O_RDONLY, 0);
inDup = dup2(inFile, STDIN_FILENO);
close(inFile);

outFile = creat(filename_out, 0644);
outDup = dup2(outFile, STDOUT_FILENO);
close(outFile);


Sei sicuro che la tecnica che stai usando funziona? L'hai provata, magari separatamente, in modo da evitare che i problemi si sommino?

dennis8787
01-10-2014, 10:46
Intendevo che il file "prova.sh", essendo unico ed essendo creato e scritto contemporaneamente da tutti i processi, ti puo' dare problemi.
Potresti decidere di usare un file diverso per ogni processo.

Una volta corretto questo potresti fare un giro di debug o magari cominciare a stampare un po' di valori, per vedere come vanno le cose. Probabilmente potresti partire da una lista con un solo valore, cosi' da creare un solo processo e vedere se lui funziona.

Un'altra cosa: scusa l'ignoranza ma per ridirigere l'output ho sempre usato la dup(), i.e. qualcosa del tipo


inFile = open(filename_in, O_RDONLY, 0);
inDup = dup2(inFile, STDIN_FILENO);
close(inFile);

outFile = creat(filename_out, 0644);
outDup = dup2(outFile, STDOUT_FILENO);
close(outFile);


Sei sicuro che la tecnica che stai usando funziona? L'hai provata, magari separatamente, in modo da evitare che i problemi si sommino?

La tecnica che uso intendi il " > out.%d"? si funziona, in pratica concateno quella stringa al mio comando. Il segno > serve a ridirezionare l'output nel file alla destra del >.
Ma, come faccio a crearmi un file diverso per ogni processo in esecuzione?

sottovento
01-10-2014, 11:00
La tecnica che uso intendi il " > out.%d"? si funziona, in pratica concateno quella stringa al mio comando. Il segno > serve a ridirezionare l'output nel file alla destra del >.
Ma, come faccio a crearmi un file diverso per ogni processo in esecuzione?
No, mi riferivo allo statement
freopen( filename, "w", stdout); // Reindirizza stdout

Per creare un file diverso per ogni processo potresti, per esempio, ottenere il process id del figlio mediante la funzione getpid() ed usarlo nel nome del file.
(Poi suggerirei anche di pensare ad un modo per cancellarli, visto che dopo un po' di prove ti troverai un sacco di questi file)

dennis8787
01-10-2014, 11:12
No, mi riferivo allo statement
freopen( filename, "w", stdout); // Reindirizza stdout

Per creare un file diverso per ogni processo potresti, per esempio, ottenere il process id del figlio mediante la funzione getpid() ed usarlo nel nome del file.
(Poi suggerirei anche di pensare ad un modo per cancellarli, visto che dopo un po' di prove ti troverai un sacco di questi file)

Questa freopen( filename, "w", stdout); // Reindirizza stdout è commentata. E' rimasta li da una vecchia versione del codice.

Forse sono arrivato ad un dunque, con questo codice, che sembra funzionare

void run_command_in_parallel_mode(struct list* cmd){
int i; //Contatore del ciclo for
pid_t pid; // Process identifier del processo generato con fork()
FILE* fd = 0;
struct node* tmp;
tmp = cmd -> header;
char filename[50];

for(i=0; i<cmd->count; i++){ //Contatore che cicla per tutta la lunghezza della lista
/*fd = fopen("prova.sh", "w+");
fprintf(fd, "#!/bin/bash\n");
chmod("prova.sh", S_IRWXU);*/
pid = fork(); // Biforcazione del processo in Padre e Figlio.
if(pid==0){ // Il processo figlio è stato generato
//printf("\nProcesso figlio con PID %d\tPPID %d\tVALORE DELLA i %d\tESEGUE %s\n", getpid(), getppid(), i, tmp->command);
sprintf(filename, "processo%d.sh", getpid());
fd = fopen(filename, "w+");
fprintf(fd, "#!/bin/bash\n");
int d = chmod(filename, S_IRWXU);
if(d==0){
printf("Permessi di esecuzione dati!\n");
}else{
printf("Permessi di esecuzione NON dati!\n");
}
fprintf(fd, "%s > out.%d", tmp->command, i);
/*sprintf(filename, "out.%d", i+1);
freopen( filename, "w", stdout); // Reindirizza stdout nel file destinazione
system(tmp -> command);*/
fclose(fd);
execv(filename, NULL);
//tmp = tmp -> next;
//fclose(fd);
exit(0);

}else{
//fclose(fd);
tmp = tmp -> next;
//printf("\nProcesso padre con PID %d e PPID %d\n", getpid(), getppid());
}
//fclose(fd);
}
//freopen( "CON", "w", stdout ); // Ripristina stdout
}

Per cancellare i vari file .sh che si vengono a creare avevo pensato di mettere system("rm p*"); nel main, appena dopo il messaggio che comunica la terminazione del programma.
Che ne pensi?

EDIT: No, mettere system("rm p*"); nel main prima della terminazione del programma non va bene perchè il programma principale termina prima che i processo finiscano il loro compito, in questo modo si cancellerebbero file ancora da eseguire, mandando in errore il programma

sottovento
02-10-2014, 08:56
Allora... per prima cosa correggo un mio errore: ti ho detto di mettere la exit(0) per essere sicuro che il processo figlio finisca, ma non mi ero accorto che c'era una exec() in mezzo, e dalla exec() non si torna.
Pertanto la exit() e' ridondante, non la eseguirai mai. scusami.

Ho controllato il tuo codice: congratulazioni! Mi sembra che sia ottimo.
Per quanto riguarda la cancellazione dei file, potresti aspettare, nel tuo programma principale, che tutti i processi che hai forkato abbiano completato l'esecuzione prima di cancellare i file.
Per farlo, puoi usare la wait() o la waitpid(). Il manuale di queste istruzioni lo puoi trovare nelle man page del tuo sistema, oppure qui: http://linux.die.net/man/3/wait