Tool and Resource Center

Help developers work more efficiently and provide tools and resources around the developer lifecycle

Developer.aliyun.com/tool?spm=a1…

1, an overview of the

This article will analyze the source code for mutex semaphores.

Mutex semaphores are similar to semaphores, but very different. The main differences are:

(1) The mutex semaphore can only be obtained by one thread at any time, unlike the semaphore, which can have multiple.

(2) Only the task that acquired the mutex can release the mutex, so the mutex cannot be released in the context of an interrupt.

(3) Support nested requests, that is, the task that obtained the mutex semaphore can request the mutex semaphore again. If the request semaphore is nested, one semaphore is consumed per request.

(4) Support to solve the priority reversal problem. Priority reversal is a classic problem in operating system design that has led to major software failures, as interested readers can learn. Since there is a lot of code in the mutex source code to solve this problem, it is worth explaining first.

Suppose there are three tasks: taskHigh, TaskMid, and tasklow. Taskhigh has the highest priority, taskMid has the next, and tasklow has the lowest priority. Assuming taskLow has acquired the Mutex, taskHigh will block when it requests a Mutex and wait until TaskLow releases the Mutex. However, if the TaskMid is ready, it will preempt the TaskLow to run, and the TaskLow will release the mutex until the TaskMid gives up the CPU. If there are taskMID2, taskMID3, etc., then taskHigh will have to wait for them to execute first. That is, high-priority tasks are not scheduled because they are waiting for mutex occupied by low-priority tasks. This is the problem of priority reversal.

AliOS Things solves the priority inversion problem with a priority inheritance strategy: when a high-priority task requests a mutex to block, the priority of the tasks that occupy the mutex is raised. In the example above, when taskHigh requests Mutex to block, the priority of tasklow will be raised to the same level as taskHigh. This will prevent taskHigh from being unscheduled because taskMid preempts TaskLow.

Let’s read the mutex source code and analyze how the above differences are achieved.

Mutex source: core/ Rhino /k_mutex. C

Mutex header file location: core/ Rhino /include/k_mutex.h

2, mutex semaphore structure kmutex_t

The header k_mutex.h defines the mutex semaphore structure kmutex_t. Typedef struct mutex_s {struct mutex_s {struct mutex_s {

/ * * (

*  Manage blocked tasks
*  List head is this mutex, list node is task, which are blocked in this mutex
*/
Copy the code

blk_obj_t blk_obj;

ktask_t mutex_task; / < Pointer to the owner task /

/ * * (

*  Manage mutexs owned by the task
*  List head is task, list node is mutex, which are owned by the task
*/
Copy the code

struct mutex_s *mutex_list;

mutex_nested_t owner_nested;

if (RHINO_CONFIG_KOBJ_LIST > 0)

klist_t mutex_item; / *< kobj list for statistics /

endif

uint8_t mm_alloc_flag; / *< buffer from internal malloc or caller input /

} kmutex_t;

Member description:

(1) BLk_obj This is a kernel infrastructure that manages basic information about kernel structures. In object-oriented terms, it is the parent class of kmutex_t. Its main domains are: blk_list blocking queue, name object name, BLk_policy blocking queue waiting policy (PRI and FIO), obj_type structure type;

(2) mutex_task refers to the task that obtains the mutex;

(3) mutex_list A task may acquire multiple mutex semaphores. Use this domain to build a list of mutex semaphores obtained by the task. As shown in the figure below, the task gets three mutex semaphores:

(4) Owner_nested records the number of applications nested for the same task;

(5) Mutex_item is a linked list node, used to insert mutex into the global list, mainly used for debugging, statistics;

(6) MM_ALLOC_flag is a memory flag that indicates whether the structure’s memory is statically or dynamically allocated.

3. Create the mutex function mutex_create

The core function for creating a mutex is mutex_create, which has the following prototype:

kstat_t mutex_create(kmutex_t mutex, const name_t name, uint8_t mm_alloc_flag);

Parameter Meaning:

Mutex: pointer to a mutex semaphore structure;

Name: name of the mutex semaphore. Users can specify a name for their own mutex semaphore to facilitate debugging.

Mm_alloc_flag: memory type, that is, whether the memory pointed to by mutex is statically allocated or dynamically allocated. For dynamic allocation, when deleting the mutex semaphores, it is necessary to release the memory of the structure pointed to by mutex.

There is no input parameter for the number of semaphores compared to the semaphores created interface. This is because the mutex semaphores can only be obtained by one task at most, and the initial state of the mutex semaphores is idle, equivalent to an initial value of 1.

This function initializes the mutex semaphore structure. The key bits of information are:

(1) blk_obj.blk_policy is initialized to BLK_POLICY_PRI, indicating that a priority-based blocking policy is adopted. This means that when multiple tasks block on the mutex, the higher-priority task gets the mutex first. Another policy is BLK_POLICY_FIFO, where tasks that block first get the mutex first.

(2) mutex_task initializes NULL, indicating that no task currently occupies the mutex;

(3) Insert a mutex structure into a global linked list using a RHINO_CRITICAL_ENTER()/RHINO_CRITICAL_EXIT() critical section protected insertion operation. When debugging, you can get all the mutex semaphores in the system through the g_kobj_list.mutex_head linked list;

(4) blk_obj.obj_type is initialized as RHINO_MUTEX_OBJ_TYPE, indicating that the structure type is a mutex.

Krhino_mutex_create () and krhino_mutex_dyn_create() are external interfaces for creating mutex semaphocles. The difference between krhino_mutex_create() and krhino_mutex_dyn_create() is that the former is created statically (K_OBJ_STATIC_ALLOC), that is, the memory of the kmutex_t structure is passed in externally. The latter is dynamically created (K_OBJ_DYN_ALLOC). In this function, krhino_mm_alloc is called to dynamically allocate the memory of the kmutex_t structure, and the created structure object is passed back to the caller through the mutex parameter. So krhino_mutex_dyn_create takes a mutex of type kmutex_t **.

4, request mutex semaphore krhino_mutex_lock

Krhino_mutex_lock can be invoked to request the mutex. The prototype is as follows:

kstat_t krhino_mutex_lock(kmutex_t *mutex, tick_t ticks);

Parameter description:

(1) a pointer to a mutex structure;

(2) Ticks blocking time. If the mutex semaphore is occupied, the task that initiated the request will be blocked. Ticks are used to specify the blocking time. The two special values are :(a)RHINO_NO_WAIT, which does not wait and is returned when a mutex cannot be obtained; (b) RHINO_WAIT_FOREVER, blocking and waiting until the mutex is obtained.

Within this function:

(1) Function entry to do some input check. The RHINO_CRITICAL_ENTER() statement is used to enter a critical section;

G_active_task [cur_cpu_num] == mutex->mutex_task g_active_task[cur_cpu_num] == mutex->mutex_task If it has been nested, then the mutex->owner_nested count is increased by 1. And then you go back, and you get the mutex semaphore nested here;

(3) The condition mutex_task == NULL is used to determine whether the mutex is idle. If the condition is true, the mutex is occupied. The main operation occupied is that the value of mutex_task is g_active_task[cur_cpu_num], that is, the task that currently requests the mutex. This will return success;

If ticks = RHINO_NO_WAIT, then the caller does not want to wait. (RHINO_NO_WAIT) If g_sched_lock[cur_cpu_num] > 0, the system is currently off scheduling, and the task cannot be blocked because blocking triggers scheduling, which also returns failure;

(5) At this point, the task will be blocked. The call to pend_to_blk_obj sets the task to an unready state. RHINO_CRITICAL_EXIT_SCHED() exits the critical section and triggers scheduling. The pend_state_end_proc function is called to determine what causes the task to be woken up. (b) the mutex semaphore is obtained;

Before calling pend_to_blk_obj, there is also a judgment statement for areas controlled by the precompiled RHINO_CONFIG_MUTEX_INHERIT macro. This statement is used to handle priority inversion:

G_active_task [cur_cpu_num]->prio < mutex_task->prio At this point, ask_PRI_change is called to dynamically promote the priority of the mutex acquisition task.

5. Release the mutex semaphore krhino_mutex_unlock

The prototype of the function to release the mutex is:

kstat_t krhino_mutex_unlock(kmutex_t *mutex)

Within this function:

(1) Function entry first checks the incoming parameters and enters the critical section;

(2) Call mutex_release to remove the mutex from the task’s mutex list. If the priority of the task has been dynamically adjusted, the priority of the task is restored. Of course, this function also takes into account the task may occupy other mutex semaphores, space reasons, not to elaborate;

(3) After releasing the mutex semaphore, judge whether there is a task blocking on the mutex semaphore, if there is no, return directly; If so, it picks up a blocked task from the blocking list, calls pend_task_wakeup, and updates the mutex information to indicate that it is occupied by a new task.

6. Delete the mutex semaphore krhino_mutex_del/ krhino_mutex_dyn_del

The krhino_mutex_del command is used to delete the mutex created by krhino_mutex_create. The krhino_mutex_dyn_del command is used to delete the mutex created by krhino_mutex_dyn_create. The two sets of functions must be used together, or the release will fail.

Krhino_mutex_dyn_del has more operations to release mutex semaphore structures than krhino_mutex_de. Here we only analyze krhino_mutex_del function.

Within this function:

(1) First check the input parameters, enter the critical section, and then judge whether the type is correct and whether the static assignment is correct;

(2) If the mutex is currently occupied by a task, call mutex_release to remove the mutex from the task’s mutex list. If the priority of the task is dynamically adjusted, the priority of the task is restored.

(3) If there are tasks blocking on the mutex semaphore, they are all woken up;

(4) klist_rm(&mutex->mutex_item) deletes the mutex from g_kobj_list.mutex_head list. As opposed to the insert operation in mutex_create;

(5) Exit the critical section and return.

7. Example

Here is an example of how to use the interface. Task 1 and Task 2 mutually exclusive access the global variable A through the mutex semaphore and determine if the mutex failed.

include <k_api.h>

/ Define test task parameters /

define TEST_TASK1_NAME “task_test1”

define TEST_TASK2_NAME “task_test2”

define TEST_TASK1_PRI 34

define TEST_TASK2_PRI 34

define TEST_TASK_STACKSIZE (512)

/ Define the mutex structure /

kmutex_t mutex_test;

/ Define task-related resources /

ktask_t test_task1_tcb;

cpu_stack_t test_task1_stack[TEST_TASK_STACKSIZE];

ktask_t test_task2_tcb;

cpu_stack_t test_task2_stack[TEST_TASK_STACKSIZE];

Declare the task entry function forward /

static void test_task1(void *arg);

static void test_task2(void *arg);

/ Defines a global variable that is mutually exclusive

int a = 0;

/ Entry function /

int application_start(int argc, char *argv[])

{

/ Statically creates a mutex with an initial number of 0 /

krhino_mutex_create(&mutex_test, “mutex_test”);

Create two test tasks /

krhino_task_create(&test_task1_tcb, TEST_TASK1_NAME, 0, TEST_TASK1_PRI, 50,

                  test_task1_stack, TEST_TASK_STACKSIZE, test_task1, 0);
Copy the code

krhino_task_create(&test_task2_tcb, TEST_TASK2_NAME, 0, TEST_TASK2_PRI, 50,

                  test_task2_stack, TEST_TASK_STACKSIZE, test_task2, 0);
Copy the code

}

/ Entry to Task 1 /

static void test_task1_entry(void *arg)

{

int b;

while (1) {

krhino_mutex_lock(&mutex_test, RHINO_WAIT_FOREVER); b = a; a = a + 1; if (a ! = b + 1) { printf("task 1 data process error\r\n"); } krhino_mutex_unlock(&mutex_test);Copy the code

}

}

/ Entry to Mission 2 /

static void test_task2(void *arg)

{

int b;

while(1) {

krhino_mutex_lock(&mutex_test, RHINO_WAIT_FOREVER); b = a; a = a + 1; if (a ! = b + 1) { printf("task 2 data process error\r\n"); } krhino_mutex_unlock(&mutex_test);Copy the code

}

}