Одна вещь, которую я нашел полезным на многих машинах, - это простой стековый коммутатор. На самом деле я не написал ни одного для PIC, но я ожидал бы, что подход будет прекрасно работать на PIC18, если оба / все потоки используют в общей сложности 31 или менее уровней стека. На 8051 основной программой является:
_taskswitch:
хч а, ИП
xch a, _altSP
хч а, ИП
RET
На 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 (беззнаковое короткое значение)
{
делать
taskswitch ();
while ((unsigned short) (millisecond_clock - val)> 0xFF00);
}
Обратите внимание, что переключатель задач не имеет каких-либо средств для выполнения «ожидания условия»; все, что он поддерживает, - спиннинг. С другой стороны, переключение задач происходит так быстро, что попытка переключателя задач (), пока другая задача ожидает истечения таймера, переключится на другую задачу, проверит таймер и переключится назад быстрее, чем обычный переключатель задач. определил бы, что это не нужно переключать задачи.
Обратите внимание, что совместная многозадачность имеет некоторые ограничения, но она устраняет необходимость в большом количестве блокировок и другого кода, связанного с мьютексом, в случаях, когда временно нарушаемые инварианты могут быть быстро восстановлены.
(Изменить): пара предостережений относительно автоматических переменных и таких:
- если подпрограмма, которая использует переключение задач, вызывается из обоих потоков, обычно необходимо скомпилировать две копии подпрограммы (возможно, #include дважды включая один и тот же исходный файл с разными операторами #define). Любой данный исходный файл будет содержать код только для одного потока, или же он будет содержать код, который будет скомпилирован дважды - один раз для каждого потока - поэтому я могу использовать макросы, такие как "#define delay (x) delay_t1 (x)" или #define delay (x) delay_tx (x) "в зависимости от того, какой поток я использую.
- Я полагаю, что компиляторы PIC, которые не могут «видеть» вызываемую функцию, будут предполагать, что такая функция может уничтожить все регистры ЦП, что исключает необходимость сохранения любых регистров в подпрограмме переключения задач [хорошее преимущество по сравнению с упреждающая многозадачность. Любой, кто рассматривает подобный переключатель задач для любого другого ЦП, должен знать об используемых соглашениях регистра. Передача регистров перед переключением задач и последующее их извлечение - это простой способ позаботиться о вещах, при условии наличия достаточного места в стеке.
Совместная многозадачность не позволяет полностью избежать проблем с блокировками и тому подобным, но она действительно сильно упрощает вещи. Например, в превентивной ОСРВ с компактным сборщиком мусора необходимо разрешить закрепление объектов. При использовании кооперативного переключателя в этом нет необходимости при условии, что в коде предполагается, что объекты GC могут перемещаться при каждом вызове taskswitch (). Уплотняющий коллектор, который не должен беспокоиться о закрепленных объектах, может быть намного проще, чем тот, который делает.