Skip to content
This repository was archived by the owner on Nov 4, 2023. It is now read-only.

interpreter : parallelExec.c

sᴀʟᴠᴀᴛᴏʀᴇ ʙ edited this page Jul 23, 2017 · 2 revisions
void parallelExec() {
	signal(SIGUSR1, SIG_IGN); //ignoro SIGUSR1 perchè serve solo per evitare race con distino
	bind_handler_sigchld(); // imposto l'handler opportuno per i SIGCHLD
	printf("Nota: Premere INVIO per terminare\n");
	int termination_flag = 0;
	int count = 0;
	pid_t pidPadre = getpid();
	do {
		printf("\n> ");
		//acquisizione comando da parte dell'utente
		char* str = readCommand();
		if (str != NULL) {
			termination_flag = 1;
			count++; //incremento dell'indice degli output file
			if (fork() == 0) {
				//processo figlio
				//la memoria dinamica istanziata in parseCommand sara'
				//liberata automaticamente al termine di questo processo
				int saved_stdout = dup(STDOUT_FILENO); //in caso di errore mantengo STDOUT
				char **command = parseCommand(str); //il comando è stato salvato localmente, segnalo al padre il via libera al prossimo comando
				kill(pidPadre, SIGUSR1);
				createFileP(count);
				//l'ouput è redirezionato anche dopo execvp
				if (execvp(command[0], command) == -1) { //errore nell'esecuzione di execvp (probabilmente comando non riconosciuto)
					dup2(saved_stdout, STDOUT_FILENO);
					printf("Comando non riconosciuto.\n");
					exit(EXIT_FAILURE);
				}
			} else {
                        //processo padre
                        //devo almeno attendere che il figlio abbia copiato localmente 
                        //il comando da eseguire
				pause(); //aspettando SIGUSR1
				signal(SIGUSR1, SIG_IGN); //ripristino l’handler
			}
		} else {
			termination_flag = 0;
		}
		free(str);
	} while (termination_flag != 0);
}

parallelExec.c mostra una struttura simile a sequential, ma in questo caso nel blocco del padre non viene eseguita nessuna wait ma solo una pause(): in effetti per evitare problemi di race condition il padre deve almeno attendere che il figlio abbia acquisito il parsing del comando, senza andare a liberare la memoria e ad acquisire il prossimo comando; per far ciò il padre attende il segnale SIGUSR1 che verrà semplicemente ignorato (ma avrà l’effetto di farlo ripartire). Dato che di default SIGUSR1 determina la terminazione del processo, esso è stato modificato attraverso -> signal(SIGUSR1, SIG_IGN); cioè la prima istruzione. La seconda riguarda invece la gestione dei figli che terminano mentre il padre è attivo, in particolare per evitare la proliferazione di zombies il padre sfrutta la funzione “handle_sigchld” descritta nel prossimo paragrafo.

Nota: Se il padre terminasse prima dei suoi figli, essi sarebbero automaticamente promossi a figli di init e sarebbe quest’ultimo ad accettare la loro terminazione. Nel blocco del figlio è stato aggiunto l’invio del segnale SIGUSR1 attraverso la kill subito dopo il parseCommand().

void bind_handler_sigchld() {
	struct sigaction sa;
	sa.sa_handler = &handle_sigchld; //imposto la funzione da eseguire
	// impedisco a segnali come ^C o ^D di interrompere l'handler
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGINT);
	sigaddset(&sa.sa_mask, SIGSTOP);
	// gestisco SIGCHLD solo per figli che hanno terminato (non sospeso)
	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
	if (sigaction(SIGCHLD, &sa, 0) == -1) {
		//errore, uscita forzata
		perror(0);
		exit(EXIT_FAILURE);
	}
}

bind_handler_sigchld() modifica l’handler per il segnale SIGCHLD, il quale viene inviato al padre al termine di ogni figlio. La funzione sigaction() permette una gestione più accurata della modifica della funzione, in particolare è stato impostata l’impossibilità di interrompere tale handler attraverso ^C (SIGINT) e ^D (SIGSTOP). Inoltre la funzione gestirà solo figli che hanno terminato e non che si sono temporaneamente sospesi, grazie al flag SA_NOCLDSTOP.

Clone this wiki locally