Une chose que j'ai trouvée utile sur un certain nombre de machines est un simple commutateur de pile. Je n'en ai pas écrit pour le PIC, mais je m'attendrais à ce que l'approche fonctionne correctement sur le PIC18 si les deux / tous les threads utilisent un total de 31 niveaux de pile ou moins. Sur le 8051, la routine principale est la suivante:
_taskswitch:
xch a, SP
xch a, _altSP
xch a, SP
ret
Sur le PIC, j'oublie le nom du pointeur de la pile, mais la routine ressemblerait à ceci:
_taskswitch:
movlb _altSP >> 8
movf _altSP, w, b
movff _STKPTR, altSP
movwf _STKPTR, c
revenir
Au début de votre programme, appelez une routine task2 () qui charge altSP avec l'adresse de la pile alternative (16 fonctionnerait probablement bien pour un PIC18Fxx) et exécute la boucle task2; cette routine ne doit jamais revenir, sinon les choses vont mourir d'une mort douloureuse. Au lieu de cela, il doit appeler _taskswitch chaque fois qu'il souhaite céder le contrôle à la tâche principale; la tâche principale doit ensuite appeler _taskswitch chaque fois qu'elle souhaite se soumettre à la tâche secondaire. Souvent, on aura de jolies petites routines comme:
void delay_t1 (unsigned short val)
{
faire
interrupteur de tâches ();
while ((unsigned short) (millisecond_clock - val)> 0xFF00);
}
Notez que le sélecteur de tâches n'a aucun moyen de faire une "attente de condition"; tout ce qu'il supporte est un spinwait. Par ailleurs, le changement de tâche est si rapide qu’une tentative de répercussion sur une tâche () alors que l’autre tâche attend l’expiration du temporisateur bascule vers l’autre tâche, vérifie le temporisateur et revient plus rapidement qu’un commutateur de tâches typique. déterminerait qu'il n'a pas besoin de taskwitch.
Notez que le multitâche coopératif a certaines limites, mais il évite le recours à de nombreux codes de verrouillage et autres codes liés aux mutex dans les cas où les invariants temporairement perturbés peuvent être rétablis rapidement.
(Edit): Quelques mises en garde concernant les variables automatiques et autres:
- si une routine qui utilise la commutation de tâches est appelée à partir des deux threads, il sera généralement nécessaire de compiler deux copies de la routine (éventuellement en incluant # le même fichier source deux fois, avec des instructions #define différentes). N'importe quel fichier source ne contiendra pas le code d'un seul thread ou le code qui sera compilé deux fois - une fois pour chaque thread - afin que je puisse utiliser des macros du type "#define delay (x) delay_t1 (x)" ou #define delay (x) delay_tx (x) "en fonction du thread que j'utilise.
- Je pense que les compilateurs PIC qui ne peuvent pas "voir" une fonction appelée supposent qu'une telle fonction peut détruire tous les registres de la CPU, évitant ainsi la sauvegarde de registres dans la routine de commutation de tâches [un avantage intéressant par rapport à multitâche préemptif]. Toute personne envisageant un sélecteur de tâches similaire pour toute autre CPU doit connaître les conventions de registre utilisées. Pousser des registres avant un changement de tâche et les afficher après est un moyen facile de s’occuper des choses, en supposant qu’il existe suffisamment d’espace de pile.
Le multitâche coopératif ne permet pas d’échapper complètement aux problèmes de verrouillage, mais il simplifie grandement les choses. Dans un RTOS préemptif avec un ramasse-miettes compacté, par exemple, il est nécessaire de permettre aux objets d'être épinglés. Lorsque vous utilisez un commutateur coopératif, cela n'est pas nécessaire, à condition que le code suppose que les objets GC peuvent être déplacés à tout moment de l'appel de taskswitch (). Un collecteur compacteur qui ne doit pas s'inquiéter des objets épinglés peut être beaucoup plus simple qu'un autre.