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

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

сигнал

сигнал(sem) в операционной системе — это механизм для реализации синхронизации между задачами и задачами, задачами и прерываниями или взаимной защитой от исключения критических ресурсов в системе. В многозадачной системе часто требуется синхронизация или взаимное исключение между задачами, и семафоры могут предоставить пользователям такую ​​поддержку.

Абстрактно говоря, семафор — это неотрицательное целое число, которое приобретается всякий раз, когда семафор приобретается (pend), целое число будет уменьшено на единицу, когда целочисленное значение0Когда это означает, что семафор находится в недопустимом состоянии и не может быть снова получен, а все задачи, пытающиеся получить его, перейдут в состояние блокировки. Обычно семафор имеет значение счетчика, и его значение счетчика можно использовать для подсчета системных ресурсов (статистики).

Вообще говоря, существует два типа значений семафора:

  • 0: означает, что не накапливаетсяpostРабота семафора, и на этом семафоре могут быть заблокированы задачи.
  • Положительное значение: указывает на наличие одного или несколькихpostСемафорные операции.

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

Семафоры также похожи на очереди.阻塞机制. Задача должна дождаться прерывания перед выполнением соответствующей обработки.Затем задача может дождаться семафора в состоянии блокировки.После освобождения семафора после возникновения прерывания задача пробуждается для выполнения соответствующей обработки. в выпуске (post) семафор может сразу перевести ожидающую задачу в состояние готовности.Если приоритет задачи самый высокий среди готовых задач, задача может быть запущена немедленно, что является ""实时响应,实时处理". Использование семафоров в операционной системе позволяет повысить эффективность обработки.

Структура данных семафора

Блок управления семафором

TencentOS tinyУправление семафором осуществляется через блок управления семафором, и его тип данныхk_sem_t , блок управления семафором состоит из нескольких элементов, в основном в том числеpend_obj_tТипpend_objа такжеk_sem_cnt_tТипcount. иpend_objНемного похоже на объектно-ориентированное наследование, наследует некоторые свойства, которые описывают тип ресурсов ядра (например, семафоры, очереди, мьютексы и т. д., а также список ожидания).list). иcount— это простая переменная (это 16-разрядное целое число без знака), представляющая значение семафора.

typedef struct k_sem_st {
    pend_obj_t      pend_obj;
    k_sem_cnt_t     count;
} k_sem_t;

Определения макросов, связанные с семафорами

существуетtos_config.h, макроопределение семафора включения:TOS_CFG_SEM_EN

#define TOS_CFG_SEM_EN              1u

Реализация семафора

TencentOS tinyОчень просто реализовать семафор только в основном коде.125Хорошо, можно сказать, что очень мало.

Создать семафор

Каждый семафор в системе имеет соответствующий блок управления семафором.Блок управления семафором содержит всю информацию о семафоре, такую ​​как список ожидания, тип ресурса и значение семафора. семафор просто для инициализации блока управления семафором? Видимо так оно и есть. Поскольку последующие операции с семафором выполняются через блок управления семафором, если в блоке управления нет информации, как им можно управлять~

Функция создания семафораtos_sem_create(), передать два параметра, один из которых является указателем на блок управления семафором*sem, другое - начальное значение семафораinit_count, значение может быть неотрицательным целым числом, но в основном не может превышать65535.

на самом деле звонкиpend_object_init()Функция помещает блок управления семафором вsem->pend_objпеременная-член инициализируется, ее тип ресурса идентифицируется какPEND_TYPE_SEM. потомsem->countПеременной-члену присваивается начальное значение семафора, переданного вinit_count.

__API__ k_err_t tos_sem_create(k_sem_t *sem, k_sem_cnt_t init_count)
{
    TOS_PTR_SANITY_CHECK(sem);

    pend_object_init(&sem->pend_obj, PEND_TYPE_SEM);
    sem->count = init_count;
    
    return K_ERR_NONE;
}

уничтожить семафор

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

TencentOS tinyПоток обработки для уничтожения семафора выглядит следующим образом:

  1. перечислитьpend_is_nopending()Функция определяет, есть ли задача, ожидающая семафора
  2. звони если естьpend_wakeup_all()Функция пробуждает эти задачи и сообщает ожидающей задаче, что семафор был уничтожен (то есть установка переменной-члена состояния ожидания в блоке управления задачей).pend_stateзаPEND_STATE_DESTROY).
  3. перечислитьpend_object_deinit()Функция очищает содержимое блока управления семафором, самое главное установить тип ресурса в блоке управления наPEND_TYPE_NONE, поэтому семафор использовать нельзя.
  4. планировать задачиknl_sched()

Примечание. Если ОЗУ блока управления семафором управляется编译器静态分配, так что даже если семафор будет уничтожен, память не может быть освобождена. Конечно, вы также можете использовать динамическую память для выделения памяти для блока управления семафором, но вам нужно освободить эту память после уничтожения, чтобы избежать утечек памяти.

__API__ k_err_t tos_sem_destroy(k_sem_t *sem)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(sem);

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (!pend_is_nopending(&sem->pend_obj)) {
        pend_wakeup_all(&sem->pend_obj, PEND_STATE_DESTROY);
    }

    pend_object_deinit(&sem->pend_obj);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

получить семафор

tos_sem_pend()Функция используется для получения семафора.Когда семафор действителен, задача может получить семафор. Когда задача получает семафор, доступный номер семафора уменьшается на 1. Когда он равен 0, задача, которая получает семафор, переходит в состояние блокировки, а время блокировкиtimeoutУказанный пользователем, когда семафор не может быть получен в течение указанного времени, будет отправлен тайм-аут, и ожидающая задача автоматически вернется в состояние готовности.

Процесс получения семафора выглядит следующим образом:

  1. Сначала проверьте правильность входящих параметров.
  2. Определите блок управления семафором вcountЯвляется ли переменная-член больше, чем0, больше 0 указывает на наличие доступного семафора,countзначение переменной-члена减1, задача возвращается после успешного полученияK_ERR_NONE.
  3. Если семафора нет, то текущее полученное задание может быть заблокировано.Посмотрите на время блокировки, указанное пользователем.timeoutЭто не блокируетTOS_TIME_NOWAIT, если не блокирует, вернуться напрямуюK_ERR_PEND_NOWAITкод ошибки.
  4. Если планировщик заблокированknl_is_sched_locked(), операция ожидания не может быть выполнена и возвращается код ошибкиK_ERR_PEND_SCHED_LOCKED, ведь надо переключать задачи, а планировщик не может переключать задачи, когда он заблокирован.
  5. перечислитьpend_task_block()Функция блокирует задачу, что фактически удаляет задачу из списка готовыхk_rdyq.task_list_head[task_prio], и внесите в лист ожиданияobject->list, если время ожидания не вечное ожиданиеTOS_TIME_FOREVER, который также вставляет задачу в список времениk_tick_list, время блокировкиtimeout, а затем выполнить планирование задачиknl_sched().
  6. когда программа можетpend_state2errno(), это значит任务等获取到信号量,или等待发生了超时, затем позвонитеpend_state2errno()Функция получает состояние ожидания задачи, видит, какая ситуация заставляет задачу возобновить выполнение, и возвращает результат задаче, которая вызвала для получения семафора.

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

__API__ k_err_t tos_sem_pend(k_sem_t *sem, k_tick_t timeout)
{
    TOS_CPU_CPSR_ALLOC();

    TOS_PTR_SANITY_CHECK(sem);
    TOS_IN_IRQ_CHECK();

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (sem->count > (k_sem_cnt_t)0u) {
        --sem->count;
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    if (timeout == TOS_TIME_NOWAIT) { // no wait, return immediately
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_NOWAIT;
    }

    if (knl_is_sched_locked()) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_PEND_SCHED_LOCKED;
    }

    pend_task_block(k_curr_task, &sem->pend_obj, timeout);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return pend_state2errno(k_curr_task->pend_state);
}

семафор выпуска

Задача или процедура обслуживания прерывания может освободить семафор (пост).Суть освобождения семафора заключается в освобождении блока управления семафором.countзначение переменной-члена加1, указывая, что семафор действителен, но если есть задача, ожидающая этого семафора, блок управления семафоромcountЗначение переменной-члена не изменится, потому что ожидающую задачу необходимо разбудить, а суть пробуждения ожидающей задачи состоит в том, чтобы дождаться, пока задача получит семафор.countзначение переменной-члена减1, это туда и обратно, блок управления семафоромcountЗначение переменных-членов не изменяется.

TencentOS tinyВ семафоре только одна ожидающая задача может получить семафор, или все ожидающие задачи могут получить семафор. Соответствующие APItos_sem_post()иtos_sem_post_all(). Кстати,tos_sem_post_all()Шаблон проектирования на самом деле является шаблоном наблюдателя. Когда наблюдаемый объект изменяется, все наблюдатели узнают, что он изменился. Подробнее см. в книге «Шаблоны проектирования Dahua».

TencentOS tinyХорошей частью дизайна является простота и низкая связанность.Эти два API-интерфейса по сути являются вызовамиsem_do_post()чтобы освободить семафор, простоoptРазные параметры выбирают разные методы обработки.

существуетsem_do_post()Обработка в функции также очень проста и понятна, а идея ее выполнения такова:

  1. Сначала определите, не переполняется ли семафор, потому что целое число всегда будет переполняться, а семафор не может быть освобожден все время.countзначение переменной-члена加1Ну, так вы должны судить, переливается ли он, еслиsem->count значение(k_sem_cnt_t)-1, это означает, что он переполнился, семафор больше не может быть освобожден, и возвращается код ошибки K.ERRSEM_ПЕРЕПОЛНЕНИЕ.
  2. перечислитьpend_is_nopending()Функция определяет, есть ли задача, ожидающая семафора, если нет, тоcountзначение переменной-члена加1,возвращениеK_ERR_NONEУказывает, что освобождение семафора прошло успешно, потому что в это время нет необходимости пробуждать задачу, поэтому нет необходимости в планировании задачи, и вы можете вернуться напрямую.
  3. Если есть задачи, ожидающие семафора, тоcountзначение переменной-члена无需加1, звоните напрямуюpend_wakeupДостаточно разбудить соответствующую задачу, а задача пробуждения основана наoptпараметр для пробуждения, вы можете разбудить задачу или все задачи в ожидании.
  4. Составьте расписание задачknl_sched().
__API__ k_err_t tos_sem_post(k_sem_t *sem)
{
    TOS_PTR_SANITY_CHECK(sem);

    return sem_do_post(sem, OPT_POST_ONE);
}

__API__ k_err_t tos_sem_post_all(k_sem_t *sem)
{
    TOS_PTR_SANITY_CHECK(sem);

    return sem_do_post(sem, OPT_POST_ALL);
}

__STATIC__ k_err_t sem_do_post(k_sem_t *sem, opt_post_t opt)
{
    TOS_CPU_CPSR_ALLOC();

#if TOS_CFG_OBJECT_VERIFY_EN > 0u
    if (!pend_object_verify(&sem->pend_obj, PEND_TYPE_SEM)) {
        return K_ERR_OBJ_INVALID;
    }
#endif

    TOS_CPU_INT_DISABLE();

    if (sem->count == (k_sem_cnt_t)-1) {
        TOS_CPU_INT_ENABLE();
        return K_ERR_SEM_OVERFLOW;
    }

    if (pend_is_nopending(&sem->pend_obj)) {
        ++sem->count;
        TOS_CPU_INT_ENABLE();
        return K_ERR_NONE;
    }

    pend_wakeup(&sem->pend_obj, PEND_STATE_POST, opt);

    TOS_CPU_INT_ENABLE();
    knl_sched();

    return K_ERR_NONE;
}

Суждение о том, почемуsem->countда(k_sem_cnt_t)-1Имеется в виду перелив? Я привел простой пример на языке C:

#include <stdio.h>

int main()
{
    unsigned int a = ~0;
    if(a == (unsigned int)0XFFFFFFFF)
    {
        printf("OK\n");
    }
    if(a == (unsigned int)-1)
    {
        printf("OK\n");
    }
    
   printf("unsigned int a = %d \n",a);
   
   return 0;
}

输出:
OK
OK
unsigned int a = -1 

Суммировать

Код лаконичен и краток, а мысли ясны, настоятельно рекомендуется изучить его поглубже~

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

欢迎关注我公众号

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