Abstract: This article leads you to analyze the source code of the semaphore module of the Hongmeng Light Kernel, including the structure of the semaphore, the initialization of the semaphore pool, the creation and deletion of the semaphore, and the application for release.

This article is shared from the Huawei Cloud Community " Light Kernel M Core Source Code Analysis Series Eleven Semaphore Semaphore ", the original author: zhushy.

Semaphore (Semaphore) is a mechanism to achieve communication between tasks, which can achieve synchronization between tasks or mutually exclusive access to shared resources. In the data structure of a semaphore, there is usually a count value used to count the number of effective resources, indicating the number of remaining shared resources that can be used. There are differences in the use of semaphores for synchronization and semaphores for mutual exclusion. This article analyzes the source code of the Hongmeng light kernel semaphore module to grasp the difference in the use of semaphores. The source code involved in this article, taking the OpenHarmony LiteOS-M kernel as an example, can be obtained from the open source site https://gitee.com/openharmony/kernel_liteos_m .

Next, let's look at the structure of the semaphore, the initialization of the semaphore, and the source code of the common operations of the semaphore.

1. Semaphore structure definition and common macro definition

1.1 Semaphore structure definition

The semaphore control block structure defined in the file kernel\include\los_sem.h is LosSemCB. The source code of the structure is as follows. Semaphore status. semStat takes the values OS_SEM_UNUSED, OS_SEM_USED, and the comments of other member variables are in the comment section.

typedef struct {
    UINT16 semStat;      /**< 信号量状态 */
    UINT16 semCount;     /**< 可用的信号量数量 */
    UINT16 maxSemCount;  /**< 可用的信号量最大数量 */
    UINT16 semID;        /**< 信号量Id */
    LOS_DL_LIST semList; /**< 阻塞在该信号量的任务链表 */
} LosSemCB;

1.2 Commonly used macro definitions of semaphores

The number of semaphores supported by the system is defined by the macro LOSCFG_BASE_IPC_SEM_LIMIT according to the development board. Each semaphore semId is of type UINT32, with a value of [0,LOSCFG_BASE_IPC_SEM_LIMIT), which represents the number of each semaphore in the semaphore pool.

The macro at ⑴ indicates that the maximum value of the binary semaphore is 1, and the macro at ⑵ and ⑶ indicates that the semaphore is not used and the state value is used. (4) Obtain the semaphore control block structure pointer according to the linked list node pointer ptr in the doubly linked list of the semaphore blocking task. (5) Obtain the semaphore control block corresponding to the specified semaphore semId from the semaphore pool.

⑴    #define OS_SEM_BINARY_MAX_COUNT     1

⑵    #define OS_SEM_UNUSED               0

⑶    #define OS_SEM_USED                 1

⑷    #define GET_SEM_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosSemCB, semList)

⑸    #define GET_SEM(semid) (((LosSemCB *)g_allSem) + (semid))

2. Semaphore initialization

The semaphore is turned on by default in the kernel, and the user can turn it off through the macro LOSCFG_BASE_IPC_SEM. When the semaphore is turned on, when the system starts, call OsSemInit() in kernel\src\los_init.c to initialize the semaphore module.

Next, we analyze the code for semaphore initialization.

(1) Initialize the two-way circular linked list g_unusedSemList, and maintain the unused semaphore pool. ⑵Apply for memory for the semaphore pool. If the application fails, an error will be returned. ⑶Initialize each semaphore in a loop, specify the index semID for each semaphore node, set .semStat as unused OS_SEM_UNUSED, and execute (4) insert the semaphore node into the unused semaphore doubly linked list g_unusedSemList.

LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)
{
    LosSemCB *semNode = NULL;
    UINT16 index;

⑴  LOS_ListInit(&g_unusedSemList);

    if (LOSCFG_BASE_IPC_SEM_LIMIT == 0) {
        return LOS_ERRNO_SEM_MAXNUM_ZERO;
    }

⑵  g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));
    if (g_allSem == NULL) {
        return LOS_ERRNO_SEM_NO_MEMORY;
    }

⑶  for (index = 0; index < LOSCFG_BASE_IPC_SEM_LIMIT; index++) {
        semNode = ((LosSemCB *)g_allSem) + index;
        semNode->semID = index;
        semNode->semStat = OS_SEM_UNUSED;
⑷      LOS_ListTailInsert(&g_unusedSemList, &semNode->semList);
    }
    return LOS_OK;
}

3. Common operations of semaphore

3.1 Semaphore creation

We can use the function LOS_SemCreate(UINT16 count, UINT32 semHandle) to create a count semaphore, and use UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 semHandle) to create a binary semaphore. Let’s analyze the source code to see how to create a semaphore.

The incoming parameters of the two functions are the same, the number of incoming semaphores count, and the semHandle that saves the semaphore number. The maximum number of counting semaphores is OS_SEM_COUNTING_MAX_COUNT, and the maximum number of binary semaphores is OS_SEM_BINARY_MAX_COUNT. The function OsSemCreate() will be further called to realize the creation of the semaphore, and the analysis will continue below.

LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemCreate(UINT16 count, UINT32 *semHandle)
{
    return OsSemCreate(count, OS_SEM_COUNTING_MAX_COUNT, semHandle);
}

LITE_OS_SEC_TEXT_INIT UINT32 LOS_BinarySemCreate(UINT16 count, UINT32 *semHandle)
{
    return OsSemCreate(count, OS_SEM_BINARY_MAX_COUNT, semHandle);
}

Let's look at the function OsSemCreate() that creates a semaphore, which requires 3 parameters, the number of semaphores to be created, the maximum number, and the semaphore number.

⑴ Determine whether g_unusedSemList is empty, and there are semaphore resources that can be used? If there is no usable semaphore, call the function OsSemInfoGetFullDataHook() to do some debugging-related tests. This function needs to turn on the debugging switch, and the subsequent series will analyze it specifically.

(2) If g_unusedSemList is not empty, get the first available semaphore node, then delete it from the doubly linked list g_unusedSemList, and then call the macro GET_SEM_LIST to get LosSemCB *semCreated
, Initialize the created semaphore information, including the status of the semaphore, the number of semaphores, and the maximum number of semaphores. (3) Initialize the doubly linked list & semCreated->semList, the tasks blocked on this semaphore will hang on this linked list. ⑷ Assign a value to the output parameter *semHandle, and the subsequent program uses this semaphore number to perform other operations on the semaphore.

LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{
    UINT32 intSave;
    LosSemCB *semCreated = NULL;
    LOS_DL_LIST *unusedSem = NULL;
    UINT32 errNo;
    UINT32 errLine;

    if (semHandle == NULL) {
        return LOS_ERRNO_SEM_PTR_NULL;
    }

    if (count > maxCount) {
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_OVERFLOW);
    }

    intSave = LOS_IntLock();

⑴  if (LOS_ListEmpty(&g_unusedSemList)) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_ALL_BUSY);
    }

⑵  unusedSem = LOS_DL_LIST_FIRST(&(g_unusedSemList));
    LOS_ListDelete(unusedSem);
    semCreated = (GET_SEM_LIST(unusedSem));
    semCreated->semCount = count;
    semCreated->semStat = OS_SEM_USED;
    semCreated->maxSemCount = maxCount;
⑶  LOS_ListInit(&semCreated->semList);
⑷  *semHandle = (UINT32)semCreated->semID;
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_SEM_CREATE, semCreated);
    return LOS_OK;

ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.2 Semaphore deletion

We can use the function LOS_semDelete(UINT32 semHandle) to delete the semaphore. Let's see how to delete the semaphore by analyzing the source code.

(1) It is judged whether the semaphore semHandle exceeds LOSCFG_BASE_IPC_SEM_LIMIT, and if it exceeds, an error code is returned. If there is no problem with the semaphore number, get the semaphore control block LosSemCB *semDeleted. (2) Judge the state of the semaphore to be deleted. If it is in an unused state, jump to the error label ERR_HANDLER: for processing. (3) If the blocked task list of the semaphore is not empty, it is not allowed to delete, and jump to the error label for processing. ⑷ If the semaphore can be deleted, the .semStat will be set to unused OS_SEM_UNUSED, and the semaphore node will be inserted into the unused semaphore doubly linked list g_unusedSemList.

LITE_OS_SEC_TEXT_INIT UINT32 LOS_SemDelete(UINT32 semHandle)
{
    UINT32 intSave;
    LosSemCB *semDeleted = NULL;
    UINT32 errNo;
    UINT32 errLine;

⑴  if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
    }

    semDeleted = GET_SEM(semHandle);
    intSave = LOS_IntLock();
⑵  if (semDeleted->semStat == OS_SEM_UNUSED) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_INVALID);
    }

⑶  if (!LOS_ListEmpty(&semDeleted->semList)) {
        LOS_IntRestore(intSave);
        OS_GOTO_ERR_HANDLER(LOS_ERRNO_SEM_PENDED);
    }

⑷  LOS_ListAdd(&g_unusedSemList, &semDeleted->semList);
    semDeleted->semStat = OS_SEM_UNUSED;
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_SEM_DELETE, semDeleted);
    return LOS_OK;
ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}

3.3 Semaphore application

We can use the function UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout) to request the semaphore. The two required parameters are the semaphore semHandle and the waiting time timeout. The value range is [0, LOS_WAIT_FOREVER], and the unit is Tick. Let's see how to request the semaphore by analyzing the source code.

When applying for a semaphore, the legality of the semaphore number and parameters will be checked first. ⑴ The code indicates that if the semaphore is greater than the configured maximum value, an error code will be returned. (2) Obtain the semaphore control block semPended to be applied for. (3) Call the function to check the semaphore control block. If the semaphore is not created, is in the interrupt processing period, or is in the lock task scheduling period, an error code will be returned. ⑷ If the verification fails, jump to ERROR_SEM_PEND: label stop semaphore application.

⑸ If the semaphore count is greater than 0, the semaphore count is decremented by 1, and the result of successful application is returned. ⑹ If the semaphore count is equal to 0, and zero waiting time timeout, the result code LOS_ERRNO_SEM_UNAVAILABLE is returned. ⑺ If the applied semaphore is all occupied and need to wait, mark the current task blocked semaphore.taskSem as the applied semaphore, and then call the function OsSchedTaskWait(). The detailed code of this function has been analyzed above, and the current task status Set it to the blocking state, and add the blocked linked list .semList of the semaphore. If you are not waiting for LOS_WAIT_FOREVER permanently, you also need to change the task status to OS_TASK_STATUS_PEND_TIME, and set the waitTimes waiting time. ⑻ Task scheduling is triggered to switch tasks, and the subsequent code will not be executed temporarily.

If the waiting time expires and the semaphore is still unavailable, if the task cannot obtain the semaphore, continue to execute ⑼, change the task status, and return an error code. If the semaphore is available, execute ⑽, this task will get the semaphore and return the application successfully.

LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
{
    UINT32 intSave;
    LosSemCB *semPended = NULL;
    UINT32 retErr;
    LosTaskCB *runningTask = NULL;

⑴  if (semHandle >= (UINT32)LOSCFG_BASE_IPC_SEM_LIMIT) {
        OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
    }

⑵  semPended = GET_SEM(semHandle);
    intSave = LOS_IntLock();

⑶  retErr = OsSemValidCheck(semPended);
    if (retErr) {
⑷      goto ERROR_SEM_PEND;
    }

⑸  if (semPended->semCount > 0) {
        semPended->semCount--;
        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
        return LOS_OK;
    }

⑹  if (!timeout) {
        retErr = LOS_ERRNO_SEM_UNAVAILABLE;
        goto ERROR_SEM_PEND;
    }

⑺  runningTask = (LosTaskCB *)g_losTask.runTask;
    runningTask->taskSem = (VOID *)semPended;
    OsSchedTaskWait(&semPended->semList, timeout);
    LOS_IntRestore(intSave);
    OsHookCall(LOS_HOOK_TYPE_SEM_PEND, semPended, runningTask);
⑻  LOS_Schedule();

    intSave = LOS_IntLock();
⑼  if (runningTask->taskStatus & OS_TASK_STATUS_TIMEOUT) {
        runningTask->taskStatus &= (~OS_TASK_STATUS_TIMEOUT);
        retErr = LOS_ERRNO_SEM_TIMEOUT;
        goto ERROR_SEM_PEND;
    }

    LOS_IntRestore(intSave);
⑽  return LOS_OK;

ERROR_SEM_PEND:
    LOS_IntRestore(intSave);
    OS_RETURN_ERROR(retErr);
}

3.4 Semaphore release

We can use the function UINT32 LOS_semPost (UINT32 semHandle) to release the semaphore. Let's look at how to release the semaphore by analyzing the source code.

When the semaphore is released, the legality of the semaphore number and parameters will be checked first. These are relatively simple, and you can read them by yourself. ⑴ Check whether the semaphore overflows or not. (2) If the task blocking list of the semaphore is not empty, execute (3) to obtain the first task from the blocking list, set .taskSem to NULL, and no longer block the semaphore. Execute (4) Adjust the status of the task that has acquired the semaphore and add it to the ready queue. ⑸ Trigger task scheduling for task switching. ⑹ If the task blocking list of the semaphore is empty, the semaphore count is increased by 1.

LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
{
    UINT32 intSave;
    LosSemCB *semPosted = GET_SEM(semHandle);
    LosTaskCB *resumedTask = NULL;

    if (semHandle >= LOSCFG_BASE_IPC_SEM_LIMIT) {
        return LOS_ERRNO_SEM_INVALID;
    }

    intSave = LOS_IntLock();

    if (semPosted->semStat == OS_SEM_UNUSED) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_SEM_INVALID);
    }

⑴  if (semPosted->maxSemCount == semPosted->semCount) {
        LOS_IntRestore(intSave);
        OS_RETURN_ERROR(LOS_ERRNO_SEM_OVERFLOW);
    }
⑵  if (!LOS_ListEmpty(&semPosted->semList)) {
⑶      resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));
        resumedTask->taskSem = NULL;
⑷      OsSchedTaskWake(resumedTask);

        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
⑸      LOS_Schedule();
    } else {
⑹      semPosted->semCount++;
        LOS_IntRestore(intSave);
        OsHookCall(LOS_HOOK_TYPE_SEM_POST, semPosted, resumedTask);
    }

    return LOS_OK;
}

4. Summary of semaphore usage

4.1 Counting semaphores, binary semaphores and mutex locks

The only difference between counting semaphore and binary semaphore is that the initial number of semaphores is inconsistent. The initial number of binary semaphores can only be 0 and 1, and the initial value of counting semaphores can be 0 and an integer greater than 1.

Mutex locks can be understood as a characteristic binary semaphore. There is no essential difference when implementing exclusive processing and mutual exclusion scenarios for critical resources. Comparing the two-valued structure, the member variable .muxCount of the mutex indicates the number of locks, and the member variable .semCount of the semaphore indicates the count of the semaphore, which has a slightly different meaning.

4.2 Mutual exclusion and synchronization of semaphores

Semaphores can be used in mutual exclusion and synchronization scenarios. The use of semaphores for synchronization and semaphores for mutual exclusion has the following differences:

  • Semaphore for mutual exclusion

The initial semaphore count value is not 0, indicating the number of available shared resources. Before you need to use the shared resource, first acquire the semaphore, then use a shared resource, and release the semaphore after use. In this way, when the shared resource is taken out, that is, when the semaphore count is reduced to 0, other tasks that need to acquire the semaphore will be blocked, thereby ensuring mutually exclusive access to the shared resource. The application and release of the semaphore need to appear in pairs and complete the application and release in the same task.

  • Semaphore used for synchronization

When multiple tasks access the same shared resource at the same time, conflicts will occur. At this time, a task synchronization mechanism needs to be introduced to make each task perform an orderly access operation to the shared resource one by one according to business requirements. The essence of task synchronization is that tasks are queued on demand.

The semaphore used for synchronization, the initial semaphore count value is 0. Task 1 applies for the semaphore and blocks. Until task 2 or an interrupt releases the semaphore, task 1 can enter the Ready or Running state, thereby achieving synchronization between tasks. Whether the semaphore can be successfully applied depends on whether other tasks release the semaphore. The application and release are completed in different tasks.

summary

This article led everyone to analyze the source code of the semaphore module of the Hongmeng Light Kernel, including the structure of the semaphore, the initialization of the semaphore pool, the creation and deletion of the semaphore, and the application for release. Thanks for reading. If you have any questions or suggestions, you can leave a message to us: https://gitee.com/openharmony/kernel_liteos_m/issues . light kernel code warehouse easier, it is recommended to visit 160f4f8c9b9a6d https://gitee.com/openharmony/kernel_liteos_m , follow Watch, like Star, and Fork to your account, thank you.

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量