Débogage des applications multicœurs en C ANSI avec LabWindows/CVI

Aperçu

L’avènement des processeurs multicœurs offre des avantages significatifs comparés aux architectures monocœurs traditionnelles, comme par exemple un débit et des performances accrus, une puissance de traitement plus économique et une consommation réduite, autant de caractéristiques primordiales pour les systèmes de test et les matériels embarqués actuels. Hormis ces atouts, la complexité matérielle supplémentaire soulève de nouveaux problèmes en matière de développement matériel. Ainsi, l’exécution de tâches en parallèle fait apparaître les défauts de conception qui passent inaperçus dans les applications monothread. Cela est particulièrement vrai si le comportement correct de l’application dépend de la synchronisation précise de l’exécution, de l’accès à la mémoire et de la communication entre plusieurs tâches. Pour relever ces défis, LabWindows/CVI met à disposition non seulement des fonctions optimisées de synchronisation et d’élaboration de threads, mais aussi des outils et options avancés qui simplifient grandement la résolution des problèmes et l’optimisation d’applications multicœurs. Ce tutorial offre un aperçu des outils de LabWindows/CVI conçus pour mettre au point des applications multicœurs.

Contenu

Introduction au multithread dans LabWindows/CVI

Le logiciel NI LabWindows/CVI supporte nativement la création d'applications multithread depuis le milieu des années 90. Désormais, avec l'adoption des unités centrales multicœurs, vous pouvez utiliser ce dernier pour profiter pleinement des caractéristiques des technologies multithread.

La Multithreading Library (bibliothèque multithreading) de LabWindows/CVI offre de nombreuses optimisations comparées à l'API de threading du Windows SDK :

  • Les objets de type Thread pools vous aident à concevoir les fonctions qui doivent s'exécuter dans des threads séparés. Ces pools de threads gèrent un cache afin de minimiser le temps système passé à la création et à la destruction de threads.
  • Les objets de type Thread-safe queues simplifient le transfert de données entre threads. Un thread peut lire depuis une file d'attente alors qu'un autre écrit dans la même file.
  • Les Thread-safe variables combinent une section critique et n'importe quel type de données. Vous invoquez une seule fonction pour acquérir la section critique, fixer la valeur de la variable et relâcher la section critique.
  • Les objets de type Thread locks simplifient l'utilisation d'une section critique ou d'un mutex en offrant une API homogène et en choisissant automatiquement le mécanisme approprié si nécessaire. Par exemple, LabWindows/CVI utilise automatiquement un mutex si le verrou doit être partagé entre plusieurs processus ou si les threads ont besoin de traiter des messages en attendant le verrou. Une section critique est utilisée dans d'autres cas parce qu'elle est alors plus efficace.
  • Les Thread-local Variables offrent des instances de variables par thread. Le système d'exploitation limite normalement le nombre de ces variables pour chaque processus. L'implémentation dans LabWindows/CVI optimise l'usage de ces variables en n'instanciant qu'une thread-local variable de processus pour toutes les thread-local variables de votre programme.

Vous pouvez trouver toutes les fonctions multithread du Library Tree de LabWindows/CVI dans Utility Library»Multithreading.

 

Pour en savoir plus :
Tutorial détaillé sur le multithreading dans LabWindows/CVI

 

Déboguer l'application

Comme avec des applications monothread, bien connaître le comportement de votre application pendant qu'elle fonctionne est tout à fait primordial. Au moment de créer des applications multithread, il est important de suivre l'exécution non pas d'un thread mais de tous les threads au fur et à mesure qu'ils évoluent en parallèle. Des outils de mise au point au niveau fonctionnel tels que des Watch, des points d'arrêt et des Stack Trace sont disponibles au moment de la mise au point des applications multithread.

En particulier, vous pouvez utiliser la fenêtre Threads de LabWindows/CVI pour visualiser des informations par thread. La fenêtre Threads liste tous les threads du programme qui font l'objet d'une mise au point.

 

Figure n°1 Avec la fenêtre Threads de LabWindows/CVI, vous pouvez facilement passer d'un thread à l'autre lors de la phase de débogage.

 

Vous pouvez utiliser cette boîte de dialogue pour choisir les threads dont vous souhaitez visualiser les variables locales et la pile d'appels. Lorsque vous sélectionnez un thread et cliquez sur View, LabWindows/CVI affiche les variables locales pour le thread sélectionné dans la fenêtre Variables ainsi que la position dans le code source. Les commandes Up Call Stack, Down Call Stack et Call Trace du menu Run affichent alors les informations relatives au thread sélectionné.

Figure n°2 LabWindows/CVI affiche les variables locales pour le thread sélectionné dans la fenêtre Variables ainsi que la position actuelle dans le thread dans la fenêtre Source.

Introduction au Toolkit NI Real-Time Execution Trace 2.0

Pouvoir accéder à des informations sur l'exécution bas niveau est primordial afin d'optimiser et de mettre au point des applications temps réel. En effet, c'est à cette condition que vous pouvez identifier les sources de jitter telles que l'affinité du processeur, les allocations de mémoire, l'héritage de priorité ou les "race conditions". Si vous développez une application temps réel, le meilleur moyen de contrôler de manière fine l'utilisation de l'unité centrale, la synchronisation de l'exécution et d'autres événements est de capturer les traces d'exécution depuis la cible temps réel. Avec le Toolkit Real-Time Execution Trace 2.0, qui supporte à la fois le Module LabWindows/CVI Real-Time et le Module LabVIEW Real-Time, vous pouvez visualiser et analyser les traces d'exécution des tâches temps réel y compris les fonctions et les threads du système d'exploitation sur des systèmes mono ou multicœurs. Avec le résultat des traces, vous pouvez alors trouver des points actifs dans votre code et détecter des comportements indésirables tels que : contention de ressources, famine de threads, problème d'allocation mémoire ou inversions de priorité de threads. De plus, les traces permettent de contrôler très finement le comportement de la synchronisation et l'utilisation du processeur. Les versions 8.5 et ultérieures de LabWindows/CVI Real-Time incluent une version d'évaluation gratuite de 30 jours du Toolkit Real-Time Execution Trace. Choisissez Tools>>Real-Time Execution Trace Tool pour afficher le Toolkit Real-Time Execution Trace.

Le Toolkit Real-Time Execution Trace se compose de deux parties : les fonctions de la classe Execution Trace que l'on retrouve dans la bibliothèque Utility Library de LabWindows/CVI Real-Time. Ensuite, il y a l'utilitaire de visualisation des traces proprement dites (Trace Viewing Utility). Vous ajoutez des fonctions de la bibliothèque Execution Trace autour du code dont vous souhaitez tracer l'exécution, lancez l'application temps réel, puis enfin, utilisez le Trace Viewing Utility pour afficher les traces d'exécution.

 

Figure n°3. Trouvez les fonctions de la classe Execution Trace dans la bibliothèque Utility.

 

 

Figure n°4. Vous pouvez tracer non seulement les fonctions définies par l'utilisateur mais aussi les fonctions des bibliothèques de LabWindows/CVI. Activez ces paramètres dans le menu Build Options.

Pour en savoir plus :
Vidéos et tutoriaux sur le Toolkit NI Real-Time Execution Trace

Vérifier le comportement de la synchronisation et les priorités des threads

Avec le Toolkit Real-Time Execution Trace, vous visualisez le comportement de l'application tant au niveau des threads que des fonctions. Ce dernier affiche en fonction du temps toutes les données de toutes les fonctions ainsi que tous les threads exécutés sur la cible temps réel. Le Toolkit Real-Time Execution Trace affiche l'échelle de temps dans la vue sélectionnée, ce qui aide à comprendre la façon dont les threads interagissent. En outre, l'activité des fonctions et des threads est identifiée par différentes couleurs afin de distinguer la priorité d'exécution de chaque événement. Avec la capacité de zoomer sur des zones spécifiques de la trace d'exécution, vous localisez très précisément les problèmes de performances de votre application.

Figure n°5. La vue de type Function affiche des informations détaillées sur le temps et la priorité d'exécution pour chaque fonction définie par l'utilisateur.

Identifier les ressources partagées et les allocations mémoire

L'utilisation de ressources partagées dans un thread temps critique (un thread qui doit s'exécuter dans une période de temps donnée) peut introduire un jitter dans votre application temps réel. Des événements systèmes tels que la mise en sommeil, des appels du gestionnaire de mémoire et des mutex de ressources, introduisent des jitters. Ceci dit, le Toolkit Real-Time Execution Trace détecte ces événements et affiche, dans la vue Threads, un drapeau suivi d'une ligne en pointillés afin d'indiquer à quel instant est apparue l'occurrence de tel ou tel événement système.

Le Toolkit Real-Time Execution Trace détecte les événements système suivants :

  • Sleep – a lieu lorsqu'un thread dort afin de permettre à des threads de priorité plus basse de s'exécuter.
  • Wait For Object – a lieu lorsqu'un thread attend une ressource de bas niveau, tel un événement ou un mutex, avant de s'exécuter.
  • Wait For Memory – a lieu lorsqu'un thread entreprend une opération de gestion de mémoire. Le "span" est la quantité de temps nécessaire pour conclure l'opération en question. Par défaut, le Toolkit Real-Time Execution Trace affiche ces événements en utilisant un drapeau vert.
  • Priority Inheritance – a lieu lorsqu'un thread hérite de la priorité d'un thread de priorité supérieure parce que le thread de priorité basse accède à une ressource partagée requise par le thread de priorité haute.

Figure n°6. Les événements système sont marqués par des drapeaux de couleur.

Par exemple, lorsqu'une boucle de priorité normale est en possession d'une ressource partagée, comme un thread lock par exemple, les autres threads, y compris les threads temps critique qui essaient d'acquérir le lock en question, doivent attendre que la ressource partagée soit libérée. Dans ce cas-là, un jitter est inévitablement introduit dans le thread temps critique. C'est une très mauvaise chose et pour y remédier, National Instruments conseille de ne pas utiliser de ressources partagées dans les boucles temps critique.

Le code suivant crée un thread haute priorité et un thread de priorité normale. Le thread de priorité normale acquiert un verrou avant le thread de priorité haute. Lorsqu'un thread de haute priorité essaie d'acquérir le verrou, il doit attendre que le thread de priorité normale relâche le verrou.

RTMain

 __declspec (dllexport) void CVIFUNC_C RTmain (void)
{


CmtNewLock (NULL, OPT_TL_PROCESS_EVENTS_WHILE_WAITING, &lock);

/* schedule the thread functions */
CmtScheduleThreadPoolFunctionAdv (pool, NormalPriorityLoop, NULL,
THREAD_PRIORITY_NORMAL, NULL, 0, NULL, 0, &functions[0]);

CmtScheduleThreadPoolFunctionAdv (pool, TimeCriticalLoop, NULL,
THREAD_PRIORITY_TIME_CRITICAL, NULL, 0, NULL, 0, &functions[1]);

}


NormalPriorityLoop

  static int CVICALLBACK NormalPriorityLoop (void *functionData)
{
CmtGetLock(lock);
Sleep(3);
CmtReleaseLock(lock);
return 0;
}


TimeCriticalLoop

   static int CVICALLBACK NormalPriorityLoop (void *functionData)
{
Sleep(1);
CmtGetLock(lock);
Sleep(3);
CmtReleaseLock(lock);
return 0;
}

Le Toolkit Real-Time Execution Trace représente le thread temps critique (voir l'objet nommé "4 : LabWindows/CVI Thread Pool Thread 3 ci-dessous) qui attend le thread de basse priorité (1 : LabWindows/CVI Thread Pool Thread 3) pour relâche un verrou, ce qui peut potentiellement conduire à un déterminisme moindre de l'application.

Figure n°7. Comme indiqué par la zone mise en surbrillance, le Toolkit Real-Time Execution Trace présente le thread temps critique qui attend le thread de priorité normale pour relâcher une ressource partagée.

Visualiser l'affinité du processeur et suivre les interactions multicœurs

Selon un sondage organisé par Virtutech interrogeant les participants à la Conférence 2007 sur les systèmes embarqués (San José, Californie), 59 % des sondés ont déclaré que leurs outils de mise au point ne supportaient pas le développement multicœur ou multiprocesseur. D'un autre côté, le Toolkit Real-Time Execution Trace inclut une option Highlight CPU Mode que vous pouvez utiliser pour mettre en avant l'activité des threads qui s'exécute sur un processeur en particulier. En visualisant l'activité des threads sur un CPU particulier, vous pouvez suivre le chemin d'exécution de chaque unité centrale dans le système afin de déterminer si oui ou non les threads s'exécutent comme prévu initialement. En outre, en affichant l'utilisation des processeurs, vous pouvez anticiper le potentiel en termes de performances de différentes conceptions en fonction de la répartition de telle ou telle portion de code sur tel ou tel processeur.

Figure n°8. Choisissez une unité centrale particulière dans les options Highlight CPU Mode pour souligner l'activité de tous les threads qui se sont exécutés sur cette dernière.

 

Voir aussi :
Multitraitement symétrique sur Windows et Real-Time avec LabWindows/CVI

Ressources supplémentaires sur la programmation et la mise au point multicœur en C ANSI

LabWindows/CVI dispose de plusieurs fonctionnalités qui simplifient le débogage d'applications multicœurs. Vous pouvez utiliser la vue Threads, la fenêtre Watch et la fenêtre Variable pour contrôler visuellement les fonctions au fur et à mesure qu'elles s'exécutent sur différents cœurs. En outre, le Toolkit Real-Time Execution Trace facilite la transition vers des processeurs multicœurs des applications temps réel en simplifiant la résolution des problèmes et l'optimisation de la conception. Parcourez les ressources suivantes pour en apprendre plus sur la façon de créer des applications C ANSI multithread qui profitent des architectures multicœurs.


Série de tutoriaux sur les principes fondamentaux de la programmation multicœur


 

La marque LabWindows est utilisée sous licence Microsoft Corporation.