Remarques 1

    Rappel : lorsque l'injecteur << est utilisé pour afficher un entier, il utilise par défaut la largeur minimale d'affichage : un nombre de n chiffres (en décimal) est affiché sur n colonnes exactement. Pour le faire afficher sur plus de colonnes, et cadré à droite dans cette zone d'affichage, il faut, juste avant d'injecter le nombre, injecter le manipulateur setw (m), où m représente la largeur de la zone d'affichage désirée. Cette modification temporaire des paramètres d'affichage ne porte que sur l'injection qui la suit immédiatement, après quoi la largeur de l'affichage reprend sa valeur par défaut, à savoir la largeur minimale.

    Ainsi, pour faire afficher i sur 5 colonnes dans l'exemple ci-dessous, faut-il écrire :
 
int i;
...
cout << "i = " << setw (5) << i;


    La fonction __libc_current_sigrtmin() donne la valeur du premier signal temps réel. Pour afficher les autres signaux, il faut faire varier l'indice dans l'intervalle semi-ouvert [1, __libc_current_sigrtmin()[.

    Une mauvaise solution serait d'écrire :
 
for (int i = 1; i < __libc_current_sigrtmin(); ++i) { ... }

    En effet, le compilateur n'a aucune raison d'imaginer que cette fonction donne toujours le même résultat (au moins lors de l'exécution d'un processus donné), et donc qu'il s'agit en réalité d'une constante. Il est extrêmement probable qu'il va évaluer la fonction à chaque boucle. Il est donc préférable d'écrire :
 
const int CstSigRTMin = __libc_current_sigrtmin();
for (int i = 1; i < CstSigRTMin; ++i) { ... }

    Dans le même fichier <csignal> existe une macro, définie par :
 
#define SIGRTMIN (__libc_current_sigrtmin ()) 

    On pourrait donc être tenté d'écrire la boucle :
 
for (int i = 1; i < SIGRTMIN; ++i) { ... }

    Il faut se souvenir qu'une macro n'est pas obligatoirement une constante, malgré l'écriture qui pourrait le laisser penser, mais que le pré-processeur la remplace par son équivalent, à savoir l'appel de la fonction. Ce n'est donc pas une bonne solution. De façon générale, utiliser une macro en C++ est rarement une bonne idée (sauf pour raccourcir des chemins d'accès, mais ce n'est pas du C++, c'est destiné à la précompilation) !


    La fonction psignal() récupère dans son paramètre une chaîne qu'elle affiche dans le flux cerr (en réalité, c'est du C, donc c'est dans le fichiers standard d'erreur stderr), avant d'y ajouter une autre information. Il vous est demandé que cette chaîne soit la suivante :
 
"Signal xx signifie "

dans laquelle xx est la représentation sous forme de chaîne de caractères d'une variable NumSig entière. Quelle que soit la solution envisagée, il faut transformer NumSig sous forme de chaîne, suivant une des solutions proposées dans le chapitre Conversions chaînes de caractères <--> Nombres.

    Une solution est de transformer NumSig en une chaîne StrNumSig, de la compléter et de l'utiliser dans psignal() :
 
char Signal [9];
    
gcvt (NumSig, /* ndigit = */ 2, Signal);

string StrNumSig ("Signal ");
StrNumSig = StrNumSig + Signal + " signifie ";

psignal (NumSig, StrNumSig.c_str());

    Une autre solution est d'injecter dans une chaîne flux les informations désirées :
 
const int CstTaille = 255;
char Signal [CstTaille];

ostrstream os (Signal, sizeof (Signal));
os << "Signal " << setw (2) << NumSig << " signifie " << ends;

psignal (NumSig, os.str());

la dernière ligne pouvant être écrite (moins élégant) :
 
psignal (NumSig, Signal);

Remarques 2

    Avant tout, consulter le document : Bibliothèque standard C en C++.

    La définition du wrapper Sigaction() doit appeler la fonction système sigaction(), défnie dans <signal.h>, mais accessible dans std si le fichier inclus est bien <csignal>. Cette définition est faite dans le fichier nsSysteme.hxx, qui ne doit pas comporter la clause using namespace std. Elle nécessite donc le préfixage de l'appel de sigaction() par std::.

Remarques 3

    Lorsqu'on essaie de définir une macro déjà rencontrée par le pré-processeur, un message d'erreur (warning) est indiqué, si toutefois l'option -W du g++ est positionnée. Pour éviter ces messages, il suffit de supprimer la définition précédente par :
 
#undef LA_MACRO
#define LA_MACRO ...

    Rappelons qu'une macro n'a aucune raison d'être dans une espace de noms, et que, même si elle est physiquement dedans, sa "visibilité" est quand même globale comme le montre l'exemple ci-dessous :
 
// Fichier NameSpace

namespace nsNameSpace 
{
   #define LA_MACRO 12345

} // nsNameSpace

// Fichier main.cxx

#include <iostream>

#include "NameSpace"

int main (void)
{
    cout << "LA_MACRO = " << LA_MACRO << endl;     {1}
    ...

    Le remplacement de la ligne {1} par :
 
cout << "LA_MACRO = " << nsNameSpace::LA_MACRO << endl; 

provoque une erreur de syntaxe.

Remarques 4

    De nouveau le problème précédemment rencontré avec l'ambiguité entre la struct stat et la fonction stat(), concernant cette fois l'utilisation de la struct sigaction alors qu'existe aussi la fonction système sigaction() : faire précéder explicitement l'identificateur sigaction du mot struct (qui devrait pourtant être facultatif) :
 
struct std::sigaction Action;

le préfixage std:: étant inutile si l'espace de noms std est visible à cet endroit du programme.

Remarques 5

    Le processus en background est détaché du clavier. Cela signifie qu'il ne reçoit plus les signaux qui lui sont envoyés par le terminal. En revanche, il est toujours sensible à ces signaux, pourvu qu'ils lui soient explicitement adressés, par l'intermédiaire de son numéro de processus ou de job. Les signaux ne sont donc ni bloqués ni ignorés par un processus en background.

Remarques 6

    Les options possibles proposées dans cet exercice sont I, D et P, qui s'excluent mutuellement. Il est convivial de laisser à l'utilisateur la possibilité de taper ces options en minuscules : i, d et p.

    Deux possibilités de codage sont possibles :
 
switch (argv [i][0])
{
    case i :
    case I : ... break;

    case d :
    case D : ... break;
    ...
}

ou bien
 
switch (toupper (argv [i][0]))
{
    case I : ... break;

    case D : ... break;
    ...
}

    Voir aussi Remarques 8 du TP sur les fichiers

Remarques 7

    Dans cet exercice, il s'agit de faire passer à une fonction externe à main() une variable initialisée dans main(), sans pouvoir ajouter un paramètre. Ce problème peut être fréquemment rencontré et sa solution est générale.

    Considérons par exemple le morceau de programme suivant, écrit en ADA :
 
procedure P1 is

    VarInterne : Integer;

    procedure PInterne (ParamInterne : in Integer) is

    begin -- PInterne

        -- utilisation de VarInterne
          
    end PInterne;

begin -- P1

    -- ...
    PInterne (QuelqueChose);
    -- ...

end P1;

    Dans un fichier C++, les fonctions ne peuvent être imbriquées. Il faut donc les séparer. La communication de VarInterne entre les deux procédures (ce seront des fonctions en C/C++), peut être résolue de deux façons.

    La première consiste à ajouter un paramètre à PInterne :
 
void PInterne (int ParamInterne, int & NewParam)
{
    // utilisation de NewParam (donc indirectement, de VarInterne)
          
} // PInterne()

void P1 ()
{
    int VarInterne;

    PInterne (QuelqueChose, VarInterne);

} // P1()

    Apparemment plus lourde que la précédente, cette solution peut être considérée comme meilleure par certains puristes de l'algorithmique, car le mélange de mode d'échange d'informations entre deux sous-programmes (passage par paramètres et variables globales) est considéré comme dangereux (error-prone). Cette solution est cependant totalement exclue lorsque le profil de la fonction PInterne() est imposé, par exemple par le système d'exploitation (cas des fonctions de traitement des signaux).

    La seconde possibilité consiste à faire passer VarInterne en variable "globale" aux deux fonctions. Cependant, la norme C++ préconise de limiter (interdire ...) l'utilisation d'identificateurs globaux, qui polluent l'espace des noms. La solution complète du problème nécessite donc de "contenir" la visibilité de cette variable localement au fichier, au moyen de l'espace de noms anonyme, ce qui donne :
 
namespace 
{
    int VarInterne;

    void PInterne (int ParamInterne)
    {
        // utilisation de VarInterne
          
    } // PInterne()

} // namespace anonyme

void P1 ()
{
    // utilisation de VarInterne;

    PInterne (QuelqueChose);

} // P1()

    Seule la fonction P1() est visible de l'extérieur, et donc utilisable.

Remarques 8

    Le corrigé proposé est volontairement faux, mais il est impossible de réaliser le comportement demandé au moyen de signaux. Considérons en effet la séquence suivante :
 
 
for ( ; !Fin; )
{
    if (!Saisie && ! Fin) /* 1 */ pause ();

    if (Saisie)
    {
        /* 2 */
        Saisie = false;
        ...
    }
}

    Si un des deux signaux est reçu entre la sortie du test et l'entrée dans la fonction pause(), (instant marqué /* 1 */ ci-dessus), situation hautement improbable mais toujours possible, le processus ne sortira jamais plus de la pause. Ce n'est pas grave car l'utilisateur, voyant qu'il ne se passe rien, renverra probablement le signal (ou même plusieurs fois s'il s'excite rageusement !). C'est en revanche catastrophique si ce signal a été généré par un processus, qui peut alors se bloquer lui-même en attente d'une réponse. C'est ainsi que se créent les situations dites d'interblocage catastrophiques lorsqu'elles interviennent à l'intérieur de processus système.

    Une autre erreur peut apparaître si SIGINT parvient au processus à l'instant indiqué  /* 2 */ ci-dessus. En effet, le traitant met le booléen Saisie à vrai (il l'est déjà), indiquant par là qu'une demande de saisie a été effectuée, mais le booléen est immédiatement remis à faux au retour du traitant. Donc ce signal est perdu.

    Il est pratiquement impossible de provoquer volontairement ces erreurs en testant, et cela laisse croire que le programme est juste (ce genre d'erreur est fréquent dans les devoirs). En revanche, si on temporise chacune des actions du programme en insérant des appels à la fonction sleep(), cela devient possible.

    De façon générale, en programmation système, dissocier le test d'un indicateur d'une action associée laisse toujours la possibilité à un événement de s'insérer entre les deux. Il faut donc que les deux opérations soient atomiques, c'est-à-dire exécutées par le système sans interruption possible. Il faut donc choisir des outils offrant cette atomicité.

   Pour conclure, il est maladroit et dangereux d'utiliser les signaux pour synchroniser des processus. Leur seul rôle est de signaler l'occurrence d'événements, dont le traitement ne doit pas être attendu, mais qui peuvent être pris en compte de façon synchrone (stratégie C) ou asynchrone (stratégie E).

    Une solution partielle au problème est l'utilisation d'une attente (semi)-active :
 
for ( ; !Fin; )
{
    for (!Saisie && ! Fin) sleep (Delai);

    if (Saisie)
    {
        /* 2 */
        Saisie = false;
        ...
    }
}

    Un Delai infini revient à la solution précédente. Un Delai raisonnable (quelques secondes) permet de consulter périodiquement les indicateurs. Cependant, cela diminue la réactivité de l'application, et devient intolérable dans un contexte "temps réel". Un Delai de quelques milli-secondes peut donner l'illusion de temps réel à l'échelle humaine (il faut alors utiliser nanosleep(), car le paramètre de sleep() est un entier exprimé en secondes). Plus le Delai est court, plus on se rapproche de l'attente active (Delai nul), qui consomme l'essentiel du temps CPU à consulter les variables.

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique

Créé le 01/08/2001 - Dernière mise à jour : 28/08/2001