:::: MENU ::::

Работа планировщика RTOS

Данная серия статей посвящена описанию работы RTOS– написанной на основе TNKernel. Исходники вы можете скачать по этой ссылке.

Если вы хотите написать свою RTOS или разобраться в TNKernel читайте так-же:

Работа планировщика

Всю работу RTOS можно разделить на два цикла:

  • Цикл выполнения функции задачи
  • Цикл переключения контекста

Переключение контекста происходит при выполнении задачи RTOS timer_task. Поэтому ее необходимо вызывать, каждый раз при необходимости переключать задачи пользователя(планирование).

Цикл переключения — цикл прерывания по системному таймеру SysTick. Прерывание по системному таймеру настраивается в main. В обработчике прерывания SysTick_Handler как следующая задача указывается timer_task (см. функцию tick_myrtos).

void tick_myrtos(void)
{
    next_task_to_run = &timer_task;
}

После чего вызывается переключение контекста задач int_exit (непосредственное переключение контекста задачи в int_exit написано на ассемблере и будет рассмотрено в др. статье).

void SysTick_Handler()
{
    tick_myrtos();
    int_exit();
}

Контекст переключается на задачу планирования timer_task. Данная задача дожна выполниться без прерываний т.к. она является основой планирования RTOS, поэтому в начале функции timer_task_func отключаются прерывания, а в ее конце включаются.

// планировщик
void timer_task_func(void *par)
{
    uint8_t priority;

    while(1)
    {
        arm_disable_interrupts();

        // приоритет для сл. готовой задачи
        priority = queue_find_priority_next_task();
        // выбираем сл. задачу из списка задач по приоритетам
        next_task_to_run = task_param_from_task_queue(task_queue_priority[priority].next);
        // переключаем контекст на сл. задачу
        switch_context();

        arm_enable_interrupts();
    }
} 

queue_find_priority_next_task производит поиск задач в списке приоритетов. Данная функция возвращает номер самого высокого приоритета у которого есть задачи в списке. Очередь задач по приоритетам представляет собой связанный список для каждого приоритета:

// 1 приоритет
task_queue_priority[1].next 
task_queue_priority[1].prev

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

// очередь задач для каждого приоритета
link_init task_queue_priority[NUM_PRIORITY]; 

// связанные списки
typedef struct _link_init
{
   struct _link_init * prev;
   struct _link_init * next;
} link_init;

ris1

uint8_t queue_find_priority_next_task(void)
{
    uint8_t i;
    // проход по всем приоритетам и проверка есть ли задача в их списке (поиск сл. задачи)
    // есть - если 1й связанный список в поле next не указывает сам на себя
    for(i = 1; i < NUM_PRIORITY; i++)
    {
        if(task_queue_priority[i].next != &(task_queue_priority[i]))
        {
            break;
        }
    }

    return(i);
}

После того как мы узнали какой приоритет будет использоваться — указываем следующую задачу с данным приоритетом из списка приоритетов. Это происходит в функции task_param_from_task_queue.

    // получение из очереди(связанного списка) - структуры к которой она относятся - задача
task_param_init * task_param_from_task_queue(link_init *queue)
{
    uint8_t *byte_offset;
    task_param_init *task;

    byte_offset = (uint8_t*)(&((task_param_init*)0)->task_queue);
    task = (task_param_init*)((uint8_t*)(queue) - byte_offset);

    return(task);
}

NOTE: Более подробно о работе данного кода, можно узнать из этой статьи.

Последний этап это переключение контекста на следующую задачу — задачу пользователя, которую мы определили выше, происходит в int_exit.