IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

POSIX C Discussion :

Fin de programme propre après interception de SIGINT


Sujet :

POSIX C

  1. #1
    Nouveau membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 22
    Points : 30
    Points
    30
    Par défaut Fin de programme propre après interception de SIGINT
    Bonjour,

    Je me demandais comment traiter proprement une fin de programme lors de l'interception d'un signal SIGINT par un gestionnaire de signaux. Prenons un programme réalisant deux allocations mémoire puis qui boucle sur un while(1) ; Lors de la réception du signal SIGINT une fonction void handler(int) ; est appelée. Comment faire pour que le programme libère la mémoire allouée avant de quitter avec un appel à la fonction exit ? Faut-il utiliser des variables globales pour connaître les adresses des pointeurs à libérer ?

    Je cherche la manière la plus propre de faire mais je ne vois pas trop. J'ai également déjà lu qu'on devait mettre le moins de code possible dans le handler ce qui à priori dans ma méthode n'est pas le cas. Voilà, donc comment faire ? Je m'en remet à vos connaissances.

    Quoiqu'il en soit merci d'avance pour vos conseils.

  2. #2
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Salut

    Si tu quittes ton programme, la mémoire allouée avec malloc() est libérée automatiquement..... puisque le programme et son espace mémoire n'existent plus ^^ Je pense que tu n'as pas besoin de t'embêter avec ça : tu termines ton programme, c'est tout.

    En revanche, tu peux avoir d'autres ressources à libérer : connexion ouverte à une base de donnée, fichier ouvert, etc.

  3. #3
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 186
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 186
    Points : 17 126
    Points
    17 126
    Par défaut
    Juste pour préciser, sur certains systèmes très anciens, ou très particulier, la mémoire allouée par un malloc() n'est pas libérée automatiquement à la mort d'un programme.

    Mais si c'était ton cas, tu le saurais.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  4. #4
    Nouveau membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 22
    Points : 30
    Points
    30
    Par défaut
    Merci pour vos réponses.
    Je sais en effet que sur les systèmes récents la mémoire est libérée automatiquement mais comme tu le dis il peut s'agir de fermer des descripteurs de fichiers ou autre. Mon exemple était peut-être mal choisi mais j'ai voulu faire simple et le problème reste le même : comment fermer ou libérer la mémoire de manière propre à l'interception d'un SIGINT ?

  5. #5
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 360
    Points : 23 600
    Points
    23 600
    Par défaut
    Bonjour,

    Citation Envoyé par Dliw0 Voir le message
    Je sais en effet que sur les systèmes récents la mémoire est libérée automatiquement mais comme tu le dis il peut s'agir de fermer des descripteurs de fichiers ou autre. Mon exemple était peut-être mal choisi mais j'ai voulu faire simple et le problème reste le même : comment fermer ou libérer la mémoire de manière propre à l'interception d'un SIGINT ?
    C'est une question pertinente car c'est un problème de conception initiale, qui devrait occuper tous les développeurs dès les premières lignes de leur programme. En gros, il faut écrire ton programme tel que tu le ferais si c'était le système d'exploitation lui-même que tu écrivais.

    D'une manière générale, on essaie d'éviter de recourir aux variables globales pour un certain nombre de raisons, mais c'est effectivement typiquement dans ce genre de cas qu'on va les trouver. En fait, on va surtout utiliser une fonction qui ne conservera qu'un pointeur dans une variable statique et qui nous enverra son contenu sur demande, pointeur vers une structure dont l'espace est alloué avec un malloc et qui, elle, va contenir toutes les infos que l'on a besoin de partager.

    Mais la manière la plus propre, dans ce cas précis, de gérer la chose reste de n'en faire toujours que le minimum dans les handlers de signaux, qui doivent rendre la main le plus vite possible. La bonne réponse, dans ton cas, consiste donc à faire en sorte que ton gestionnaire de signal place un flag quelque part, qui soit interprété ensuite comme une demande de sortie ordinaire par ton programme. Ainsi, tu es sûr que le workflow de ton programme ira quand même jusqu'à son terme, ce qui est le meilleur moyen de ne rien oublier.

    Tu peux également utiliser atexit() pour appeler des fonctions de nettoyage dès que ton programme se termine. Attention : c'est valable pour les sorties en conditions normales avec exit() ou lorsque que l'on sort de main(), mais pas sur _exit() ou lorsque tu reçois un signal tueur non pris en charge. En revanche, tu peux invoquer plusieurs fois cette fonction, ce qui te permet de la placer partout où tu fais des allocations. Par contre, à terme, il vaut mieux s'en passer et écrire un programme qui soit naturellement conçu pour passer par les phases de sortie.

    À noter enfin qu'il faut également prendre en compte la sémantique des signaux eux-mêmes (pas toujours très claire, d'ailleurs) : SIGINT demande au programme de s'interrompre et SIGTERM de prendre fin normalement. Ça veut dire que dans le premier cas, on ne souhaite pas nécessairement mener à terme l'opération en cours (ce qui ne veut pas dire qu'il ne faut pas sortir proprement). Dans le second, on termine ce que l'on est en train de faire, mais sans entamer une nouvelle tâche s'il en reste. :-)

    Empiriquement, on peut se laisser une à deux secondes de grâce pour finir ce que l'on est en train de faire quand on reçoit SIGTERM mais guère plus, car lorsque l'on fait un shutdown ou un redémarrage d'un système Unix lors d'un init 0 ou d'un init 6, c'est typiquement ce délai que nous laisse le système : il envoie SIGTERM à tout le monde, attend quelques secondes, puis fais un SIGKILL sur ce qui reste.

  6. #6
    Nouveau membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 22
    Points : 30
    Points
    30
    Par défaut
    Bonjour Obsidian et merci pour cette réponse très enrichissante.

    La solution d'utiliser une structure dédiée aux éléments partagés dans le programme est en effet bien pratique et je m'en vais de ce pas la mettre en oeuvre.
    J'aurai cependant une petite question concernant la sortie ordinaire d'un programme :
    Citation Envoyé par Obsidian
    La bonne réponse, dans ton cas, consiste donc à faire en sorte que ton gestionnaire de signal place un flag quelque part, qui soit interprété ensuite comme une demande de sortie ordinaire par ton programme.
    Dans l'exemple que j'ai formulé dans mon premier message cela reviendrai grosso-modo à remplacer (en considérant pour faire simple une variable globale continuer initialisée à 1) le while(1) ; par while(continuer) ; le handler ayant donc pour simple fonction de mettre la valeur de continuer à zéro, c'est bien ça ? Mais prenons le cas d'un programme travaillant avec des tubes nommés réalisant une ouverture bloquante dans la boucle. L'utilisation de cette méthode propre est donc impossible et du coup pas d'autre solution que d'appeler un atexit() ?

    Autre petite chose au risque de me faire taper sur les doigts : bien que je ne l'ai jamais utilisée et que je ne le souhaite pas, la fumeuse instruction goto ne serait-elle pas utile dans ce dernier cas (sûrement une méthode très sale puisqu'elle casse fortement l'exécution structurelle des instructions mais qui aurai le mérite de fonctionner, et donc de terminer le programme de manière ordinaire, non ?)

  7. #7
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 360
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 360
    Points : 23 600
    Points
    23 600
    Par défaut
    Citation Envoyé par Dliw0 Voir le message
    Dans l'exemple que j'ai formulé dans mon premier message cela reviendrai grosso-modo à remplacer (en considérant pour faire simple une variable globale continuer initialisée à 1) le while(1) ; par while(continuer) ; le handler ayant donc pour simple fonction de mettre la valeur de continuer à zéro, c'est bien ça ?
    Dans le principe, oui. Cependant, ce n'est pas une manière universelle de faire. L'idée générale consiste à placer certaines informations au bon endroit de façon à ce que le programme principal prenne la décision de sortir. C'est le cas lorsque, par exemple, l'utilisateur saisit « quit » sur la ligne de commande, mais également s'il clique sur la croix de la fenêtre de l'application ou, ici, si le handler du signal change l'état d'un flag donné.

    Cela permet aussi de se pencher sur la façon dont cette sortie est effectuée en temps normal dans son programme. Souvent, c'est à l'aide d'un gros « exit() ». Ce n'est pas un mal en soi si l'on sait ce que l'on fait, et l'utilisation de de « atexit() » permet justement de ne rien oublier et de distinguer ce cas de figure du reste de l'algorithme. Par contre, si c'est pour se sortir d'une impasse, cela ne vaut pas mieux qu'un goto.

    Mais prenons le cas d'un programme travaillant avec des tubes nommés réalisant une ouverture bloquante dans la boucle. L'utilisation de cette méthode propre est donc impossible et du coup pas d'autre solution que d'appeler un atexit() ?
    Bonne question :

    1. La réception d'un signal va toujours débloquer un appel en attente. Y compris sleep() ! C'est un cas de figure prévu par le système et il appartient donc au programmeur de le prendre en charge (même s'il est possible dans certains cas de demander à reprendre l'attente… si c'est possible) ;
    2. Que ferais-tu si tu devais surveiller non pas un mais deux tubes nommés, voire plus ? C'est spécialement intéressant lorsque tes tubes sont en fait des sockets et que ton application est un serveur à l'écoute de plusieurs clients. Il te faut un appel système pour cela : http://man.developpez.com/man2/select.2.php et ses dérivés : pselect(), poll() et ppoll().



    Autre petite chose au risque de me faire taper sur les doigts : bien que je ne l'ai jamais utilisée et que je ne le souhaite pas, la fumeuse instruction goto ne serait-elle pas utile dans ce dernier cas (sûrement une méthode très sale puisqu'elle casse fortement l'exécution structurelle des instructions mais qui aurai le mérite de fonctionner, et donc de terminer le programme de manière ordinaire, non ?)
    Si. Mais pas forcément de la manière dont tu l'envisages.

    D'abord, un goto ne te permet pas de sauter d'une fonction à une autre, ne serait-ce que parce que l'étiquette de la destination est forcément définie à l'intérieur du bloc d'une fonction et que sa portée est donc limitée à celui-ci. Ensuite, l'un des principaux problèmes du goto est que, par définition, il fait un saut arbitraire d'une position à une autre et qu'il laisse donc la pile dans l'état où elle l'était déjà.

    La norme précise notamment ceci :

    Citation Envoyé par C99 n1256
    6.8.6.1 The goto statement
    Constraints
    1
    The identifier in a goto statement shall name a label located somewhere in the enclosing
    function. A goto statement shall not jump from outside the scope of an identifier having
    a variably modified type to inside the scope of that identifier.

    Ça veut dire que tu ne peux pas sauter de l'extérieur vers l'intérieur d'un bloc qui définit et utilise un identifiant pouvant être variable. Autrement dit, tu ne peux pas faire ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    int main (void)
    {
        goto dedans;
     
        if (1)
        {
            int tab[200];
     
            dedans:
            tab[100] = 1;
        }
     
        return 0;
    }

    … et ceci parce le tableau « tab[200] » est réservé dans la pile au moment où tu entres dans le bloc « if ». Si tu n'es pas passé par cette étape, le tableau n'existera pas encore au moment où tu voudras l'utiliser. Note que le problème est exactement le même dans l'autre sens : si tu sors du bloc artificiellement avec un goto, tu ne libères pas le tableau !

    En fait, le compilateur C va quand même retomber sur ses pattes car il va généralement directement réserver dans la pile tout l'espace dont il aura besoin tout au long de la fonction. Sauf dans le cas des VLA, mais des tests avec GCC montrent que, dans ce cas, le compilateur reconnaît le cas et sauve auparavant le pointeur de pile dans un registre (autre que [ER]BP déjà utilisé à cette fin lors de l'établissement du cadre de pile).

    Pour éviter ce genre de problème et pouvoir sauter d'une fonction à une autre, on utilise setjmp() et longjmp. Le premier appel sauvegarde l'état de pile à un moment donné et le second le rétablit et ramène l'exécution juste après le setjmp initial. C'est déjà ce qui se passe lorsque tu quittes une fonction avec return avant son terme : le compilateur replace BP dans le pointeur de pile et, de là, est capable de retrouver et dépiler l'adresse de retour. setjmp et longjmp vont faire à peu près la même chose et en sautant vers l'adresse explicitement sauvegardée plutôt que celle empilée.

    Cela dit, ces fonctions sont déconseillés pour les mêmes raisons que le goto : ça brise l'enchaînement des fonctions, c'est difficile à suivre et à débuguer et ça pose exactement les mêmes problèmes que ceux que tu cites au départ : on n'est pas sûr de libérer les ressources qui auront été allouées entre temps.

    Elles ont donc deux intérêts principaux : permettre la mise en place d'un système « d'exceptions » permettant de remonter jusqu'à un point donné où l'on peut la rattraper et, par extension, établir une sorte de checkpoint où l'on peut reprendre lorsque quelque chose de grave et d'imprévu se produit, par exemple une corruption de pile. À l'instar des bornes « SAVE » que l'on trouve parfois dans les jeux d'aventure, cela permet de revenir à un état en principe connu plutôt que de laisser le programme planter complètement et, de là, tenter de sauver ce qui peut l'être et préparer une sortie prématurée mais propre.

  8. #8
    Nouveau membre du Club
    Profil pro
    Étudiant
    Inscrit en
    Mars 2013
    Messages
    22
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Mars 2013
    Messages : 22
    Points : 30
    Points
    30
    Par défaut
    Eh bien merci beaucoup pour tous ces éclaircissements.
    Je commence justement à travailler sur des programmes client / serveur et ces informations me seront bien utiles.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Runtime.getRuntime().exec exécute le processus après la fin du programme Java
    Par scalande dans le forum API standards et tierces
    Réponses: 4
    Dernier message: 14/02/2012, 18h56
  2. Erreur bizarre APRES la fin du programme
    Par Papy214 dans le forum C#
    Réponses: 14
    Dernier message: 17/03/2009, 11h37
  3. Réponses: 6
    Dernier message: 22/11/2007, 23h45
  4. [LG] Problème avec la Fonction ReadLn en fin de programme
    Par killermano dans le forum Langage
    Réponses: 6
    Dernier message: 23/07/2005, 16h16
  5. Fin de programme dans une procédure
    Par Sinclair dans le forum Langage
    Réponses: 13
    Dernier message: 29/11/2002, 23h30

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo