Lancer un sleep en bash (sans freezer le programme c)

a marqué ce sujet comme résolu.

Bonjour, je voudrais dans mon programme C lancer cette commande bash :

1
2
system("sleep 10; hostname");
printf("test");

je voudrais lancer hostname dans 10 seconde mais je voudrais que mon code en C execute les instruction suivante, actuelement avec cette commande mon programme C attends/freeze pendant 10 seconde.

je voudrais que printf("test") se lance et que mon programme en C se termine et se kill sans attendre 10 secondes

mon programme affiche "test" puis se termine/meurt et dans 10 seconde c’est la commande hostname qui est exécuté

j’ai essayé de tricher en mettant un & mais cela ne marche pas :

1
system("sleep 10; hostname &");
+0 -0

Le problème est que system induit un fork, exec puis un wait. En gros si l’exécution est dans un autre processus (un sous-shell), ton programme va attendre la fin de celui-ci avant de poursuivre.

Si tu veux éviter ce problème, le mieux est donc de le faire manuellement à coup de fork et exec sans l’attente.

Après je trouve cela sale de faire comme tu procèdes, la commande system doit être réservé à certains cas assez limités (en gros si tu veux justement faire la suite d’appels évoqués plus haut sans t’emmerder et que tu n’as pas d’alternatives).

Or justement, tu as une alternative, tu peux appeler sleep et gethostname en C directement ce qui évite de passer par un sous shell pour si peu. Ce sera plus portable, plus souple et joli. Tu peux dans ce cas même utiliser un thread au lieu de sous processus.

+3 -0

j’ai présenter ici un cas simple, mais le but après c’est de lancer d’autres commande bash, autres que hostname j’ai pas envie d’utiliser des threads (il faut rajouter des imports et j’aimerais éviter d’utiliser des threads dans mon environnement de production)

je maitrise pas très bien la commande fork, je vais peut etre dire une bêtise mais si le processus parent meurt, il me semble que sa tue aussi le fork (l’enfant) ?

je maitrise pas très bien la commande fork, je vais peut etre dire une bêtise mais si le processus parent meurt, il me semble que sa tue aussi le fork (l’enfant) ?

Non, ce raisonnement n’est valable que pour les thread, où si l’application principale meurt le reste des thread aussi.

+0 -0

en faite j’ai mal exprimé mon probleme. C’est pas une commande bash que je veut exécuter dans 10 secondes mais un script bash ou un binaire que je veut executer dans 10 secondes (meme si mon programme c a fini et ces killé entre temps)

j’ai essayé avec exec et fork, du coup j’ai utilisé la fonction execl mais mon programme en c ne lance plus les scripts

1
2
3
4
5
6
7
    int pid;
    pid = fork (); 
    if (pid == 0) {
        execl("/bin/sh", "sh", "-c", "sleep 10; /tmp/monscript.sh", NULL);
        execl("/bin/sh", "sh", "-c", "sleep 10; hostname", NULL);
        execl("/bin/sh", "sh", "-c", "sleep 10; firefox", NULL);
    }
+0 -0

Tu n’as pas saisi le fonctionnement de fork et exec.

Après ta fonction fork, tu as deux processus qui vont exécuter le même code. La seule différence sera la valeur de pid, le fils aura comme valeur 0 et le père le pid du fils dont à priori tu t’en fou.

Le père va poursuivre sa vie sans exécuter le if. Le fils non. Seulement, exec va remplacer tout le binaire du fils par celui que tu passes en paramètre. En gros ton processus fils va devenir le sous-shell que tu appelles. Et forcément le sous-shell ne connait pas et n’exécutera pas le code provenant du fils qui en instancies d’autres. Donc après l’exécution du sous-shell c’est fini, les autres ne seront jamais appelés.

Donc pour résoudre ton problème, plusieurs solutions :

  • Appeler minimum 3 fois fork, attention aux conditions pour avoir le comportement attendu ;
  • Utiliser un script ;
  • Trouver une autre méthode.
+0 -0

Si tu n’as jamais été confrontée à la programmation système, ce n’est pas étonnant d’être un peu perdu au début car il est vrai que c’est déroutant.

Pour que ton exemple fonctionne comme attendu, il faudrait faire ça:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    int pid;
    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; /tmp/monscript.sh", NULL);

    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; hostname", NULL);

    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; firefox", NULL);

Car comme j’ai expliqué, exec remplace ton binaire par un autre. Donc tu arrives à la situation suivante.

Le processus père voit ton if qui ne le concerne pas (il a le pid de son fils qui est forcément différent de 0). Dans ton cas il n’ira jamais dans le bloc avec tous tes exec.

Le fils entre dedans, il exécute la fonction exec qui remplace le binaire. La prochaine instruction du processus fils sera donc le sous-shell contenu dans l’appel d’exec. Mais pour le fils, tout ton code a maintenant disparu, les autres exec ont disparu car maintenant le processus fils est un sous-shell. Il n’est plus le binaire que tu as écrit.

Donc pour que tes différents exec fonctionnent, il faut faire autant de fork que d’appels à exec pour que tu aies toujours un processus fils capable d’exécuter le suivant.

C’est d’ailleurs ce que fait system, chaque appel à cette fonction implique (de manière cachée) un fork suivi d’un exec pour le fils et d’un wait pour le processus père appelant.

Pour l’histoire du script, oui, peut être fusionner tes appels dans un seul script serait plus simple. Je ne sais pas si ton deuxième code d’exemple reste juste une idée, mais si c’est globalement ce que tu veux faire, je suis quasiment convaincu qu’une situation plus élégante existe. Mais si tu n’expliques pas ton objectif concret on ne pourra rien te conseiller d’autres.

+1 -0

fork() ne crée pas des threads mais bien des processus à part entière

Un processus peut continuer de s’exécuter alors que son père s’est terminé mais alors lorsque le fils se termine il devient alors un processus zombie puisque son code de retour n’a pas été récupéré par le père.
Pour éviter ça, le père doit attendre le retour de ses fils avant de se terminer avec la fonction bloquante wait()

donc j’ajouterais tout de même à l’exemple de Renault

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    int pid;
    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; /tmp/monscript.sh", NULL);

    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; hostname", NULL);

    pid = fork (); 
    if (pid == 0)
        execl("/bin/sh", "sh", "-c", "sleep 10; firefox", NULL);

    wait(NULL);
    wait(NULL);
    wait(NULL);

et un petit exemple tout nul pour que tu ressentes comment fonctionne fork()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
  pid_t pid = 0;

  pid = fork();
  if(pid == 0)
  {
//    sleep(5);
    printf("fils termine\n");
  }
  else
  {
//    wait(NULL);
    printf("pere termine\n");
  }
  return EXIT_SUCCESS;
}

Amuses-toi à exécuter en commentant puis en décommentant ces appels, tu verra que l’ordre dans lequel apparaissent les phrases change complètement.
Tel quel, il n’est pas possible de prévoir, de s’assurer de l’ordre.
Avec sleep, le processus fils se fige pendant 5 secondes donc le père continue son exécution et se termine en premier, laissant le fils orphelin.
Avec wait, le père attend que son fils se termine donc tout se passe bien

PS : Au passage, je pense aussi qu’un script est plus approprié pour ce genre de tâche, mais puisque je ne saisi pas vraiment la finalité je ne peux pas être catégorique.

+1 -0

Bonjour,

En simple, si tu veux lancer un script bash qui lance une commande au bout d’un temps donné et que tu n’as pas besoin d’avoir un retour depuis ton programme, il suffit que tu lance celui-ci en nohup (pour qu’il ne soit pas arrêté si ton programme C meurt entre temps) et en backgound pour qu’il rende la main à ton programme C tout de suite.

merci pour votre aide. hélas dans mon cas c’est plus compliqué.

mon code est exécuté par un programme interne de la boite, et en faite quand le processus parent est tué il me tue aussi tous les processus enfant :colere:

il faudrait lancer un fork qui devienne indépendant par rapport au père, je suis en train de regarder du coté de setsid et de vfork, apparemment ces fonction pourrais me permette d’y arriver.

+0 -0

le nohup empêche ça justement. Dans la pratique, en C, il faudrait inhiber le signal HANGUP et incrémente de 5 la priorité du processus enfant (et ne pas oublier aussi de fermer ou rediriger les entrée/sortie standard coté enfant si tu es sur du tty)

on est bien d’accord que nohup c’est du bash ?

du coup je le lance comment le nohup ? j’ai repris le code d’avant mais j’ai le même probleme :

1
2
3
4
    int pid;
    pid = fork (); 
    if (pid == 0)
        execl("nohup ", "/bin/sh", "sh", "-c", "sleep 10; hostname", NULL);

Voici un exemple avec system:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main(){
        system("nohup bash -c 'date; sleep 10 ; date' &");
        printf("test\n");
}

`

le nohup va créer un fichier nohup.out pour les redirections tty ou tu auras les 2 lignes de date avec un écart de 10 secondes (qui correspond à la durée du sleep).

Ok, dans ce cas, il faut que tu inhibes le signal qu’il envoie pour "killer" les enfants…

Sinon, sais-tu comment le programme interne de ta boite arrête le parent ?

Et même comment il le lance ?

En fait la grande question c’est de savoir si tes commandes ne sont pas lancé dans une vm de type docker ou autre et que quand le parent est arrêter, il arrête la vm et donc les enfants avec et dans ce cas tu ne pourras pas faire grand chose…

Sinon, un petit détail qui peut avoir son importance (désolé pour l’oubli): le petit bout de code que je t’ai fourni fonctionne bien en local, mais il ne fonctionne pas correctement si par exemple tu l’utilises via ssh sans terminal. Exemple:

1
ssh -t localhost "$HOME/a.out"

La raison est liée au chargement de contexte du programme lancer par le programme C alors que celui-ci a déjà rendu l’âme. en rajoutant un petit sleep(1) juste avant qu’il ne quitte permet en général de résoudre le problème. Ton souci est peut-être à ce niveau…

PS: par souci de compréhension, le petit bout de C complété:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

main(){
        system("nohup bash -c 'date; sleep 10 ; date' >/tmp/nohup.out &");
        sleep(1);
        printf("test\n");
}

Ici, je redirige la sortie de la commande lancé par nohup (bash) car sinon le nohup.out sera créé dans le répertoire courant d’où est lancée la commande nohup.

Salut,

Un processus peut continuer de s’exécuter alors que son père s’est terminé mais alors lorsque le fils se termine il devient alors un processus zombie puisque son code de retour n’a pas été récupéré par le père.

leroivi

Un processus ne devient un « zombie » que si le père n’a pas terminé son exécution et que ce dernier ne récupère pas le statut du processus fils. Si le père se termine avant le fils, alors le fils est rattaché au processus init (’fin ça c’était avant systemd, je suppose que maintenant c’est lui qui récupère les processus orphelins).

Sinon, il est possible de « démoniser » un processus comme ci-dessous. Dans le cas où un fichier /tmp/out.log contenant la ligne Je suis un démon. n’est pas présent, c’est a priori que le processus fils a été stoppé lors du second fork() (et donc lors de la fin du premier fils et de son père).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int
main(void)
{
    pid_t pid;
    int fd;

    if (pid = fork(), pid < 0)
        err(EXIT_FAILURE, "fork");
    if (pid > 0)  {
        wait(NULL);
        return 0;
    }

    if (fd = open("/tmp/out.log", O_CREAT | O_TRUNC | O_RDWR, S_IWUSR | S_IRUSR), fd < 0)
        err(EXIT_FAILURE, "open");

    for (unsigned i = 0; i < 3; ++i)
        if (dup2(fd, i) < 0)
            err(EXIT_FAILURE, "dup2");

    if (setsid() < 0)
        err(EXIT_FAILURE, "setsid");
    if (pid = fork(), pid < 0)
        err(EXIT_FAILURE, "fork");
    if (pid > 0)
        return 0;

    if (chdir("/") < 0)
        err(EXIT_FAILURE, "chdir");

    fprintf(stderr, "Je suis un démon.\n");
    return 0;
}
+1 -0

je me suis renseigné de mon coté, alors non il n’a pas de vm ou de docker

comme je l’ai compris, l’applie kill tous les process qui appartienne au meme group

cela dépasse mes connaissance en C, j’ai regardé sur le net et tenté plusieurs trucs mais sans succès, mais en plus fork et de execl, il faut le lancer dans un autre group

j’ai actuelement ce code, l’applie fais comme avant, elle attends 2 secondes avant de faire rebooter la machine (j’ai pris la commande reboot au moins je suis sur que la commande s’execute… ou pas) mais la machine ne reboot pas, il semblerais que la aussi il soit tuer avant

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        pid_t process_id = 0;
        pid_t sid = 0;

        process_id = fork();
        if (process_id < 0)
        {
        printf("fork failed!\n");
        exit(1);
        }

        if (process_id > 0)
        {
        printf("process_id of child process %d \n", process_id);
        exit(0);
        }

        sid = setsid();
        if(sid < 0)
        {
        exit(1);
        }

        execl("/bin/sh", "sh", "-c", "sleep 2; reboot", NULL);
        return (0); 
+0 -0

Avec le bout de code que tu montres, tu termines l’exécution du père (retour de fork() supérieur à zéro) directement, il y a donc de forte chance pour que le fils n’aie pas le temps de faire quoi que ce soit avant d’être terminé. C’est pour cela que, dans le code que je tai donné, je faisais attendre le père avec wait() et que je duplicais le processus fils après l’appel à setsid() de sorte que le père ne termine son exécution qu’après qu’un processus appartenant à autre groupe (et une autre session) ne soit crée.

As-tu testé le code que je t’ai fourni sur ta machine ?

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte