Son absolutely four: do not mind, do not need, do not solid, do not self. The Analects of Confucius: A rare chapter

A hundred blog series. This is: the v29. Xx HongMeng kernel source code analysis (semaphore) | who is responsible for solving task synchronization

Process communication related articles are:

  • V26. Xx HongMeng kernel source code analysis (spinlocks) | when chastity memorial arch good comrade
  • V27. Xx HongMeng kernel source code analysis (mutex) | than full spinlock mutex
  • V28. Xx HongMeng kernel source code analysis (process) | took nine interprocess communication speed
  • V29. Xx HongMeng kernel source code analysis (semaphore) | who is responsible for solving task synchronization
  • V30. Xx HongMeng kernel source code analysis control (events) | intertask synchronization scheme of many-to-many
  • V33. Xx HongMeng kernel source code analysis (message queue) | how to asynchronous communication between processes the data

This article explains the semaphore

Before reading this article recommended reading hongmeng kernel source code analysis (total directory) other space.

The basic concept

Semaphore is a mechanism to implement inter-task communication, 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, which is used to count the number of available resources and represents the number of available shared resources. The value can be defined in two ways:

0: indicates that the semaphore is not available, so there may be tasks waiting for the semaphore.

A positive value indicates that the semaphore is currently available.

Semaphores for synchronization and mutex are used differently as follows:

When used as a mutual exclusion, the initial semaphore count is not 0, indicating the number of available shared resources. A semaphore is acquired before a shared resource is used, then a shared resource is used, and the semaphore is released when it is used. In this way, when the shared resource is exhausted, that is, when the semaphore count is reduced to 0, other tasks that need to acquire the semaphore will be blocked, thus ensuring mutually exclusive access to the shared resource. In addition, when the number of shared resources is 1, it is recommended to use a binary semaphore, a mechanism similar to mutex.

When used for synchronization, the initial semaphore count is 0. Task 1 gets the semaphore and blocks until task 2 or an interrupt releases the semaphore and task 1 enters the Ready or Running state, thus achieving synchronization between tasks.

How the semaphore works

Semaphore initialization, allocate memory for the configured N semaphore (N value can be customized by the user, through the macro LOSCFG_BASE_IPC_SEM_LIMIT), and initialize all semaphore to unused, add to the unused list for the system to use.

  • Semaphore creation, obtains a semaphore from an unused semaphore linked list and sets the initial value.
  • Semaphore request, if its counter value is greater than 0, directly decrement 1 returns success. Otherwise, the task blocks and waits for other tasks to release the semaphore.

The waiting timeout period is configurable. When a task is blocked by a semaphore, the task is suspended to the end of the semaphore waiting task queue.

  • The semaphore is released, and if no task is waiting for the semaphore, the counter is returned by adding 1. Otherwise wake up the semaphore to wait for the first task on the task queue.
  • Semaphore deletion sets the semaphore in use to unused and hangs back to the unused linked list.

Semaphores allow multiple tasks to access a shared resource at the same time, but limit the maximum number of tasks that can access the resource at the same time.

When the number of tasks accessing a resource reaches the maximum number allowed by the resource, other tasks attempting to access the resource are blocked until a task releases the semaphore.

What does a semaphore look like?


typedef struct {
    UINT8 semStat; /**< Semaphore state */// State of the semaphore
    UINT16 semCount; /**< Number of available semaphores */// The number of valid semaphores
    UINT16 maxSemCount;  /**< Max number of available semaphores */// The maximum number of valid semaphores
    UINT32 semID; /**< Semaphore control structure ID */// Semaphore index number
    LOS_DL_LIST semList; /**< Queue of tasks that are waiting on a semaphore */// Queue of tasks waiting for semaphore. Tasks are mounted via blocking nodes
} LosSemCB;

Copy the code

SemList, this is a bidirectional list, bidirectional list is the most important structure of the kernel, you can go to the source code analysis (general directory) view bidirectional list, LOS_DL_LIST is like a dog’s skin sticking stick on the host structure, semList is hanging all future tasks waiting for this semaphore.

Initialize the semaphore module

#ifndef LOSCFG_BASE_IPC_SEM_LIMIT
#define LOSCFG_BASE_IPC_SEM_LIMIT 1024 // The maximum number of semaphore
#endif

LITE_OS_SEC_TEXT_INIT UINT32 OsSemInit(VOID)// The semaphore is initialized
{
    LosSemCB *semNode = NULL;
    UINT32 index;

    LOS_ListInit(&g_unusedSemList);/ / the initial
    /* system resident memory, don't free */
    g_allSem = (LosSemCB *)LOS_MemAlloc(m_aucSysMem0, (LOSCFG_BASE_IPC_SEM_LIMIT * sizeof(LosSemCB)));// Allocate the signal pool
    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;G_allSem [index] = g_allSem[index]
        semNode->semID = SET_SEM_ID(0The index);/ / save the ID
        semNode->semStat = OS_SEM_UNUSED;// The flag is not in use
        LOS_ListTailInsert(& g_unusedSemList, & semNode - > semList);// Attach signal blocks to the free linked list via semList
    }

    if (OsSemDbgInitHook() != LOS_OK) {
        return LOS_ERRNO_SEM_NO_MEMORY;
    }
    return LOS_OK;
}
Copy the code

Analysis is as follows:

  • Initialization creates a semaphore pool to centrally manage semaphores. Default is 1024 semaphores
  • Signal ids range from [0,1023]
  • Semaphores that are not allocated for use are attached to global variablesg_unusedSemListOn.

Tips: Other pools (such as process pool and task pool) in hongmeng kernel use free to name idle linked lists, while unused is used here, the naming style is not very strict and needs to be improved.

Create semaphore

LITE_OS_SEC_TEXT_INIT UINT32 OsSemCreate(UINT16 count, UINT16 maxCount, UINT32 *semHandle)
{
    unusedSem = LOS_DL_LIST_FIRST(&g_unusedSemList);// Select the first semaphore pool that has never been used
    LOS_ListDelete(unusedSem);// Remove from the free list
    semCreated = GET_SEM_LIST(unusedSem);// Select LosSemCB from semList. Processes, threads, and other structures do the same thing.
    semCreated->semCount = count;// Set the number
    semCreated->semStat = OS_SEM_USED;// Set the available state
    semCreated->maxSemCount = maxCount;// Set the maximum number of signals
    LOS_ListInit(&semCreated->semList);PendList -> semList -> semList -> semList -> semList -> semList -> semList
    *semHandle = semCreated->semID;// Take semID away
    OsSemDbgUpdateHook(semCreated - > semID,OsCurrTaskGet() - > taskEntry, count);return LOS_OK;

ERR_HANDLER:
    OS_RETURN_ERROR_P2(errLine, errNo);
}
Copy the code

Analysis is as follows:

  • Take the first semaphore from an unused free list for allocation.
  • The maximum number of semaphores and the number of semaphores are specified by parameters.
  • Semaphore state byOS_SEM_UNUSEDTurned out to beOS_SEM_USED
  • semHandleWith the semaphore ID, the outside world knows that it successfully created a number called*semHandleThe amount of signal

Application semaphore

LITE_OS_SEC_TEXT UINT32 LOS_SemPend(UINT32 semHandle, UINT32 timeout)
{
    UINT32 intSave;
    LosSemCB *semPended = GET_SEM(semHandle);// Get the semaphore by ID
    UINT32 retErr = LOS_OK;
    LosTaskCB *runTask = NULL;

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

    if (OS_INT_ACTIVE) {
        PRINT_ERR("!!! LOS_ERRNO_SEM_PEND_INTERR!!! \n");
        OsBackTrace(a);return LOS_ERRNO_SEM_PEND_INTERR;
    }

    runTask = OsCurrTaskGet(a);// Get the current task
    if (runTask->taskStatus & OS_TASK_FLAG_SYSTEM_TASK) {
        OsBackTrace(a);return LOS_ERRNO_SEM_PEND_IN_SYSTEM_TASK;
    }

    SCHEDULER_LOCK(intSave);

    if((semPended->semStat == OS_SEM_UNUSED) || (semPended->semID ! = semHandle)) { retErr = LOS_ERRNO_SEM_INVALID;goto OUT;
    }

    /* Update the operate time, no matter the actual Pend success or not */
    OsSemDbgTimeUpdateHook(semHandle);

    if (semPended->semCount > 0) {// If semCount=0, task must go to sleep
        semPended->semCount--;// The resource is missing
        goto OUT;// Notice that retErr = LOS_OK, so return OK
    } else if(! timeout) { retErr = LOS_ERRNO_SEM_UNAVAILABLE;goto OUT;
    }

    if (!OsPreemptableInSched()) {// Can't apply for scheduling (can't schedule because no scheduling task spinlocks are held)
        PRINT_ERR("!!! LOS_ERRNO_SEM_PEND_IN_LOCK!!! \n");
        OsBackTrace(a); retErr = LOS_ERRNO_SEM_PEND_IN_LOCK;goto OUT;
    }

    runTask->taskSem = (VOID *)semPended;// indicates that the current task is waiting on this semaphore
    retErr = OsTaskWait(&semPended->semList, timeout, TRUE);// The task enters the wait state, and the current task is hung on the semList, where the task context is switched
    if (retErr == LOS_ERRNO_TSK_TIMEOUT) {// This is a task switch. Suspend yourself and wake up other tasks
        runTask->taskSem = NULL;
        retErr = LOS_ERRNO_SEM_TIMEOUT;
    }

OUT:
    SCHEDULER_UNLOCK(intSave);
    return retErr;
}
Copy the code

The analysis is as follows: This function is a bit complicated, lots of goto, but don’t get fooled by it. Keep an eye on the return value. There is only one case in which the semaphore application can succeed (retErr == LOS_OK)

    if (semPended->semCount > 0) {// If semCount=0, task must go to sleep
        semPended->semCount--;// The resource is missing
        goto OUT;// Notice that retErr = LOS_OK, so return OK
    }
Copy the code

The remaining reasons for failure are:

  • Semaphore ID out of range (default: 1024)
  • Duration of interruption
  • System task
  • The semaphore status is incorrect. The semaphore IDS do not match

In normal case semPended->semCount = 0, what if there are no resources? The task enters the OsTaskWait sleep state. How to sleep and how long to sleep are determined by the timeout parameter. The timeout value can be divided into the following three modes:

Non-blocking mode: When a task applies for a semaphore, timeout is 0. If the current semaphore count is not 0, the application succeeds. Otherwise, the application fails immediately.

Permanent blocking mode: when a task requests a semaphore, timeout is equal to 0xFFFFFFFF. If the current semaphore count is not 0, the application succeeds.

Otherwise, the task is blocked, and the system switches to the task with the highest priority. After a task enters a blocked state, it will not resume execution until another task releases the semaphore.

Timed blocking mode: 0

Otherwise, the task is blocked, and the system switches to the task with the highest priority. After a task enters the blocked state, if other tasks release the semaphore before timeout, the task can successfully obtain the semaphore and continue to execute. If no semaphore is obtained before timeout, the interface returns a timeout error code.

In OsTaskWait, tasks are attached to a semList of tasks waiting for this semaphore.

Release semaphore

LITE_OS_SEC_TEXT UINT32 OsSemPostUnsafe(UINT32 semHandle, BOOL *needSched)
{
    LosSemCB *semPosted = NULL;
    LosTaskCB *resumedTask = NULL;

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

    semPosted = GET_SEM(semHandle);
    if((semPosted->semID ! = semHandle) || (semPosted->semStat == OS_SEM_UNUSED)) {return LOS_ERRNO_SEM_INVALID;
    }

    /* Update the operation time, no matter the actual Post success or not */
    OsSemDbgTimeUpdateHook(semHandle);

    if (semPosted->semCount == OS_SEM_COUNT_MAX) {// The current signal resource cannot be greater than the maximum resource
        return LOS_ERRNO_SEM_OVERFLOW;
    }
    if (!LOS_ListEmpty(&semPosted->semList)) {// There is a task hanging on the semList
        resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(semPosted->semList)));Pendlist -> select the first task and wake it up
        resumedTask->taskSem = NULL;// The task does not need to wait for the signal to return to NULL
        OsTaskWake(resumedTask);// resumedTask must not be the current task. OsTaskWake does not change the task context itself, but sets the state
        if(needSched ! =NULL) {// If the parameter is not empty, return the label to be scheduled
            *needSched = TRUE;//TRUE indicates that scheduling is required}}else {// There are currently no tasks hanging on semList,
        semPosted->semCount++;// There is one more signal resource
    }

    return LOS_OK;
}

LITE_OS_SEC_TEXT UINT32 LOS_SemPost(UINT32 semHandle)
{
    UINT32 intSave;
    UINT32 ret;
    BOOL needSched = FALSE;

    SCHEDULER_LOCK(intSave);
    ret = OsSemPostUnsafe(semHandle, & needSched);SCHEDULER_UNLOCK(intSave);
    if (needSched) {// The need for scheduling
        LOS_MpSchedule(OS_MP_CPU_ALL);// Send scheduling instructions to all cpus
        LOS_Schedule(a);//// Initiating scheduling
    }

    return ret;
}
Copy the code

Analysis is as follows:

  • Notice what happenssemPosted->semCountPlus plus, is whenLOS_ListEmptyWhen it’s true,semListIs the task waiting for this semaphore.

Tasks on semList are mounted in OsTaskWait. All waiting for the signal.

  • Every timeOsSemPostWill wake upsemListList a task untilsemListIs empty.
  • At the heart of mastering semaphores is understandingLOS_SemPendLOS_SemPost

Programming examples

This example implements the following functions:

  • Test task Example_TaskEntry create a semaphore, lock task scheduling, and create two tasks Example_SemTask1, Example_SemTask2, Example_SemTask2 has a higher priority than Example_SemTask1, Two tasks apply for the same semaphore. After task scheduling is unlocked, the two tasks block. Test task Example_TaskEntry releases the semaphore.

  • Example_SemTask2 gets semaphore, is scheduled, then the task sleeps for 20 ticks, Example_SemTask2 is delayed, Example_SemTask1 is woken up.

  • Example_SemTask1 timed blocking mode applying for semaphores, waiting for 10 ticks because the semaphore is still held by Example_SemTask2, Example_SemTask1 is suspended, and no semaphore is received after 10 ticks,

Example_SemTask1 wakes up, tries to apply semaphore in permanent blocking mode, Example_SemTask1 hangs.

  • After 20Tick, Example_SemTask2 wakes up, releases semaphore, Example_SemTask1 gets semaphore, schedules semaphore, and releases semaphore.

  • Example_SemTask1 after the semaphore is deleted, 40Tick task Example_TaskEntry is woken up, and two tasks are deleted.

/* Task ID */
static UINT32 g_testTaskId01;
static UINT32 g_testTaskId02;
/* Test task priority */
#define TASK_PRIO_TEST  5
/* Semaphore structure id */
static UINT32 g_semId;

VOID Example_SemTask1(VOID)
{
    UINT32 ret;

    printf("Example_SemTask1 try get sem g_semId ,timeout 10 ticks.\n");
    /* The timer blocking mode applies for a semaphore with a timer of 10Ticks */
    ret = LOS_SemPend(g_semId,10);

    /* Request semaphore */
    if (ret == LOS_OK) {
         LOS_SemPost(g_semId);
         return;
    }
    /* No semaphore is applied */
    if (ret == LOS_ERRNO_SEM_TIMEOUT) {
        printf("Example_SemTask1 timeout and try get sem g_semId wait forever.\n");
        /* Permanently blocking mode application semaphore */
        ret = LOS_SemPend(g_semId LOS_WAIT_FOREVER);printf("Example_SemTask1 wait_forever and get sem g_semId .\n");
        if (ret == LOS_OK) {
            LOS_SemPost(g_semId);
            return; }}}VOID Example_SemTask2(VOID)
{
    UINT32 ret;
    printf("Example_SemTask2 try get sem g_semId wait forever.\n");
    /* Permanently blocking mode application semaphore */
    ret = LOS_SemPend(g_semId LOS_WAIT_FOREVER);if (ret == LOS_OK) {
        printf("Example_SemTask2 get sem g_semId and then delay 20ticks .\n");
    }

    /* The task sleeps 20 ticks */
    LOS_TaskDelay(20);

    printf("Example_SemTask2 post sem g_semId .\n");
    /* Release semaphore */
    LOS_SemPost(g_semId);
    return;
}

UINT32 ExampleTaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;
    TSK_INIT_PARAM_S task2;

   /* Create semaphore */
    LOS_SemCreate(0, & g_semId);/* Lock task scheduling */
    LOS_TaskLock(a);/* Create task 1*/
    (VOID)memset_s(& task1,sizeof(TSK_INIT_PARAM_S),0.sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask1;
    task1.pcName       = "TestTsk1";
    task1.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio   = TASK_PRIO_TEST;
    ret = LOS_TaskCreate(& g_testTaskId01, & task1);if(ret ! = LOS_OK) {printf("task1 create failed .\n");
        return LOS_NOK;
    }

    /* Create task 2 */
    (VOID)memset_s(& task2,sizeof(TSK_INIT_PARAM_S),0.sizeof(TSK_INIT_PARAM_S));
    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_SemTask2;
    task2.pcName       = "TestTsk2";
    task2.uwStackSize  = OS_TSK_DEFAULT_STACK_SIZE;
    task2.usTaskPrio   = (TASK_PRIO_TEST - 1);
    ret = LOS_TaskCreate(& g_testTaskId02, & task2);if(ret ! = LOS_OK) {printf("task2 create failed .\n");
        return LOS_NOK;
    }

    /* Unlock task scheduling */
    LOS_TaskUnlock(a); ret =LOS_SemPost(g_semId);

    /* The task sleeps 40 ticks */
    LOS_TaskDelay(40);

    /* Delete semaphore */
    LOS_SemDelete(g_semId);

    /* Delete task 1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if(ret ! = LOS_OK) {printf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* Delete task 2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if(ret ! = LOS_OK) {printf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}
Copy the code

Example running results:

Example_SemTask2 try get sem g_semId wait forever.
Example_SemTask1 tryGet sem g_semId, timeout10 ticks.
Example_SemTask2 get sem g_semId and then delay 20ticks .
Example_SemTask1 timeout and try get sem g_semId wait forever.
Example_SemTask2 post sem g_semId .
Example_SemTask1 wait_forever and get sem g_semId .
Copy the code

Intensive reading of the kernel source code

Four code stores synchronous annotation kernel source code, >> view the Gitee repository

Analysis of 100 blogs. Dig deep into the core

Add comments to hongmeng kernel source code process, sort out the following article. Content based on the source code, often in life scene analogy as much as possible into the kernel knowledge of a scene, with a pictorial sense, easy to understand memory. It’s important to speak in a way that others can understand! The 100 blogs are by no means a bunch of ridiculously difficult concepts being put forward by Baidu. That’s not interesting. More hope to make the kernel become lifelike, feel more intimate. It’s hard, it’s hard, but there’s no turning back. 😛 and code bugs need to be constantly debug, there will be many mistakes and omissions in the article and annotation content, please forgive, but will be repeatedly amended, continuous update. Xx represents the number of modifications, refined, concise and comprehensive, and strive to create high-quality content.

Compile build The fundamental tools Loading operation Process management
Compile environment

The build process

Environment script

Build tools

Designed.the gn application

Ninja ninja

Two-way linked list

Bitmap management

In the stack way

The timer

Atomic operation

Time management

The ELF format

The ELF parsing

Static link

relocation

Process image

Process management

Process concept

Fork

Special process

Process recycling

Signal production

Signal consumption

Shell editor

Shell parsing

Process of communication Memory management Ins and outs Task management
spinlocks

The mutex

Process of communication

A semaphore

Incident control

The message queue

Memory allocation

Memory management

Memory assembly

The memory mapping

Rules of memory

Physical memory

Total directory

Scheduling the story

Main memory slave

The source code comments

Source structure

Static site

The clock task

Task scheduling

Task management

The scheduling queue

Scheduling mechanism

Thread concept

Concurrent parallel

The system calls

Task switching

The file system Hardware architecture
File concept

The file system

The index node

Mount the directory

Root file system

Character device

VFS

File handle

Pipeline file

Compilation basis

Assembly and the cords

Working mode

register

Anomaly over

Assembly summary

Interrupt switch

Interrupt concept

Interrupt management

HongMeng station | into a little bit every day, the original is not easy, welcome to reprint, please indicate the source.