Bonsoir à tous.
Je suis en train de me poser plusieurs questions à propos de la façon de déclarer des variables devant être modifiées par une interruption.
Je dispose d'une structure de ce type :
struct buffer_t {
byte *buff;
int size;
int head=0;
int tail=0;
byte status=BUFF_EMPTY;
};
Cette structure représente un buffer circulaire rempli par une routine d'interruption, et (probablement) vidé par une autre routine d'interruption (pour l'instant je considère que ce n'est pas le cas).
Je sais que lorsqu'on utilise une variable devant être modifiée par une interruption, on déclare cette variable volatile.
Quand il s'agit d'une seule variable c'est simple. Par contre, quid d'une structure ?
Personnellement, je pense que je dois déclarer volatile les éléments de la structure pouvant être modifiés par les interruptions. Dans mon cas il s'agit de:
- head : modifié par l'interruption qui vide mon buffer si j'utilise une routine d'interruption pour le vider. Si ce n'est pas le cas, pas besoin de la déclarer volatile.
- tail : modifié par l'interruption qui remplit le buffer.
- status : modifié également par la lecture ou l'écriture d'un élément dans mon buffer.
Les éléments size, ainsi que le pointeur sur byte buff ne changent pas, eux. par contre les éléments pointés par buff seront modifiés par la routine qui écrit (mais pas par celle qui lit puisqu'elle n'aura qu'à incrémenter l'index head pour supprimer l'élément de tête de mon buffer). Ma première question : je déclare ça comment ? a première vue je ferais un truc du genre :
typedef volatile byte v_byte;
puis je déclarerais ma structure ainsi :
struct buffer_t {
v_byte *buff;
[ … ]
};
Qu'en pensez vous ?
Enfin, mon buffer sera déclaré comme variable globale de la façon suivante :
struct buffer_t buffer;
Aurai-je besoin de déclarer un pointeur de type volatile sur ce buffer pour pouvoir y accéder dans mon interruption ? A priori, je pense que si je déclare un pointeur, il ne doit pas être de type volatile car cette valeur n'est jamais modifiée durant toute la vie du programme. Et vous, que me conseillez-vous ?
# multithread et optimisation
Posté par Space_e_man (site web personnel) . Évalué à 2.
Il me semble qu'il y a deux aspects à distinguer, d'une part prévenir la concurrence malheureuse en multithread et d'autre part prévenir une optimisation malheureuse d'accès à la mémoire vive.
volatile ne permet d'éviter que les optimisations malheureuses d'accès à la mémoire vive.
Par contre, cela ne concerne que la variable en question… Ce que je veux dire, c'est que dans le traitement de ce buffer, il y a typiquement un pointeur propre de parcourt initialisé avec le buff de ta structure (et si volatile, toujours par une lecture de la mémoire vive, même si pour l'optimisateur un registre contient toujours la valeur) mais ensuite, que se passera-t-il ?
Comme son nom l'indique, une interruption est susceptible d'interrompre le fil d'exécution où qu'il en soit (il y a peut-être possibilité de protéger un petit groupe d'instructions, à la manière "atomique", mais je suis pas sûr…). Par exemple, les allocations dynamiques ne devraient, a priori, pas être libérées par le traitement d'une interruption.
[^] # Re: multithread et optimisation
Posté par totof2000 . Évalué à 2. Dernière modification le 14 janvier 2016 à 13:57.
Je n'ai pas cette problématique : c'est du développement sur microcontroleur AVR donc pas de multithread.
C'est surtout cet aspect qui m'intéresse aujourd'hui.
Ca tombe bien, c'est ce que je cherche à faire ;)
C'est ce qui m'a semblé également, mais j'avais un doute d'ou mes questions.
Dans le parcours, j'accède de façon indirecte via *(buff + head) ou *(buff + tail).
Est-ce à dire que même si buff e ne bouge jamais (initialisé tout au début du code), j'ai intérêt à le déclarer volatile quand même ?
C'est ce que j'aimerais savoir …
Dans ce cas précis, une interruption ne peut pas être interrompue. Mais malhereusement, il n'y a pas possibilité de générer une interruption logicielle (sauf à passer par un petit hack que je souhaite éviter), sinon, je pourrais passer par une interruption pour protéger le code en question. Mais je vois l'idée.
En tout cas, merci d'avoir pris un peu de temps pour répondre
[^] # Re: multithread et optimisation
Posté par Space_e_man (site web personnel) . Évalué à 2.
La la valeur de buff est stable, alors c'est head et tail qui sont susceptible d'être modifiés lors du remplissage ? Ce serait alors éventuellement ces deux variables qui devraient être volatiles.
Qu'en penses-tu ?
Essayes d'imaginer des scénarios…
Dis-toi que sans volatile, un code peut-être optimisé de sorte à ne pas systématiquement lire ou écrire en mémoire vive mais soit récupérer une valeur toujours présente dans un registre ou postposer l'écriture en mémoire tant que le registre ne doit pas être libéré.
[^] # Re: multithread et optimisation
Posté par totof2000 . Évalué à 2. Dernière modification le 14 janvier 2016 à 17:05.
Je suis d'accord (c'est ce que j'avais mis il me semble dans mon post initial, mais je n'ai peut-être pas été clair, vu l'heure ou je l'ai fait).
Les scénarios :
lors de la phase d'initialisation, je crée mon buffer (j'initialise la structure avec la zone mémoire à remplir/vider, et je positionne les variables tail et head, aisi que le status de mon buffer à Empty).
j'initialise les routines d'interruption qui vont remplir le buffer (3 sources différentes).
lorsque je reçois une interruption de l'une de ces 3 sources :
Note : quand je remplis mon buffer, je positionne la variable status à NON_EMPTY, et je la positionne à FULL lorsque je n'ai plus de place.
Pour info, sur mon microcontroleur, les interruptions ne peuvent être interrompues.
Dans ma routine principale, j'envisage de:
- tester le taux de remplissage du buffer (vérifier qu'il y a plus de N octets dispo).
- si le buffer est plein à plus de n%, je démarre la transmission des données sur une UART. L'idée serait de transmettre via interruption tant que le buffer n'est pas vide. Lorsque le buffer est vide, je stoppe le transfert (ais pour cette partie, je ne suis pas sur que ce soit la meilleure façon de faire, je n'ai pas encore les idées tout à fait claires, il est possible que je vienne à emettre via le a boucle principale).
Donc au final:
- dans mes interruptions de remplissage, je n'accède qu'à la variable tail (et à mon buffer)
- dans la boucle principale : je n'ai pas encore déterminé comment détecter le taux de remplissage. Probablement en lisant les variables head, tail et size de ma structure.
- si j'émets via interruption, je ne modifie que la variable head dans cette interruption. Comme une it ne peut être interrompu, je suis sur qu'il n'y aura rien de modifié pendant le déroulement de celle-ci
- si j'emets dans ma boucle principale, je suis suceptible d'être interrompu par une interruption de remplissage. Mais comme les variables modifiées pendant l'émission (head) n'est pas modifiée par la routine de remplissage. Par contre cette routile va la lire pour savoir si mon buffer est plein ou non. C'est là qu'il faut que je m'assure que ça ne pose pas de pb.
C'est bien ce que je pensais avoir compris, mais toujours avec un doute quelque part. Merci à toi, tu as levé mes doutes.
[^] # E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2. Dernière modification le 14 janvier 2016 à 22:28.
Quelques remarques en attendant que tes algos soit spécifiés et que l'on puisse te donner une réponse précise.
si ton interruption ne se fait pas interrompre, un volatile dans le code de l'interruption ne sert effectivement à rien.
un volatile dans le code "main" peut-être utile mais certainement pas suffisante (enfin faut voir ton algo toujours). Je conjecture que tu auras quand même besoin d'implémenter un mécanisme de type mutex pour te prémunir des problèmes de TOCTOU.
inspecte le code assembleur pour être sûr de savoir ce que ton compilo a produit.
mettre volatile est souvent une fausse bonne idée. Je te laisse rechercher les références de Linus sur la LKML ;-) La démonstration suit…
Admettons que ton main ressemble à cela:
Au niveu de l'interruption:
NB: c'est une implémentation naïve d'un buffer circulaire. Je n'ai jamais fait ce genre de chose, je te conseille quand même de consulter wikipedia avant ;-)
Donc nous avons ton interruption qui modifie tail et ton main qui modifie head.
Il y a-t-il un risque que l'interruption utilise une mauvaise valeur pour head ? C'est-à-dire entre le momen où tu calcules freesz et le moment où tu modifie tail.
-> Non je ne crois pas, au pire tu as un overrun du à un freesz sous-estimé.
Il y a-t-il un risque que main utilise une mauvaise valeur de tail ? Idem, non je ne crois pas.
Le problème se situe au moment où l'on calcule sz et freesz !! En fait là tu ne veux surtout pas de volatile : que se passerait-il si tail est accèdée en volatile et qu'une interruption fait wrapper tail et survient juste après que tu aies testé qu'il n'y a pas de wrap ? Oops un freesz < 0… ça sent pas bon hein… ;-)
Donc bref: tu copies tes volatiles dans des variables locales au début de ta routine pour être certain que tu n'accède pas à tes variables en ram directement. Ton algo doit être conçu pour fonctionner sur des valeurs qui ne reflètent peut-être plus la réalité mais qui son cohérentes entre elles, pas pour fonctionner avec des valeurs certes actuelles mais qui changent au gré du vent/des lignes!!!
(Ma) conclusion : pas besoin de mutex, surtout pas besoin de volatile, sous réserve que le code "main" soit dans une fonction séparée.
Maintenant si tu passes à plusieurs producteurs/consommateurs, ben ça se complique : ne le fais pas, utilise plusieurs buffers :p
PS: je viens de me rendre compte que c'est l'inverse que tu veux faire, soit vider un buffer par une interruption. Cela revient au même.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2.
Pour complèter un peu au sujet des mutex.
Ici il n'y a qu'une variable suceptible d'être modifiée par dessous le tapis -> pas besoin de mutex.
S'il y avait plusieurs valeurs à lire, alors dans ce cas tu es obligé de trouver un mécanisme de synchronisation, pour t'assurer qu'il n'y aie pas eu d'interruption entre le moment où tu as lu la première valeur et la dernière. Dans ton cas, un simple compteur de génération devrait suffire car tu as la certitude que celui qui incrémente le compeur ne peut pas être interrompu. Sinon tu devras passer par un compare-and-swap, mutex ou rcu (mais bon là on est loin du cadre du dévelopement de micro-controlleurs).
Au sujet des volatile: le volatile ne sert qu'à "forcer" la lecture en ram, ce qui n'est que (probablement) uniquement nécessaire dans le cas d'une boucle, pas si tu lis les valeurs dans le prologue d'une fonction (car le compilo ne peut pas supposer que telle valeur sera déja présente dans un registre, sauf si tu utilises un compileur qui fait de l'analyse globale avec un -O42 et -f-i-dont-care-about-c-standard).
Par contre pour le cas d'un compteur de génération, c'est peut-être mieux de le mettre en volatile car tu veux probablement "forcer" une lecture à chaque fois qu'il est utilisé. L'important c'est de bien raisonner en fonction de ton code-flow. Si une lecture détermine un chemin d'exécution particulier alors de deux choses l'une: soit tu ne veux probablement plus réutiliser sa valeur et dans ce cas un volatile est OK, soit tu veux réutiliser sa valeur plus tard et un volatile n'est absolument pas OK! Dans le cas d'un compteur, il faut voir sa valeur comme une valeur à un instant T, chaque fois que tu vas le lire, c'est par définition une valeur différente.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 3.
J'entends bien "par un seul des agents"… Évidemment si plusieurs agents (ou contextes d'exécution) modifient la même variable, il y a besoin d'une synchronisation.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
Et bien si!
Et bien si!
a2g
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
Mais à la limite on s'en fiche : je ne calcule pas la taille dans mon cas, je me contente juste de comparer tail et head pour savoir si mon buffer est plein. La seule chose que je risque, c'est de croire que mon buffer est plein alors qu'un espace a été libérée par la routine de lecture, ce qui n'est pas si grave que ça dans mon cas.
Là encore je ne pense pas que ce soit grave : dans la boucle, il considèrera que le buffer est vide, alors que l'IT aura ajouté un caractère dans le buffer, et attendra la prochaine itération de la boucle pour voir qu'en fait il y a un caractère à émettre.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
Tu te méprends, cf. mon post plus bas.
Je fais référénce au code ci-dessus et les déclarations de ton premier post.
C'est au moment où tu vas calculer ton "sz" que ça va partir en live: si tu es en train de le calculer, et que l'écriture en mémoire n'est pas finie (par exemple), que ton IT claque, tu vas calculer un "freesz" qui peut être complètement faux!
si tu considère que l'opération "sz = tail + (bufsz - head);" se fait en un coup d'horloge, uniquement par des registres, qu'il n'y a qu'un accès mémoire à sz, alors pourquoi pas!
Mais celà n'a pas l'air d'être le cas pour la "plus" simple raison que toutes ces variables sont des 16bits sur un AVR alors que ta cpu est en 8bits.
Donc avec le code proposé, tu auras des bugs aléatoirement et un résultat inattendu voir plantage du micro, mais si tu penses que ce n'est pas grave…
Je ne peux que t'encourager vivement à lire le fichier .lss généré pour comprendre comment une instruction 'C' est compilée et optimisée
a2g
ps: par curiosité, quel est le micro que tu utilises et ça fait combien de temps que tu codes sur de l'AVR ou un micro 8bits?
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
Avant de continuer, je vais donner une petite explication sur la façon dont je remplis/vide mon buffer, ce sera plus simple et plus compréhensible. Je n'ai pas le code de gestion du buffer sous la main pour le moment mais voilà en gros ce que je fais :
Si tu regardes bien ma structure, j'ai une variable status dans mon buffer. Cette variable peut prendre plusieurts état : vide, non vide (équivalent à non plein), et plein.
J'ai défini un truc du genre :
#define BUFF_EMPTY 0
#define BUFF_NON_EMPTY 1
#define BUFF_NON_FULL BUS_NON_EMPTY
#define BUFF_FULL 2
Lorsque le buffer est vide, cette variable est à l'état vide. Lorsquer je remplis mon buffer, cette variable passe à l'état non-vide. Lorsque je le vide, elle passe à l'état non plein (qui est la même chose que non vide).Si je détecte après ajout d'un élément que mon buffer est plein, je positionne cet indicateur à plein. Si je détecte après vidage d'un élément que mon buffer est vide, je positionne cet indicateur à vide.
Pour le remplissage, j'ajoute mon élément dans le buffer, et je modifie l'index tail ensuite Dans le détail ça donne :
pour le vidage je récupère ma valeur, et je modifie mon index head ensuite Le détail :
Comme tu le vois, je ne calcule pas de taille en tant que telle, par contre je pense que la comparaison entre tail et head équivaut à un calcul de taille (il faudrait juste voir comment le compilo génère le code assembleur dans ce cas pour en être sur).
C'est pour ça que je me demande si je ne devrais pas vider mon buffer sur interruption, pour m'assurer que les calculs se fassent sans accès concurrent potentiel.
Faire un truc du genre tester dans la boucle principale l'état de la variable status de ma structure, et si elle n'est pas vide, j'initie le transfert sur ma liaison série via It. Mais comme je n'ai pas pris le temps de réfléchir plus loin à cette partie, je ne suis pas vraiment sur de mon coup. Comme dit plus haut, il faudrait que je me refasse un petit schema du workflow.
Bien vu, je n'avais pas pensé à ce détail.
C'est ce que j'ai l'intention de faire de toute façon.
J'utilise un atmega328. Ca fait déjà plusieurs dizaines d'années que je code sur du micro 8 bits (mais pas à plein temps) , mais jusqu'aujourd'hui, je ne faisais que de l'assembleur (sur ATTiny), donc un certain nombre de questions ne se posaient pas (notamment toutes ces histoires d'optimisation du compilateur).
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2. Dernière modification le 15 janvier 2016 à 15:57.
Euh .. erreur dans cette partie :
pour le vidage je récupère ma valeur, et je modifie mon index head ensuite Le détail :
tailhead à la taille du buffer (qui elle ne bouge jamais) : si index=taille de buffer-1, je positionne l'index à 0. Sinon, j'incrémente cet index.Est-ce qu'un modérateur pourrait corriger ? Sinon ça n'a pas de sens.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
Le problème n'est pas lié au "calcul" mais à l'architecture 8bits pour des calculs 16 bits et au delà.
Toute donnée significative de 16bits utilisée (que celà soit écriture ou lecture) dans une interruption doit être protégée ailleurs dans une section "protégée" par désactivation/activation de l'interruption. C'est une règle à laquelle tu n'échapperas pas.
voici un extrait d'un code (atxmega) de gestion d'un buffer circulaire de 512 octets aligné sur une adresse en 210, juste la partie réception (exemple pas bien optimisé mais osef) qui utilise des index. (l'utilisation des index est, dans certains cas, mieux optimisable par le compilateur que par pointeur. C'est l'expérience et l'étude du code généré qui le dit, mais ça tu devrais y arriver avec ta connaissance de l'asm)
C'est clair que de passer de l'assembleur au C n'est pas simple car il y a pleins de subtilités. Ensuite ça va tout seul lorsqu'on connait le fonctionnement du compilateur en C. Ca met du temps…
a2g
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
en fait à l'origine, je voulais éviter de désactiver/activer les it dans ma boucle principale pour éviter que ça "bagotte" et de perdre des interruptions, mais en m'arrangeant pour "endormir" mon processeur dans la boucle principale, ça devrait limiter la casse.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2.
Ok. C'est plus simple de raisonner sur un code fonctionnel :-)
Pourquoi désactiver les interruptions dans getreceiveddata ?
Avec mon algorithme 1) copier tail dans une locale 2) calculer sz/freesz; il n'y a pas de problèmes vu que head n'est jamais touché par l'interruption. En gros à mes deux questions tu réponds: "je ne sais pas, j'empêche au cas où…".
Pour désactiver les interruptions dans le gestionnaire d'interruption tu as sans-doutes raison d'insister sur ce point, j'imagine qu'un avr est trop petit pour avoir un interrupt controller qui gère les priorités entre les interruptions. Mais cela dépend vraiment de la platforme et de comment qu'on programme l'IC, ça n'est pas toujours nécessaire.
À part ça, merci pour partager ton code. J'aime bien l'idée d'utiliser un masque pour gèrer le clamping, peut-être qu'il serait encore plus malin de le définir en puissance de 2 (2 << POWER) pour éviter une bête erreur, fin bon ça c'est du détail…
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2. Dernière modification le 15 janvier 2016 à 17:09.
Ok lu ton message plus bàs, donc ce serait parce que c'est une quantité de 16 bits sur un micro 8 (en plus du fait que tes 2 routines modifient la mêm variable)? Admettons mon algorithme, est-ce vraiment un problème ? Par exemple si on écrit d'abord les bits de poids faibles, et comme la valeur est monotonique, on ne risque jamais d'avoir un "tail" qui pointe trop loin. Au pire on peut peut-être tomber sur un tail < head, ce qui peut facilement être détecté par main. Ça me semble bon, non ? Ou me tromperais-je ?
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 1.
Ok j'ai fini de lire ton sus-cité message dans son intégralité :-P Merci j'ai eu ma réponse et je me coucherai moins ignorant ce soir!
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2.
Je me re-réponds à moi-même: dans ce cas ça ne marche évidemment pas car on utilise tail < head pour détecter un wrap… Désolé pour le bruit.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
D'après la doc AVR :
When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled. The user software can write logic one to the I-bit to enable nested interrupts. All enabled interrupts can then interrupt the current interrupt routine. The I-bit is automatically set when a Return from Interrupt instruction – RETI – is executed.
Les It sont désactivées si tu ne les réactives pas lors du traitement des It.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
En effet, il faut sauvegarder le SREG avant de désactiver les IT pour ne pas perdre le SREG
C'est un exemple à la RACHE. (j'ai même introduit un bug: c'est align(512) pour l'alignement du buffer)
sur les softs que j'ai en production actuellement, je n'ai que des buffers de 256bytes pour les AVR, donc moins de données en format 16bits: ce code est extrait d'un prototype qui fonctionne très bien.
sur les uC qui gèrent plus que l'interruption UART, j'utilise plutôt des macros du genre:
Je conseille de ne surtout pas réactiver les interruptions dans les routines d'interruption en utilisant ces macros si on ne sait pas gérer les interruptions imbriqués ("nested") et attention à l'explosion de la pile avec la réentrance… etc.
a2g
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
Soit j'ai mal compris, soit sreg est sauvegardé par le hard.
Je ne compte pas non plus le faire, c'est déjà assez compliqué comme ça.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
J'ai vérifié, oui, SREG est sauvegardé.
J'ai jeté un coup d'oeil là où je l'ai utilisé: c'est parce que SREG est modifié par du code entre l'entrée et la sortie de la région critique et qu'en sortant de cette région, je voulais récupérer l'ancien SREG (cas particulier)
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 1.
Ton alogithme me semble de loin similaire à ce que j'ai écris, il serait bien que tu l'écrive en formules et que tu le simplifie en suite. Le problème c'est que tu parles d'index, de status, etc. et au final tu te retrouve avec 4 variables écrites par les deux routines, ce qui est une recette pour un désastres et qui t'oblige à suspendre les interruptions (ce qui cf. les messages de a2g n'est sans doutes pas une mauvaise idée).
Au final, ton "index" vaut head pour le lecteur et tail pour l'écriveur… sz (== la taillle à lire) est calculée justement en faisant la différence des deux (ou en prennant le cas du wrap). Le lecteur se sert de tail pour avoir une estimmation de jusque où il peut lire, et tail utilise head pour savoir jusqu'où il peut écrire… Bref 2 variables, chacunes écrites une fois par chaque routine. Pas besoin de statuts vide, plein, demi-vide ou demi-plein ;-)
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
Comme je l'ai dit c'est un truc "simpliste" que j'ai écrit à la rache vite fait le soir tard, c'est d'ailleurs pour ça que je n'en a vais pas vraiment parlé au début : je sais que je dois y revenir. Ce n'était pas l'objet initial de mon post, mais j'ai tenu à en parler histoire de clarifier certaines choses, notamment le contexte dans lequel je lis/écris.
3 variables modifiées par 2 routines : head, tail et status.
Je t'avoue que celle-là j'en ai un peu honte : comme tu dis il faut que je le reprenne. Je me doutais que je pourrais m'en passer, mais la flemme de réfléchir sur ça lorsque j'ai codé mon truc (a 1h du matin on a pas toujours les idées très claires …). J'ai donc fait un truc "vite fait" parce que je voulais tester un autre truc qui avait besoin d'un buffer de ce genre.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2.
Salut a2g,
Merci pour ton input et tes idées.
Je crois que tu souffres du même problème que j'ai à lire les messages dans leur intégralité: deux lignes plus loin j'écris exactement la même chose que toi concernant sz/freesz avec en substance le même message que tu aides à faire passer du coup, parfait ;-)
Ici j'évoquais le problème que main pourrait avoir à avoir une valeur de tail moins actuelle: donc soit une estimmation trop petite du nombres des données, soit ne pas détecter un wrapping qui sont tous deux sans conséquence.
L'objet de mon message étant quand même d'insister sur le fait que mettre volatile à tout bout de champs n'est certainement pas une bonne idée… Bref, pas de volatile du tout avec un code proprement découpé en fonctions couplé d'une inspection de l'assembleur généré est une possibilité tout à fait viable et bug-free !
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par plr . Évalué à 2.
[HS]
Après avoir eu des problèmes d'optimisation sur du code pour de l'AVR qui allait partir en production il y a bien longtemps, je ne mets dorénavent que des "volatile" pour le code AVR à moins que je ne sache exactement comme le code est optimisé: je préfère utliser d'autres astuces d'optimisation et perdre quelques instructions et coups de clock plutôt que de partir faire le tour du monde pour mettre à jour les produits déployés.
Par contre, ayant commencé ma carrière avec de l'assembleur dsp, j'ai gardé l'habitude de maîtriser entièrement toutes les variables, leur utilisation, leur optimisation, le mapping, les garder en "privé", les définir en variables locales ou en registre si je considère qu'elle peut être optimisée.
-> celà ne me choque donc pas, l'utilisation des "volatile" quand le code est bien architecturé, bien "modularisé" et bien écrit.
[/HS]
a2g
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par totof2000 . Évalué à 2.
Effectivement, vu que la routine d'it est censée sauvegarder le contexte d'exécution du programme interrompu.
C'est pour ça que je n'ai pas encore spécifié mon algo: je "sens" qu'il y a un truc à faire à ce niveau (d'ailleurs tu as mis le doigt dessus, voir plus bas).
Je pars du principe que volatile est utile, mais pas suffisant. si j'ai bien compris, volatile sert à "forcer" le compilateur à insérer une instruction de relecture de la valeur depuis la mémoire (pour des questions d'optimisation, le compilateur ne force pas la relecture de la valeur lorsque celle-ci est déjà dans un des registres du processeur).
Ca a son utilité lorsqu'une valeur est susceptible d'être écrite par un autre thread entre deux (ou plus de deux ) lectures successives (ou une interruption). Par contre ça ne règle pas les problèmes d'accès concurrents à une valeur, problème résolus par les mécanismes de mutex. L'un ne remplace pas l'autre, les deux ont une utilité différente.
Ca y ressemble, au détail près que j'ai déporté la gestiuon du buffer dans des fonctions, appeléers par le main ou les routines d'IT.
Tu en es pas très loin, au détail près que je ne calcule pas de taille, je me contente juste de comparer les index head et tail : s'ils ont la même valeur, ça signifie que le buffer est plein.
Mon implémentation n'est pas non plus très subtile non plus (j'ai fait au plus simple). Mais je peux (et devrai certainement) la modifier un peu (une idée m'est venu suite à vos remarques).
La conclusion est bonne : l'interruption remplit, donc modifie le tail, et le main (et/ou une it) vide, donc modifie le head.
Comme je ne calcule pas (pour le moment) la taille, pas de risque.
Là par contre c'est peut-être possible. Le main est un classique : une boucle sans fin qui fait des trucs à chaquie itération. Comme je ne calcule pas de taille de buffer lors du remplissage, c'est peuit-être là que je devrais le calculer (pour déterminer le taux de remplissage de mon buffer et déclencher le transfert). Mais je pourrais simplifier en déclenchant le transfert dès que le buffer n'est pas vide.
Ca me parait une bonne idée.
Ca me parait logique. Pour moi, le seul moment ou j'ai besoin d'une représentation réelle est lorsque le buffer est vide ou lorsqu'il est plein.
C'est justement pour ça que je n'ai pas encore spécifié définitivement mon algo : j'avais senti cette mauvaise odeur de loin, mais pas eu le temps d'approfondir et de détailler la réflexion.
Je suis d'accord sur ce point.
Là je suis moins d'accord (en fait ça dépendra de mes choix) : je peux en avoir besoin si je calcule le taux d'occupation de mon buffer dans le main. Mais je crois que je vais éviter …
Je ne compte pas le faire.
Pas encore décidé : Ce qui est sur c'est que je remplis via une interruption. Par contre pour vider je me tate : soit je vide dans le main, soit je vide via l'interruption UART. L'avantage de l'interruption, c'est que je suis sur qu'elle ne sera pas interrompue durant son exécution. L'inconvénient : un peu plus de taf.
en tout cas merci pour ces indications qui me feront avancer. J'ai une ou deux idées que je vous présenterai si ça vous intéresse.
[^] # Re: E_NOENOUGH_INFO | E_UNNEEDED
Posté par benja . Évalué à 2.
Tout à fait! Merci d'avance :)
# volatile et interruptions
Posté par plr . Évalué à 3.
L'utilisation du mot clé "volatile" est indispensable à maîtriser lorsqu'on écrit du code embarqué: elle permet à l'utilisateur d'éviter que le compilateur, lorsqu'utilisé avec les optimisations qui vont bien n'en fasse trop. L'exemple le plus courant est le polling sur une pin d'entrée:
si pin_b0 est le nom de la "variable" qui permet d'accéder à la pin 0 du port B du uC, alors, sans le mot clé "volatile", le code suivant qui permet de détecter le passage à 1 de cette entrée a pour conséquence de faire "disparaitre" la boucle while et de se faire rick-roller et c'est surtout pas ça ce qu'on veut:
Jeter un coup d'oeil à sfr_defs.h, io.h (etc.) pour la définition des accès mémoires des pins du uC de type AVR. Pour définir une variable volatile de type "la structure que tu veux définir":
Pour l'embarqué tel que pour un AVR, lorsqu'on veut modifier une variable par le code principal et par une interruption, il faut désactiver les interruptions dans le code principal lorsqu'on y accède.
exemple, pour modifier aWord par le code principal ET par l'interruption pour un uC de type AVR:
Sans désactiver les interruptions, sur un micro 8bit, on risque l'interruption en plein cours de modification de aWord dans le programme principal et là, c'est le drame!
Et même si la variable est de type 8bit, parce qu'entre le moment où la variable va être chargée dans un registre et le moment où le registre va être modifié et le moment où il va être réécrit dans la mémoire, il y a des malchances qu'une interruption claque et qu'à la fin, la variable se retrouve avec une valeur totalement indéterminée. Je te laisse imaginer le cas où cette variable est de type pointeur, à côté des dégats que celà peut engendrer, tu préfèrerais être rickrollé!
Mutex, semaphore et autres mécanismes du même type sont des implémentations pour langage "haut-niveau" (os, multithread, etc.) pour éviter ces problèmes, mais la base est ci-dessus!
a2g
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.