The Master said, “Crazy but not straight, dong but unwilling, 悾悾 but unconvinced, I do not know.” The Analects of Confucius: Tabor
A hundred blog series. This is: v27. Xx HongMeng kernel source code analysis (mutex) | than full spinlock mutex
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 mutex
Where in the kernel are mutex used? Look at the picture:
In this picture, the kernel module initializes the mutex, has files, has memory, uses the message queue, and so on, uses the very wide range. In fact, in the process of annotating the kernel source code, you will see a large number of spin locks and mutex, their existence in order to ensure the normal operation of the kernel and application. Is a very basic and important function.
An overview of the
Spin-locks and mutex are both locks, but solve different problems. Spin-locks solve the race for shared memory between CPU cores, while mutex solves the race for shared memory between threads (tasks).
A spin lock is a feature that the CPU is busy waiting for another CPU to release the resource. Therefore, the shared code segment should not be too complex, otherwise it is prone to deadlock, shock.
The feature of a mutex is that the original task is blocked when the lock is not obtained, and a new task is switched to run. The CPU will always run. It’s easy to think of a few questions:
First, there will be many tasks in the same lock situation, because switching new tasks may also be blocked by the same lock, and the CPU is switched to new tasks. This creates an equal-lock linked list.
Second: can the party who holds the lock apply for the same lock again successfully? The answer is yes, this type of locking is called recursive locking and is the default mode of the hongmeng kernel.
Third: when A task with A high priority fails to lock, it actively lets the CPU go to sleep. What if B task with A lock has A low priority and cannot wait for the task to run and cannot release the lock? The answer is that the task priority of B is temporarily adjusted to be as high as that of A, so that B can be quickly dispatched, and when B releases the lock, its priority is reversed. So the priority of a task will rise and fall depending on the situation.
Fourth: after task B releases the lock, it should actively wake up the linked list of tasks with equal locks so that they can join the ready queue and wait to be scheduled. The scheduling algorithm is non-discriminatory, it only looks at priority.
With these problems, into the hongmeng kernel mutex implementation code, this code is large, each line of code is annotated.
What does a mutex look like?
enum {
LOS_MUX_PRIO_NONE = 0.// Thread priority and scheduling are not affected by mutex, first come first served, normal queuing.
LOS_MUX_PRIO_INHERIT = 1.// While the higher-priority thread waits for the lower-priority thread to release the lock, the lower-priority thread runs at the higher-priority thread's priority.
// When a thread unlocks a mutex, its priority is automatically restored to its original priority
LOS_MUX_PRIO_PROTECT = 2 // See the notes in OsMuxPendOp that specify the meaning of LOS_MUX_PRIO_PROTECT
};
enum {
LOS_MUX_NORMAL = 0.// A non-recursive lock has only [0.1] two states. No special error detection is performed, and no deadlock detection is performed.
LOS_MUX_RECURSIVE = 1.Recursive locking allows the same thread to lock the mutex multiple times before it is unlocked. A recursive mutex maintains the count of locks. If the number of unlocked and locked times is different, the lock is not released, and no other thread can lock the mutex.
LOS_MUX_ERRORCHECK = 2.// Error checking. If a thread attempts to relock a locked mutex or unlock an unlocked mutex, an error is returned.
LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE // Recursive locking is used by default
};
typedef struct { // Mutex attributes
UINT8 protocol; / / agreement
UINT8 prioceiling; // Priority upper limit
UINT8 type; // Type attributes
UINT8 reserved; // Reserve the field
} LosMuxAttr;
typedef struct OsMux { // Mutex structure
UINT32 magic; /**< magic number */ // Magic number
LosMuxAttr attr; /**< Mutex attribute */ // Mutex attribute
LOS_DL_LIST holdList; /**< The task holding the lock change */ // When a task holds the lock, attach it to the task's chain list via the holdList node
LOS_DL_LIST muxList; /**< Mutex linked list */ // This is a holdList of tasks.
VOID *owner; /**< The current thread that is locking a mutex */ // The task that currently owns the lock
UINT16 muxCount; /**< Times of locking a mutex */ // The number of times a mutex is locked. Recursive locking is allowed multiple times
} LosMux;
Copy the code
This mutex is significantly longer than a spinlock, remember what spinlocks look like, with one variable, painfully thin.
Initialize the
LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux * mutex,const LosMuxAttr *attr)
{ / /...
SCHEDULER_LOCK(intSave); // Get the scheduling spin lock
mutex->muxCount = 0; // The number of times the mutex was locked
mutex->owner = NULL; // The task that holds the lock
LOS_ListInit(&mutex->muxList); // Initializes the list of tasks waiting for the lock
mutex->magic = OS_MUX_MAGIC; // Fixed identifier, mutex magic number
SCHEDULER_UNLOCK(intSave); // Release the scheduling spin lock
return LOS_OK;
}
Copy the code
Note that mutex->muxList is a bidirectional list, which is the most important element of the kernel. In the Linux kernel, list_head, as well as list_head, is a fixture of the host structure.
Three application modes
There are three modes for applying for a mutex: non-blocking mode, permanent blocking mode, and periodic blocking mode.
Non-blocking mode: When a task applies for a mutex, timeout is 0. If no task currently holds the mutex, or the task holding the mutex and the task applying for the mutex are the same, the application is successful. Otherwise, the application fails immediately.
Permanent blocking mode: When a task applies for a mutex, timeout is equal to 0xFFFFFFFF. If no task currently holds the mutex, the application is successful. Otherwise, the task enters the blocking state, and the system switches to the task with the highest priority in the ready task. After a task enters the blocking state, it cannot be executed again until another task releases the mutex.
Timed blocking mode: 0<timeout<0xFFFFFFFF when a task applies for a mutex. If no task currently holds the mutex, the application is successful. 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 mutex before time out, the task can successfully obtain the mutex and continue to execute the task. If the mutex is not obtained before time out, the interface returns a timeout error code.
If any task is blocked by the mutex, the task with the highest priority is awakened, the task enters the ready state, and the task is scheduled. If no task is blocking the mutex, the mutex is released successfully.
Apply the mutex main function OsMuxPendOp
// The mutex body function, called by OsMuxlockUnsafe, one of the most important functions of the mutex module
// The worst case scenario is that the lock fails, the CPU is released, and the task becomes blocked, waiting for another task to release the lock and then execute it.
STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux * MUtex, UINT32 Timeout)
{
UINT32 ret;
LOS_DL_LIST *node = NULL;
LosTaskCB *owner = NULL;
if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {// The list is empty
/* This is for mutex macro initialization. */
mutex->muxCount = 0;// lock counter clear 0
mutex->owner = NULL;// The lock has no owning task
LOS_ListInit(&mutex->muxList);// Initializes the lock task list, which will be attached to the subsequent application for this lock task
}
if (mutex->muxCount == 0) {// If no task uses a lock, you must get the lock. Return inside
mutex->muxCount++; // The mutex counter increases by 1
mutex->owner = (VOID *)runTask; // The current task is to get the lock
LOS_ListTailInsert(& runTask - > lockList, & mutex - > holdList);// The task holding the lock has changed, and the node is attached to the current task's chain list
if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {// How does the protection protocol work?
LOS_BitmapSet(& runTask - > priBitMap runTask - > priority);//1. PriBitMap is a bitmap that records task priority changes. The current priority of the task is recorded in priBitMap
OsTaskPriModify(runTask, mutex - > attr. Prioceiling);//2. Set mutex->attr. Prioceiling to the priority of the current task.
}// Note that there are 32 task priorities, 0 is the highest and 31 is the lowest!! In this case, the priority of the task is increased, so that the probability of the task being selected continues to increase in the next schedule, so as to quickly release the lock.
return LOS_OK;
}
// muxCount>0; // muxCount>0; The lock was taken by another mission
if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {// In the first case runTask is the lock holder
mutex->muxCount++; LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE; LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE; LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE
return LOS_OK; // Exit successfully
}
// The current task can only be blocked.
if(! timeout) {// The timeout parameter indicates how long to wait before picking up the lock
return LOS_EINVAL;//timeout = 0 See LOS_MuxTrylock
}
// If you want to block, you can only apply for scheduling, freeing CPU core for other tasks
if (!OsPreemptableInSched()) {// Can't apply for scheduling (can't schedule because no scheduling task spinlocks are held)
return LOS_EDEADLK;// Return error, the spin lock is held by another CPU core
}
OsMuxBitmapSetMutex, runTask, (LosTaskCB *)mutex->owner);// Set the lock bitmap to increase the priority of lock holding task as much as possible
owner = (LosTaskCB *)mutex->owner; // Record the task holding the lock
runTask->taskMux = (VOID *)mutex; // Note that the current task is waiting for the lock
node = OsMuxPendFindPos(runTask, mutex);// Find a task with a lower priority than the current task in the list of equal chains
ret = OsTaskWait(node, timeout, TRUE);// The task is in the waiting state. TRUE indicates that the task needs to be scheduled
if (ret == LOS_ERRNO_TSK_TIMEOUT) {// This line of code is next to OsTaskWait, but it takes a long time to execute because the CPU switches the task context in OsTaskWait
runTask->taskMux = NULL;// So when I come back here, I may have run out of time
ret = LOS_ETIMEDOUT;// Return timeout
}
if(timeout ! = LOS_WAIT_FOREVER) {// It is not a case of waiting forever
OsMuxBitmapRestore(mutex, runTask, owner);// Restore the bitmap of the lock
}
return ret;
}
Copy the code
The main lock release function OsMuxPostOp
// Is there any other task holding the mutex blocking? If so, wake it up. Note that waking one task is done by another task
//OsMuxPostOp is only addressed by OsMuxUnlockUnsafe, the unlockbroadening task returns the lock, and the unlockbroadening task returns the lock
STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{
LosTaskCB *resumedTask = NULL;
if (LOS_ListEmpty(&mutex->muxList)) {// If the mutex list is empty
LOS_ListDelete(&mutex->holdList);// Remove the mutex
mutex->owner = NULL;
return LOS_OK;
}
resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));// Get the first task entity waiting for the mutex chain list, then wake up the task
if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {// How does the mutex protocol work if it is inherited?
if (resumedTask->priority > taskCB->priority) {// The priority of the task to get the lock is lower than that of the parameter task
if (LOS_HighBitGet(taskCB->priBitMap) ! = resumedTask->priority) {// The lowest priority in the parameter task bitmap is not equal to the priority of the task waiting for the lock
LOS_BitmapClr(& taskCB - > priBitMap resumedTask - > priority);// Record the priority of the task awaiting the task lock in the bitmap of the parameter task}}else if(taskCB->priBitMap ! =0) {// If the bitmap is not equal to 0, the parameter task has the priority of task scheduling at least
OsMuxPostOpSub(taskCB, mutex);//
}
}
mutex->muxCount = 1;// The number of mutex is 1
mutex->owner = (VOID *)resumedTask;// The owner of the mutex has changed
resumedTask->taskMux = NULL;//resumedTask no longer waits for the lock
LOS_ListDelete(&mutex->holdList);// Remove yourself from the list of chains
LOS_ListTailInsert(& resumedTask - > lockList, & mutex - > holdList);// Attach the lock to the recovery task's chain list. The lockList is all the lock records held by the task
OsTaskWake(resumedTask);//resumedTask wakes up when it has the lock because it was in the pend state when it didn't get the lock
if(needSched ! =NULL) {// If not empty
*needSched = TRUE;// Start scheduling the process again
}
return LOS_OK;
}
Copy the code
Programming instance
This example implements the following flow.
- Task Example_TaskEntry creates a mutex, locks task scheduling, and creates two tasks Example_MutexTask1 and Example_MutexTask2. Example_MutexTask2 has a higher priority than Example_MutexTask1, unlocks task scheduling, and then Example_TaskEntry sleeps 300 ticks.
- Example_MutexTask2 is scheduled, applies for a mutex in permanent blocking mode, obtains the mutex successfully, then sleeps for 100 ticks, Example_MutexTask2 is suspended, and Example_MutexTask1 is woke up.
- Example_MutexTask1 applies for a mutex in timed blocking mode, waits 10Tick, because the mutex is still held by Example_MutexTask2, and Example_MutexTask1 is suspended. When the 10Tick time expires, Example_MutexTask1 is woken up and applies for a mutex in permanent blocking mode. The mutex is still held by Example_MutexTask2, and Example_MutexTask1 is suspended.
- 100Tick sleep time expires, Example_MutexTask2 is awakened, the mutex is released, and Example_MutexTask1 is awakened. Example_MutexTask1 successfully obtains a mutex, and releases the lock.
- After the 300Tick sleep period expires, task Example_TaskEntry is scheduled and run. The mutex is deleted and two tasks are deleted.
/* Mutex handle id */
UINT32 g_testMux;
/* Task ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;
VOID Example_MutexTask1(VOID)
{
UINT32 ret;
printf("Task1 try to get mutex, wait 10 ticks.\n");
/* Request a mutex */
ret = LOS_MuxPend(g_testMux,10);
if (ret == LOS_OK) {
printf("task1 get mutex g_testMux.\n");
/* Release the mutex */
LOS_MuxPost(g_testMux);
return;
} else if (ret == LOS_ERRNO_MUX_TIMEOUT ) {
printf("task1 timeout and try to get mutex, wait forever.\n");
/* Request a mutex */
ret = LOS_MuxPend(g_testMux LOS_WAIT_FOREVER);if (ret == LOS_OK) {
printf("task1 wait forever, get mutex g_testMux.\n");
/* Release the mutex */
LOS_MuxPost(g_testMux);
return; }}return;
}
VOID Example_MutexTask2(VOID)
{
printf("Task2 try to get mutex, wait forever.\n");
/* Request a mutex */
(VOID)LOS_MuxPend(g_testMux LOS_WAIT_FOREVER);printf("task2 get mutex g_testMux and suspend 100 ticks.\n");
/* The task sleeps 100Ticks */
LOS_TaskDelay(100);
printf("task2 resumed and post the g_testMux\n");
/* Release the mutex */
LOS_MuxPost(g_testMux);
return;
}
UINT32 Example_TaskEntry(VOID)
{
UINT32 ret;
TSK_INIT_PARAM_S task1;
TSK_INIT_PARAM_S task2;
/* Create a mutex */
LOS_MuxCreate(&g_testMux);
/* Lock task scheduling */
LOS_TaskLock(a);/* Create task 1 */
memset(& task1,0.sizeof(TSK_INIT_PARAM_S));
task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;
task1.pcName = "MutexTsk1";
task1.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
task1.usTaskPrio = 5;
ret = LOS_TaskCreate(& g_testTaskId01, & task1);if(ret ! = LOS_OK) {printf("task1 create failed.\n");
return LOS_NOK;
}
/* Create task 2 */
memset(& task2,0.sizeof(TSK_INIT_PARAM_S));
task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;
task2.pcName = "MutexTsk2";
task2.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
task2.usTaskPrio = 4;
ret = LOS_TaskCreate(& g_testTaskId02, & task2);if(ret ! = LOS_OK) {printf("task2 create failed.\n");
return LOS_NOK;
}
/* Unlock task scheduling */
LOS_TaskUnlock(a);/* Sleep 300Ticks */
LOS_TaskDelay(300);
/* Remove the mutex */
LOS_MuxDelete(g_testMux);
/* 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
results
task2 tryTo get mutex, wait forever. Task2 get mutex g_testMuxand suspend 100 ticks.
task1 tryTo get mutex, wait10 ticks.
task1 timeout and tryTo get mutex, wait forever. Task2 resumedandPost the g_testMux task1 wait forever, get mutex g_testMux.Copy the code
conclusion
1. Mutex solves the problem of competing for shared memory among tasks.
2. The task that fails to apply for the lock will enter the sleep OsTaskWait. The kernel will compare the priority of the task that holds the lock with that of the task that applies for the lock, and adjust the priority of the task that holds the lock as high as possible, so that it can be scheduled and executed more quickly and the lock can be released as soon as possible.
3. The lock release task will find a high-priority task in the chain list, wake it up through OsTaskWake, and apply for scheduling from the scheduling algorithm. However, it should be noted that the scheduling algorithm only schedules according to the priority, and does not guarantee that the scheduled task is the task to wake up.
4. The key is to understand OsMuxPendOp and OsMuxPostOp functions.
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.