Journal SIGUSR1, SIGUSR2,..., SIGUSR_N ?

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
12
10
mai
2023

Demati'Nal,

À la lecture de signal(7), on se rend compte que deux signaux sont réservés pour les signaux utilisateurs SIGUSR1 et SIGUSR2. Deux, c'est peu. Je vous propose donc cette technique qu'on baptisera le signal knocking par analogie au Port_knocking

#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int signalvalue = 0;

void sigusr1handler(int signum);
void sigusr2handler(int signum);

void sigusr1handler(int signum) {
  ++signalvalue;
}
void sigusr2handler(int signum) {
  switch(signalvalue) {
    case 0: puts("action 0"); break;
    case 1: puts("action 1"); break;
    case 2: puts("action 2"); break;
    case 3: puts("action 3"); break;
  }
}

int main() {
  signal(SIGUSR1, sigusr1handler);
  signal(SIGUSR2, sigusr2handler);
  while(1) sleep(10);
  return 0;
}

Et voilà, on peut faire les traitements qu'on veut suivant la séquence de SIGUSR qu'on envoie :-)

  • # Séquence

    Posté par  (site web personnel, Mastodon) . Évalué à 1. Dernière modification le 10 mai 2023 à 17:24.

    Donc si je comprends bien, une séquence sera composée d'un nombre caractéristique de SIGUSR1, et terminée par un SIGUSR2, c'est bien ça ?

    Pas bête.

    On pourrait aussi imaginer un dictionnaire de séquences non préfixé (aucun mot du dictionnaire n'étant le préfixe de l'autre) pour pouvoir réduire un peu en moyenne la taille des séquences en alternant SIGUSR1 et SIGUSR2.

    Un gentil du net

    • [^] # Re: Séquence

      Posté par  (Mastodon) . Évalué à 9.

      Du coup faudrait juste remettre à zéro le compteur de séquence dans sigusr2handler() pour pouvoir enchaîner des actions différentes.

      En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

    • [^] # Re: Séquence

      Posté par  (site web personnel) . Évalué à 9.

      Ou sinon, on pourrait imaginer une séquence unique de début de trame, une séquence unique de fin de trame, et au milieu de ces deux séquences, sigusr1 signifierai un bit à 0 et siguser2 un bit à 1.

      Bien sur, il faudrait faire des traitements sur les signaux du milieu pour éviter que les séquences de début et fin ne se retrouvent dans les données.

      En fait, on peut refaire toute la théorie de traitement du signal.

    • [^] # Re: Séquence

      Posté par  . Évalué à 4. Dernière modification le 11 mai 2023 à 19:09.

      On pourrait représenter SIGUSR1 par un point et SIGUSR2 par un trait, ainsi on pourrait avoir ce genre de tableau :
      Titre de l'image

      • [^] # Re: Séquence

        Posté par  (Mastodon) . Évalué à 5.

        De ce que j'ai compris, non ça ne marcherait pas. Il te faut un moyen de séparer les caractères. En morse c'est fait par le temps (pause entre les caractères et les mots). Or dans le cas des SIGUSR il n'y a aucune garantie de temps, ni même d'ordre.

        En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

        • [^] # Re: Séquence

          Posté par  . Évalué à 1.

          Si un caractère est égale à un signal géré par ton programme, ça ferait déjà une 40 aine de possibilités d'après le tableau. De la à écrire des phrases avec des signaux…

    • [^] # Re: Séquence

      Posté par  (Mastodon) . Évalué à 3.

      euh…si tu en arrives à ce genre de besoin, utilise un socket ou d-bus.

  • # Ça marche pas.

    Posté par  . Évalué à 7.

       Queueing and delivery semantics for standard signals
           If multiple standard signals are pending for a process, the
           order in which the signals are delivered is unspecified.
    
           Standard signals do not queue.  If multiple instances of a
           standard signal are generated while that signal is blocked,
           then only one instance of the signal is marked as pending (and
           the signal will be delivered just once when it is unblocked).
           In the case where a standard signal is already pending, the
           siginfo_t structure (see sigaction(2)) associated with that
           signal is not overwritten on arrival of subsequent instances of
           the same signal.  Thus, the process will receive the
           information associated with the first instance of the signal.
    

    La bonne méthode :
    1. Avoir un thread à demeure qui se met en lecture sur une FIFO.
    2. Ou déclencher la lecture de la FIFO à réception du signal (évite de créer un thread dédié).

    • [^] # Re: Ça marche pas.

      Posté par  . Évalué à 4. Dernière modification le 10 mai 2023 à 18:09.

      PS : bien faire attention à la cinématique, on se retrouve dans le cas d’une communication multi-thread. read/write est atomique en-dessous de PIPE_BUF, pas atomique au-dessus (attention aux blagues si plusieurs process veulent écrire dans la même FIFO), les appels sont bloquants (sauf précision contraire), bien gérer leurs codes retour.

      Ça peut vite être assez tricky.

    • [^] # Re: Ça marche pas.

      Posté par  (site web personnel) . Évalué à 2.

      Si le premier SIGUSR1 est géré avant réception du deuxième SIGUSR1, c'est ok, non ? EN tout cas c'est le comportement que j'observe…

      • [^] # Re: Ça marche pas.

        Posté par  . Évalué à 2.

        Si il n’y a pas de "ack" il suffit que le processus soit bloqué un moment par un sigstop ou autre pour que la mécanique s’enraye par contre non ?

      • [^] # Re: Ça marche pas.

        Posté par  . Évalué à 4. Dernière modification le 10 mai 2023 à 18:50.

        Il est possible que :

        1. Linux (dans la version que tu utilises, avec les paramètres de compilation que tu as passé au noyau, etc.) ait une queue en interne (je ne sais pas comment il gère les signaux).

        2. Dans le cas simple où tu as juste deux programmes en charge CPU, ça passe crème : le noyau va recevoir le signal de l’émetteur, basculer sur le code du récepteur (par optimisation par exemple : le noyau sait que le récepteur a du code à exécuter donc il va préférer basculer sur lui), il va exécuter entièrement le handler, avant de rebasculer sur l’émetteur, et ainsi de suite. Ça peut très bien se passer. Dans un cas de test simple. Mais sur une machine qui a un peu de load ou autre, multi-processeur, etc., rien ne te garantit que le noyau va exécuter alternativement les codes de l’un et de l’autre.

        Si tu veux coder propre, il faut pas juste se contenter du “chez moi ça marche” mais prendre en compte les cas de comportement indéterminés. Cela implique de lire la doc à fond. En pur C/posix c’est incontournable. Dans le cas contraire tu t’exposes au risque que ça te pète à la gueule (au pire moment dira Murphy). Ça peut marcher pendant 10 ans, puis un beau jour casser parce que la mécanique interne du noyau et/ou de la lib a changé.

        Sinon si t’as un peu de temps et de la motivation, tu peux essayer un émetteur qui envoie 10, 100 voir 1000 signaux dans la boucle la plus rapide possible, un récepteur qui a un handler artificiellement long (absolument via un sleep ou usleep ça obligera le noyau à reprendre la main). Vois si ton handler récupère tous les signaux.

        • [^] # Re: Ça marche pas.

          Posté par  . Évalué à 3.

          Il me semble qu'il ne faut pas trop s'embêter, il s'agit d'un petit hack pour jouer. Il existe un tas d'ipc qui seront plus à même de faire des choses plus sophistiqué que ces 2 signaux utilisateurs.

          https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

  • # KillN

    Posté par  . Évalué à 3.

    killN() { for((i=0; i<$1; i++)); do kill -s SIGUSR1 $2 ; done ; kill -s SIGUSR2 $2; }
    

    Amiralgaby#1847

  • # Utilisation

    Posté par  (site web personnel) . Évalué à 2.

    J'ai eu à faire quelques exercices avec les signaux, et je me demande si c'est encore beaucoup utilisé aujourd'hui "dans la vrai vie". Ça m'a l'air vachement limité, d'ailleurs le fait que ce journal parle d'un hack pour contourner une limite du truc me conforte un peu dans cet avis.

    Un LUG en Lorraine : https://enunclic-cappel.fr

    • [^] # Re: Utilisation

      Posté par  (site web personnel) . Évalué à 9.

      Les signaux sont toujours utilisés et utiles dans la vraie vie.
      Mais leur usage est de fait limité à cause des limitations de cette techno.

      Car les signaux sont un moyen simple d’interagir avec un programme qui tourne, c'est standard, c'est facilement utilisable via la ligne de commande, un script shell ou un programme quelconque.

      Mais ce n'est pas bidirectionnel, il y a peu de garanties en cas d'envoi multiples ou parallèles et peu extensibles. Donc pour s'en servir comme moyen de communication complexe je trouve que c'est bof.

      Mais en usage réel que j'ai rencontré récemment il y a :

      • Dump à la demande de la base de données de systemd-resolved dans les logs ;
      • Renouveler le bail DHCP (ou en demander un) dans udhcpc si utilisé comme démon ;
      • Un autre cas d'usage que j'ai oublié mais dans la même veine.

      Bref, c'est utile mais ça soit être réservé à des usages où la concurrence des signaux et l'empilement des requêtes n'est pas critique. C'est donc bien pour éventuellement un contexte de débogue ou effectuer une action dynamique précise, mais guère plus.

      • [^] # Re: Utilisation

        Posté par  (Mastodon) . Évalué à 7.

        Un autre cas d'usage que j'ai oublié mais dans la même veine

        Recharger le fichier de config par exemple.

        En théorie, la théorie et la pratique c'est pareil. En pratique c'est pas vrai.

      • [^] # Re: Utilisation

        Posté par  (site web personnel) . Évalué à 8.

        • rotation des logs via logrotate pour notifier le processus qui produit les logs
        • génération de plantages volontaires à coup de SIGILL et autres sur un processus victime
        • commande kill qui sert notamment à arrêter des processus, rebelles ou non, proprement ou non (sinon faudrait demander direct au noyau de le faire à coup de magic sys requests)
      • [^] # Re: Utilisation

        Posté par  . Évalué à 6.

        Afficher les stats lors de la copie d'une ISO avec dd : Sending a USR1 signal to a running 'dd' process makes it print I/O statistics to standard error and then resume copying.

      • [^] # Re: Utilisation

        Posté par  . Évalué à 3.

        Une version moderne, ce serait les messages sur dbus.

    • [^] # Re: Utilisation

      Posté par  . Évalué à 4. Dernière modification le 10 mai 2023 à 23:09.

      Perso j'ai un petit projet en tête et le seul moyen que je trouve pour demander à un service (daemon) de lancer son traitement à un instant T pour du debuggage (parce que sinon il le lance à un intervalle régulier) est de le faire avec un signal.

      Vive leur existence.

      Amiralgaby#1847

      • [^] # Re: Utilisation

        Posté par  (site web personnel) . Évalué à 3. Dernière modification le 10 mai 2023 à 23:31.

        Les signaux ne répondent pas au problème de lancer un traitement à un instant T puisqu'il n'y a pas de garantie du moment où ils sont livrés. Pour du débogage tu as plusieurs systèmes de breakpoints et tracepoints fait exprès pour ça mais ça varie généralement selon le système utilisé.

        pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

        • [^] # Re: Utilisation

          Posté par  (Mastodon) . Évalué à 4.

          Tout dépend de ton besoin de « à un instant T ».
          Si c'est pour en crontab demander à un processus de faire un traitement, ça peut arriver n'importe quand selon la cron configurée, le programme ne sait pas à l'avance, donc c'est un instant T quelconque.
          Par contre ya pas forcément besoin d'une réponse immédiate et synchrone, juste que ça soit fait.

          C'est exactement ce qu'il se passe avec les logrotate d'ailleurs, où on peut envoyer un signal au processus pour lui demander de rouvrir son fichier de log.
          Ça arrive à un instant T quelconque, mais t'as pas besoin d'être synchrone, faut juste que ça soit fait.

          • Yth.
          • [^] # Re: Utilisation

            Posté par  (site web personnel) . Évalué à 2.

            Tout dépend de ton besoin de « à un instant T ».

            Heureusement que c'était précisé dans le message auquel j'ai répondu alors. Dommage que ça n'ai rien à voir avec un cron ou logrotate.

            pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

            • [^] # Re: Utilisation

              Posté par  (Mastodon) . Évalué à 4.

              Mais son besoin n'est pas plus urgent.
              Tel que je le comprend c'est manuel : « Tiens processus, file-moi tes stats », et après t'attends le fichier.
              Si au premier « ls » il n'est pas là, tu recommences, et le signal a normalement eu le temps de passer…

              En tout cas son « à l'instant T » je l'ai compris comme « à un instant non défini à l'avance, mais qui peut arriver n'importe quand. » plutôt qu'à un appel synchrone genre « je veux ces données tout de suite ».

              Dans le second cas, en effet, le signal n'est pas adapté, puisque c'est asynchrone comme fonctionnement.

              • Yth.
        • [^] # Re: Utilisation

          Posté par  . Évalué à 1.

          Si je lance un signal à un service, il devrait quand même arriver assez vite pour lancer le traitement quasi dans la demi-seconde qui suit, non ?

          Amiralgaby#1847

          • [^] # Re: Utilisation

            Posté par  (site web personnel) . Évalué à 2.

            « dans la demi-seconde qui suit » c'est pas très « instant T » dans un contexte informatique. Quand on me parle de débogage je pense à une série d'instructions qui prennent chacune de l'ordre de la nanoseconde pour s'exécuter.

            Le délai va dépendre de plein de trucs mais il y a pas mal de situations où ça peut prendre plus d'une demi seconde. Ça dépend de ton cas d'utilisation précis. Sur un système puissant qui fait rien d'autre, probablement que ça va arriver à temps.

            pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

            • [^] # Re: Utilisation

              Posté par  . Évalué à 1.

              Je m'étais mal exprimé.

              Mon intérêt c'est de ne pas attendre que le service lance son traitement.

              Je lance le signal
              Il le reçoit
              Il fait le traitement

              Et moi j'ai des logs qui seront disponibles du fait de ce traitement

              Amiralgaby#1847

              • [^] # Re: Utilisation

                Posté par  (site web personnel, Mastodon) . Évalué à 3.

                Quand j'ai besoin de gérer des signaux sous Linux, je le fait plutôt à l'aide d'tn signalfd qui permet de récupérer les évènements via un descripteur de fichiers et de les faire rentrer dans ma boucle d'évènements avec tous les autres trucs que mon code doit traiter. Ça rend pour moi les choses beaucoup plus simples, mais le traitement un petit peu moins immédiat.

                Mais si le code est architecturé comme il faut, ça se comptera au pire en centaines de millisecondes.

    • [^] # Re: Utilisation

      Posté par  . Évalué à 3.

      Ça sert à ce pour quoi c'est faire. Lancer un signal à un programme. Pour l'arrêter c'est beaucoup utiliser, pour demander des rechargements de configuration aussi. A savoir quand un processus fils est mort.

      C'est la solution la plus simple et la plus légère de toutes les IPC, mais tu en a pleins d'autres :

      • les pipes nommées ou non
      • les socket unix ou tcp
      • les fichiers
      • la mémoire partagée

      et un paquet d'autres plus ou moins spécifique (genre pour l'un des plus sophistiqué sous linux : dbus).

      À noter, je crois que personne ne l'a cité, mais tu n'a aucune garantie que ton signal arrive sur le processus que tu attends, il me semble. Il peut être mort et son pid être réutilisé entre le moment où tu as lu le pid et le moment où tu envoi le signal.

      https://linuxfr.org/users/barmic/journaux/y-en-a-marre-de-ce-gros-troll

    • [^] # Re: Utilisation

      Posté par  . Évalué à 1.

      Sur les calculateurs massivement parallèle gérés avec SLURM, on peut utiliser les signaux pour arrêter un programme proprement à la fin du temps alloué.

      https://services.criann.fr/services/hpc/cluster-myria/guide/signals-sent-by-slurm/

  • # Réponse sérieuse

    Posté par  (site web personnel, Mastodon) . Évalué à 6.

    Il y a aussi les signaux entre SIGRTMIN et SIGRTMAX qui sont disponibles. Ça en fait une trentaine de plus sous Linux.

    • [^] # Re: Réponse sérieuse

      Posté par  (site web personnel) . Évalué à 7. Dernière modification le 11 mai 2023 à 14:57.

      Ça vient en fait de POSIX.1b qui définit les signaux temps réels. Ça défini aussi une union (int, void*) qui peut être passée entre l'envoyeur et le receveur et du coup on a besoin que d'un seul numéro de signal. L'ordre de réception pour un même numéro de signal est garanti. Mais du coup il faut utiliser l'API adéquat (sigqueue).

      Par contre, si on utilise directement plusieurs numéros de signal entre SIGRTMIN et SIGRTMAX, l'ordre garanti c'est que le numéro de signal le plus bas est reçu en premier. Ce qui va rajouter encore plus de confusion si on ne prend pas ça en compte. Si j'ai bien compris…

      https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_02

      pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

      • [^] # Re: Réponse sérieuse

        Posté par  (site web personnel) . Évalué à 2. Dernière modification le 11 mai 2023 à 23:18.

        L'avantage aussi c'est que dans le shell on peut dire kill -q 42 SIGRTMIN plutôt que l'horrible killN mentionné ci-dessus.

        pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

        • [^] # Re: Réponse sérieuse

          Posté par  . Évalué à 1.

          Mon killN :'(

          Mdr, j'avoue c'est horrible et de toute façon, selon les post par-ci par-là ça devrait être proscrit.

          Amiralgaby#1847

  • # oui mais non

    Posté par  (site web personnel) . Évalué à 9.

    Plusieurs commentaires ont tenté d'expliquer pourquoi cette approche est problématique. Une exploration plus détaillée des problème de concurrence peut être trouvée dans « Is Parallel Programming Hard, And, If So, What Can You Do About It? » de Paul E. McKenney (aka « perfbook ») : https://cdn.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html

    TL;DR : c'est compliqué et réinventer la roue est un excellent moyen de se retrouver avec un système cassé de manière subtile et difficile à déboguer et corriger. Un peu comme en cryptographie.

    pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

  • # Une autre approche

    Posté par  (site web personnel) . Évalué à 10. Dernière modification le 11 mai 2023 à 11:04.

    Lorsque le programme reçoit un SIGUSR1, il fait une requête grpc+mtls+oauth2 à une API web hébergé sur un kubernetes dans le cloud qui lui renvoie le signal métier au format json.

    L'API web stocke l'association host+pid => signal métier json dans une base Cassandra.

    Pour envoyer un signal, il faut donc d'abord utiliser l'API web via une IHM qui délègue l'authentification à un Keycloak configuré en double facteur (password + biométrie).

    Pour aller plus loin, il faudrait… Heu c'était quoi déjà le problème à résoudre?

    Le post ci-dessus est une grosse connerie, ne le lisez pas sérieusement.

    • [^] # Re: Une autre approche

      Posté par  (site web personnel) . Évalué à 10.

      Ça manque de blockchain et de machine learning comme même.

      pertinent adj. Approprié : qui se rapporte exactement à ce dont il est question.

  • # Sigurður

    Posté par  . Évalué à 3.

    C'est le plus sûr !!

    -->[]

  • # prochaine étape

    Posté par  (site web personnel) . Évalué à 8.

    Le inotify knocking, créer/supprimer les bons fichiers dans le bon ordre ?

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.