Данные переопределяются при переходе на PendSV - PullRequest
2 голосов
/ 09 марта 2019

У меня есть маленькая "ОС" для руки Кора M4. Я реализовал функцию ожидания. Но с тех пор переключение контекста как-то повреждено Проходя по инструкциям, я заметил, что по какой-то причине переменная current_task переопределяется при вводе прерывания PendSV.

Это глобальные переменные

volatile struct OS_task * current_task;
volatile struct OS_task * next_task;

следующего типа:

struct OS_task{
    volatile unsigned int *sp;
    void (*handler)(void * params);
    void * params;
    volatile enum task_state state;
    volatile unsigned char number;
    volatile unsigned int delay;
};

Это функция планировщика. Он также вызывается из прерывания Systick.

void OS_Scheduler(void)
{
    current_task = &OS_tasktable.task_list[OS_tasktable.current_task];
    current_task->state = OS_TASK_STATE_IDLE;

    int next = OS_GetNextTask(OS_tasktable.current_task);
    while (1)
    {
        if (OS_tasktable.task_list[next].delay == 0)
            break;
        OS_tasktable.task_list[next].delay--;
        next = OS_GetNextTask(next);
    }
    OS_tasktable.current_task = next;

    next_task = &OS_tasktable.task_list[OS_tasktable.current_task];
    next_task->state = OS_TASK_STATE_ACTIVE;
    S32_SCB->ICSR |= S32_SCB_ICSR_PENDSVSET_MASK;
}

Это обработчик PendSV. Хотя я работаю над Cortex-M4F, я не сохраняю регистры FPU просто потому, что мне не нужна арифметика с плавающей запятой.

.syntax unified

.thumb

.global PendSV_Handler
.type PendSV_Handler, %function
PendSV_Handler:
    /* Disable interrupts: */
    cpsid   i

    /* Save registers R4-R11 (32 bytes) onto current PSP (process stack
       pointer) and make the PSP point to the last stacked register (R8).*/
    mrs r0, psp
    subs    r0, #16
    stmia   r0!,{r4-r7}
    mov r4, r8
    mov r5, r9
    mov r6, r10
    mov r7, r11
    subs    r0, #32
    stmia   r0!,{r4-r7}
    subs    r0, #16

    /* Save current task's SP: */
    ldr r2, =current_task
    ldr r1, [r2]
    str r0, [r1]

    /* Load next task's SP: */
    ldr r2, =next_task
    ldr r1, [r2]
    ldr r0, [r1]

    /* Load registers R4-R11 (32 bytes) from the new PSP and make the PSP
       point to the end of the exception stack frame. */
    ldmia   r0!,{r4-r7}
    mov r8, r4
    mov r9, r5
    mov r10, r6
    mov r11, r7
    ldmia   r0!,{r4-r7}
    msr psp, r0

    /* EXC_RETURN - Thread mode with PSP: */
    ldr r0, =0xFFFFFFFD

    /* Enable interrupts: */
    cpsie   i

    bx  r0

.size PendSV_Handler, .-PendSV_Handler

Это функция ожидания. Он вызывается через прерывание SVC. После того, как это выполнено, переменные current_task и next_task установлены правильно. Только при входе в следующее прерывание PendSV каким-то образом current_task переопределяется. Что приводит к тому, что обе задачи устанавливаются в один и тот же стек -> не хорошо.

void __os_wait_ms(unsigned int ms)
{
    struct OS_task * current;
    current = &OS_tasktable.task_list[OS_tasktable.current_task];
    current->delay = ms * OS_tasktable.delay_factor;
    OS_Scheduler();
    return;
}

Если это поможет: я использую S32K146EVB от NXP, если быть точным.

РЕДАКТИРОВАТЬ: я отключаю прерывания во время выполнения функции ожидания, чтобы избежать Systick, вызывающего планировщик и путаницы.

1 Ответ

1 голос
/ 11 марта 2019

Может быть несколько причин для поведения, которое вы наблюдаете.Во-первых, это на самом деле не происходит;отладка через границы прерываний является сложной задачей, потому что приостановка и пошаговое выполнение отключают прерывания, поэтому может быть трудно отслеживать порядок, в котором происходят события.Учитывая, что рассматриваемая переменная статически размещена, это вряд ли приведет к повреждению стека, что сужает ее.Но из предоставленного вами кода причина не очевидна.

Если бы я хотел, я бы хотел посвятить большую часть этого ответа решению некоторых странностей в переключателе контекста и планировщике.Возможно, взглянув на них, вы найдете ответ на исходную проблему!

  1. Я бы посоветовал вам вызвать планировщик из переключателя контекста.Это имеет несколько преимуществ: это упрощает вызов переключателя контекста (вам просто нужно установить бит PENDSV, нет необходимости сначала вызывать планировщик; вам больше не нужна глобальная переменная next_task, потому что планировщик возвращает указатель на следующеезадача непосредственно к переключению контекста; вам больше не нужно отключать прерывания во время переключения контекста (худшее, что может произойти, если ISR запускает переключение контекста во время выполнения другого, если вы просто получаете два подряд), и вам больше не нужночтобы вызвать планировщик, когда задача переходит в состояние ожидания (хотя вам потребуется установить бит PENDSV, самый простой способ - использовать обработчик SVC).

    Примерно так в переключателе контекста:

    LDR r0, =OS_Scheduler
    BLX r0
    /* Pointer to next task is now in r0 */
    

    Очевидно, вам также придется переписать ваш планировщик, чтобы он возвращал указатель на структуру задачи.

  2. Сохранение и загрузка r4-r11 немного странноЗачем использовать STMIA и арифметику указателя, а не STMDB или STMFD или PUSH (все синонимы)?Почему бы не нажать четыре регистра одновременно?Сделав, например,

    MRS r1, PSP
    

    , вы можете просто написать

    STMFD r1!, {r4-r11}
    

    , чтобы выдвинуть весь лот, и то же самое с поп-музыкой (используя LDMFD или POP).

  3. Мне не понятно, почему вы создаете и используете код возврата конкретного исключения.Правильный код уже должен быть загружен в LR при входе в обработчик PendSV.Простой

    BX lr
    

    - это все, что вам нужно.

...