120 lines
5.3 KiB
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
|
||
|
|
}
|