The first, the public number

The introduction

Mutex, or Mutual Exclusion Semaphores (short for Mutex, Mutual Exclusion). The main function of a mutex is to achieve mutually exclusive access to resources. Binary semaphores can also implement mutually exclusive access to resources, so why introduce mutex? What’s the difference between mutex and semaphore?

There are two important points:

  • Priority reversal
  • Priority inheritance

Once you understand these two things, you basically know the mutex.

Understand mutex

A mutex is a way of protecting a shared resource. When one thread owns a mutex, it protects the shared resource from being destroyed by other threads.

When a thread holds a mutex, no other thread can hold it, and the thread that holds the mutex can hold it again without being suspended, that is, the mutex can be held recursively. For semaphores, recursive acquisition is not supported, which would result in a deadlock.

Mutex prevents thread priority flipping; binary semaphores are not supported. So what is priority flipping?

Priority flipping problem

Priority reverse popular explanation: when a higher-priority thread tries to through the semaphore mechanism when access to a Shared resource, if the signal has been a lower priority thread holds, and the lower priority thread may again be in the process of running some other medium priority thread preemption, thus causing a higher-priority thread by many has lower priority thread block.

An example is shown below:

Thread1 > Thread2 > Thread3 in descending order of priority. Thread Thread1 needs to access a shared resource. When thread Thread3 is accessing a shared resource, Thread1 enters the suspended state and waits for Thread3 to release the shared resource.

During Thread3 execution, Thread2 is ready, preempting Thread3’s execution. After Thread2 completes, Thread1 continues to execute, releasing the shared resource and allowing Thread1 to continue running.

In this case, priority flipping occurs: thread Thread2 has precedence over Thread1, meaning that Thread1 has to wait for Thread2 to complete before it has a chance to run. This is the reverse of preemptive scheduling based on priority.

Therefore, RTOS introduced mutex to avoid the priority flipping problems associated with binary semaphore use.

Mutex can prevent priority flipping because priority inheritance algorithm is adopted in the implementation process.

Priority inheritance

Priority inheritance refers to raising the priority of a low-priority thread holding a resource to equal the priority of the highest priority thread of all threads waiting for the resource, and then executing it. When the low-priority thread releases the resource, the priority reverts to the original setting.

Thus, a thread that inherits priority prevents system resources from being preempted by any intermediate priority thread.

In the example above, the lowest priority thread, Thread3, temporarily increases its priority to the same as that of Thread1 when it owns the mutex. Even if Thread2 reaches the ready state, it cannot execute immediately and needs to wait for Thread1 to complete its execution before it is ready to run.

Note that the mutex should be released as soon as possible after it is acquired, and the priority of the thread holding the mutex should not be changed while it is being held.

Mutex control block

The data structure for rt-thread management of mutex is the mutex control block, represented by struct rt_mutex, which is defined as follows:

struct rt_mutex { struct rt_ipc_object parent; /* Inherit from ipc_object */ rt_uint16_t value; /* The value of the mutex */ rt_uint8_t original_priority; /* Hold the thread's original priority */ rt_uint8_t hold; Struct rt_thread *owner; /* The thread that currently owns the mutex */}; /* typedef struct rt_mutex* rt_mutex_t;Copy the code

In addition, rT_mutex_t represents the mutex handle, which is a pointer to the mutex control block.

From an object-oriented perspective, the Rt_MUtex object is derived from rt_IPc_object and is managed by the IPC container.

Mutex operations

In rt-thread, operations on a mutex include:

  • Creates/initializes a mutex
  • Gets the mutex
  • Release the mutex
  • Delete/detach mutex

The common operations are: create a mutex, get a mutex, and release a mutex.

Note: Mutex cannot be used in interrupt service routines.

1. Create a mutex

Rt-thread creates mutex dynamically as follows:

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag)
Copy the code

When this function is called to create a mutex, the kernel automatically creates a mutex control block, allocates a MUtex object from the kernel object manager, and initializes it.

The parameter name is the name of the mutex; Flag sets the sorting mode of thread queues waiting for mutex.

If the mutex is created successfully, the function returns the mutex handle. If the creation fails, RT_NULL is returned.

There are two values of Flag:

  • RT_IPC_FLAG_PRIO, multiple threads waiting for mutex are sorted in order of priority.
  • RT_IPC_FLAG_FIFOThreads waiting for mutex are sorted on a first-in, first-out basis.

Creating a mutex statically requires two steps :(1) defining a mutex control block struct variable, and (2) calling a function to initialize it.

The function interface for initializing a mutex is as follows:

rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag)
Copy the code

This function initializes the mutex control block specified by the mutex argument. The other two parameters, name and flag, are the same as the dynamically created function.

2. Obtain the mutex

Rt-thread provides the following interface for obtaining a mutex. The thread calls this function to obtain a mutex.

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time)
Copy the code

The mutex argument is a mutex handle; Time Indicates the waiting timeout duration, expressed in system beats.

If the mutex is available, the thread that requested the mutex will successfully acquire it, and the thread has ownership of the mutex. If the thread continues to acquire the mutex, the hold count of the mutex increases by one, and the prior thread does not hang and wait (i.e., supports recursive acquisition of the mutex).

If the mutex is already occupied by another thread, the current thread enters a suspended state, waiting for another thread to release the mutex or for the timeout to be reached.

Rt_mutex_take () returns RT_EOK indicating success; -rt_etimeout indicates timeout. -RT_ERROR Failed to obtain information.

3. Release the mutex

When a thread runs out of mutex resources, it should release the mutex it occupies as soon as possible so that other threads can acquire it in time.

The functional interface for releasing mutex is:

rt_err_t rt_mutex_release(rt_mutex_t mutex)
Copy the code

Only threads that already own a mutex can release it, and each time the mutex is released, its hold count decreases by one. When the mutex’s hold count reaches zero (that is, the holding thread has released all hold operations), it becomes available and threads waiting on the mutex are awakened.

If the thread priority is raised by the mutex, the thread reverts to its original priority when the mutex is released.

Examples demonstrate

Let’s look at the whole example.

Create two threads. Thread 1 increments two numeric variables by 1. Thread 2 also increments both numbers by one, using a mutex to keep the thread from changing two values. The code is as follows:

#include <rtthread.h> #define THREAD_PRIORITY 8 #define THREAD_TIMESLICE 5 /* pointer to mutex */ static rt_mutex_t dynamic_mutex = RT_NULL; /* static uint8_t number1,number2 = 0; Static void rt_thread1_entry(void *parameter) {while(1) { Then release the mutex */ rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); number1++; number2++; rt_mutex_release(dynamic_mutex); rt_thread_delay(5); }} static void rt_thread2_entry(void *parameter) {while(1) {/* Thread2_entry (void *parameter) { */ rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); if(number1 ! = number2) { rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2); } else { rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1); } number1++; number2++; rt_mutex_release(dynamic_mutex); if(number1 >= 10) { return; }}} int main() {rt_thread_t thread1 = RT_NULL; rt_thread_t thread2 = RT_NULL; /* Create a dynamic mutex */ dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO); if (dynamic_mutex == RT_NULL) { rt_kprintf("create dynamic mutex failed.\n"); return -1; Rt_thread_create ("thread1", rt_thread1_entry, RT_NULL, 1024, THREAD_PRIORITY, THREAD_TIMESLICE); if(thread1 ! RT_NULL) {/* Start thread */ rt_thread_startup(thread1); Rt_thread_create ("thread2", rt_thread2_entry, RT_NULL, 1024, thread_priority-1, THREAD_TIMESLICE); if(thread2 ! RT_NULL) {/* Start thread */ rt_thread_startup(thread2); }}Copy the code

Thread 1 and thread 2 both use mutex protection on both numers, and the program runs as follows

Not commonly used function interfaces

For mutex operations, there are still deletion functions that are not covered.

To delete a dynamically created mutex, run the following command:

rt_err_t rt_mutex_delete (rt_mutex_t mutex);
Copy the code

When a mutex is no longer needed, you can release system resources by deleting it. When a mutex is deleted, all threads waiting for the mutex are woken up and the mutex space is freed.

To delete a statically created mutex, run the following command:

rt_err_t rt_mutex_detach (rt_mutex_t mutex);
Copy the code

This function removes the mutex object from the kernel manager. The kernel wakes up all threads suspended on the mutex.

OK, I’ll stop there for today and continue next time. Come on ~


Public number [learn embedded together], sharing RTOS, Linux, C language knowledge