[TencentOS tiny] Углубленный анализ исходного кода (1) — задача

Интернет вещей

Основные концепции задач

С точки зрения системы задача — это наименьшая единица операции, конкурирующая за системные ресурсы. TencentOS tiny — это операционная система, которая поддерживает многозадачность. Задачи могут использовать системные ресурсы, такие как ЦП и память, или ожидать их, и выполняться независимо от других задач. Теоретически любое количество задач может иметь одинаковый приоритет, чтобы они были готовы Несколько задач с одинаковым приоритетом в состоянии будут совместно использовать процессор в режиме переключения временных интервалов.

нообращать вниманиеДело в том, что в TencentOS tiny невозможно создать задачу с тем же приоритетом, что и бездействующая задача.K_TASK_PRIO_IDLE, задачам с одинаковым приоритетом должно быть разрешено использовать планирование временных интервалов, открытьTOS_CFG_ROUND_ROBIN_EN.

Вкратце: задачи TencentOS tiny можно рассматривать как набор независимых задач. Каждая задача выполняется в своей среде. В любой момент времени выполняется только одна задача, и крошечный планировщик TencentOS решает, какую задачу запустить. от宏观Получается, что все задачи выполняются одновременно.

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

Система может поддерживать 10 приоритетов по умолчанию,0~TOS_CFG_TASK_PRIO_MAX, это определение макроса можно изменить, чем больше значение приоритета, тем ниже приоритет задачи,(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u)Он имеет самый низкий приоритет и назначается бездействующим задачам.

#define K_TASK_PRIO_IDLE         (k_prio_t)(TOS_CFG_TASK_PRIO_MAX - (k_prio_t)1u)
#define K_TASK_PRIO_INVALID      (k_prio_t)(TOS_CFG_TASK_PRIO_MAX)

статус задачи

Статус задачи TencentOS tiny выглядит следующим образом.

  • Состояние готовности (КTASKSTATE_READY): задача находится в списке готовых, готовая задача имеет возможность выполнения, просто подождите, пока планировщик запланирует, и вновь созданная задача будет инициализирована до состояния готовности.
  • Рабочее состояние (КTASKSTATE_READY): Это состояние указывает на то, что задача выполняется, и в это время она занимает процессор. На самом деле задача в это время все еще находится в списке готовности. Планировщик TencentOS всегда выбирает запуск задачи в состоянии готовности с наивысший приоритет.Когда задача запущена, ее состояние задачи становится состоянием выполнения.
  • Состояние сна (КTASKSTATE_SLEEP): Если задача в данный момент спит, чтобы отказаться от права использования ЦП, то можно сказать, что задача находится в спящем состоянии, задача не находится в списке готовности, а задача находится в списке сна (или список задержек).
  • Состояние ожидания (КTASKSTATE_PEND): задача ожидает состояния, такого как семафор, очередь или ожидание события.
  • Подвешенное состояние (КTASKSTATE_SUSPENDED): задача приостановлена, и в настоящее время она невидима для планировщика.
  • Выходное состояние (КTASKSTATE_DELETED): задача завершена и удалена.
  • Дождитесь состояния тайм-аута (KTASKSTATE_PENDTIMEOUT): задача ожидает семафора, очереди или истечения времени ожидания события.
  • Приостановка сна (KTASKSTATESLEEPПРИОСТАНОВЛЕНО): состояние, когда задача приостановлена ​​во время сна.
  • Ожидание состояния ожидания (KTASKSTATEPENDПРИОСТАНОВЛЕНО): состояние, в котором задача приостановлена ​​во время ожидания семафора, очереди или события.
  • Ожидание состояния ожидания тайм-аута (KTASKSTATEPENDTIMEOUTПРИОСТАНОВЛЕНО): задача ожидает семафора, очереди или истечения времени ожидания события, но в это время задача приостановлена.
// ready to schedule
// a task's pend_list is in readyqueue
#define K_TASK_STATE_READY                (k_task_state_t)0x0000

// delayed, or pend for a timeout
// a task's tick_list is in k_tick_list
#define K_TASK_STATE_SLEEP                (k_task_state_t)0x0001

// pend for something
// a task's pend_list is in some pend object's list
#define K_TASK_STATE_PEND                 (k_task_state_t)0x0002

// suspended
#define K_TASK_STATE_SUSPENDED            (k_task_state_t)0x0004

// deleted
#define K_TASK_STATE_DELETED              (k_task_state_t)0x0008

// actually we don't really need those TASK_STATE below, if you understand the task state deeply, the code can be much more elegant. 

// we are pending, also we are waitting for a timeout(eg. tos_sem_pend with a valid timeout, not TOS_TIME_FOREVER)
// both a task's tick_list and pend_list is not empty
#define K_TASK_STATE_PENDTIMEOUT                      (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SLEEP)

// suspended when sleeping
#define K_TASK_STATE_SLEEP_SUSPENDED                  (k_task_state_t)(K_TASK_STATE_SLEEP | K_TASK_STATE_SUSPENDED)

// suspened when pending
#define K_TASK_STATE_PEND_SUSPENDED                   (k_task_state_t)(K_TASK_STATE_PEND | K_TASK_STATE_SUSPENDED)

// suspended when pendtimeout
#define K_TASK_STATE_PENDTIMEOUT_SUSPENDED            (k_task_state_t)(K_TASK_STATE_PENDTIMEOUT | K_TASK_STATE_SUSPENDED)

Структура данных задач обслуживания в TencentOS

готовый список

TencentOS tiny поддерживает готовый список для монтирования всех задач в готовом состоянии в системе.readyqueue_t Список типов, чьи переменные-члены следующие:

readyqueue_t        k_rdyq;

typedef struct readyqueue_st {
    k_list_t    task_list_head[TOS_CFG_TASK_PRIO_MAX];
    uint32_t    prio_mask[K_PRIO_TBL_SIZE];
    k_prio_t    highest_prio;
} readyqueue_t;

task_list_headэто тип спискаk_list_tМассив , TencentOS tiny выделяет список для каждой приоритетной задачи, а система поддерживает максимальный приоритетTOS_CFG_TASK_PRIO_MAXprio_maskЭто массив масок приоритетов, представляющий собой массив переменных 32-битного типа, а количество членов массива определяетсяTOS_CFG_TASK_PRIO_MAXПринять решение:

#define K_PRIO_TBL_SIZE         ((TOS_CFG_TASK_PRIO_MAX + 31) / 32)

когдаTOS_CFG_TASK_PRIO_MAXКогда оно не превышает 32, имеется только одна переменная-член массива, представляющая собой 32-разрядное значение переменной, тогда каждый бит переменной представляет собой приоритет. например, когдаTOS_CFG_TASK_PRIO_MAX64 года,prio_mask[0]Каждый бит (бит) переменной представляет0-31приоритет, покаprio_mask[1]Каждый бит переменной представляет32-63приоритет.

highest_prioЭто запись самого высокого приоритета из текущего списка приоритетов, что удобно для индексации.task_list_head.

Список задержек

В этот список будут монтироваться задачи, связанные с системным временем, это может быть сон, ожидание семафоров, события, очереди сообщений и т.д.

k_list_t             k_tick_list;

блок управления полетом

В многозадачной системе выполнение задач планируется системой. Для плавного планирования задач система определяет дополнительный блок управления задачами для каждой задачи.Этот блок управления задачами эквивалентен идентификационной карточке задачи, которая содержит всю информацию о задаче, такую ​​как указатель стека задачи. , название задачи, формальные параметры задачи и т.д. С помощью этого блока управления задачами все последующие операции системы над задачей могут быть реализованы через этот блок управления задачами. Блок управления задачами TencentOS выглядит следующим образом:

typedef struct k_task_st {
    k_stack_t          *sp;                 /**< 任务栈指针,用于切换上下文*/

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    knl_obj_t           knl_obj;            /**< 只是为了验证,测试当前对象是否真的是一项任务。*/
#endif

    char               *name;               /**< 任务名称 */
    k_task_entry_t      entry;              /**< 任务主体 */
    void               *arg;                /**< 任务主体形参 */
    k_task_state_t      state;              /**< 任务状态 */
    k_prio_t            prio;               /**< 任务优先级 */

    k_stack_t          *stk_base;           /**< 任务栈基地址 */
    size_t              stk_size;           /**< 任务栈大小 */

    k_tick_t            tick_expires;       /**< 任务阻塞的时间 */

    k_list_t            tick_list;          /**< 延时列表 */
    k_list_t            pend_list;          /**< 就绪、等待列表 */

#if TOS_CFG_MUTEX_EN > 0u
    k_list_t            mutex_own_list;     /**< 任务拥有的互斥量 */
    k_prio_t            prio_pending;       /*< 用于记录持有互斥量的任务初始优先级,在优先级继承中使用 */
#endif

    pend_obj_t         *pending_obj;       /**< 记录任务此时挂载到的列表 */
    pend_state_t        pend_state;         /**< 等待被唤醒的原因(状态) */

#if TOS_CFG_ROUND_ROBIN_EN > 0u
    k_timeslice_t       timeslice_reload;   /**< 时间片初始值(重装载值) */
    k_timeslice_t       timeslice;          /**< 剩余时间片 */
#endif

#if TOS_CFG_MSG_EN > 0u
    void               *msg_addr;           /**< 保存接收到的消息 */
    size_t              msg_size;            /**< 保存接收到的消息大小 */
#endif

#if TOS_CFG_EVENT_EN > 0u
    k_opt_t             opt_event_pend;     /**< 等待事件的的操作类型:TOS_OPT_EVENT_PEND_ANY 、 TOS_OPT_EVENT_PEND_ALL */
    k_event_flag_t      flag_expect;        /**< 期待发生的事件 */
    k_event_flag_t     *flag_match;         /**< 等待到的事件 */
#endif
} k_task_t;

Создать задачу

В TencentOS крошечный, любое использование__API__ Пользователю предоставляются оформленные функции, а использование__KERNEL__Декорированный код используется ядром. Функция создания задачи TencentOS имеет несколько параметров:

параметр значение
task
блок управления полетом
name
название задачи
entry
Тема задания
arg
параметр задачи
prio
приоритет
stk_base
Базовый адрес стека задач
stk_size
размер стека задач
timeslice
квант времени

Подробное объяснение параметра (источник TencentOS крошечное руководство по разработке):

  • task

это кtaskуказатель типа t, ktaskt — тип структуры задачи ядра. Примечание. Указатель задачи должен указывать на k, чей жизненный цикл больше, чем жизненный цикл создаваемого тела задачи.taskt, если жизненный цикл переменной, на которую указывает указатель, короче жизненного цикла создаваемого тела задачи, например, это может быть переменная в стеке функций с экстремальным жизненным циклом, а тело задачи может все еще выполняться, пока ktaskПеременная t была уничтожена, что вызвало непредсказуемые проблемы с системным планированием.

  • name

Указатель на строку имени задачи. Примечание: Как и в случае с задачей, жизненный цикл строки, на которую указывает указатель, должен быть больше, чем жизненный цикл создаваемого тела задачи.Вообще говоря, достаточно передать постоянный указатель на строку.

  • entry

Запись функции для выполнения тела задачи. После того, как задача создана и переходит в состояние выполнения, запись является точкой входа для выполнения задачи, и пользователь может написать бизнес-логику в этой функции.

  • arg

Аргументы, переданные в функцию ввода задачи.

  • prio

приоритет задачи. Чем меньше значение prio, тем выше приоритет. Пользователи могутconfig.h, через TOSCFGTASKPRIOMAX используется для настройки максимального значения приоритета задачи, в реализации ядра приоритет простаивающей задачи будет назначен как TOSCFGTASKPRIOMAX - 1, этот приоритет может использоваться только бездействующими задачами. Таким образом, для задачи, созданной пользователем, разумным диапазоном приоритета будет [0, TOSCFGTASKPRIOМАКС-2]. Дополнительные условия обслуживанияCFGTASKЗначение конфигурации PRIO_MAX должно быть больше или равно 8.

  • stk_base

Начальный адрес пространства стека, используемого задачей во время выполнения. Примечание. Как и в случае с задачей, жизненный цикл области памяти, на которую указывает этот указатель, должен быть больше, чем жизненный цикл создаваемого тела задачи. сткбаза kНачальный адрес массива типа stack_t.

  • stk_size

Размер пространства стека для задачи. Примечание: потому что сткбаза kstackУказатель массива типа t, поэтому реальное пространство стека занимает в памяти размер stk.размер * размер (k_stack_t).

  • timeslice

Размер кванта времени текущей задачи в соответствии с механизмом поворота кванта времени. Когда временной интервал равен 0, для временного интервала планирования задач будет установлен размер по умолчанию (TOSCFGCPUTICKPER_SECOND / 10), количество тактов системных часов (systick) / 10.

Реализация задачи создания такова: Сначала проверяются параметры, а потом отмечу:В TencentOS нельзя создать задачу с тем же приоритетом, что и бездействующая задача.K_TASK_PRIO_IDLE. тогда позвониcpu_task_stk_initФункция инициализирует стек задач и записывает входящие параметры в блок управления задачами. если открытоTOS_CFG_ROUND_ROBIN_EN определение макроса, это означает, что планирование временного интервала поддерживается, тогда вам необходимо настроить информацию, связанную с временным интерваломtimeslice в блок управления задачами. тогда позвониtask_state_set_readyФункция переводит вновь созданную задачу в состояние готовностиK_TASK_STATE_READY, затем позвонитеreadyqueue_add_tailФункция для вставки задачи в готовый списокk_rdyqсередина. Если планировщик запущен, выполните планирование задач.

Лично я немного жалею, что нет динамического выделения из кучи, я предпочитаю простой интерфейс функций~!

код показывает, как показано ниже:

__API__ k_err_t tos_task_create(k_task_t *task,
                                char *name,
                                k_task_entry_t entry,
                                void *arg,
                                k_prio_t prio,
                                k_stack_t *stk_base,
                                size_t stk_size,
                                k_timeslice_t timeslice)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_IN_IRQ_CHECK();

    TOS_PTR_SANITY_CHECK(task);
    TOS_PTR_SANITY_CHECK(entry);
    TOS_PTR_SANITY_CHECK(stk_base);

    if (unlikely(stk_size < sizeof(cpu_context_t))) {
        return K_ERR_TASK_STK_SIZE_INVALID;
    }

    if (unlikely(prio == K_TASK_PRIO_IDLE && !knl_is_idle(task))) {
        return K_ERR_TASK_PRIO_INVALID;
    }

    if (unlikely(prio > K_TASK_PRIO_IDLE)) {
        return K_ERR_TASK_PRIO_INVALID;
    }

    task_reset(task);
#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    knl_object_init(&task->knl_obj, KNL_OBJ_TYPE_TASK);
#endif

    task->sp        = cpu_task_stk_init((void *)entry, arg, (void *)task_exit, stk_base, stk_size);
    task->entry     = entry;
    task->arg       = arg;
    task->name      = name;
    task->prio      = prio;
    task->stk_base  = stk_base;
    task->stk_size  = stk_size;

#if TOS_CFG_ROUND_ROBIN_EN > 0u
    task->timeslice_reload = timeslice;

    if (timeslice == (k_timeslice_t)0u) {
        task->timeslice = k_robin_default_timeslice;
    } else {
        task->timeslice = timeslice;
    }
#endif

    TOS_CPU_INT_DISABLE();
    task_state_set_ready(task);
    readyqueue_add_tail(task);
    TOS_CPU_INT_ENABLE();

    if (tos_knl_is_running()) {
        knl_sched();
    }

    return K_ERR_NONE;
}

уничтожение задачи

Эта функция очень проста: задача уничтожается в соответствии с переданным блоком управления задачей, или может быть передан NULL для уничтожения текущей задачи. Но нельзя уничтожать неработающие задачиk_idle_task, когда планировщик не может уничтожить себя, когда он заблокирован, он вернетсяK_ERR_SCHED_LOCKEDкод ошибки. Если используется мьютекс, мьютекс освобождается при уничтожении задачи и уничтожается в соответствии с состоянием задачи, например, если задача находится в состоянии готовности, отложенном состоянии или состоянии ожидания. ,对应的状态列表удалено в. Код реализован следующим образом:

__API__ k_err_t tos_task_destroy(k_task_t *task)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_IN_IRQ_CHECK();

    if (unlikely(!task)) {
        task = k_curr_task;
    }

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!knl_object_verify(&task->knl_obj, KNL_OBJ_TYPE_TASK)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    if (knl_is_idle(task)) {
        return K_ERR_TASK_DESTROY_IDLE;
    }

    if (knl_is_self(task) && knl_is_sched_locked()) {
        return K_ERR_SCHED_LOCKED;
    }

    TOS_CPU_INT_DISABLE();

#if TOS_CFG_MUTEX_EN > 0u
    // when we die, wakeup all the people in this land.
    if (!tos_list_empty(&task->mutex_own_list)) {
        task_mutex_release(task);
    }
#endif

    if (task_state_is_ready(task)) { // that's simple, good kid
        readyqueue_remove(task);
    }
    if (task_state_is_sleeping(task)) {
        tick_list_remove(task);
    }
    if (task_state_is_pending(task)) {
        pend_list_remove(task);
    }

    task_reset(task);
    task_state_set_deleted(task);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

задача сон

Сон задачи очень прост, основная идея — убрать задачу из списка готовых и добавить в список отложенныхk_tick_list, если планировщик заблокирован, вернуть код ошибки напрямуюK_ERR_SCHED_LOCKED, если время сна равно 0, вызовитеtos_task_yieldФункция инициирует планирование задачи; вызовtick_list_addфункция поставит задачу插入延时列表средний, время снаdelayзадается пользователем. нотребует вниманиядаЕсли время сна задачи постоянноеTOS_TIME_FOREVER, вернет код ошибкиK_ERR_DELAY_FOREVER, это потому, что задача sleep主动行为, если вы постоянно спите, вы не сможете активно проснуться, а такие задачи, как ожидание событий, семафоров, очередей сообщений и т. д., являются пассивным поведением, которое может быть постоянным ожиданием. , а очередь сообщений не пуста. будет разбужена, это被动行为, нужно различать два момента. последний звонокreadyqueue_removeФункция удаляет задачу из списка готовых, а затем вызываетknl_schedКогда функция инициирует планирование задачи, она может переключиться на другую задачу. Код для сна задачи выглядит следующим образом:

__API__ k_err_t tos_task_delay(k_tick_t delay)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_IN_IRQ_CHECK();

    if (knl_is_sched_locked()) {
        return K_ERR_SCHED_LOCKED;
    }

    if (unlikely(delay == (k_tick_t)0u)) {
        tos_task_yield();
        return K_ERR_NONE;
    }

    TOS_CPU_INT_DISABLE();

    if (tick_list_add(k_curr_task, delay) != K_ERR_NONE) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_DELAY_FOREVER;
    }

    readyqueue_remove(k_curr_task);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

Следуй за мной, если хочешь!

欢迎关注我公众号

Соответствующий код можно получить в фоне официального аккаунта. Добро пожаловать в публичный аккаунт «Развития Интернета вещей IoT»