~~NOCACHE~~
====== TNKernel : Задачи ======
===== Введение =====
Задача в TNKernel это часть программного кода, которая с точки зрения программиста выполняется //одновременно// с другими задачами, что обеспечивается разделением процессорного времени между ними. Каждая задача может быть представлена как независимое приложение, которое владеет уникальными ресурсами (регистры процессора, указатель стека и т.п.). Эти ресурсы называются контекстом задачи, а время в течении которого задача выполняется можно назвать временем //в контексте задачи//.
Когда текущая задача приостанавливает выполнение (в случае прерывания или вызова сервиса), осуществляется //переключение контекста// - контекст текущей задачи сохраняется в ее стеке, а контекст наиболее приоритетной задачи из готовых к выполнению восстанавливается. Этот механизм в TNKernel называется "диспетчером".
Определение наиболее приоритетной задачи в момент переключения контекста осуществляется на основании набора правил, а механизм, который обеспечивает соблюдение этих правил называется "планировщиком".
В TNKernel используется приоритетное вытесняющее планирование, основанное на приоритете, назначаемом каждой задаче, при этом чем меньше величина, тем выше уровень приоритета. В TNKernel доступно 32 уровня приоритета для 32-битных контроллеров (ARM, MIPS) и 16 уровней приоритета для 16-битных контроллеров (PIC24/dsPIC).
Приоритеты 0 (самый высокий) и 31(**15**) (самый низкий) зарезервированы для системных задач. Для пользовательских задач доступны приоритеты от 1 до 30(**14**) включительно. В TNKernel несколько задач могут иметь одинаковый приоритет.
~~UP~~
===== Состояния задач =====
Задачи в TNKernel могут находится в одном из четырех состояний:
; ''RUNNING'' : Задача выполняется в данный момент
; ''READY'' : Задача готова к выполнению, но не может получить , так как в данный момент выполняется задача с более высоким (или равным) приоритетом. В TNKernel оба состояния ''RUNNING'' и ''READY'' называются ''RUNNABLE''
; ''WAIT/SUSPEND'' : Когда задача находится в состоянии ''WAIT/SUSPEND'' она не может начать выполнение до тех пор пока не выполнится условие, которого задача ожидает. При входе в состояние ''WAIT/SUSPEND'' контекст задачи сохраняется, при выходе из этого состояния контекст восстанавливается. Состояние ''WAIT/SUSPEND'' делится на три типа:
{| class = "fpl"
|-
| ''WAITING''
| Задача находится в состоянии ''WAIT/SUSPEND'' до тех пор пока не наступит событие, которого она ожидает - завершится таймаут, освободится семафор, установится флаг и т.п.
|-
| ''SUSPENDED''
| Задача перемещена в состояние ''WAIT/SUSPEND'' другой задачей или самостоятельно путем вызова специального сервиса
|-
| ''WAITING_SUSPENDED''
| Задача находится как в состоянии ''WAITING'', так и в состоянии ''SUSPENDED'' (ожидает события и приостановлена специальным сервисом). Если задача освобождается от состояния ''WAITING'' (ожидаемое событие наступило), то она остается в состоянии ''SUSPENDED'' и наоборот.
|}
; ''DORMANT'' : Задача уже создана, но еще ни разу не запускалась : Выполнение задачи завершено с помощью специального таймера.
\\
Можно так же выделить состояние задачи, в котором она еще не создана - состояние ''NON-EXISTENT''.
Граф перехода между состояниями изображен на рисунке:
{{ tnkernel:ref:task:tn_task_states_diagram.png }}
Сервисы, вызов которых приводит к изменению состояния задачи указаны возле направлений перехода. Для простоты префикс ''tn_task_'' и префикс ''i'' (вызов из прерывания) опущены.
Переход задачи из состояния ''READY'' в состояние ''RUNNING'' происходит тогда, когда ее приоритет наивысший из приоритетов всех задач находящихся в состоянии ''READY''.
Переход задачи из состояния ''RUNNING'' в состояние ''READY'' происходит тогда, когда ее приоритет меньше чем приоритет одной из задач, находящихся в состоянии ''READY''.
Задача переходит из состояния ''RUNNING'' в состояние ''WAITING'' при вызове любого из системных сервисов, блокирующих выполнение задачи. Например, если задача пытается захватить семафор, а он занят другой задачей, ей ничего не остается как ожидать его освобождения в состоянии ''WAITING''.
Задача переходит из состояния ''WAITING'' в состояние ''READY'' когда событие, которого она ожидала произошло. Например, освободился семафор или окончился таймаут ожидания.
~~UP~~
===== Правила планирования =====
В TNKernel во время выполнения задачи с наивысшим приоритетом ни одна из других задач не может получить управление до тех пор, пока эта задача не перейдет в состояние ''WAITING/SUSPEND'' или ''DORMANT''.
Если несколько задач с //разными приоритетами// готовы к выполнению (т.е. находятся в состоянии ''READY''), управление получит задача с наивысшим приоритетом.
Если несколько задач с //одинаковым приоритетом// готовы к выполнению, то управление получит задача, которая перешла в состояние ''READY'' раньше остальных, т.е. первой стоит в очереди готовых к выполнению.
> **Пример:** пусть задача **А** имеет приоритет 1, задачи **Б**, **В**, **Г**, **Д** - приоритет 3, задачи **Е** и **Ж** - приоритет 4, задача **З** - приоритет 5. Если все задачи находятся в состоянии ''READY'' последовательность выполнения будет следующая: \\
> 1. Задача **А** с наиболее высоким приоритетом (приоритет 1)
> 2. Задачи **Б**, **В**, **Г** и **Д** в той последовательности в которой они перешли в состояние ''READY'' (приоритет 3)
> 3. Задачи **Е** и **Ж** в той последовательности, в которой они перешли в состояние ''READY'' (приоритет 4)
> 4. Задача **З**, так как она имеет наиболее низкий приоритет (приоритет 5)
В TNKernel задачи с одинаковым приоритетом могут получать управление в соответствии с правилами round-robin планировщика (планировщика карусельного типа). В этом случае каждой задаче выделяется квант времени. Квант времени может быть выбран для каждого приоритета.
~~UP~~
===== Системные задачи =====
В TNKernel существует две системные задачи, которые создаются при запуске системы.
Одна из этих задач (''timer_task'') имеет наивысший приоритет (0) и обеспечивает функционирование системного таймера.
Вторая задача (''idle_task'') имеет минимальный приоритет (31/15) и получает управление тогда, когда нет пользовательских задач готовых к выполнению. Эта задача может использоваться для сбора статистики или перевода процессора в состояние пониженного потребления энергии.
Размеры стеков системных задач определяет пользователь, исходя из особенностей приложение. Кроме того, необходимо объявить функцию ''idle_user_cb()'', которая циклически вызывается из задачи ''idle_task''.
~~UP~~
===== Структура управления задачей =====
Каждая задача ассоциируется со структурой управления:
typedef struct TN_TCB_S_STRUCT
{
TN_UWORD * task_stk;
CDLL_QUEUE_S task_queue;
CDLL_QUEUE_S timer_queue;
CDLL_QUEUE_S block_queue;
CDLL_QUEUE_S create_queue;
CDLL_QUEUE_S mutex_queue;
CDLL_QUEUE_S * pwait_queue;
struct TN_TCB_S_STRUCT * blk_task;
TN_UWORD * stk_start;
TN_UWORD stk_size;
void * task_func_addr;
void * task_func_param;
TN_UWORD base_priority;
TN_UWORD priority;
TN_UWORD id_task;
TN_WORD task_state;
TN_UWORD task_wait_reason;
TN_WORD task_wait_rc;
TN_UWORD tick_count;
TN_UWORD tslice_count;
TN_UWORD ewait_pattern;
TN_UWORD ewait_mode;
void * data_elem;
TN_UWORD activate_count;
TN_UWORD wakeup_count;
TN_UWORD suspend_count;
} TN_TCB_S;
В состав структуры управления задачей входят следующие элементы:
{| class = "fpl"
|-
| ''task_stk''
| Указатель на вершину стека задачи
|-
| ''task_queue''
| Элемент очереди для включения задачи в список существующих задач
|-
| ''timer_queue''
| Элемент очереди для включения задачи в список задач, ожидающих события таймера (таймаут и т.п.)
|-
| ''block_queue''
| Элемент очереди для включения задачи в список заблокированных задач (используется в протоколе priority ceiling системных мютексов)
|-
| ''create_queue''
| Элемент очереди для включения задачи в список созданных задач
|-
| ''mutex_queue''
| Список всех мютексов, заблокированных задачей
|-
| ''pwait_queue''
| Указатель на очередь объектов (семафоров, флагов), ожидаемых задачей
|-
| ''blk_task''
| Указатель на структуру задачи которая заблокировала эту задачу (используется в протоколе priority ceiling системных мютексов)
|-
| ''stk_start''
| Указатель на базовый адрес стека задачи
|-
| ''stk_size''
| Размер стека задачи
|-
| ''task_func_addr''
| Указатель на функцию задачи
|-
| ''task_func_param''
| Укащатель на параметр, передаваемый в функцию задачи
|-
| ''base_priority''
| Базовый приоритет задачи
|-
| ''priority''
| Текущий приоритет задачи
|-
| ''id_task''
| Поле идентификации объекта как задачи
|-
| ''task_state''
| Состояние задачи
|-
| ''task_wait_reason''
| Причина нахождения в состоянии ''WAITING''
|-
| ''task_wait_rc''
| Код, возвращаемый задачей при выходе из состояния ''WAITING'' (причина, по которой задача вышла из состояния ожидания)
|-
| ''tick_count''
| Время до истечения таймаута в системных тиках
|-
| ''tslice_count''
| Счетчик кванта времени для round-robin планирования в системных тиках
|-
| ''ewait_pattern''
| Маска ожидаемых флагов
|-
| ''ewait_mode''
| Тип ожидания флагов (И или ИЛИ, т.е. все флаги, или хотя бы один из маски)
|-
| ''data_elem''
| Указатель на очередь сообщений
|-
| ''activate_count''
| Счетчик запросов на активацию задачи
|-
| ''wakeup_count''
| Счетчик запросов на пробуждение задачи
|-
| ''suspend_count''
| Счетчик запросов на останов задачи
|}
Структура задачи доступна только при определении ''TN_DEBUG''. Тем не менее, прямой доступ к элементам структуры задачи крайне не рекомендуется, так как это является вмешательством в работу планировщика и других сервисов RTOS.
~~UP~~
===== Сервисы управления задачами =====
TNKernel имеет следующий набор функций (сервисов) для управления задачами:
^ Сервис ^ Описание ^ Свойства ^
| **Создание и удаление задачи** |||
| \ \ [[tnkernel:ref:task:tn_task_create|tn_task_create()]] | Создание задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} |
| \ \ [[tnkernel:ref:task:tn_task_delete|tn_task_delete()]] | Удаление задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} |
| **Перезапуск задачи** |||
| \ \ [[tnkernel:ref:task:tn_task_exit|tn_task_exit()]] | Выход из текущей задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_terminate|tn_task_terminate()]] | Завершение работы задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_activate|tn_task_activate()]] | Перезапуск задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_iactivate|tn_task_iactivate()]] | Перезапуск задачи из прерывания | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| **Останов и восстановление задачи** |||
| \ \ [[tnkernel:ref:task:tn_task_suspend|tn_task_suspend()]] | Останов задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_isuspend|tn_task_isuspend()]] | Останов задачи в прерывании | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_resume|tn_task_resume()]] | Восстановление задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_iresume|tn_task_iresume()]] | Восстановление задачи в прерывании | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| **Приостановка выполнения и пробуждение задачи** |||
| \ \ [[tnkernel:ref:task:tn_task_sleep|tn_task_sleep()]] | Приостановка выполнения задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_wakeup|tn_task_wakeup()]] | Пробуждение приостановленной задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_iwakeup|tn_task_iwakeup()]] | Пробуждение приостановленной задачи в прерывании | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| **Форсированный вывод задачи из состояния ''WAITING''** |||
| \ \ [[tnkernel:ref:task:tn_task_release_wait|tn_task_release_wait()]] | Форсированный вывод задачи из состояния ''WAITING'' | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| \ \ [[tnkernel:ref:task:tn_task_irelease_wait|tn_task_irelease_wait()]] | Форсированный вывод задачи из состояния ''WAITING'' в прерывании | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| **Изменение приоритета задачи** |||
| \ \ [[tnkernel:ref:task:tn_task_change_priority|tn_task_change_priority()]] | Изменение приоритета задачи | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} {{tnkernel:ref:attr_call_ct_sw.png|Может привести к переключению контекста}} |
| **Получение информации о задаче** |||
| \ \ [[tnkernel:ref:task:tn_task_reference|tn_task_reference()]] | Получение информации о задаче | {{tnkernel:ref:attr_call_task.png|Разрешен вызов только в контексте задачи}} |
| \ \ [[tnkernel:ref:task:tn_task_ireference|tn_task_ireference()]] | Получение информации о задаче в прерывании | {{tnkernel:ref:attr_call_int.png|Разрешен вызов только в прерывании}} |
~~UP~~