Partage
  • Partager sur Facebook
  • Partager sur Twitter

[C - Système] Du côté des signaux

Approfondir un sujet, sans forcément passer par une question

    26 septembre 2011 à 22:38:40

    Yop,

    Aujourd'hui, en allant à droite à gauche sur les fora du SdZ, je me suis rendu compte que la majorité des threads, sinon tous, sont de la forme « Question - réponses ». Si c'est certes très intéressant, on ne va pas dire le contraire, je me suis dit qu'on pourrait peut-être essayer quelque chose de nouveau : approfondir un sujet... pour le plaisir de l'approfondir. Histoire que ceux qui ne connaissent pas apprennent, en découvrant une notion dont ils n'ont peut être jamais soupçonné l'existence, par exemple. Et puis, ça permet à ceux qui pensent connaître d'approfondir et ceux qui gèrent de communiquer leur savoir.

    Bref, pour commencer doucement mais surement, je me suis dit que les signaux ça pourrait être une bonne idée. Bon, on commence par du UNIX, du coup. Mais bon, j'avais pas d'autres idées.

    Donc, pour ceux qui ne connaissent pas du tout, les signaux, c'est un mécanisme assez bas niveau du C qui permet d'interrompre la marche normale d'un programme pour lui faire exécuter une fonction (on parle de handler) particulier. Je crois qu'il y a un tuto sur le SdZ qui en parle, sinon je vous invite à regarder un pdf que j'ai écris ( http://thomasletan.fr/systeme.pdf ) qui explique rapidement les bases.

    Bref, à partir de ces connaissances là, j'ai essayé de mettre en pratique, en codant un petit programme tout bête.

    #include <stdlib.h>
    #include <stdio.h>
    #include <signal.h>
    
    void test();
    
    void test()
    {
    	printf("\nTada\n");
    }
    
    int main()
    {
    	struct sigaction *ancienne = malloc(sizeof(struct sigaction));
    	/* Sans malloc : segfault */
    
    	struct sigaction* nouvelle = malloc(sizeof(struct sigaction));
    
    	nouvelle->sa_handler = test;
    	
    	sigaction(SIGINT, nouvelle, ancienne);
    
    	while(1);
    
    	free(ancienne);
    	free(nouvelle);
    
    	return 0;
    }
    


    Maintenant, comment l'utiliser ?

    Ouvrez deux terminaux. Dans le premier, compilez le programme :

    gcc -c signaux.c && gcc -o sig signaux.o


    Lancez le :

    ./sig


    Maintenant, depuis l'autre terminal :

    ps aux | grep sig


    A supposer que $pid contienne le pid de sig

    kill -2 $pid


    Vous devriez voir apparaître, si tout va bien, des "Tada" dans votre premier terminal \ o /

    Voilà, donc ça c'était une petite intro, maintenant, surtout, n'hésitez pas à y aller de vos commentaires (c'est le premier code que j'écris sur les signaux, si ça se trouve, il est très moche :') ). Allez y de vos expériences, aussi, si vous avez pu utiliser ce concept dans un projet concret, perso ça m'intéresse.

    Voilà, en espérant que ce concept plaise et que ça fasse pas un flop. Si c'est le cas... tant pis x)
    • Partager sur Facebook
    • Partager sur Twitter
    Small is Beautiful — E.F. Schumacher | Blog (fr)
    Anonyme
      27 septembre 2011 à 3:04:24

      Bah pour moi, les gens peuvent approfondir par eux même et cherchant sur le net ou en lisant les nouveaux tutos qui sortent régulièrement, sachant que l’intérêt d'un tuto par rapport a un post forum est que la source est contrôlée (Si tu préfère: Il y a moins de chance de lire n'importe quoi). Et quand ils rencontrent un problème, on est la.

      Cependant, une initiative est toujours bonne a prendre ;)

      Pour ton programme, je dirais juste que l'attente active c'est le mal.
      Un wait très long ferait très bien l'affaire.

      Pour ton PDF, je te dirais de repasser dessus, il y a surement pas mal de fautes d'orthographes (J'ai survole, j'ai lu "zombi", et "On peut faire exécuter un nouveau programme à un pxecution." )
      Il te manque également beaucoup de signaux (SIGFPE, SIGBUS, SIGHUP... J'ai pas fait la liste, mais man 3/7 signal)


      Un des projets de première année a (censuré) est un 'minitalk'. Le principe est simple:
      Avoir 2 programmes, un serveur et un client, qui communiquent. On lance le client comme suis:
      ./client "Un message texte"
      Et le serveur doit écrire la chaîne de caractère sur le terminal.
      On n'a le droit d'utiliser que SIGUSR1 et SIGUSR2 (Et seulement ceux propres au système, je vous vois déjà venir les petits malins du fond!) et signal (car sigaction a un système de queueing, donc on perd tout l’intérêt).
      Dans mon souvenir c’était assez chiant de le faire bien, sachant qu'on ne peut utiliser que 2 signaux, donc 0 et 1, et qu'il fallait bien se renseigner sur la façon dont c'est géré (si tu le fais trop vite, et sans vérification, on perd des infos sur un texte assez long...).
      • Partager sur Facebook
      • Partager sur Twitter
        27 septembre 2011 à 6:51:32

        C'est très possible pour les fautes... x) *retravaillera*

        Je suis bien d'accord, l'attente active c'est pas le top. Au début, je faisais un sleep(1000); le souci étant que SIGINT, le signal utilisé, fait une demande d'interruption, or une interruption interrompt le sleep, du coup tu te retrouves avec un Tada qui entraîne la fin du programme.

        Sympa l'exercice, je regarderai à l'occasion : o
        • Partager sur Facebook
        • Partager sur Twitter
        Small is Beautiful — E.F. Schumacher | Blog (fr)
          27 septembre 2011 à 9:36:23

          Il y a quelques trucs qui me dérangent dans ton code qui n'ont pas grand chose à voir avec les signaux :

          - Pourquoi ne pas allouer tes structures sigaction sur la pile plutôt que d'allouer sur le tas ?

          - Ici, mettre le prototype de la fonction test juste avant l'implémentation de la fonction n'est pas utile.

          - Lorsqu'une fonction ne prends aucuns paramètre, on préfère le préciser avec void. Car une liste de paramètre vide signifie en fait que la fonction peut prendre un nombre variable de paramètre, et le compilateur ne t'avertiras pas si tu lui passe plus d'un paramètre.

          - Pourquoi ne pas mettre NULL en troisième paramètre de sigaction, vu que tu ne l'utilise pas ensuite ?

          Sinon, l'initiative est intéressante ;) .
          • Partager sur Facebook
          • Partager sur Twitter
            27 septembre 2011 à 10:17:05

            Salut,

            J'ajouterais que tu ne mets pas les champs sa_mask et sa_flags de la structure sigaction à zéro, ce qui peut réserver des surprises (leur valeur étant indéterminée après l'allocation dynamique). Aussi, il faut définir la macroconstante _POSIX_C_SOURCE (ou _POSIX_SOURCE ou _XOPEN_SOURCE) pour disposer du prototype de sigaction. Un petit exemple:


            #define _POSIX_C_SOURCE 1
            #include <signal.h>
            #include <stdlib.h>
            #include <unistd.h>
            
            
            int
            main(void)
            {
            	struct sigaction act;
            
            	act.sa_handler = SIG_IGN;
            	sigemptyset(&act.sa_mask);
            	act.sa_flags = 0;
            
            	sigaction(SIGINT, &act, NULL);
            	pause();
            	return EXIT_SUCCESS;
            }
            

            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              27 septembre 2011 à 12:22:21

              Je trouve ton idée plutôt bonne, c'est sympa. :)
              • Partager sur Facebook
              • Partager sur Twitter
                27 septembre 2011 à 13:42:44

                Je pense que pour une première approche, il est préférable de ne pas parler de sigaction, mais simplement des fonctions signal et kill, et n'introduire sigaction qu'en cas de multi-clients par exemple.

                boris.p> Pourquoi ne pas utiliser simplement la fonction pause qui remplit très bien ce travail, plutôt que de faire un wait ?
                (Et je rajouterai que pour le projet dont tu parles, sigaction était autorisé, justement en cas de multi-clients)
                • Partager sur Facebook
                • Partager sur Twitter
                  27 septembre 2011 à 13:59:46

                  Pour connaître le pid d'un programme, il suffit d'appeler la fonction getpid() et d'afficher ce qu'elle retourne.
                  • Partager sur Facebook
                  • Partager sur Twitter
                  Pour ceux qui souhaiteraient apprendre à développer en Rust, un tuto en français est dispo ici. Pour voir mes projets : github.
                    27 septembre 2011 à 16:57:05

                    Salut,

                    Citation

                    Aujourd'hui, en allant à droite à gauche sur les fora du SdZ, je me suis rendu compte que la majorité des threads, sinon tous, sont de la forme « Question - réponses ». Si c'est certes très intéressant, on ne va pas dire le contraire, je me suis dit qu'on pourrait peut-être essayer quelque chose de nouveau : approfondir un sujet... pour le plaisir de l'approfondir. Histoire que ceux qui ne connaissent pas apprennent, en découvrant une notion dont ils n'ont peut être jamais soupçonné l'existence, par exemple. Et puis, ça permet à ceux qui pensent connaître d'approfondir et ceux qui gèrent de communiquer leur savoir.



                    Pour commencer, je trouve l'idée louable, bien que cela ne rejoint pas vraiment le principe de base d'un forum : on se rapproche plus du développement d'un sujet, mais c'est très intéressant, d'autant plus que le sujet me plaît vraiment, moi qui me suis mis à la programmation système il y a pas mal de temps déjà.

                    Pour revenir au sujet, je vais donner mon avis sur la façon dont les choses ont été abordées.

                    Citation

                    Bref, pour commencer doucement mais surement, je me suis dit que les signaux ça pourrait être une bonne idée. Bon, on commence par du UNIX, du coup. Mais bon, j'avais pas d'autres idées.



                    C'est une très bonne idée, détrompe-toi ! Toutefois, cela pose plusieurs problèmes :
                    - la majorité des visiteurs de ce forum sont sous Windows (personnellement, cela ne me pose pas de problème ; je trouve que c'est mieux que de s'embarquer dans des codes moches de l'API Win32...
                    - deuxièmement, les signaux, c'est un sujet qu'on aborde habituellement après une étude détaillée des processus sous Unix. Tu parles de processus, mais peut-être que certains ont presque oublié ce que c'est. Quelques liens vers des ressources extérieures pourraient être utiles : processus et signaux sont difficilement dissociables.

                    Citation

                    Donc, pour ceux qui ne connaissent pas du tout, les signaux, c'est un mécanisme assez bas niveau du C qui permet d'interrompre la marche normale d'un programme pour lui faire exécuter une fonction (on parle de handler) particulier. Je crois qu'il y a un tuto sur le SdZ qui en parle, sinon je vous invite à regarder un pdf que j'ai écris ( http://thomasletan.fr/systeme.pdf ) qui explique rapidement les bases.



                    Il serait intéressant de voir comment le système gère les signaux.
                    Tout d'abord, on pourrait mentionner les champs du PCB (Process Control Block) qui renvoient aux signaux :

                    • signal, une variable de type sigset_t (ce type est soit un entier, soit une structure suivant le système ; généralement c'est une structure contenant deux entiers de 32 bits : un bit par signal - il y en a 64 sous Linux 2.2 - qui vaut 0 si le signal n'a pas été reçu ou 1 le cas contraire) ;
                    • blocked, une variable de sigset_t qui stocke les signaux bloqués ;
                    • sigpending, qui vaut 1 si il y a un signal non bloqué en attente, ou 0 sinon ;
                    • gsig, un pointeur de structure signal_struct qui contient, entres autres, la définition de l'action qui est associée à chaque signal.


                    Ensuite, on peut diviser le sujet en deux parties :
                    • l'envoi d'un signal ;
                    • la réception d'un signal.


                    Envoi d'un signal



                    Un signal peut avoir deux causes :

                    • l'utilisation de la primitive kill ;
                    • une erreur grave lors de l'exécution du processus.


                    Pour envoyer un signal, le noyau exécute la routine nommée seng_sig_info, qui positionne à 1 le bit correspondant au signal dans le champ signal du PCB.
                    Le signal est délivré, on qualifie le signal ainsi envoyé de signal pendant, c'est-à-dire que le processus a reçu le signal mais qu'il ne l'a pas encore traité.

                    Réception d'un signal



                    Lorsqu'un processus quitte le mode noyau pour passer en mode utilisateur, la routine do_signal traite les signaux pendants du processus. Il y a trois possibilités de réaction de la routine :

                    • soit elle ignore le signal ;
                    • soit elle exécute l'action par défaut (abandon, abandon avec fichier core, signal ignoré, processus stoppé, processus redémarré) qui est attribuée à chaque type signal ;
                    • soit elle exécute une fonction spécifique (c'est ce que fait ton code).


                    Voilà pour un petit intermède « culture générale » qui n'est pas indispensable pour les signaux, mais j'ai trouvé que puisqu'il fallait discuter du sujet, autant le faire de manière constructive, non ? ;)

                    Pour revenir à ton code, je trouve que tu abordes le sujet de manière plutôt originale : on apprend d'abord à utiliser kill, puis à la rigueur le blocage des signaux.
                    Mais bon, peut-être que tu cherchais à nous montrer quelque chose de plus spécifique...

                    Ensuite, pour ceux qui se demandent pourquoi ne pas utiliser signal() qui est C standard alors que sigaction() n'est que Posix, voici une petite réponse piochée sur stackoverflow.com :

                    Citation

                    Use sigaction() unless you've got very compelling reasons not to do so.

                    The signal() interface has antiquity (and hence availability) in its favour, and it is defined in the C standard. Nevertheless, it has a number of undesirable characteristics that sigaction() avoids - unless you use the flags explicitly added to sigaction() to allow it to faithfully simulate the old signal() behaviour.

                    • The signal() function does not block other signals from arriving while the current handler is executing; sigaction() can block other signals until the current handler returns.
                    • The signal() function resets the signal action back to SIG_DFL (default) for almost all signals. This means that the signal() handler must reinstall itself as its first action. It also opens up a window of vulnerability between the time when the signal is detected and the handler is reinstalled during which if a second instance of the signal arrives, the default behaviour (usually terminate, sometimes with prejudice - aka core dump) occurs.



                    These are generally good reasons for using sigaction() instead of signal(). However, the interface of sigaction() is undeniably more fiddly.

                    Whichever of the two you use, do not be tempted by the alternative signal interfaces such as sighold(), sigpause(), sigignore() and sigrelse(). They are nominally alternatives to sigaction(), but they are only barely standardized and are present in POSIX for backwards compatibility rather than for serious use. Note that the POSIX standard says their behaviour in multi-threaded programs is undefined.

                    (Multi-threaded programs and signals is a whole other complicated story. AFAIK, both signal() and sigaction() are OK in multi-threaded applications.)



                    Pour ton code, je rejoins les remarques de Tosh, à savoir :

                    Citation


                    Il y a quelques trucs qui me dérangent dans ton code qui n'ont pas grand chose à voir avec les signaux :

                    - Pourquoi ne pas allouer tes structures sigaction sur la pile plutôt que d'allouer sur le tas ?

                    - Ici, mettre le prototype de la fonction test juste avant l'implémentation de la fonction n'est pas utile.

                    - Lorsqu'une fonction ne prends aucuns paramètre, on préfère le préciser avec void. Car une liste de paramètre vide signifie en fait que la fonction peut prendre un nombre variable de paramètre, et le compilateur ne t'avertiras pas si tu lui passe plus d'un paramètre.

                    - Pourquoi ne pas mettre NULL en troisième paramètre de sigaction, vu que tu ne l'utilise pas ensuite ?



                    Ainsi que celle de Taurre :

                    Citation

                    Salut,

                    J'ajouterais que tu ne mets pas les champs sa_mask et sa_flags de la structure sigaction à zéro, ce qui peut réserver des surprises (leur valeur étant indéterminée après l'allocation dynamique).



                    Il aurait pu être intéressant de faire ça dans un seul programme qui provoque un signal au processus (un SEGFAULT est si facile à obtenir...). J'ai fait ça, à l'arrache :

                    #include <stdio.h>
                    #include <stdlib.h>
                    #include <errno.h>
                    #include <unistd.h>
                    #include <sys/types.h>
                    #include <signal.h>
                    
                    /* On déclare la variable en globale à l'arrache */
                    pid_t pid_fils;
                    
                    /* Handler */
                    void mon_handler(int signal)
                    {
                        fprintf(stdout, "Signal %d reçu.\n", signal);
                        /* On tue le processus fils */
                        kill(SIGKILL, pid_fils);
                        exit(EXIT_SUCCESS);
                    }
                    
                    int main(void)
                    {
                        /* Un petit char pour faire déborder la boucle */
                        char s[4];
                        int i;
                        struct sigaction action;
                    
                        /* On initialise l'action */
                        action.sa_flags = 0;
                        action.sa_handler = mon_handler;
                        sigemptyset(&action.sa_mask);
                    
                        /* On met en place le handler */
                        sigaction(SIGSEGV, &action, 0);
                    
                        /* On crée un nouveau processus */
                        do {
                    	pid_fils = fork();
                        } while (pid_fils == -1 && errno == EAGAIN);
                    
                        if (pid_fils == -1) {
                    	perror("fork");
                    	return EXIT_FAILURE;
                        } else if (pid_fils == 0) {
                    	/* On le fait attendre un peu, histoire qu'il puisse se faire tuer */
                    	sleep(10000);
                        } else {
                      	/* Hop, une boucle infinie bien moche pour SEGFAULT */
                    	for (i = 0 ; ; i++) {
                    	    s[i] = i;
                       	}
                        }
                    
                        return EXIT_SUCCESS;
                    }
                    


                    Voilà, mon monologue est fini, et j'espère que ce principe sera repris, c'est très intéressant comme principe...

                    PS : Pour mon 800ème message sur le forum, il fallait bien enfin un truc intéressant ! :lol:
                    • Partager sur Facebook
                    • Partager sur Twitter
                    Staff désormais retraité.
                      29 septembre 2011 à 18:29:47

                      Alors, je vous ai cloué le bec ? ^^
                      • Partager sur Facebook
                      • Partager sur Twitter
                      Staff désormais retraité.
                        29 septembre 2011 à 19:21:30

                        J'ai eu pas mal d'occupations, je comptais reprendre un peu tout ce qui avait été dit et rajouter quelques notions vues en TP, par dessus le marché.

                        Si le topic avance bien, on pourrait limite envisager de le compiler en tutoriel : o
                        • Partager sur Facebook
                        • Partager sur Twitter
                        Small is Beautiful — E.F. Schumacher | Blog (fr)
                          29 septembre 2011 à 19:26:20

                          Ah zut, les signaux sont prévus pour un chapitre de la programmation système en C sous Unix. :lol:
                          • Partager sur Facebook
                          • Partager sur Twitter
                          Staff désormais retraité.
                            29 septembre 2011 à 23:36:56

                            Rien ne t'empêche de te servir de ce topic comme source supplémentaire *bim*
                            • Partager sur Facebook
                            • Partager sur Twitter
                            Small is Beautiful — E.F. Schumacher | Blog (fr)

                            [C - Système] Du côté des signaux

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown