/* * 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 // fprintf, perror #include // atoi, calloc, free, EXIT_* #include // fork, execlp, usleep #include // wait, WIFSIGNALED, WTERMSIG #include // kill #include // 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 }