Files
BUT2/TP_SCR3.1/TP_SIGNAUX/parexeclim.c

120 lines
5.3 KiB
C

/*
* parexec_lim.c
* Q3 : Lancer au plus N instances en parallèle : ./parexec_lim prog N arg1 [arg2 ...]
* Q4 : Si une instance se termine à cause d'un SIGNAL -> tuer toutes les autres et arrêter.
*/
#include <stdio.h> // fprintf, perror
#include <stdlib.h> // atoi, calloc, free, EXIT_*
#include <unistd.h> // fork, execlp, usleep
#include <sys/wait.h> // wait, WIFSIGNALED, WTERMSIG
#include <signal.h> // kill
#include <errno.h> // errno
int main(int argc, char *argv[]) {
// Vérifie la syntaxe minimale : prog + N + au moins 1 argument pour prog
if (argc < 4) {
fprintf(stderr, "Usage: %s prog N arg1 [arg2 ...]\n", argv[0]);
return EXIT_FAILURE;
}
char *prog = argv[1]; // le programme à lancer (ex: "./rebours")
int N = atoi(argv[2]); // nombre maximum d'enfants en parallèle
if (N <= 0) { // N doit être strictement positif
fprintf(stderr, "N doit être > 0\n");
return EXIT_FAILURE;
}
int next = 3; // index du prochain argument à donner à prog (argv[3] au début)
int running = 0; // nombre d'enfants actuellement en cours d'exécution
int stop = 0; // =1 si on a détecté une fin anormale -> on arrête de relancer
// Tableau des PIDs des enfants "actifs" (taille N). 0 signifie "case libre".
pid_t *slots = calloc((size_t)N, sizeof(pid_t));
if (!slots) { perror("calloc"); return EXIT_FAILURE; }
// ===== 1) Lancer tout de suite jusqu'à N enfants (ou jusqu'à épuisement des args) =====
while (!stop && running < N && next < argc) {
pid_t pid = fork(); // crée un nouveau processus
if (pid == 0) { // dans l'enfant
execlp(prog, prog, argv[next], NULL); // remplace l'enfant par "prog argi"
_exit(127); // si exec échoue, sortir avec code 127
}
if (pid < 0) { // fork a échoué dans le parent
perror("fork");
stop = 1; // on arrête les lancements
break;
}
// Ici on est dans le parent et fork a réussi : on mémorise le PID dans une case libre 2 5 4 3
for (int i = 0; i < N; ++i) {
if (slots[i] == 0) { // 0 = case libre
slots[i] = pid; // on range ce PID
break;
}
}
running++; // on a un enfant de plus
next++; // prochain argument à utiliser
}
// ===== 2) Boucle principale : attendre les fins, réagir, et (éventuellement) relancer =====
while (running > 0) { // tant qu'il reste des enfants actifs
int status = 0;
pid_t pid = wait(&status); // attendre qu'UN enfant se termine
if (pid < 0) { // erreur (souvent EINTR). On simplifie : on réessaie.
if (errno == EINTR){
continue;
}
perror("wait");
break;
}
// Retirer ce PID du tableau des actifs (libérer sa case)
for (int i = 0; i < N; ++i) {
if (slots[i] == pid) { // on a trouvé la case de cet enfant
slots[i] = 0; // on marque la case vide
break;
}
}
running--; // un enfant en moins
// ===== Q4 : si l'enfant s'est terminé à cause d'un SIGNAL =====
if (WIFSIGNALED(status)) {
stop = 1; // ne plus lancer de nouveaux enfants
// D'abord demander gentiment aux autres de s'arrêter
for (int i = 0; i < N; ++i) {
if (slots[i] > 0) kill(slots[i], SIGTERM);
}
usleep(100000); // petite pause (100 ms)
// Puis forcer l'arrêt si certains sont encore vivants
for (int i = 0; i < N; ++i) {
if (slots[i] > 0) kill(slots[i], SIGKILL);
}
// on n'ajoute PAS de relance ici (stop=1)
}
// ===== Relancer si : pas d'arrêt global ET il reste des arguments à lancer =====
if (!stop && next < argc) {
pid_t npid = fork(); // crée un nouvel enfant
if (npid == 0) { // dans le nouvel enfant
execlp(prog, prog, argv[next], NULL); // lance "prog arg"
_exit(127); // si exec échoue
}
if (npid < 0) { // fork a échoué
perror("fork");
stop = 1; // arrêter les relances
} else {
// ranger ce nouveau PID dans une case libre
for (int i = 0; i < N; ++i) {
if (slots[i] == 0) { slots[i] = npid; break; }
}
running++; // un enfant de plus en cours
next++; // passera à l'argument suivant lors de la prochaine relance
}
}
}
free(slots); // libérer la mémoire
if (stop) return 1; // échec
return 0; // succès
}