Одна річ, яку я знайшов корисною на багатьох машинах, - це простий перемикач стеків. Я фактично не написав жодного для PIC, але я би сподівався, що підхід буде добре працювати на PIC18, якщо обидва / всі потоки використовують загалом 31 або менше рівнів стеку. У 8051 році основним розпорядком є:
_taskswitch:
xch a, SP
xch a, _altSP
xch a, SP
рет
На PIC я забуваю назву вказівника стека, але рутина буде щось на зразок:
_taskswitch:
movlb _altSP >> 8
movf _altSP, w, b
movff _STKPTR, altSP
movwf _STKPTR, c
повернення
На початку вашої програми викликайте програму task2 (), яка завантажує altSP з адресою альтернативного стека (16, ймовірно, буде добре працювати для PIC18Fxx) і запускає цикл task2; ця рутина ніколи не повинна повертатися, інакше речі помруть болісною смертю. Натомість він повинен викликати _taskswitch, коли хоче отримати контроль над основним завданням; тоді первинне завдання повинно викликати _taskswitch, коли воно хоче перейти до другого завдання. Часто у вас будуть маленькі маленькі підпрограми, такі як:
void delay_t1 (непідписаний короткий val)
{
робити
taskwitch ();
while ((непідписаний короткий) (milisecond_ clock - val)> 0xFF00);
}
Зауважте, що програма переключення завдань не має жодних засобів виконувати будь-які "очікування умови"; Все, що він підтримує, - це закрутка. З іншого боку, перемикач завдань настільки швидкий, що спроба перемикача задач (), поки інша задача чекає закінчення таймера, переключиться на інше завдання, перевірить таймер і повернеться назад швидше, ніж типовий перемикач завдань визначив би, що це не потрібно, щоб завдання перемикати.
Зауважте, що багатозадачність кооперативу має деякі обмеження, але це дозволяє уникнути потреби у великій кількості блокування та іншого коду, пов’язаного з мютексами, у випадках, коли інваріанти, які тимчасово порушуються, можуть бути відновлені швидко.
(Редагувати): пара застережень щодо автоматичних змінних та таких:
- якщо рутина, яка використовує перемикання завдань, викликається з обох потоків, зазвичай потрібно буде скласти дві копії процедури (можливо, #including один і той же вихідний файл двічі, з різними операторами #define). Будь-який заданий вихідний файл буде або містити код лише для одного потоку, або ж буде містити код, який буде скомпільовано двічі - один раз для кожного потоку - тому я можу використовувати макроси типу "#define delay (x) delay_t1 (x)") або #define delay (x) delay_tx (x) "залежно від того, який потік я використовую.
- Я вважаю, що компілятори PIC, які не можуть "побачити" викликану функцію, припускають, що така функція може переносити будь-які регістри процесорів, тим самим уникаючи необхідності зберігати будь-які регістри в програмі переключення завдань [приємна вигода порівняно з переважне багатозадачність]. Кожен, хто розглядає подібний перемикач завдань для будь-якого іншого процесора, повинен бути обізнаний з умовами використання реєстру. Натискання на регістри перед перемикачем завдань і вискакування після них - простий спосіб піклуватися про речі, якщо припустити, що існує достатня кількість стека.
Спільна багатозадачність не дозволяє повністю уникати питань блокування та подібного, але це дійсно значно спрощує речі. Наприклад, у превентивному RTOS із ущільнювальним сміттєзбірником потрібно дозволити закріплювати предмети. При використанні кооперативного комутатора це не обов'язково за умови, що код передбачає, що об'єкти GC можуть переміщуватися в будь-який час виклику taskwitch (). Колектор для ущільнення, який не повинен турбуватися про закріплені об'єкти, може бути набагато простішим, ніж той, що робить.