Миро Самек, Роберт Вард "Построение наипростейшего диспетчера задач" (перевод)


Обслуживание прерываний в SST

Одно из самых заметных преимуществ SST - простота обслуживания прерываний, которая становится столь же несложной, как и в случае простого "суперцикла" (известного также, как сочетание "main()" и обработчиков прерываний). SST никак не использует сведения о формате стекового кадра прерывания и поэтому обработчики прерываний для него могут писаться на чистом Си с использованием большинства компиляторов для этого языка. Единственным заметным отличием от варианта "суперцикла" является необходимость добавления в пролог и эпилог каждого обработчика нескольких простых действий, каковые действия собраны в макросах "SST_ISR_ENTRY()" и "SST_ISR_EXIT()". Фрагменты кода из файла "example\bsp.c", приведённые в листинге 1, показывают как эти макросы используются в обработчиках прерываний от таймера и клавиатуры из примера.

Листинг 1. Обработчики прерываний (ISR) для SST из примера

    void interrupt tickISR() {                    /* every ISR is entered with interrupts locked */
        uint8_t pin;                              /* temporary variable to store the initial priority */
        SST_ISR_ENTRY(pin, TICK_ISR_PRIO);

        SST_post(TICK_TASK_A_PRIO, TICK_SIG, 0);  /* post the Tick to Task A */
        SST_post(TICK_TASK_B_PRIO, TICK_SIG, 0);  /* post the Tick to Task B */

        SST_ISR_EXIT(pin, outportb(0x20, 0x20));
    }

    /*..........................................................................*/

    void interrupt kbdISR() {                     /* every ISR is entered with interrupts locked */
        uint8_t pin;                              /* temporary variable to store the initial priority */
        uint8_t key = inport(0x60);               /* get scan code from the 8042 kbd controller */
        SST_ISR_ENTRY(pin, KBD_ISR_PRIO);

        SST_post(KBD_TASK_PRIO, KBD_SIG, key);    /* post the Key to the KbdTask */

        SST_ISR_EXIT(pin, outportb(0x20, 0x20));
    }

				

Отметим встречающееся в тексте зарезервированное слово "interrupt", указывающее компилятору Turbo-C сформировать пролог и эпилог функции, которые будут сохранять контекст, восстанавливать его и выполнять выход из прерывания. Отметим присутствие макросов входа в прерывание и выхода из него в начале и конце каждого обработчика. (Если источник прерываний требует очистки перед новым циклом работы, то делать её надо до вызова "SST_ISR_ENTRY()"). Макросы "SST_ISR_ENTRY()" и "SST_ISR_EXIT()" определяются в заголовочном файле "includes\sst.h" как показано в листинге 2 (конструкция "do..while(0)", обрамляющая макросы, используется для независящего от контекста, синтаксически корректного группирования инструкций).

Листинг 2. Макросы для входа и выхода из прерываний

    #define SST_ISR_ENTRY(pin_, isrPrio_) do { \
        (pin_) = SST_currPrio_; \
        SST_currPrio_ = (isrPrio_); \
        SST_INT_UNLOCK(); \
    } while (0)

    #define SST_ISR_EXIT(pin_, EOI_command_) do { \
        SST_INT_LOCK(); \
        (EOI_command_); \
        SST_currPrio_ = (pin_); \
        SST_schedule_(); \
    } while (0)

				

Макрос "SST_ISR_ENTRY()" вызывается с заблокированными прерываниями и выполняет три действия: (1) сохраняет текущий приоритет SST в стековой переменной "pin_", (2) поднимает приоритет до уровня обработчика и (3) разрешает прерывания, позволяя их вложенность. Макрос "SST_ISR_EXIT()" вызывается с разблокированными прерываниями и проходит четыре этапа: (1) запрещает прерывания, (2) записывает в контроллер прерываний команду "EOI" (например, для 8259A PIC она выглядит как: "outportb(0x20, 0x20)"), (3) восстанавливает первоначальный приоритет процесса и (4) вызывает диспетчер, который выполняет асинхронную передачу управления, если это необходимо.

Блок параметров задачи (TCB)

Подобно другим операционным системам SST сохраняет сведения о задачах в массивах структур, именуемых "блок параметров задачи" (task control blocks - TCB). Каждцй такой блок содержит указатель на код задачи, маску приоритета задачи (вычисляемую по формуле "1 << (priority - 1)") и очередь связанных с задачей событий. Размер TCB зависит от разрядности указателя и составляет от восьми до десяти байт. Кроме того, при создании задачи требуется предоставить место под буфер событий. TCB, используемый в примере, оптимизирован для простых событий и небольшого числа фиксированных уровней прерываний в соответствии с ограничениями для классических встраиваемых систем. Ни одно из ограничений не накладывается используемыми в SST алгоритмами.


Передача событий задаче

В минимальном варианте SST события представляются структурой из двух элементов размером один байт каждый: идентификатора типа события (например, нажатие клавиши) и ассоциированного с событием параметра (например, скан-код). События хранятся в очереди событий, организованной в виде обычного кольцевого буфера. Состояние всех очередей отражается в переменной "SST_readySet_". Её структура показана на рисунке 6 и имеет размер один байт, ограничивая систему восемью уровнями приоритетов. Каждый бит "n" переменной отвечает за "n+1" уровень приоритета и устанавливается в единицу, если очередь сообщений задачи с приоритетом "n+1" не пуста (биты нумеруются с 0 по 7). Соответственно, бит "m" переменной "SST_readySet_" равен нулю, если очередь сообщений процесса с приоритетом "m+1" пуста или такой уровень приоритета не используется.


Рисунок 6. SST_readySet_, SST_currPrio_ и блоки параметров задач
Рисунок 6. TCBs

Листинг 3 демонстрирует код функции "SST_post()", которая занимается отправкой событий, используя алгоритм обычного кольцевого буфера (FIFO). Если событие ставится в пустую очередь (1), то взводится соответствующий бит в "SST_readySet_" (2) и для проверки возникновения условий синхронной передачи управления вызывается диспетчер (3).

Листинг 3. Отправка события в SST

    uint8_t SST_post(uint8_t prio, SSTSignal sig, SSTParam par) {
        TaskCB *tcb = &l_taskCB[prio - 1];
        SST_INT_LOCK();
        if (tcb->nUsed__ < tcb->end__) {
            tcb->queue__[tcb->head__].sig = sig;  /* insert the event at the head */
            tcb->queue__[tcb->head__].par = par;
            if ((++tcb->head__) == tcb->end__) {
                tcb->head__ = (uint8_t)0;         /* wrap the head */
            }
(1)         if ((++tcb->nUsed__) == (uint8_t)1) { /* the first event? */
(2)             SST_readySet_ |= tcb->mask__;     /* insert task to the ready set */
(3)             SST_schedule_();                  /* check for synchronous preemption */
            }
            SST_INT_UNLOCK();
            return (uint8_t)1;                    /* event successfully posted */
        }
        else {
            SST_INT_UNLOCK();
            return (uint8_t)0;                    /* queue full, event posting failed */
        }
    }

				

ПредпросмотрAttachmentSize
super_simple_tasker.zip293.38 КБ
sst_code.zip43.69 КБ