Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Atomicité ? Problème tordu de gestion de signaux...

10 réponses
Avatar
JKB
Bonjour à tous,

J'ai un petit problème avec gcc et (il me semble) les opérations
atomiques, mais je n'en suis vraiment pas sûr... Je m'explique :

1/ considérons un processus père qui lance des tas de processus de
calculs à l'aide de fork(). Chaque fils peut recevoir du père un
certain nombre de données par un pipe.
2/ un fils donné est averti de la disponibilité d'une donnée par un
signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.
3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
simultanées à traiter en même temps).
4/ il y a un thread de surveillance qui tourne dans le fils, donc
j'ai collé un mutex dans le traitement de l'interruption histoire
d'être sûr de ne pas accéder en même temps à la même variable.

pthread_mutex_t exclusion = PTHREAD_MUTEX_INITIALIZER; // Var globale

void
interruption6(int signal)
{
if (var_globale_debug_signaux != 0)
{
printf("[%d] SIGINJECT/SIGQUIT\n", (int) getpid());
fflush(stdout);
}

pthread_mutex_lock(&exclusion);
var_globale_nombre_objets_injectes++; // ce truc est un sig_atomic_t
pthread_mutex_unlock(&exclusion);

return;
}

Chaque fois que le père désire envoyer une donnée à l'un de ses
fils, il commence par envoyé le signal SIGQUIT au fils en question.
Cette étape se passe bien (kill() ne renvoie aucune erreur).

Le fils utilise la variable var_globale_nombre_objets_injectes comme
suit :

int tampon;
int nombre_variables_disponibles;

pthread_mutex_lock(&exclusion);
tampon = var_globale_nombre_objets_injectes;

if (tampon != 0)
{
var_globale_nombre_objets_injectes -= tampon;
nombre_variables_disponibles += tampon;
}

pthread_mutex_lock(&exclusion);

Lorsque nombre_variables_disponibles est positif, le fils peut lire
une donnée.

Problème : au bout d'un certain temps (en environnement parallèle),
le fils rate une donnée. En fait, la donnée a bien été envoyée dans
le pipe, mais var_globale_nombre_objets_injectes n'a pas été
incrémentée par le SIGQUIT (alors que la sortie console me prouve
que le SIGQUIT a été traité). Le programme s'arrête donc et il
suffit de signaler le fils (par un kill -SIGQUIT pid) pour le
relancer.

Questions :
1/ le problème se situe-t-il au niveau du gestionnaire de signal
(interruption6) ou de l'utilisation de la variable globale ?
2/ Si le problème se situe au niveau de
var_globale_nombre_objets_injectes++, opération qui est non atomique
(si j'ai bien compris), comment la rendre atomique (et portable) ?
3/ le compilo est gcc. Est-il assez intelligent pour optimiser
l'utilisation de la variable tampon ? J'ai essayé de faire un calcul
sur celle-ci histoire d'essyer de forcer gcc à ne pas la supprimer
sans succès...

Merci de vos lumières,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.

10 réponses

Avatar
Antoine Leca
En news:, JKB va escriure:
J'ai un petit problème avec gcc et (il me semble) les opérations
atomiques,



Quelle version ?
GCC est renommé pour avoir un comportement plus ou moins ératique à ce
niveau (souvent appellé « variable volatiles »), ce qui génère des plaintes,
et en conséquence les développeurs de GCC ont pas mal louvoyé.


1/ considérons un processus père qui lance des tas de processus de
calculs à l'aide de fork().



Mmmm, peut-être qu'en postant sur fcou, non ?

Moi j'en étais resté sur le fait que beaucoup de fils + signal = problèmes
avec le modèle de synchronisation de Unix. Donc ÀMHA la question risque de
déborder passablement le cadre de ce groupe.


2/ un fils donné est averti de la disponibilité d'une donnée par un
signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.



Et deux SIGQUIT envoyés groupés _avant_ que le fils ne se réveille,
qu'est-ce que cela donne ?

3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
simultanées à traiter en même temps).



Donc tu contournes en utilisant une construction encore moins portable,
requerrant Posix-2001... J'espère que tu es conscient :
1. qu'il ne faut pas dédaigner de possible bogues au niveau de
l'implémentation par le système utilisé ;
2. que la question est passablement hors sujet ici.


Questions :
1/ le problème se situe-t-il au niveau du gestionnaire de signal
(interruption6) ou de l'utilisation de la variable globale ?



Pour le savoir, tu découpes le problème en deux, en remplaçant l'un puis
l'autre des bouts de code et le reste inchangé.
Une manière simple de commencer serait d'écrire en assembleur l'opération
sur la variable globale (ce qui va probablement priver le compilateur
d'opporrtunité pour faire des choses incorrectes, ou qui va te montrer tout
de suite qu'il y a un souci s'il y a un problème d'aliasing ce qui est
probable).

Une autre manière de faire est d'essayer un autre compilateur...


2/ Si le problème se situe au niveau de
var_globale_nombre_objets_injectes++, opération qui est non atomique
(si j'ai bien compris), comment la rendre atomique (et portable) ?



man pthread

3/ le compilo est gcc. Est-il assez intelligent pour optimiser
l'utilisation de la variable tampon ?



Je ne sais pas si GCC est intelligent.
Mais je crois que ses concepteurs sont suffisament <xxx> pour essayer de le
lui faire faire des optimisations de cet acabit...


Antoine
Avatar
Jean-Marc Bourguet
JKB writes:

4/ il y a un thread de surveillance qui tourne dans le fils, donc
j'ai collé un mutex dans le traitement de l'interruption histoire
d'être sûr de ne pas accéder en même temps à la même variable.



Je comprends bien? Tu utilises un mutex dans un gestionnaire de signal?

Il ne me semble pas que ce sont des fonctions "async safe" (ce qui est
plus exigent que thread safe).

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
JKB
Le 17-10-2008, ? propos de
Re: Atomicité ? Problème tordu de gestion de signaux...,
Antoine Leca ?crivait dans fr.comp.lang.c :
En news:, JKB va escriure:
J'ai un petit problème avec gcc et (il me semble) les opérations
atomiques,



Quelle version ?



cauchy:[~] > gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.3.2-1'
--with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs
--enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr
--enable-shared --with-system-zlib --libexecdir=/usr/lib
--without-included-gettext --enable-threads=posix --enable-nls
--with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3
--enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc
--enable-mpfr --enable-cld --enable-checking=release
--build=x86_64-linux-gnu --host=x86_64-linux-gnu
--target=x86_64-linux-gnu
Thread model: posix
gcc version 4.3.2 (Debian 4.3.2-1)
cauchy:[~] >

GCC est renommé pour avoir un comportement plus ou moins ératique à ce
niveau (souvent appellé « variable volatiles »), ce qui génère des plaintes,
et en conséquence les développeurs de GCC ont pas mal louvoyé.



J'ai oublié de dire que toutes les variables utilisées
(var_globale_nombre_objets_injectes et tampon) sont volatiles.

1/ considérons un processus père qui lance des tas de processus de
calculs à l'aide de fork().



Mmmm, peut-être qu'en postant sur fcou, non ?



fu2 et Xpost positionnés.

Moi j'en étais resté sur le fait que beaucoup de fils + signal = problèmes
avec le modèle de synchronisation de Unix. Donc ÀMHA la question risque de
déborder passablement le cadre de ce groupe.


2/ un fils donné est averti de la disponibilité d'une donnée par un
signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.



Et deux SIGQUIT envoyés groupés _avant_ que le fils ne se réveille,
qu'est-ce que cela donne ?



Le fils n'est jamais en sommeil (il fait du polling et récupère les
données dès que la variable var_globale_nombre_objets_injectes est
positive).

3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
simultanées à traiter en même temps).



Donc tu contournes en utilisant une construction encore moins portable,
requerrant Posix-2001... J'espère que tu es conscient :
1. qu'il ne faut pas dédaigner de possible bogues au niveau de
l'implémentation par le système utilisé ;
2. que la question est passablement hors sujet ici.



Disons que j'essaye de contourner les problèmes comme je peux (dans
une portabilité Solaris/Linux/FreeBSD et dans une moindre mesure
NetBSD).

Si j'utilise un signal temps réel, je puis enlever l'option NODEFER,
et ça fonctionne parfaitement. Néanmoins, je ne peux plus utiliser
le bout de code en question sous NetBSD.

Questions :
1/ le problème se situe-t-il au niveau du gestionnaire de signal
(interruption6) ou de l'utilisation de la variable globale ?



Pour le savoir, tu découpes le problème en deux, en remplaçant l'un puis
l'autre des bouts de code et le reste inchangé.
Une manière simple de commencer serait d'écrire en assembleur l'opération
sur la variable globale (ce qui va probablement priver le compilateur
d'opporrtunité pour faire des choses incorrectes, ou qui va te montrer tout
de suite qu'il y a un souci s'il y a un problème d'aliasing ce qui est
probable).

Une autre manière de faire est d'essayer un autre compilateur...


2/ Si le problème se situe au niveau de
var_globale_nombre_objets_injectes++, opération qui est non atomique
(si j'ai bien compris), comment la rendre atomique (et portable) ?



man pthread

3/ le compilo est gcc. Est-il assez intelligent pour optimiser
l'utilisation de la variable tampon ?



Je ne sais pas si GCC est intelligent.
Mais je crois que ses concepteurs sont suffisament <xxx> pour essayer de le
lui faire faire des optimisations de cet acabit...



Ça, ce n'est pas très gentil ;-)

La question devient donc : comment faire pour émuler avec un signal
standard un signal à temps réel ?

Merci de vous être penché sur mon problème...

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
JKB
Le 17-10-2008, ? propos de
Re: Atomicité ? Problème tordu de gestion de signaux...,
Jean-Marc Bourguet ?crivait dans fr.comp.lang.c :
JKB writes:

4/ il y a un thread de surveillance qui tourne dans le fils, donc
j'ai collé un mutex dans le traitement de l'interruption histoire
d'être sûr de ne pas accéder en même temps à la même variable.



Je comprends bien? Tu utilises un mutex dans un gestionnaire de signal?

Il ne me semble pas que ce sont des fonctions "async safe" (ce qui est
plus exigent que thread safe).



Non, à la base, je n'utilise pas de mutex dans un gestionnaire de
signal. J'ai fait un essai parce que je sais que le problème est
dans cette @^@|@^ d'incrémentation. Lorsque deux interruptions
arrivent en même temps, comme l'incrémentation n'est pas atomique,
la variable peut n'être incrémentée qu'une seule fois au lieu de
deux...

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
Alain
On Fri, 17 Oct 2008 10:39:06 +0000 (UTC)
JKB [JKB] wrote:

JKB> Bonjour à tous,
JKB>
JKB> J'ai un petit problème avec gcc et (il me semble) les opérations
JKB> atomiques, mais je n'en suis vraiment pas sûr... Je m'explique :
JKB>
JKB> 1/ considérons un processus père qui lance des tas de processus de
JKB> calculs à l'aide de fork(). Chaque fils peut recevoir du père un
JKB> certain nombre de données par un pipe.
JKB> 2/ un fils donné est averti de la disponibilité d'une donnée par un
JKB> signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.
JKB> 3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
JKB> le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
JKB> simultanées à traiter en même temps).
JKB> 4/ il y a un thread de surveillance qui tourne dans le fils, donc
JKB> j'ai collé un mutex dans le traitement de l'interruption histoire
JKB> d'être sûr de ne pas accéder en même temps à la même variable.

petite question: pourquoi ne pas simplement utiliser select() pour savoir
quand il y a des choses a processer et traiter ce que read() nous donne ?

--
Alain
Avatar
JKB
Le 18-10-2008, ? propos de
Re: Atomicité ? Problème tordu de gestion de signaux...,
Alain ?crivait dans fr.comp.lang.c :
On Fri, 17 Oct 2008 10:39:06 +0000 (UTC)
JKB [JKB] wrote:

JKB> Bonjour à tous,
JKB>
JKB> J'ai un petit problème avec gcc et (il me semble) les opérations
JKB> atomiques, mais je n'en suis vraiment pas sûr... Je m'explique :
JKB>
JKB> 1/ considérons un processus père qui lance des tas de processus de
JKB> calculs à l'aide de fork(). Chaque fils peut recevoir du père un
JKB> certain nombre de données par un pipe.
JKB> 2/ un fils donné est averti de la disponibilité d'une donnée par un
JKB> signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.
JKB> 3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
JKB> le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
JKB> simultanées à traiter en même temps).
JKB> 4/ il y a un thread de surveillance qui tourne dans le fils, donc
JKB> j'ai collé un mutex dans le traitement de l'interruption histoire
JKB> d'être sûr de ne pas accéder en même temps à la même variable.

petite question: pourquoi ne pas simplement utiliser select() pour savoir
quand il y a des choses a processer et traiter ce que read() nous donne ?



J'avais dans un premier temps pensé à utiliser pselect(), mais cela
ne convient pas. Le fils a besoin de connaître le nombre de données
envoyées _avant_ de les lire, d'autant que certaines de ces données
sont utilisées groupées. L'utilisation de pselect() permet de savoir
qu'il y a au moins une donnée dans le pipe, mais pas d'en connaître le
nombre. Par ailleurs, comme le fils peut lire cette donnée longtemps
après l'appel pselect(), cela ne fonctionnera pas car il faudrait
lire chaque donnée _juste_ après le retour de pselect().

Cordialement,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
Patrick Lamaizière
JKB:

petite question: pourquoi ne pas simplement utiliser select() pour savoir
quand il y a des choses a processer et traiter ce que read() nous donne ?



J'avais dans un premier temps pensé à utiliser pselect(), mais cela
ne convient pas. Le fils a besoin de connaître le nombre de données
envoyées _avant_ de les lire, d'autant que certaines de ces données
sont utilisées groupées. L'utilisation de pselect() permet de savoir
qu'il y a au moins une donnée dans le pipe, mais pas d'en connaître le
nombre. Par ailleurs, comme le fils peut lire cette donnée longtemps
après l'appel pselect(), cela ne fonctionnera pas car il faudrait
lire chaque donnée _juste_ après le retour de pselect().



Dans ce cas tu peux utiliser le truc du "self-pipe" dans ton
gestionnaire de signal. Tu ajoutes dans un pipe un caractère à chaque
signal reçu.

Tu ne précises pas une chose importante : c'est dans quel contexte de
thread est appelé ton gestionnaire, et si le mutex est récursif. D'après
ce que j'ai lu la méthode canonique est de bloquer tous les signaux
dans le thread qui travaille et d'avoir un autre thread qui ne gère
que les signaux.
Avatar
JKB
Le 18-10-2008, ? propos de
Re: Atomicité ? Problème tordu de gestion de signaux...,
Patrick Lamaizière ?crivait dans fr.comp.lang.c :
JKB:

petite question: pourquoi ne pas simplement utiliser select() pour savoir
quand il y a des choses a processer et traiter ce que read() nous donne ?



J'avais dans un premier temps pensé à utiliser pselect(), mais cela
ne convient pas. Le fils a besoin de connaître le nombre de données
envoyées _avant_ de les lire, d'autant que certaines de ces données
sont utilisées groupées. L'utilisation de pselect() permet de savoir
qu'il y a au moins une donnée dans le pipe, mais pas d'en connaître le
nombre. Par ailleurs, comme le fils peut lire cette donnée longtemps
après l'appel pselect(), cela ne fonctionnera pas car il faudrait
lire chaque donnée _juste_ après le retour de pselect().



Dans ce cas tu peux utiliser le truc du "self-pipe" dans ton
gestionnaire de signal. Tu ajoutes dans un pipe un caractère à chaque
signal reçu.



Groumpffff... Dire que j'utilise ce système dans l'autre sens et que
je n'y avais pas pensé. Problème corrigé. Merci de la suggestion qui
fonctionne parfaitement.

Tu ne précises pas une chose importante : c'est dans quel contexte de
thread est appelé ton gestionnaire, et si le mutex est récursif.



Il n'est pas récursif. Je vais me répéter, mais ce mutex n'était là
qu'à des fins de débogage. Il n'était pas à demeure dans mon code
(les signaux sont bloqués dans les threads, mais comme je titillais
les limites des systèmes, je me demandais s'il n'y avait pas un
problème... Donc ceinture _et_ bretelles ;-) ).

D'après
ce que j'ai lu la méthode canonique est de bloquer tous les signaux
dans le thread qui travaille et d'avoir un autre thread qui ne gère
que les signaux.



Merci pour tout,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.
Avatar
Selso
Je ne vais pas polémiquer sur l'architecture de ta solution, cela a été
suffisamment fait.
En ce qui concerne les variables "atomiques", il y a des opérations
atomiques associés.
En utilisant des signaux non temps-réel, tu peux en perdre. Au passage
pourquoi ne pas avoir pris les signaux SIGUSR1 ou SIGUSR2 ?
Dans ton cas tu ne peux empiler que deux signaux : celui que tu traites
et celui en attente de traitement. Si un troisième signal arrive, tu
perds un cycle. Puisque des signaux temps-réel sont disponibles
utilise-les !
Maintenant si tu as des données de taille variable à transmettre tu peux
utiliser des sockets unix et passer des messages avec une entete
rensiegnant la taille.
++



JKB a écrit :
Bonjour à tous,

J'ai un petit problème avec gcc et (il me semble) les opérations
atomiques, mais je n'en suis vraiment pas sûr... Je m'explique :

1/ considérons un processus père qui lance des tas de processus de
calculs à l'aide de fork(). Chaque fils peut recevoir du père un
certain nombre de données par un pipe.
2/ un fils donné est averti de la disponibilité d'une donnée par un
signal (SIGQUIT ici) qui est déclanché par un kill() depuis le père.
3/ le gestionnaire du SIGQUIT est initialisé par un sigaction() dans
le fils avec l'option NODEFER (il peut y avoir plusieurs interruptions
simultanées à traiter en même temps).
4/ il y a un thread de surveillance qui tourne dans le fils, donc
j'ai collé un mutex dans le traitement de l'interruption histoire
d'être sûr de ne pas accéder en même temps à la même variable.

pthread_mutex_t exclusion = PTHREAD_MUTEX_INITIALIZER; // Var globale

void
interruption6(int signal)
{
if (var_globale_debug_signaux != 0)
{
printf("[%d] SIGINJECT/SIGQUITn", (int) getpid());
fflush(stdout);
}

pthread_mutex_lock(&exclusion);
var_globale_nombre_objets_injectes++; // ce truc est un sig_atomic_t
pthread_mutex_unlock(&exclusion);

return;
}

Chaque fois que le père désire envoyer une donnée à l'un de ses
fils, il commence par envoyé le signal SIGQUIT au fils en question.
Cette étape se passe bien (kill() ne renvoie aucune erreur).

Le fils utilise la variable var_globale_nombre_objets_injectes comme
suit :

int tampon;
int nombre_variables_disponibles;

pthread_mutex_lock(&exclusion);
tampon = var_globale_nombre_objets_injectes;

if (tampon != 0)
{
var_globale_nombre_objets_injectes -= tampon;
nombre_variables_disponibles += tampon;
}

pthread_mutex_lock(&exclusion);

Lorsque nombre_variables_disponibles est positif, le fils peut lire
une donnée.

Problème : au bout d'un certain temps (en environnement parallèle),
le fils rate une donnée. En fait, la donnée a bien été envoyée dans
le pipe, mais var_globale_nombre_objets_injectes n'a pas été
incrémentée par le SIGQUIT (alors que la sortie console me prouve
que le SIGQUIT a été traité). Le programme s'arrête donc et il
suffit de signaler le fils (par un kill -SIGQUIT pid) pour le
relancer.

Questions :
1/ le problème se situe-t-il au niveau du gestionnaire de signal
(interruption6) ou de l'utilisation de la variable globale ?
2/ Si le problème se situe au niveau de
var_globale_nombre_objets_injectes++, opération qui est non atomique
(si j'ai bien compris), comment la rendre atomique (et portable) ?
3/ le compilo est gcc. Est-il assez intelligent pour optimiser
l'utilisation de la variable tampon ? J'ai essayé de faire un calcul
sur celle-ci histoire d'essyer de forcer gcc à ne pas la supprimer
sans succès...

Merci de vos lumières,

JKB



Avatar
JKB
Le 21-10-2008, ? propos de
Re: Atomicité ? Problème tordu de gestion de signaux...,
Selso ?crivait dans fr.comp.lang.c :
Je ne vais pas polémiquer sur l'architecture de ta solution, cela a été
suffisamment fait.
En ce qui concerne les variables "atomiques", il y a des opérations
atomiques associés.
En utilisant des signaux non temps-réel, tu peux en perdre. Au passage
pourquoi ne pas avoir pris les signaux SIGUSR1 ou SIGUSR2 ?



Parce qu'ils sont _déjà_ utilisés.

Dans ton cas tu ne peux empiler que deux signaux : celui que tu traites
et celui en attente de traitement. Si un troisième signal arrive, tu
perds un cycle. Puisque des signaux temps-réel sont disponibles
utilise-les !



Non, c'est bien le problème. Dans mon cahier des charge, j'ai la
portabilité NetBSD et ces signaux ne sont pas disponibles sous
NetBSD (peut-être dans le 5.0).

Pour revenir à l'empilement de signaux standard, je ne vois toujours
pas pourquoi en autorisant le déclanchement d'un signal depuis son
propre gestionnaire, je peux en rater (option NO_DEFER).

Maintenant si tu as des données de taille variable à transmettre tu peux
utiliser des sockets unix et passer des messages avec une entete
rensiegnant la taille.
++



Pas possible non plus (mais ce serait trop long à expliquer ici).
J'ai résolu le problème avec un pipe et un coup de pselect().

Cordialement,

JKB

--
Le cerveau, c'est un véritable scandale écologique. Il représente 2% de notre
masse corporelle, mais disperse à lui seul 25% de l'énergie que nous
consommons tous les jours.