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 variables
g_unusedSemList
On.
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 by
OS_SEM_UNUSED
Turned out to beOS_SEM_USED
semHandle
With the semaphore ID, the outside world knows that it successfully created a number called*semHandle
The 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 happens
semPosted->semCount
Plus plus, is whenLOS_ListEmpty
When it’s true,semList
Is the task waiting for this semaphore.
Tasks on semList are mounted in OsTaskWait. All waiting for the signal.
- Every time
OsSemPost
Will wake upsemList
List a task untilsemList
Is empty. - At the heart of mastering semaphores is understanding
LOS_SemPend
和LOS_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.