Una cosa che ho trovato utile su un numero di macchine è un semplice switcher di stack. In realtà non ne ho scritto uno per il PIC, ma mi aspetto che l'approccio funzioni perfettamente sul PIC18 se entrambi / tutti i thread utilizzano un totale di 31 o meno livelli di stack. Sull'8051, la routine principale è:
_taskswitch:
xch a, SP
xch a, _altSP
xch a, SP
macerare
Sul PIC, ho dimenticato il nome del puntatore dello stack, ma la routine sarebbe qualcosa del tipo:
_taskswitch:
movlb _altSP >> 8
movf _altSP, w, b
movff _STKPTR, altSP
movwf _STKPTR, c
ritorno
All'inizio del programma, chiama una routine task2 () che carica altSP con l'indirizzo dello stack alternativo (16 probabilmente funzionerebbe bene per un PIC18Fxx) ed esegue il ciclo task2; questa routine non deve mai tornare altrimenti le cose moriranno in una morte dolorosa. Invece, dovrebbe chiamare _taskswitch ogni volta che vuole cedere il controllo all'attività principale; l'attività primaria dovrebbe quindi chiamare _taskswitch ogni volta che vuole cedere all'attività secondaria. Spesso si avranno piccole routine carine come:
void delay_t1 (valore breve senza segno)
{
fare
taskswitch ();
while ((unsigned short) (millisecond_clock - val)> 0xFF00);
}
Si noti che il commutatore di attività non ha alcun mezzo per eseguire alcuna "attesa di condizione"; tutto ciò che supporta è uno spinwait. D'altra parte, l'interruttore di attività è così veloce che un tentativo di interruttore di attività () mentre l'altra attività è in attesa della scadenza di un timer passerà all'altra attività, controllerà il timer e tornerà più velocemente di un tipico switcher di attività determinerebbe che non è necessario cambiare task.
Si noti che il multitasking cooperativo presenta alcune limitazioni, ma evita la necessità di molti blocchi e altri codici relativi al mutex nei casi in cui gli invarianti temporaneamente disturbati possono essere ristabiliti rapidamente.
(Modifica): un paio di avvertimenti riguardanti le variabili automatiche e simili:
- se una routine che utilizza il cambio di attività viene chiamata da entrambi i thread, sarà generalmente necessario compilare due copie della routine (possibilmente # includendo due volte lo stesso file sorgente, con diverse istruzioni #define). Ogni dato file sorgente conterrà il codice per un solo thread, oppure conterrà il codice che verrà compilato due volte - una volta per ogni thread - in modo da poter utilizzare macro come "#define delay (x) delay_t1 (x)" o #define delay (x) delay_tx (x) "a seconda del thread che sto usando.
- Credo che i compilatori PIC che non possono "vedere" una funzione chiamata supporranno che tale funzione possa eliminare tutti i registri della CPU, evitando così la necessità di salvare i registri nella routine switch-task [un bel vantaggio rispetto a multitasking preventivo]. Chiunque consideri un task switcher simile per qualsiasi altra CPU deve essere a conoscenza delle convenzioni del registro in uso. Spingere i registri prima di un cambio di attività e farli scoppiare dopo è un modo semplice per prendersi cura delle cose, supponendo che esista uno spazio di stack adeguato.
Il multitasking cooperativo non consente di sfuggire completamente ai problemi di blocco e simili, ma semplifica notevolmente le cose. In un RTOS preventivo con un Garbage Collector compatto, ad esempio, è necessario consentire agli oggetti di essere bloccati. Quando si utilizza uno switcher cooperativo, ciò non è necessario a condizione che il codice presupponga che gli oggetti GC possano spostarsi ogni volta che viene chiamato taskwitch (). Un collettore compatto che non deve preoccuparsi di oggetti appuntati può essere molto più semplice di uno che lo fa.