Сначала опишу то, где это возникает:
Я делаю простейший переключатель задач, используя Keil Uvision и процессор STM32. Диспетчер срабатывает от прерывания таймера и в зависимости от статуса и значения «количества шагов» либо выходит из прерывания на нужную задачу, либо переключается на следующую. Реализовано это таким образом:
1. Массив указателей на задачи.
Код
TaskPointer
DCD TaskTableStart
TaskTableStart
DCD ddd1
DCD ddd2
DCD ddd3
DCD ddd4
TaskTableEnd
END
DCD TaskTableStart
TaskTableStart
DCD ddd1
DCD ddd2
DCD ddd3
DCD ddd4
TaskTableEnd
END
Где ddd – указатель на задачу, вида:
Код
ddd1={1,1,4,0,(int*)task1,(int*)task1,(int*)Stack_task1,(int*)Stack_task1};
Первое число это статус, второе количество шагов, также есть указатели на метки задачи и метку стэка задачи.
В стэк, для того чтобы из прерывания корректно выходило в задачу, изначально дописываю такие числа:
Код
int Stack_task1[512]={0xfffffff9,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,(int*)task1,(int*)task1,0x21000000};
Структура полностью повторяет то, что делает Cortex-M3 при срабатывании прерывания (0 поставил на месте регистров R1-R4,R12 так как их состояние при первом запуске неважно)
При входе в прерывание диспетчер вызывается так
Код
Systick_Handler
{
PUSH {LR}
BL scheduler
POP {pc}
}
{
PUSH {LR}
BL scheduler
POP {pc}
}
Вызов задачи делаю так:
Код
LDR r0, =TaskPointer
LDR r0, [r0]
LDR r0, [r0]
MOV r1, r0
LDR SP, [r1,#24]
BX LR
LDR r0, [r0]
LDR r0, [r0]
MOV r1, r0
LDR SP, [r1,#24]
BX LR
Тем самым после BX LR идет выход из диспетчера на команду POP {pc} и срабатывает выход из прерывания на задачу по метке записанной в стэке.
Смену указателей на задачу делаю прибавлением 4 битов к текущему адресу и записью в TaskTableStart. Тем самым перед следующим срабатыванием диспетчера указатель заменяется.
Задачи task1, task2, task3, task4 абсолютно идентичны (отличаются только метками) .
И вот тут возникает проблема со стэками этих задач.
При первом срабатывании диспетчер подгружает указатель и стэк для первой задачи и запускает ее, выходя из прерывания и потом вновь приходит на прерывание. Вид стэка задачи при первом заходе на прерывание и втором (после выполнения команды PUSH {LR} приведены на рисунках.


Как видно все отработало нормально и число 0xfffffff9 осталось на месте.
Однако при переключении на вторую задачу и выполнение тех же самых действий получается вот так:


Причем если выбрать адрес за 4 бита до метки до число 0xfffffff9, используемое для выхода из прерывания окажется там.

Однако так как я вызываю стэк по метке при выполнении команды POP {pc} туда грузится там самая 0x00000001 и уводит в ошибку.
Причем самое интересное, если поменять порядок указателей например на
Код
TaskTableStart
DCD ddd1
DCD ddd3
DCD ddd2
DCD ddd4
DCD ddd1
DCD ddd3
DCD ddd2
DCD ddd4
То сработает первая задача, переключится на третью, третья переключится на вторую и та опять выдаст ошибку. Понять такую ненависть программы к двойке я не могу, потому спрашиваю здесь.
Почему в случае с первой задачей стэк по метке оказался без изменений, а во втором случилась такая беда?