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

[C/Unix] Problème de copie à l'écriture avec fork()

13 réponses
Avatar
JKB
Bonjour à tous,

Je butte sur un problème qui doit être idiot. J'ai un programme
écrit en C sous Unix qui a une fuite de mémoire assez conséquente
lors d'un fork (ça peut faire quelques Mo).

Considérons une processus père qui effectue une boucle. Dans cette
boucle, il fait un fork() et continue ses traitements dans lesquels
il y a des allocations de gros blocs de mémoire.

Le fils est fichu de voir ces allocations alors qu'elles ont lieu
_après_ le fork().

Grossièrement, l'algorithme est le suivant :

while(1)
{
ios = fork();

if (ios = 0)
{
// plein de trucs tordus
exit(0);
}
else
{
// sleep();
mem = malloc();
// plein de trucs abscons
free(mem);
}
}

et je récupère le pointeur mem dans le processus fils ! Je ne vois
qu'une seule explication, le fork() en question est une copie à
l'écriture pour le fils et se fait entre le malloc() et le free().
Ou alors, le free() est reporté à plus tard et s'exécute en tâche de
fond lors de l'appel à fork(). Il faut noter que je ne
récupère ce pointeur qu'après quelques itérations. Je suis incapable
de le recevoir lors de la première, mais je ne sais pas si c'est
significatif.

Le père n'a _aucune_ fuite de mémoire (testé dans tous les sens avec
valgrind et des outils fait maison). La fuite n'apparaît que dans
les fils.

Si je rajoute un sleep() pour bloquer le père durant quelques
dixièmes de secondes, la fuite de mémoire disparaît dans les fils.
Il y a un thread parallèle au père mais il ne fait strictement
aucune allocation et j'ai testé avec hellgrind qui ne m'a trouvé
aucune race condition.

Est-ce que l'un d'entre vous aurait une idée pour forcer la copie
dès le fork() et éliminer ce problème ? Je ne suis pas sûr que ça
vienne de là, mais je préfère ôter ce doute avant de chercher
ailleurs.

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.
=> http://grincheux.de-charybde-en-scylla.fr

10 réponses

1 2
Avatar
espie
In article ,
JKB wrote:
while(1)
{
ios = fork();

if (ios = 0)


^
Bon, je sais que c'est un exemple, mais j'espere que tu n'as pas la meme
faute stupide dans le programme lui-meme...
Avatar
JKB
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Marc Espie ?crivait dans fr.comp.lang.c :
In article ,
JKB wrote:
while(1)
{
ios = fork();

if (ios = 0)


^
Bon, je sais que c'est un exemple, mais j'espere que tu n'as pas la meme
faute stupide dans le programme lui-meme...



Non, c'est un simple exemple que je n'ai même pas compilé parce que
le programme en lui-même est _beaucoup_ plus long.

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.
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Marc Boyer
Le 17-06-2010, JKB a écrit :
Bonjour à tous,

Je butte sur un problème qui doit être idiot. J'ai un programme
écrit en C sous Unix qui a une fuite de mémoire assez conséquente
lors d'un fork (ça peut faire quelques Mo).



Et ça dure combien de temps ?
free est asynchrone en général. Est-ce que ça ne vient pas
simplement du fait que chaque fils ne vis pas assez longtemps pour
que l'OS décide de libérer vraiment la mémoire ?

et je récupère le pointeur mem dans le processus fils !



Que veux-tu dire ? Il est normal, dès la deuxième itération de
la boucle, que les fils voient mem != NULL.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
JKB
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Marc Boyer ?crivait dans fr.comp.lang.c :
Le 17-06-2010, JKB a écrit :
Bonjour à tous,

Je butte sur un problème qui doit être idiot. J'ai un programme
écrit en C sous Unix qui a une fuite de mémoire assez conséquente
lors d'un fork (ça peut faire quelques Mo).



Et ça dure combien de temps ?



Je ne saisis pas la question.

free est asynchrone en général. Est-ce que ça ne vient pas
simplement du fait que chaque fils ne vis pas assez longtemps pour
que l'OS décide de libérer vraiment la mémoire ?



Si je colle un sleep dans le fils, le résultat est identique.

et je récupère le pointeur mem dans le processus fils !



Que veux-tu dire ? Il est normal, dès la deuxième itération de
la boucle, que les fils voient mem != NULL.



Abus de langage : je récupère un pointeur valide (sur lequel je peux
faire un free() sans que valgrind ne râle).

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.
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Dominique MICOLLET
JKB wrote:

Un problème de fuite



Il semble que mem soit déclaré en dehors de la boucle while : il est donc
normal que le second fork et ses suivants voient dans mem une valeur qui a
pointé à une époque sur un bloc alloué et qui ne devrait plus l'être.



Par contre je ne comprends pas qu'il y ait une fuite mémoire dans ce cas,
puisque les deux processus devraient travailler dans des zones mémoires
différentes.


Ceci étant dit, le man de fork sous linux dit qu'il travaille en copy on
write : je ne suis pas très sûr de ce que ça implique mais il se pourrait
bien que mem ne soit pas véritablement dupliqué.... Dans cette même doc, il
est écrit que fork est en fait réalisé à partir de clone, qui sert
normalement à faire du multiflots, pour lequel l'espace est partagé.
Donc ce que vous constatez est peut-être "normal" au vu de l'implémentation.

Compte tenu des allocations/desallocations, essayez de déclarer mem dans le
bloc else du processus parent, ou forcez le à NULL après la désallocation.
Essayez aussi éventuellement de forcer mem à NULL (voire n'importe quoi)
dans le processus fils : si j'ai bien compris le concept de copy-on-write,
les fuites devraient disparaître.

Précisez éventuellement la plateforme sur la laquelle vous travaillez.

Cordialement.
--
Dominique MICOLLET
Adresse email : enlever deux francs
Avatar
Marc Boyer
Le 17-06-2010, JKB a écrit :
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Marc Boyer ?crivait dans fr.comp.lang.c :
Le 17-06-2010, JKB a écrit :
Bonjour à tous,

Je butte sur un problème qui doit être idiot. J'ai un programme
écrit en C sous Unix qui a une fuite de mémoire assez conséquente
lors d'un fork (ça peut faire quelques Mo).



Et ça dure combien de temps ?



Je ne saisis pas la question.



Ben, mon propos, c'est qu'une fuite mémoire, c'est difficile à
identifier. faire free(p), c'est dire à l'environnement (libc, OS), qu'on
a plus besoin de cette mémoire, et qu'il peut la récupérer. Mais
il ne dit pas quand.
Une fuite mémoire, tu la détectes souvent parce que la consommation
mémoire du processus ne cesse de monter, alors que le code est
"sensé" avoir une consommation bornée. Comme tes fils avaient
(d'après le code) une vie bornée, elle pouvait être insuffisante
pour que l'OS se décide à vraiment libérer la mémoire.

Une hypothèse (avec les quelques explications fournies)
pouvait être que chaque fils était crée avec disons X Ko
de mémoire déclarée inutile par free, mais que la durée
de vie de chacun étant trop faible, ils n'étaient jamais
réellement récupéré (avant le exit(0)).
Si n fils vivent en parallèle, on a toujours X*n Ko
de mémoires libérées mais pas récupérés.

free est asynchrone en général. Est-ce que ça ne vient pas
simplement du fait que chaque fils ne vis pas assez longtemps pour
que l'OS décide de libérer vraiment la mémoire ?



Si je colle un sleep dans le fils, le résultat est identique.



Faudrait plus de connaissance que moi sur le fonctionnement
de l'allocateur pour savoir si sleep peut avoir une
influence.

et je récupère le pointeur mem dans le processus fils !



Que veux-tu dire ? Il est normal, dès la deuxième itération de
la boucle, que les fils voient mem != NULL.



Abus de langage : je récupère un pointeur valide (sur lequel je peux
faire un free() sans que valgrind ne râle).



C'est peut-être valgrind qui a un bug, qui gère mal les fork ?

Si tu es sous Linux (problable puisque valgrind), tu peux jouer
avec MALLOC_CHECK_ pour voir si la libc considère elle aussi
que le free dans le fils est valide.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
JKB
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Dominique MICOLLET ?crivait dans fr.comp.lang.c :
JKB wrote:

Un problème de fuite



Il semble que mem soit déclaré en dehors de la boucle while : il est donc
normal que le second fork et ses suivants voient dans mem une valeur qui a
pointé à une époque sur un bloc alloué et qui ne devrait plus l'être.



C'est exactement ça. J'ai surchargé malloc() par un truc à ma sauce
et le bloc reste alloué.

Par contre je ne comprends pas qu'il y ait une fuite mémoire dans ce cas,
puisque les deux processus devraient travailler dans des zones mémoires
différentes.



Nous sommes bien d'accord et c'est mon problème.

Ceci étant dit, le man de fork sous linux dit qu'il travaille en copy on
write : je ne suis pas très sûr de ce que ça implique mais il se pourrait
bien que mem ne soit pas véritablement dupliqué.... Dans cette même doc, il
est écrit que fork est en fait réalisé à partir de clone, qui sert
normalement à faire du multiflots, pour lequel l'espace est partagé.
Donc ce que vous constatez est peut-être "normal" au vu de l'implémentation.

Compte tenu des allocations/desallocations, essayez de déclarer mem dans le
bloc else du processus parent, ou forcez le à NULL après la désallocation.
Essayez aussi éventuellement de forcer mem à NULL (voire n'importe quoi)
dans le processus fils : si j'ai bien compris le concept de copy-on-write,
les fuites devraient disparaître.



Ce que j'ai écrit plus haut est un exemple minimal. Dans la pratique, je
n'ai aucun moyen de faire cela.

Précisez éventuellement la plateforme sur la laquelle vous travaillez.



Je teste actuellement avec un Linux/debian/amd64 avec valgrind. Le
problème apparaît aussi sour Solaris/sparc (ou un problème
similaire).

En fait, pour éviter d'incriminer le copy-on-write, le mieux serait
de forcer la copie des pages dès le fork, mais je n'ai rien trouvé
dans ce sens.

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.
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
JKB
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Marc Boyer ?crivait dans fr.comp.lang.c :
Le 17-06-2010, JKB a écrit :
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Marc Boyer ?crivait dans fr.comp.lang.c :
Le 17-06-2010, JKB a écrit :
Bonjour à tous,

Je butte sur un problème qui doit être idiot. J'ai un programme
écrit en C sous Unix qui a une fuite de mémoire assez conséquente
lors d'un fork (ça peut faire quelques Mo).



Et ça dure combien de temps ?



Je ne saisis pas la question.



Ben, mon propos, c'est qu'une fuite mémoire, c'est difficile à
identifier. faire free(p), c'est dire à l'environnement (libc, OS), qu'on
a plus besoin de cette mémoire, et qu'il peut la récupérer. Mais
il ne dit pas quand.
Une fuite mémoire, tu la détectes souvent parce que la consommation
mémoire du processus ne cesse de monter, alors que le code est
"sensé" avoir une consommation bornée. Comme tes fils avaient
(d'après le code) une vie bornée, elle pouvait être insuffisante
pour que l'OS se décide à vraiment libérer la mémoire.

Une hypothèse (avec les quelques explications fournies)
pouvait être que chaque fils était crée avec disons X Ko
de mémoire déclarée inutile par free, mais que la durée
de vie de chacun étant trop faible, ils n'étaient jamais
réellement récupéré (avant le exit(0)).
Si n fils vivent en parallèle, on a toujours X*n Ko
de mémoires libérées mais pas récupérés.

free est asynchrone en général. Est-ce que ça ne vient pas
simplement du fait que chaque fils ne vis pas assez longtemps pour
que l'OS décide de libérer vraiment la mémoire ?



Si je colle un sleep dans le fils, le résultat est identique.



Faudrait plus de connaissance que moi sur le fonctionnement
de l'allocateur pour savoir si sleep peut avoir une
influence.

et je récupère le pointeur mem dans le processus fils !



Que veux-tu dire ? Il est normal, dès la deuxième itération de
la boucle, que les fils voient mem != NULL.



Abus de langage : je récupère un pointeur valide (sur lequel je peux
faire un free() sans que valgrind ne râle).



C'est peut-être valgrind qui a un bug, qui gère mal les fork ?

Si tu es sous Linux (problable puisque valgrind), tu peux jouer
avec MALLOC_CHECK_ pour voir si la libc considère elle aussi
que le free dans le fils est valide.



C'est assez difficile à faire puisque le problème ne se reproduit
pas à chaque coup...

D'un autre côté, j'ai écrit une surcharge à malloc() et free() qui
montre à peu près le même problème.

Je continue à chercher,

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.
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
Antoine Leca
JKB a écrit :
while(1)
{
ios = fork();

if (ios = 0)
{
// plein de trucs tordus



La fuite apparaît ici, exact ? Et jamais au premier tour ?

exit(0);
}
else
{
// sleep();
mem = malloc();



Au premier tour : allongement du tas alloué par l'OS.
Aux autres : on ne bouge plus, on récupère avec l'allocateur C.

// plein de trucs abscons
free(mem);
}

Est-ce que l'un d'entre vous aurait une idée pour forcer la copie
dès le fork() et éliminer ce problème ?



mem = 0;
juste avant le fork()

Autre idée, faire suivre le free() avec mem=0;


Antoine
Avatar
JKB
Le 17-06-2010, ? propos de
Re: [C/Unix] Problème de copie à l'écriture avec fork(),
Antoine Leca ?crivait dans fr.comp.lang.c :
JKB a écrit :
while(1)
{
ios = fork();

if (ios = 0)
{
// plein de trucs tordus



La fuite apparaît ici, exact ? Et jamais au premier tour ?



Exact.

exit(0);
}
else
{
// sleep();
mem = malloc();



Au premier tour : allongement du tas alloué par l'OS.
Aux autres : on ne bouge plus, on récupère avec l'allocateur C.

// plein de trucs abscons
free(mem);
}



Est-ce que l'un d'entre vous aurait une idée pour forcer la copie
dès le fork() et éliminer ce problème ?



mem = 0;
juste avant le fork()

Autre idée, faire suivre le free() avec mem=0;



Je vais essayer ça...

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.
=> http://grincheux.de-charybde-en-scylla.fr
1 2