STM32F1与STM32CubeIDE编程实例-ThreadX中的互斥体(Mutex)服务

ThreadX中的互斥体(Mutex)服务

文章目录

1、互斥体(Mutex)介绍

在实际应用中,可能会出现多个线程访问同一个共享资源或临界资源的情况。在这种情况下,需要保证线程对共享资料或临界资源具有独占访问权,即对共享资源或临界资源的访问具有排他性。互斥体(Mutex)具有这样的属性。它的设计目的是通过避免线程之间的冲突和防止线程之间不必要的交互来提供互斥保护。

互斥锁是一种公共资源,在任何时间点最多只能由一个线程拥有。 此外,准确地说,一个线程(并且只有同一个线程)可以重复 1 获得相同的互斥锁

2

32

1

2^{32}-1

232−1 次。 但是,同一个线程(并且只有那个线程)必须在互斥锁再次可用之前放弃该互斥锁相同的次数。

如下图所示,线程对临界区(实际是一个代码片段,其指令必须按顺序执行而不会中断)的访问。

在这里插入图片描述

要进入这个临界区,线程必须首先获得某个保护临界区的互斥体的所有权。 因此,当线程准备好开始执行此代码段时,它首先尝试获取该互斥体。 线程获得互斥体后,执行代码段,执行完成后释放互斥体。

互斥锁可以提供对一个共享资源的独占访问,就像它可以保护临界区一样。 也就是说,线程必须首先获得互斥体才能访问共享资源。 但是,如果一个线程必须同时独占访问两个(或更多)共享资源,那么它必须使用单独的互斥锁来保护每个共享资源。 在这种情况下,线程必须首先为每个共享资源获取特定的互斥锁,然后才能继续。如下图所示:

在这里插入图片描述

当线程准备好访问这些资源时,它首先获取保护这些资源的两个互斥锁。 在线程获得两个互斥体后,它访问共享资源,然后在完成这些资源后放弃这两个互斥体。

2、ThreadX的互斥体控制块(Mutex Control Block)

ThreadX中的互斥体控制块(Mutex Control Block,MCB)是ThreadX在运行时对Mutext状态管理的一种数据结构。它包含多种信息,包括互斥体所有者、所有权计数、优先级继承标志、拥有线程的原始优先级、拥有线程的原始抢占阈值、挂起计数和指向挂起列表的指针。

在大多数情况下,可以忽略 MCB 的内容。 但是,在某些情况下,尤其是在调试期间,检查 MCB 的某些成员很有用。 请注意,尽管 ThreadX 允许检查 MCB,但它严格禁止对其进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/\* Define the mutex structure utilized by the application. \*/

typedef struct TX\_MUTEX\_STRUCT
{

/\* Define the mutex ID used for error checking. \*/
ULONG tx_mutex_id;

/\* Define the mutex's name. \*/
CHAR \*tx_mutex_name;

/\* Define the mutex ownership count. \*/
UINT tx_mutex_ownership_count;

/\* Define the mutex ownership pointer. This pointer points to the
the thread that owns the mutex. \*/
TX_THREAD \*tx_mutex_owner;

/\* Define the priority inheritance flag. If this flag is set, priority
inheritance will be in effect. \*/
UINT tx_mutex_inherit;

/\* Define the save area for the owning thread's original priority. \*/
UINT tx_mutex_original_priority;

/\* Define the mutex suspension list head along with a count of
how many threads are suspended. \*/
struct TX\_THREAD\_STRUCT
\*tx_mutex_suspension_list;
UINT tx_mutex_suspended_count;

/\* Define the created list next and previous pointers. \*/
struct TX\_MUTEX\_STRUCT
\*tx_mutex_created_next,
\*tx_mutex_created_previous;

/\* Define the priority of the highest priority thread waiting for
this mutex. \*/
UINT tx_mutex_highest_priority_waiting;

/\* Define the owned list next and previous pointers. \*/
struct TX\_MUTEX\_STRUCT
\*tx_mutex_owned_next,
\*tx_mutex_owned_previous;

#ifdef TX\_MUTEX\_ENABLE\_PERFORMANCE\_INFO

/\* Define the number of mutex puts. \*/
ULONG tx_mutex_performance_put_count;

/\* Define the total number of mutex gets. \*/
ULONG tx_mutex_performance_get_count;

/\* Define the total number of mutex suspensions. \*/
ULONG tx_mutex_performance_suspension_count;

/\* Define the total number of mutex timeouts. \*/
ULONG tx_mutex_performance_timeout_count;

/\* Define the total number of priority inversions. \*/
ULONG tx_mutex_performance_priority_inversion_count;

/\* Define the total number of priority inheritance conditions. \*/
ULONG tx_mutex_performance__priority_inheritance_count;
#endif

/\* Define the port extension in the mutex control block. This
is typically defined to whitespace in tx\_port.h. \*/
TX_MUTEX_EXTENSION

} TX_MUTEX;


3、互斥体服务

ThreadX提供了完整的互斥体管理服务,包括:互斥体创建与删除、互斥体获取与释放、互斥体信息查询等。

3.1 创建互斥体

互斥体通过TX_MUTEX数据类型声明并通过tx_mutex_create服务定义。其原型如下:

UINT tx_mutex_create(TX_MUTEX *mutex_ptr, CHAR *name_ptr,UINT inherit);

其中:

  • mutex_ptr:为指向互斥体数据结构的指针。
  • name:为互斥体名字
  • inherit:优先级继承选项。取值为:TX_NO_INHERIT和TX_INHERIT。

如果创建成功,则返回TX_SUCCESS。

简单示例如下:

1
2
3
4
5
6
TX_MUTEX my_mutex;
UINT status;

// 创建互斥体,优先级继承
status = tx\_mutex\_create(&my_mutex,"my\_mutex\_name",TX_INHERIT);

3.2 删除互斥体

通过调用tx_mutex_delete服务可以删除互斥体。当删除互斥体后,在互斥锁上挂起的所有线程都将使用 TX_DELETED 状态码恢复。这些线程中的每一个都将从其对tx_mutex_get 的调用中接收到 TX_DELETED 返回状态。其原型如下:

UINT tx_mutex_delete(TX_MUTEX *mutex_ptr);

当成功删除互斥体,函数返回TX_SUCCESS。

示例如下:

1
2
3
4
5
6
TX_MUTEX my_mutex;
UINT status;


status = tx\_mutex\_delete(&my_mutex);

3.3 获取互斥体独占所有权

tx_mutex_get服务使线程能够尝试获得互斥锁的独占所有权。 如果没有线程拥有该互斥锁,则该线程将获得该互斥锁的所有权。 如果调用线程已经拥有互斥锁,则 tx_mutex_get 增加所有权计数器并返回成功状态。 其原型如下:

UINT tx_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option);

其中:

  • mutex_ptr:互斥体指针
  • wait_option:等待类型,TX_NO_WAIT、TX_WAIT_FOREVER、超时时长。

如果另一个线程已经拥有互斥锁,则采取的操作取决于与 tx_mutex_get 一起使用的调用选项,以及互斥锁是否启用了优先级继承。如下表所示:

等待选项 启用优先级继承 关闭优先继承
TX_NO_WAIT 立即返回 立即返回
TX_WAIT_FOREVER 如果调用线程具有更高的优先级,则将拥有线程的优先级提升到调用线程的优先级,然后将调用线程置于挂起列表中,调用线程无限期等待 线程置于挂起列表并无限期等待
超时值 如果调用线程具有更高的优先级,则将拥有线程的优先级提高到调用线程的优先级,然后将调用线程置于挂起列表中,调用线程等待直到指定的timer-ticks数到期 线程置于挂起列表中并等待直到指定的计时器滴答数到期

如果使用优先级继承,请确保不允许外部线程修改在互斥锁所有权期间继承了更高优先级的线程的优先级。

示例如下:

1
2
3
4
5
TX_MUTEX my_mutex;
UINT status;

status = tx\_mutex\_get(&my_mutex, TX_WAIT_FOREVER);

如果变量status的值为 TX_SUCCESS,则成功的获取互斥体独占所有权。 此示例中使用了 TX_WAIT_FOREVER 选项。 因此,如果互斥锁已被另一个线程拥有,则调用线程将在挂起列表中无限期地等待。

3.4 查询互斥体信息

ThreadX提供了三个用于查询互斥体信息的服务,其中,tx_mutex_info_get service用于查询互斥体在运行时的互斥体控制块信息。当该服务调用时,该服务提供了特定时刻的Snapshot(快照)。另外两个服务 tx_mutex_performance_info_gettx_mutex_performance_system_info_get用于收集的运行时性能数据的摘要信息。tx_mutex_performance_info_get用于在调用时提供特定互斥锁的信息摘要。tx_mutex_performance_system_info_get用于查询所有互斥体运行时性能数据的摘要信息。这些服务有助于分析系统的行为并确定是否存在潜在的问题区域。

tx_mutex_info_get 服务获取的信息包括所有权计数、拥有线程的位置、挂起列表中第一个线程的位置、挂起的线程数以及下一个创建的互斥锁的位置。其原型如下:

UINT tx_mutex_info_get(TX_MUTEX *mutex_ptr, CHAR **name,
ULONG *count, TX_THREAD **owner,
TX_THREAD **first_suspended,
ULONG *suspended_count,
TX_MUTEX **next_mutex);

当查询成功时,服务返回TX_SUCCESS。

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TX_MUTEX my_mutex;
CHAR \*name;
ULONG count;
TX_THREAD \*owner;
TX_THREAD \*first_suspended;
ULONG suspended_count;
TX_MUTEX \*next_mutex;
UINT status;

...

status = tx\_mutex\_info\_get(&my_mutex, &name,
&count, &owner,
&first_suspended,
&suspended_count,
&next_mutex);

3.5 优先考虑互斥挂起列表

当一个线程因为等待一个互斥锁而被挂起时,它会以先进先出的方式被放入挂起列表中。 当互斥体可用时,挂起列表中的第一个线程(无论优先级如何)将获得该互斥体的所有权。 tx_mutex_prioritize 服务将因特定互斥锁的所有权而暂停的最高优先级线程放在暂停列表的前面。 所有其他线程保持与它们被挂起时相同的 FIFO 顺序。其原型如下:

UINT tx_mutex_prioritize(TX_MUTEX *mutex_ptr);

当服务请求成功时,返回TX_SUCCESS。

示例如下:

1
2
3
4
5
6
TX_MUTEX my_mutex;
UINT status;


status = tx\_mutex\_put(&my_mutex);

如果变量 status 的值为 TX_SUCCESS,则挂起列表中等待名为“my_mutex”的互斥锁的最高优先级线程已被放置在挂起列表的前面。 如果没有线程在等待这个互斥体,返回值也是 TX_SUCCESS 并且挂起列表保持不变。

3.6 释放互斥体独占所有权

tx_mutex_put 服务使线程能够释放互斥锁的所有权。 假设线程拥有互斥锁,则所有权计数递减。 如果所有权计数变为零,则互斥锁变为可用。 如果互斥锁变得可用并且为此互斥锁启用了优先级继承,则释放线程的优先级将恢复到它最初获得互斥锁所有权时的优先级。 在互斥锁所有权期间对释放线程所做的任何其他优先级更改也可以撤消。其原型如下:

UINT tx_mutex_put(TX_MUTEX *mutex_ptr);

示例如下:

1
2
3
4
5
6
TX_MUTEX my_mutex;
UINT status;


status = tx\_mutex\_put(&my_mutex);

如果变量 status 的值为 TX_SUCCESS,则挂起列表中等待名为“my_mutex”的互斥锁的最高优先级线程已被放置在挂起列表的前面。 如果没有线程在等待这个互斥体,返回值也是 TX_SUCCESS 并且挂起列表保持不变。

4、避免死锁

使用互斥锁的潜在陷阱之一是所谓死锁。 这是不希望出现的情况,其中两个或多个线程在尝试获取已由其他线程拥有的互斥锁时无限期挂起。考虑下面的情况:

  1. Thread 1 获得 Mutex 1 的所有权
  2. Thread 2 获得 Mutex 2 的所有权
  3. Thread 1 挂起,因为它试图获得 Mutex 2 的所有权
  4. Thread 2 挂起,因为它试图获得 Mutex 1 的所有权

因此,Thread 1 和Thread 2 进入了致命的死锁,因为它们已无限期挂起,每个线程都在等待另一个线程拥有的互斥体。如下图所示:

在这里插入图片描述

如何避免死锁? 应用程序级别的预防是实时系统的唯一方法。 保证不存在致命拥抱的唯一方法是允许线程在任何时候最多拥有一个互斥锁。 如果线程必须拥有多个互斥锁,那么如果让线程以相同的顺序获取互斥锁,通常可以避免死锁。例如,如果线程总是以连续的顺序获取两个互斥锁,即Thread 1(或线程 2)将尝试获取Mutex1,然后立即尝试获取 Mutex 2,则可以上述示例中的死锁问题。 另一个线程将尝试以相同的顺序获取 Mutex 1 和 Mutex 2。

从死锁中中恢复的一种方法是使用与 tx_mutex_get 服务相关的挂起超时功能,这是三个可用的等待选项之一。 从死锁中恢复的另一种方法是让另一个线程调用 tx_thread_wait_abort 服务来中止被困在死锁中的线程的挂起。

5、完整示例

下面示例演示了生产者和消费者经典实现。生产者线程需要500ms将计数器增加1,消费者线程需要100ms将计数器减1。通过mutex同步两个线程,让计数器的值不能小于0。

5.1 应用程序定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/\*
\* app.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_APP\_H\_\_
#define \_\_APP\_H\_\_

#include "tx\_api.h"

extern TX_MUTEX app_producer_consumer_mutex;
extern int32\_t app_counter;

void app\_start(void);

uint16\_t create\_task\_stack(void\*\* statck,uint32\_t size);

#endif /\* \_\_APP\_H\_\_ \*/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/\*
\* app.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/
#include <stdio.h>
#include "main.h"
#include "app.h"
#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdbool.h>
#include "tasks/consumer\_task.h"
#include "tasks/producer\_task.h"

// 程序计数器同步Mutex
TX_MUTEX app_producer_consumer_mutex;
// 程序计数器
int32\_t app_counter = 0;

// 10k应用程序内存
#define APP\_MEMORY\_SIZE 1024 \* 10

// 字节内存句柄
static TX_BYTE_POOL __s_app_statc_pool;

static uint8\_t __app_memory_area[APP_MEMORY_SIZE];

// 创建字节内存池
uint16\_t create\_app\_byte\_pool() {
UINT status;
// 创建字节内存池
status = tx\_byte\_pool\_create(&__s_app_statc_pool, "app\_byte\_pool",
__app_memory_area, APP_MEMORY_SIZE);
return status;
}

// 创建字节栈
uint16\_t create\_task\_stack(void \*\*statck, uint32\_t size) {
UINT status;
// 分配字节内存
status = tx\_byte\_allocate(&__s_app_statc_pool, statck, size, TX_NO_WAIT);
return status;
}

void tx\_application\_define(void \*first_unused_memory) {

uint16\_t status = create\_app\_byte\_pool();
if (status != TX_SUCCESS) {
printf("create app memory byte pool failed:code = %d\r\n", status);
while (true)
;
}
// 创建Mutex
status = tx\_mutex\_create(&app_producer_consumer_mutex,
"app\_producer\_consumer\_mutex", TX_NO_INHERIT);
if (status != TX_SUCCESS) {
printf("create mutex failed:code = %d\r\n", status);
while (true)
;
}

create\_producer\_task();
create\_consumer\_task();
}

void app\_start(void) {
// 启用内核调度
tx\_kernel\_enter();
}

5.2 生产者线程

1)基本定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/\*
\* producer\_task.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_PRODUCER\_TASK\_H\_\_
#define \_\_PRODUCER\_TASK\_H\_\_

void create\_producer\_task(void);

#endif /\* \_\_PRODUCER\_TASK\_H\_\_ \*/


2)生产者线程实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/\*
\* producer\_task.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdio.h>
#include <tasks/producer\_task.h>
#include "app.h"

#define PRODUCER\_THREAD\_STACK\_SIZE 512

TX_THREAD producer_thread_handle;
uint8\_t \* producer_thread_stack = NULL;


void producer\_task\_entry(void \*params) {
printf("[task]producer started\r\n");
for (;;) {
// 获取Mutex
tx\_mutex\_get(&app_producer_consumer_mutex, TX_WAIT_FOREVER);
app_counter++;
printf("producer:counter = %d\r\n",app_counter);
tx\_thread\_sleep(500);
// 释放Mutex
tx\_mutex\_put(&app_producer_consumer_mutex);
}
}

void create\_producer\_task(void){
uint16\_t status = create\_task\_stack(&producer_thread_stack, PRODUCER_THREAD_STACK_SIZE);
if(status != TX_SUCCESS){
printf("cannot create producer task stack\r\n");
return;
}
tx\_thread\_create(/\*创建线程\*/
&producer_thread_handle, /\*线程句柄\*/
"producer\_task", /\*线程名称\*/
producer_task_entry, /\*线程执行函数\*/
NULL, /\*线程函数\*/
producer_thread_stack, /\*线程堆栈入口\*/
PRODUCER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/
3, /\*优先级\*/
3, /\*抢占阈值\*/
TX_NO_TIME_SLICE, /\*线程时间切片类型\*/
TX_AUTO_START /\*线程启动类型:自动启动\*/
);
printf("created producer task\r\n");
}

5.3 消费者线程

1)基本定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/\*
\* consumer\_task.h
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#ifndef \_\_CONSUMER\_TASK\_H\_\_
#define \_\_CONSUMER\_TASK\_H\_\_

void create\_consumer\_task(void);

#endif /\* \_\_CONSUMER\_TASK\_H\_\_ \*/

2)消费者线程实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/\*
\* consumer\_task.c
\*
\* Created on: May 3, 2022
\* Author: jenson
\*/

#include "tx\_api.h"
#include "tx\_thread.h"
#include <stdio.h>
#include <tasks/producer\_task.h>
#include "app.h"

#define CONSUMER\_THREAD\_STACK\_SIZE 512

TX_THREAD consumer_thread_handle;
uint8\_t \* consumer_thread_stack = NULL;


void consumer\_task\_entry(void \*params) {
printf("[task]consumer started\r\n");
for (;;) {
// 获取Mutex
tx\_mutex\_get(&app_producer_consumer_mutex, TX_WAIT_FOREVER);
app_counter--;
printf("consumer:counter = %d\r\n",app_counter);
tx\_thread\_sleep(100);
// 释放Mutex
tx\_mutex\_put(&app_producer_consumer_mutex);
}
}

void create\_consumer\_task(void){
uint16\_t status = create\_task\_stack(&consumer_thread_stack, CONSUMER_THREAD_STACK_SIZE);
if(status != TX_SUCCESS){
printf("cannot create consumer task stack\r\n");
return;
}
tx\_thread\_create(/\*创建线程\*/
&consumer_thread_handle, /\*线程句柄\*/
"consumer\_task", /\*线程名称\*/
consumer_task_entry, /\*线程执行函数\*/
NULL, /\*线程函数\*/
consumer_thread_stack, /\*线程堆栈入口\*/
CONSUMER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/
3, /\*优先级\*/
3, /\*抢占阈值\*/
TX_NO_TIME_SLICE, /\*线程时间切片类型\*/
TX_AUTO_START /\*线程启动类型:自动启动\*/
);
printf("created consumer task\r\n");
}

5.3 主程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/\* Private includes ----------------------------------------------------------\*/
/\* USER CODE BEGIN Includes \*/
#include "app.h"
#include <stdio.h>
/\* USER CODE END Includes \*/

int main(void) {
/\* USER CODE BEGIN 1 \*/

/\* USER CODE END 1 \*/

/\* MCU Configuration--------------------------------------------------------\*/

/\* Reset of all peripherals, Initializes the Flash interface and the Systick. \*/
HAL\_Init();

/\* USER CODE BEGIN Init \*/

/\* USER CODE END Init \*/

/\* Configure the system clock \*/
SystemClock\_Config();

/\* USER CODE BEGIN SysInit \*/

/\* USER CODE END SysInit \*/

/\* Initialize all configured peripherals \*/
MX\_GPIO\_Init();
MX\_USART1\_UART\_Init();
/\* USER CODE BEGIN 2 \*/
printf("\*\*\*\*STM32CubeIDE:ThreadX Demo\*\*\*\*\r\n");
app\_start();
/\* USER CODE END 2 \*/

/\* Infinite loop \*/
/\* USER CODE BEGIN WHILE \*/
while (1) {
/\* USER CODE END WHILE \*/

/\* USER CODE BEGIN 3 \*/
}
/\* USER CODE END 3 \*/
}

文章来源: https://iotsmart.blog.csdn.net/article/details/125125771