Mise en pratique des tests sur l’utilisation de la mémoire
Il est utile de bien connaître l’état d’évolution de la mémoire dans son programme. Les dérives d’allocation de la mémoire sont une source classique de crache informatique. Ce chapitre donne quelques pistes permettant d’observer et de corriger ce type de problèmes.
Sondes d’état mémoire
Pour surveiller l’état de la mémoire, nous allons créer des Sondes.
C’est des classes qui permettent de stocker l’état de la mémoire à un instant donné et de le comparé après une opération.
Bien entendu ces fonctions ne doivent pas impacter les résultats et donc sont hors des champs analysés.
Nous désirons surveiller le tas et les piles des threads.
Ces sondes sont créer en début de programme si possible et permettent de connaître:
- la variation de mémoire entre deux instants,
- la valeur actuelle occupée et libre,
- la valeurs maximale d’occupation atteinte jusqu’à cet instant.
Pour réaliser ces sondes, vous pouvez vous inspirez des exemples de Memory tracing et Mbed statistics de Mbed OS. La classe Thread contient également des méthodes d’état de la pile du thread. Il ne faut pas hésiter non plus à entrer dans le code pour bien comprendre les méthodes et classes comme par exemple mbed_stats.h.
Noter que les fonctions statistiques de Mbed OS ajoutent 8 bytes à chaque allocation mémoire sur le tas.
Question
Comment réaliser et tester les Sondes ?
Les threads
Le premier cas d’analyse porte sur la création et l’usage des threads. Reprenez par exemple les exercices en fin du chapitre sur l’utilisation de la mémoire dynamique et ajouter y vos Sondes intelligemment afin d’observer les états mémoire du tas. Répéter le même exercice en suivant les consignes de l’exercice suivant sans le MemoryPool (vous pouvez au choix utiliser des allocations C ou CPP).
Question
Pourquoi nous n’avons qu’un seul tas (heap
) dans Mbed OS et une pile (stack
) par thread ?
A quel moment la pile de thread est créée sur le tas ?
Pourquoi ne peut-on pas redémarrer un thread une fois arrêté ?
Les piles
Le deuxième cas d’analyse porte sur les piles. Pour cela, nous pouvons par exemple implémenter des algorithmes récursifs dangereux qui mangent la pile.
Vous pouvez réaliser dans un thread
- les tours de Hanoi :
void hanoi(const char* src, const char* dst, const char* tmp, int n)
{
static uint32_t maxStack = 0;
uint16_t stackValue = th.used_stack();
if (stackValue > maxStack){
maxStack = stackValue;
}
if(n == 1)
{
printf("Move %s --> %s : %ld\n", src, dst, maxStack);
}
else
{
hanoi(src, tmp, dst, n-1); // sous-solution problème a
hanoi(src, dst, tmp, 1); // sous-solution problème b
hanoi(tmp, dst, src, n-1); // sous-solution problème c
}
}
// appel
hanoi("a", "c", "b", 15);
- ou la suite de Fibonacci :
int fib(int n)
{
if (n <= 1)
return n;
return fib(n-1) + fib(n-2);
}
En regardant l’évolution de la pile grace à vos Sondes et à la taille de la pile, vous devriez pouvoir prédire à partir de quelle valeur votre programme va “cracher”.
Questions
Où définisez-vous la taille des piles pour les threads ?
Est-il possible d’avoir des tailles de piles différentes pour chaque thread ?
Les objets sur la pile de thread
Une initialisation telle que présentée dans le code ci-dessous va manger sur une pile.
SensorDataServer::SensorDataServer(DataMailBox& dataMailBox)
: dataMailBox_(dataMailBox),
sensor_(ClickBoard::SDA, ClickBoard::SCL, 0xEC),
eventQueue_(),
thread_()
{
sensor_.init();
}
Question
Observez l’état des piles des threads et du tas avant et après cette initialisation et déterminez sur quelle pile sont initialisés les objets dataMailBox_
à thread_
et de quelle est l’occupation de la pile et du tas résiduelle après un appel similaire.