Данная серия статей посвящена описанию работы RTOS– написанной на основе TNKernel. Исходники вы можете скачать по этой ссылке.
Если вы хотите написать свою RTOS или разобраться в TNKernel читайте так-же:
- Задачи
- Работа планировщика
- Последовательность действий при инициализации
- Assembler, переключение контекста
- Чередование задач при помощи отправки задачи в конец очереди для данного приоритета
- Отправление задачи в режим ожидания на определенное время
- Простой семафор
Чередование задач при помощи отправки задачи в конец очереди для данного приоритета
Допустим мы создали две задачи, каждая из которых моргает собственным светодиодом. Приоритеты этих задач одинаковы, но одна из этих задач будет всегда следовать за другой в очереди по данному приоритету, а поскольку у нас из очереди вызывается всегда первая задача(которая чередуется с timer_task), то вторая задача в этой очереди выполнятся никогда не будет.
Для исправления этого недостатка в конце первой задачи будем вызывать функцию, которая будет перекладывать выполняемую(первую) задачу в конец очереди по данному приоритету.
Итак у нас есть две задачи:
void task_1_func(void *par)
{
while(1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_6);
GPIO_ResetBits(GPIOB, GPIO_Pin_6);
task_move_tail_queue_priority();
}
}
void task_2_func(void *par)
{
while(1)
{
GPIO_SetBits(GPIOB, GPIO_Pin_7);
GPIO_ResetBits(GPIOB, GPIO_Pin_7);
task_move_tail_queue_priority();
}
}
Функция task_move_tail_queue_priority — перекладывает выполняемую задачу в конец очереди и дает следующей задачи в очереди выполнится.
// задача переходит в конец очереди для данного приоритета
void task_move_tail_queue_priority(void)
{
uint8_t priority;
arm_disable_interrupts();
queue_remove_task(&(curr_run_task->task_queue));
queue_task_add(curr_run_task);
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();
}
Поскольку это не задача пользователя, а функцияRTOS, то при ее выполнении не должно произойти переключение контекста — поэтому вначале ее выключаются, а в конце включаются, прерывания.
Функция queue_remove_task удаляет элемент данной задачи из связанного списка — удаление элемента из очереди.
// удаление задачи из списка
void queue_remove_task(link_init *task_queue)
{
if(task_queue->next == task_queue)
{
task_queue->prev->next = task_queue->prev;
}
else
{
task_queue->prev->next = task_queue->next;
}
task_queue->next->prev = task_queue->prev;
}
Функция queue_task_add добавляет данную задачу в конец связанного списка — в конец очереди задач для данного приоритета(приоритет определяется по приоритету данной задачи). Т.е. в совокупности с предыдущей функцией эта функция удаляет задачу из начала очереди и добавляет в конец очереди.
Note: последний элемент в очереди указывает сам на себя.
// добавление задачи в конец очереди по своему приоритету
void queue_task_add(task_param_init* task)
{
link_init *temp_link;
// ищем последний элемент в очереди для приотритета данной задачи
temp_link = &task_queue_priority[task->task_priority];
while((*temp_link).next != temp_link)
{
temp_link = temp_link->next;
}
// добавляем в элемент данной задачи в конец очереди
temp_link->next = &(task->task_queue);
task->task_queue.prev = temp_link;
task->task_queue.next = &task->task_queue;
}
После находим по приоритетам и очереди задач по приоритетам следующую задачу(подробнее см. в статье Работа планировщика RTOS) и переключаем контекст на новую задачу.
Таким же образом можно и совсем удалить задачу. Функция удаления будет отличаться от функции переноса в конец очереди — только добавлением удаленного элемента обратно в очередь:
// выход из задачи навсегда - задача не будет учавствовать в планировании
void task_exit(void)
{
uint8_t priority;
arm_disable_interrupts();
queue_remove_task(&(curr_run_task->task_queue));
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();
}