Question:
Pourquoi les interruptions devraient-elles être courtes dans un système bien configuré?
oneat
2019-11-26 20:32:09 UTC
view on stackexchange narkive permalink

Pourquoi les interruptions devraient-elles être courtes?

J'utilise actuellement STM32 et il a une fonction de hiérarchisation des interruptions.

Pour cette raison, je ne vois aucune différence entre ces deux options:

  • la boucle principale et les interruptions
  • une grosse interruption avec une priorité très faible et les autres interruptions

Pourquoi devrions-nous les garder courts?Est-ce à cause de la mise en cache?Petite pile?Ou autre chose?Pourquoi ne pouvons-nous pas simplement créer tout le système lors des interruptions et mettre la boucle principale en veille?

Des idées?

Les IRQ critiques sont utilisées pour synchroniser les activités de la machine d'état, pas pour faire des choses asynchrones.Gardez une trace de la hauteur de la pile.Si le travail dépasse la puissance de traitement uC disponible, il se désynchronise en raison du débordement.
Une * interruption * ne peut pas être courte;c'est un signal à un moment donné.Ne voulez-vous pas dire * "temps de traitement des interruptions" * (heure), * "temps de service d'interruption" * (heure), * "routines de service d'interruption (ISR)" * (lignes de code, probablement exécutées rapidement), * "ISR"* (lignes de code, probablement exécutées rapidement) ou similaire?Pouvez-vous résoudre ce problème (en [modifiant votre question] (https://electronics.stackexchange.com/posts/469121/edit))?
@PeterMortensen: "interruption" est un raccourci assez valable pour la gestion des * interruptions *, du moins parmi les personnes qui comprennent ce que tout cela signifie.Votre commentaire est un bon résumé.Ou vous pouvez penser à l'interruption en termes de * interruption * du code principal qui sera finalement retourné après ISR.(À moins que nous ne décidions de changer de contexte et de ne revenir à ce point dans l'espace utilisateur que beaucoup plus tard, alors le temps ISR par rapport au retard de cette tâche n'est pas proche.) Le processus de livraison d'une interruption prend également quelques cycles;vous pouvez simplement appeler cette surcharge inévitable pour un gestionnaire.
Onze réponses:
#1
+29
old_timer
2019-11-26 21:35:36 UTC
view on stackexchange narkive permalink

Non seulement il n'y a aucune raison pour que vous ne puissiez pas faire cela. Un système piloté par les événements n'est pas rare. MAIS. Sur de nombreuses architectures, si vous êtes dans une interruption, vous ne pouvez pas en avoir une autre, donc même une interruption de faible priorité peut dans certains systèmes empêcher une interruption de priorité plus élevée de se produire, donc la règle générale est d'entrer et de sortir rapidement. Souvent, une approche de conception est que l'interruption entre et effectue un certain traitement, mais laisse une tâche pour le noyau ou la tâche de premier plan à terminer, plus vue avec les systèmes d'exploitation.

Je pense que le Cortex-M peut imbriquer les interruptions, donc si l'une vient en plus d'une autre, elle est gérée. La plupart des puces ne sont pas liées à ARM, il n'y a donc aucune raison pour que le fournisseur de puces ne puisse pas mettre un gestionnaire d'interruption devant le Cortex-M et / ou un gestionnaire de priorité, en plus de ce qui est dans le bras fin des choses.

Néanmoins, une conception basée sur les interruptions doit être conçue. Comme pour toute autre conception, vous devez effectuer l'ingénierie de votre système. Vous devez connaître toutes les interruptions possibles (dans des situations normales, des instructions non définies, etc. sont fatales) et à quelle fréquence elles se produisent. Beaucoup seront des périodes régulières.

Vous devez également savoir combien de temps prend chaque interruption et planifier cela de telle sorte que vous puissiez vous assurer que toutes les interruptions seront traitées dans un laps de temps conforme à vos spécifications. Par exemple, un événement d'interruption basé sur une minuterie pour quelque chose prend 50 ms à traiter normalement, mais il existe une interruption aléatoire non périodique qui prend 20 ms. Est-ce que 70 ms sont tolérables pour l'un ou l'autre? La puce / le cœur a-t-il une solution prioritaire? Sinon, avez-vous défini vos priorités d'interruption de manière à pouvoir respecter le timing sur tout?

À la fin de la journée, il y a peu de différence entre toutes les interruptions et certaines interruptions et un certain premier plan en ce qui concerne la conception et le calendrier des interruptions; vous avez le même problème.

Donc, la règle générale est de rendre les interruptions simples, moyennes et rapides, mais la réalité est de faire de l'ingénierie de votre système et de savoir combien de temps vous avez pour chacun ou pour des groupes combinés.Cela doit prendre moins de x temps.

Linux appelle cette conception du premier paragraphe "la moitié supérieure / la moitié inférieure" de la gestion des interruptions.La "moitié supérieure" est le * vrai * ISR qui peut même ne pas parler au matériel du périphérique, mais juste faire la queue pour une moitié inférieure qui est planifiée comme n'importe quelle autre tâche.
#2
+16
MB.
2019-11-27 06:12:30 UTC
view on stackexchange narkive permalink

La réintégration est / devient un casse-tête majeur plus vos ISR sont complexes. L ' entrée wikipedia a une description assez succincte d'un ISR rentrant:

Un gestionnaire d'interruption réentrante est un gestionnaire d'interruption qui réactive les interruptions au début du gestionnaire d'interruption. Cela peut réduire la latence d'interruption. [6] En général, lors de la programmation des routines de service d'interruption, il est recommandé de réactiver les interruptions dès que possible dans le gestionnaire d'interruption. Cette pratique permet d'éviter de perdre des interruptions. [7]

En supposant que vous n'écrivez pas un système d'exploitation à usage général où la ré-entrée pourrait très bien être inévitable, vous devez garder à l'esprit la complexité supplémentaire d'avoir une ré-entrée dans votre code de contrôleur personnalisé personnalisé pourrait ne pas valoir la peine facilité d'écriture des ISR de longue durée paresseux.

Voici un exemple:

  • ISR de longue durée et de faible priorité commence à faire quelque chose, appelle une version de malloc que vous avez implémentée.
  • Au cours d'un appel malloc , vous êtes interrompu par un thread de priorité plus élevée: il appelle également malloc
  • Scénario A: ISR haute priorité quitte malloc avant la basse priorité
  • scénario b: ISR haute priorité sort de malloc après une priorité basse *

Ok, direz-vous, je vais mettre un verrou tournant sur malloc, alors tout ce que vous avez à faire est de répéter la condition ci-dessus à la primitive spinlock_aquire que vous avez créée: is il rentrant?

Je dois souligner ici que le simple fait de gifler des choses et de l'appeler un jour est une recette pour les blocages basés sur l'inversion de priorité. Ce n’est pas si simple.

La solution du pauvre homme à tous ces problèmes est de garder vos ISR courts. Par exemple, dans le noyau NT, il y a longtemps (pas tenu à jour), tout IRQL au-dessus des deux derniers n'était pas autorisé à même regarder la mémoire paginée. En effet, la pagination était gérée par une interruption ...

Le choix devient donc: implémenter un mécanisme de mise en file d'attente de base et faire répartir vos unités de travail ISR, ou faire en sorte que vos ISR se comportent librement, mais assurez-vous d'avoir un environnement extrêmement robuste qui ne s'étouffera pas sur des problèmes étranges (par exemple inversion de priorité).

Si votre tâche est super simple, comme allumer une lumière dans votre drone contrôlé par arduino, alors allez-y. Mais si vous voulez éviter les maux de tête d'ingénierie plus tard à mesure que votre code devient plus complexe, vous devriez vraiment éviter l'avantage perçu de ne vous donner aucune contrainte avec un ISR.


* clarification: le scénario b ne peut pas se produire à sa valeur nominale puisque l'ISR de priorité supérieure s'exécutera et se terminera toujours avant celui de priorité inférieure. Cependant, le chemin-code emprunté par l'un ou l'autre peut être échangé. Ainsi, dans la routine malloc elle-même, s'il y a un accès à une structure de données globale, ce code peut être interrompu de toutes les combinaisons possibles.

En plus de ce point, il convient de préciser que l'exigence de ré-entrée pour toute fonction concerne l'intégralité de son arbre d'appels. Cela devient très difficile à garantir si vous utilisez des bibliothèques tierces.

A noter également: pour répondre à un commentaire de @ user253751, la rentrée n'est pas résolue en enveloppant simplement les choses dans un verrou. Au contraire, la réintégration a un ensemble d ' exigences qui sont bien comprises. Voici quelques exemples de code triviaux qui illustrent le problème.

On peut voir, en regardant cela, que l'écriture d'une fonction réentante de malloc ou d'acquisition de ressources devient très difficile.

Mais les ISR réentrants ne sont pas courants - vous devez normalement activer explicitement les interruptions dans les ISR.
@PeterMortensen Le modèle "tout faire dans une interruption et dormir dans la boucle principale" de l'OP exige que vous le fassiez.
Un verrou n'aidera pas car le thread d'origine ne peut pas déverrouiller le verrou tant que l'interruption de haute priorité n'est pas terminée, ce qui ne le sera pas tant qu'il n'aura pas obtenu le verrou.
@user253751 C'est pourquoi j'ai dit que c'était une recette pour les blocages basés sur l'inversion de priorité.Soit dit en passant, les verrous rentrants existent.(Ils sont utiles pour le multitâche coopératif via des fibres ou du code asynchrone comme les futurs, etc.)
Les verrous rentrants ne protégeront pas votre `malloc` contre la corruption du tas.
Sur une machine monocœur, vous désactiveriez normalement simplement les interruptions au lieu de prendre un verrou, pour créer une petite section critique atomique.Parce qu'il ne s'agit pas seulement d'une inversion de priorité, cela ressemble plus à une fonction parent qui ne peut pas s'exécuter (et se déverrouiller) tant que vous n'avez pas terminé.Ainsi, empêcher les autres ISR de s'exécuter à des moments inopportuns fonctionne mieux, tant que vous ne laissez pas les interruptions désactivées trop longtemps.
Bien sûr, si vous envisagez d'obtenir des sections critiques à l'intérieur des ISR (en désactivant les interruptions), vous passerez un mauvais moment si vous souhaitez utiliser un processeur multicœur plus puissant.Avec la possibilité que les ISR fonctionnent sur les deux cœurs à la fois (à moins que vous ne limitiez la gestion des interruptions à un cœur?), Vous aurez besoin de grandes refontes.
@PeterCordes Souvent, il n'y a aucune raison d'aller vers un processeur multicœur plus puissant.Vous n'êtes pas obligé d'écrire votre code pour couvrir toutes les plates-formes matérielles imaginables, comme vous le faites lors de l'écriture de logiciels pour PC.
@user253751: C'est vrai, mais c'est quelque chose dont il faut être conscient si jamais vous souhaitez réutiliser une partie du code pour un système embarqué * différent *.Vous devez décider si les avantages de simplicité pour le projet actuel l'emportent sur la réutilisabilité du code.Ce sera souvent le cas car c'est un gros avantage.
Peut-être que je ne comprends pas de quoi vous (@user253751 et @PeterCodes) discutez.En supposant que nous ne "désactivions pas simplement les interruptions" (* étant donné * que la question initiale est d'avoir des ISR de longue durée), la ré-entrée est en effet ce qui est nécessaire pour protéger le tas.J'ai dit très clairement que gifler un verrou dessus et l'appeler un jour n'était pas la solution, mais même si c'était * était * la solution, cela imposerait désormais une exigence de ré-entrée sur le code de verrouillage. @user253751: êtes-vous en train de dire que les mallocs rentrants n'existent pas?
#3
+10
Peter Smith
2019-11-26 21:35:03 UTC
view on stackexchange narkive permalink

Pour un ordinateur à usage général, garder le gestionnaire d'interruption court permet au traitement normal d'être raisonnablement déterministe, ce qui peut ou non être un problème selon l'application.

Dans un processus embarqué en temps réel difficile (où le déterminisme est d'une importance critique), cela a beaucoup de sens.

J'ai implémenté un capteur d'inclinaison très précis (2 axes) où le lecteur du capteur était contrôlé par des minuteries et la boucle principale était en mode veille jusqu'à ce que le compteur déclenche l'interruption pour mettre à jour l'axe qui était piloté; le ADC externe que j'utilisais était également contrôlé par un minuteur (différent) pour minimiser la gigue d'acquisition pour les échantillons moyennés (ce qui peut causer des ravages dans un système de données échantillonné).

Pour conserver la possibilité de faire certains calculs dans une fenêtre de temps très étroite, les gestionnaires d'interruptions devaient être rapides (prendre l'interruption, faire le minimum requis et quitter).

Dans d'autres applications, toutes les choses intéressantes peuvent se produire dans les gestionnaires d'interruption (comme c'était le cas lorsque j'étais impliqué dans un système de vidéo à la demande avec jusqu'à 2000 flux simultanés disponibles).

Il n'y a pas de réponse unique pour les gestionnaires d'interruptions et dépend de l'application, bien que, comme indiqué, les interruptions imbriquées peuvent devenir compliquées, en particulier si deux gestionnaires doivent partager une seule ressource (ce qui peut entraîner une corruption des données si elles ne sont pas traitées. correctement et dans un système multitâche peut également conduire à une inversion de priorité).

L'approche adoptée pour interrompre les gestionnaires dépend de l'application, mais une première approche consistant à «les garder simples et rapides» est toujours un bon point de départ.

#4
+7
bta
2019-11-27 05:47:42 UTC
view on stackexchange narkive permalink

J'ai beaucoup traité ce genre de choses sur un projet précédent, et voici quelques choses que j'ai traitées (souvent apprises à la dure). Certains détails peuvent varier en fonction de l'architecture de votre puce et de votre proximité avec le bare metal, mais les idées générales s'appliquent toujours.

  • Au cours de la période précédant la suppression / la réactivation de l'interruption, une autre interruption non liée pourrait se déclencher. Vous avez peut-être mal agi en traitant une interruption de faible priorité lorsqu'une interruption de priorité critique est en attente. Certains gestionnaires d'interruption peuvent en fait avoir une priorité inférieure à celle des threads sans interruption, ce qui est une relation que vous ne pouvez pas implémenter si vous faites tout votre travail à l'intérieur du gestionnaire.
    • Cela devient encore plus problématique si vous partagez une interruption entre plusieurs sources. Une deuxième interruption peut être complètement masquée si vous n'avez pas encore terminé de traiter la première. Certaines architectures matérielles sont pires à ce sujet que d'autres.
  • Lorsque les interruptions proviennent de périphériques externes, ces périphériques peuvent avoir des attentes quant au temps qu'il vous faut pour répondre à leur interruption. J'ai eu des ISR complexes qui prenaient trop de temps à exécuter et le périphérique externe a supposé que j'étais mort et a cessé de me parler.
  • Sur certaines architectures, les ISR sont exécutés par-dessus la pile que le programme en cours d’exécution utilise (pour éviter d’allouer de la mémoire pour une pile distincte, ce qui pourrait bloquer, échouer ou déclencher des interruptions). Vous ne savez pas combien d'espace de pile vous allez avoir disponible, vous ne pouvez donc vraiment pas vous permettre de faire autre chose que d'effacer l'état d'interruption et de signaler autre chose pour faire le travail.
  • Certaines architectures exécutent les ISR à un niveau de privilège supérieur à celui des threads normaux. Ils peuvent avoir accès à des ressources matérielles de bas niveau qui sont normalement protégées. Cela implique qu'il peut y avoir des implications de sécurité à faire beaucoup de travail dans un ISR.
  • Les ISR ont la priorité la plus élevée dans votre système. L'appel d'une fonction qui bloque ou repose en interne sur des interruptions peut entraîner des blocages.
  • Certaines architectures utilisent des interruptions en interne, par exemple pour déclencher des vidages de cache entre les cœurs ou notifier l'ALU qu'un coprocesseur mathématique a terminé de calculer quelque chose. Le service de ces interruptions peut être bloqué pendant que vous êtes toujours à l'intérieur de votre ISR, donc passer trop de temps ici peut avoir des impacts non évidents sur les performances.
    • Une autre utilisation interne des interruptions pourrait être la communication avec les moteurs de débogage (JTAG, etc.). J'ai travaillé avec des puces où les sondes de débogage n'étaient pas très efficaces dans un ISR mais fonctionnaient très bien autrement. Le débogage du code ISR était un ordre de grandeur plus difficile que le débogage du code normal.
  • Certains processeurs multicœurs desserviront toujours les interruptions sur un cœur spécifique. Faire tout votre travail au sein de l'ISR peut forcer votre code à s'exécuter efficacement en monothread.

Heureusement, tous ces problèmes ont la même solution simple. Votre gestionnaire d'interruption doit stocker les informations nécessaires sur l'interruption, effacer et réactiver l'interruption, puis signaler un autre morceau de code pour effectuer le travail réel. C'est assez facile à faire sur la plupart des plates-formes qu'il n'y a vraiment pas beaucoup d'avantages à essayer d'entasser tout votre travail dans l'ISR lui-même.

"Certains gestionnaires d'interruption peuvent en fait avoir une priorité inférieure à celle des threads sans interruption, ce qui est une relation que vous ne pouvez pas implémenter si vous faites tout votre travail à l'intérieur du gestionnaire."Peut-être que je comprends mal cela, mais alors comment cette interruption se déclenche-t-elle?Peut-être que je ne comprends tout simplement pas ce qu'est un fil.
Les interruptions @DKNguyen- ont toujours la priorité sur les threads normaux, donc le thread est mis en attente et l'ISR s'exécute.Cependant, le travail que vous souhaitez effectuer en réponse à une telle interruption n'est pas aussi important, vous voulez donc le planifier pour qu'il s'exécute plus tard et revenir rapidement à la tâche importante que vous faisiez.
Je pense que vous avez mal compris par une question mal formulée.Je suppose que dans mon esprit, je considère un thread comme la boucle principale, ce qui n'est probablement pas le cas, car si quelque chose avait une priorité inférieure à la boucle principale, il ne fonctionnerait tout simplement jamais.
Il y a deux notions de priorité ici: la priorité technique (quel thread le CPU choisit d'exécuter) et la priorité du monde réel (le clignotement d'une LED n'est pas un gros problème, mais la boucle de contrôle de votre réacteur nucléaire l'est).Lorsque vous faites votre travail dans l'ISR, vous pouvez vous retrouver dans des cas où ces deux notions de priorité se désynchronisent et votre thread de contrôle du réacteur est bloqué en attendant que votre ISR ait fini de clignoter une LED.
oh, je vois ce que vous voulez dire: "Certains gestionnaires d'interruption peuvent en fait avoir une importance / urgence moindre que les threads sans interruption, ce qui est une relation que vous ne pouvez pas implémenter si vous faites tout votre travail à l'intérieur du gestionnaire."
* pour éviter d'allouer de la mémoire pour une pile séparée, ce qui pourrait bloquer, échouer ou déclencher des interruptions * - Je ne pense pas qu'un système qui exécute des interruptions sur une pile de noyau séparée * alloue * une nouvelle pile au sommet d'un ISR.Ce serait lent et insensé.Ce qu'il évite, c'est la nécessité pour chaque tâche de l'espace utilisateur d'avoir une "pile noyau" distincte utilisée pour les interruptions.(Ou juste une pile principale ou d'interruption dans un système plus simple, je suppose?).Ou du moins éviter de charger un nouveau pointeur de pile de quelque part, et de * déterminer * si nous utilisions déjà la pile d'interruption.
Attention: ma connaissance des threads n'est pas tout ce qu'elle pourrait être, donc je me trompe peut-être.Votre boucle principale s'exécute dans un thread.Il peut engendrer d'autres threads, qui s'exécutent indépendamment de lui.Pensez-y comme ceci: le gestionnaire (boucle principale) est le plus important.Ses subordonnés (fils supplémentaires) font le travail qu'il / elle leur dit de faire, indépendamment de ce qu'il / elle fait.Ils peuvent terminer leur travail avant le responsable, qui peut les vérifier (mais ce n'est pas obligatoire).Ils peuvent également communiquer des choses au gestionnaire qui modifient ce que fait le gestionnaire (interruptions), comme s'occuper de la correction d'erreurs ou d'autres tâches.
... Cependant, vous ne voulez pas que la communication prenne trop de temps car elle refuse aux autres la possibilité de communiquer avec le responsable ou, pire, deux subordonnés (threads) tentent de communiquer avec le responsable, ce qui entraîne des communications brouillées que le responsablene peut pas bien comprendre ni répondre à deux.(Pensez à avoir affaire à deux personnes qui vous parlent en même temps et s'interrompent.)
#5
+6
DKNguyen
2019-11-26 20:39:18 UTC
view on stackexchange narkive permalink

Parce que les interruptions empêchent d'autres interruptions de s'exécuter.Les garder courts signifie simplement ne rien faire de plus que ce dont vous avez besoin dans l'interruption.

une grosse interruption avec une priorité très faible et l'autre interruption

Cela nécessite des interruptions imbriquées (pour permettre aux interruptions de s'interrompre) qui peuvent devenir compliquées.Tous les processus ne peuvent pas être interrompus en toute sécurité.

Pour développer le point de DKNguyen: les interruptions imbriquées deviennent désordonnées parce que vous avez besoin de ce que l'on appelle [re-entrancy] (https://en.wikipedia.org/wiki/Reentrancy_%28computing%29). Pour les primitives de synchronisation et également les primitives d'allocation de ressources, cela peut être un ajout de complexité considérable.Par exemple, Windows NT 4 (à l'époque) était rentrant alors que je pense que Linux à l'époque ne l'était pas.En recherchant les mots, vous verrez la quantité de littérature qui leur est consacrée.Ce n'est pas une question triviale. Dans tous les cas, lorsque vous écrivez un contrôleur à partir de zéro, cette complexité supplémentaire peut ne pas être souhaitable.
@user247243 Cela me fait tourner la tête juste en pensant à essayer d'écrire une fonction réentrante, sans parler d'un gestionnaire d'interruption.
Ouais, c'est un vrai pita.Je dois préciser que lorsque je dis "NT était entièrement réentrant", je veux dire que "votre code devait être ré-entrant et autant sentir un peu non-rentrant déclencherait un bugcheck (BSOD) ".C'est juste un fardeau généralement plus lourd.
Ce.Parfois, vous devez gérer le matériel rapidement, sinon les données seront perdues, ce qui peut se produire si votre gestionnaire ne peut pas s'exécuter à cause d'un autre gestionnaire de longue durée.
#6
+5
Jeroen3
2019-11-27 14:26:43 UTC
view on stackexchange narkive permalink

Je peux prendre une décision de conception consciente pour déplacer votre charge de travail uniquement vers des interruptions.
Mais beaucoup de précautions et de précautions doivent être prises pour s'assurer que les délais et la pile ne seront pas dépassés.

Un NVIC est fondamentalement un ordonnanceur très grossier. Les tâches seront exécutées en tant que priorité définie, lorsqu'elles seront déclenchées par un minuteur, un logiciel ou une autre source.
Dans un ARM Cortex M3, vous avez un contrôle précis sur les priorités et l'imbrication. Dans un ATMega ou 8051 vous ne le faites pas.

Si vous regardez ce que vous faites par fil avec ce modèle, vous aurez:

  • Fil principal via le gestionnaire de réinitialisation. (tourner au ralenti)
    • Interrompre TIMER_ISR_A avec un thread normal.
      • Interrompre TIMER_ISR_B avec un thread irrégulier.
        • Interrompre UART_ISR avec un thread de tampon en temps réel dur.

Vous avez déjà 3 niveaux de profondeur, cela signifie au moins 3 stackframes!
Avec le regroupement par priorité, vous pouvez limiter l'imbrication à quelques niveaux. Mais cela peut vous coûter des délais.

Avec un RTOS préemptif, vous aurez un contrôle plus fin et moins d'interruptions imbriquées et moins de risque de faire exploser la pile. Et souvent inclus des mesures de performances.

Et évidemment tous les effets secondaires des données partagées asynchrones et de la rentrée. Étant donné que dans un ISR, vous ne pouvez pas attendre qu'une ressource soit disponible. Vous empêcherez tous les threads de priorité égale et inférieure de s'exécuter du tout. À moins que seuls les threads de priorité plus élevée l'utilisent.
Beaucoup de contraintes implicites!

Si vous pouvez adapter la conception de votre logiciel pour qu'elle fonctionne dans les limites de ce modèle, souvent combiné avec le modèle superloop pour des tâches qui ne comportent pas de délai serré, alors oui, c'est une conception valide .
Si correctement documenté pour que votre équipe le comprenne également.

#7
+5
Dmitry Grigoryev
2019-11-27 18:57:14 UTC
view on stackexchange narkive permalink

Pour les applications en temps réel, un timing prévisible est souvent plus important que les performances.Les interruptions externes auront un timing imprévisible, donc plus elles dureront longtemps et plus elles auront d'impact sur le timing global de votre système.Notez que je parle d'interruptions de haute priorité ici: avoir une longue interruption de basse priorité n'est pas un problème, tant que cela n'affecte pas les éléments critiques en temps (mais attention au problème de rentrée mentionné dans une autre réponse).

J'ai vu plusieurs projets qui avaient du mal à obtenir un timing d'E / S cohérent malgré une charge CPU raisonnablement faible.Remplacer les ISR plus longs en les interrogeant ou en les réduisant à de simples gestionnaires de "copier octet / ensemble d'indicateur" et faire le reste dans des tâches périodiques était le seul moyen de maîtriser ce timing, au détriment d'une charge CPU supplémentaire.

#8
+2
Scott Seidman
2019-11-26 20:52:31 UTC
view on stackexchange narkive permalink

Le concept d'interruptions vous permet essentiellement de faire deux choses: les choses normales que vous feriez habituellement et les choses immédiates qui doivent être gérées lorsqu'un événement se produit.

Vous n'avez pas besoin de faire les choses de cette façon, mais si vous ne le faites pas, vous ne tirez pas vraiment parti de la structure d'interruption.Votre code sera probablement plus difficile à comprendre, à maintenir et à déboguer.

Quelque chose à propos de vos architectures suggérées me rappelle les RTOS, cependant.

À propos de RTOS, il est légitime d'utiliser irq pour réveiller un thread, mais le thread ne s'exécute pas en tant qu'interruption, la routine d'interruption est très courte car elle indique simplement au système d'exploitation que le thread doit être replanifié car son événement est prêt.
@crasic, il existe un modèle pour un système d'exploitation de commutation de tâches préventives où les threads s'exécutent réellement dans les routines de service d'interruption.Il peut être trouvé à https://www.embedded.com/build-a-super-simple-tasker/.Cela nécessite bien sûr des interruptions d'imbrication, mais peut être très utile pour des plates-formes comme le 8051 avec un espace de pile limité.
@semaj Merci pour cette référence.Il existe certainement d'autres architectures logicielles et exigences d'application à prendre en compte.
#9
+2
sktpin
2019-11-28 15:34:54 UTC
view on stackexchange narkive permalink

Je ne peux pas encore commenter, je vais donc le publier ici:

Certains ont mentionné que l'on pourrait concevoir avec soin un système de planification basé sur les interruptions sur un matériel prenant en charge les interruptions d'interruption.

Je voudrais ajouter: Sur le Cortex-M, cela semblerait ridicule, cependant - car il dispose d'installations dédiées pour prendre en charge la commutation de contexte pour les "threads" sans interruption, en d'autres termes, au lieu d'aller à la tête pour concevoir un système tout-interruption fonctionnel, autant utiliser quelque chose comme FreeRTOS?

Changement de contexte sur ARM Cortex-M

#10
+2
Dakkaron
2019-11-29 17:03:30 UTC
view on stackexchange narkive permalink

La grande différence entre une boucle principale et des interruptions est la planification.

Une boucle principale fonctionne comme un système fifo. Pas à pas, il traite ce qui lui est demandé. Elle est linéaire, donc la consommation de mémoire est plutôt déterministe et si vous demandez à la boucle principale de faire plus qu'elle ne peut gérer, chaque tâche sera toujours effectuée, mais juste plus lentement. Il est également assez facile à déboguer.

Les interruptions, lorsqu'elles sont émises, interrompent la boucle principale. Ainsi, la boucle principale est mise en pause, ce qui peut entraîner des problèmes de synchronisation si la boucle principale est suspendue trop longtemps.

Si une deuxième interruption est émise alors que la première est toujours en cours d'exécution, un processeur peut gérer cela de deux manières. Le chemin est déterminé par le processeur, mais certains processeurs laissent le choix au programmeur. Soit le processeur peut rejeter d’autres interruptions pendant qu’une est en cours de traitement, soit il peut interrompre la dernière interruption, la mettre sur la pile, exécuter la nouvelle interruption puis revenir à la dernière interruption.

La première option est assez simple, mais elle pourrait entraîner la perte des interruptions, car la condition d'interruption n'est plus valide au moment où la première interruption termine son exécution. De plus, de cette façon, les interruptions de priorité inférieure peuvent entraver l'exécution des interruptions de priorité plus élevée.

L'autre option permet aux interruptions de priorité plus élevée d'interrompre les interruptions de priorité inférieure et les interruptions ne peuvent pas non plus être perdues. Mais c'est au prix de l'introduction de problèmes liés à la concurrence, même sur un système à un seul thread. De plus, cela rend la consommation de mémoire non déterministe, car de nombreuses exécutions d'interruptions poussées sur la pile augmentent considérablement la consommation de mémoire.

J'ai eu ce problème une fois lors de la programmation d'une application qui gère les signaux IR sur un ATMega328p.Ma routine d'interruption était trop longue et trop lente et si deux émetteurs infrarouges envoyaient un signal en même temps à l'ATMega (ce qui devrait entraîner juste un signal méconnaissable), l'exécution d'interruption du premier flanc de signal n'était pas terminée avant l'arrivée du signal suivant.in, ce qui a poussé l'exécution de l'interruption sur la pile.Cela a été répété jusqu'à ce que les deux émetteurs IR arrêtent d'envoyer.Jusque-là, la pile avait des centaines d'exécutions d'interruptions empilées, ce qui provoquait un débordement de pile.Maintenant, l'ATMega328p n'a pas de prévention de débordement de pile, il a donc continué à se répandre dans la zone du tas et a complètement corrompu toute la mémoire.

#11
+1
Dirk Bruere
2019-11-26 20:53:15 UTC
view on stackexchange narkive permalink

Idéalement, vous voulez en faire le moins possible via des interruptions.C'est beaucoup plus facile de déboguer une boucle que d'avoir des interruptions apparemment aléatoires.Moins il y en a, mieux c'est.

Au détriment du scrutin?
L'interrogation @PeterMortensen est une boucle.Autant que possible devrait être fait sous sondage.


Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 4.0 sous laquelle il est distribué.
Loading...