STM32F1与STM32CubeIDE编程实例-ThreadX中的信号量(Semaphore)服务 ThreadX中的信号量(Semaphore)服务 文章目录
1、信号量(Semaphore)介绍 ThreadX 提供 32 位计数信号量,计数值范围为 0 到
2
32
−
1
2^{32} - 1
232−1,或 4,294,967,295(含)。有两种操作会影响计数信号量的值:tx_semaphore_get和tx_semaphore_put。get 操作将信号量减一。 如果信号量为 0,则获取操作失败。 get 操作的逆操作是put 操作,它将信号量加一。
每个计数信号量都是公共资源。 ThreadX 对如何使用计数信号量没有任何限制。
计数信号量的一个实例是单个计数。 例如,如果计数为 5,则该信号量有 5 个实例。 同样,如果计数为零,则该信号量没有实例。 get 操作通过递减计数从计数信号量中获取一个实例。 类似地,put 操作通过增加其计数将一个实例放入计数信号量中。
与互斥体一样,计数信号量通常用于互斥。 这些对象之间的一个主要区别是计数信号量不支持所有权,这是互斥锁的核心概念。 即便如此,计数信号量也更加通用。 它们还可以用于事件通知和线程间同步。
互斥涉及控制线程对某些应用程序区域(通常是临界区和应用程序资源)的访问。 当用于互斥时,信号量的**当前计数(current count)表示允许访问该信号量的线程总数相关资源。 在大多数情况下,用于互斥的计数信号量的初始值为 1,这意味着一次只有一个线程可以访问关联的资源。 值限制为 0 或 1 的计数信号量通常称为 二进制信号量(Binary Semaphore)**。
如果使用二进制信号量,用户必须阻止同一线程对它已经控制的信号量执行 get 操作。 第二次获取将失败并可能无限期地挂起调用线程,并使资源永久不可用。
计数信号量(Counting Semaphore)也可用于事件通知,如在生产者-消费者应用程序中。 在这个应用程序中,消费者在 消费 资源(例如队列中的数据)之前尝试获取计数信号量; 生产者在使某些东西可用时增加信号量计数。 换句话说,生产者将实例放在信号量中,而消费者尝试从信号量中获取实例。 这种信号量的初始值通常为 0,并且在生产者为消费者准备好东西之前不会增加。
应用程序可以在初始化期间或运行时创建计数信号量。信号量的初始计数在创建期间指定。 应用程序可以使用无限数量的计数信号量。
应用程序线程可以在尝试对当前计数为零的信号量执行 get 操作时挂起(取决于等待选项的值)。
当对信号量执行 put 操作并且在该信号量上挂起的线程时,挂起的线程完成其 get 操作并恢复。 如果多个线程在同一个计数信号量上挂起,它们将按照它们在挂起列表中出现的顺序(通常按 FIFO 顺序)恢复。
如果应用程序在信号量 put 调用之前调用 tx_semaphore_prioritize,则应用程序可以导致优先级更高的线程首先恢复。 信号量优先级服务将最高优先级的线程放在挂起列表的最前面,同时让所有其他挂起的线程保持相同的 FIFO 顺序。
2、计数信号量控制块(Counting Semaphore Control Block,CSCB) 每个计数信号量的特性都在其控制块中找到。它包含有用的信息,例如当前信号量计数和为此计数信号量挂起的线程数。 计数信号量控制块可以位于内存中的任何位置,但最常见的是通过在任何函数范围之外定义控制块来使控制块成为全局结构。
当使用 TX_SEMAPHORE 数据类型声明计数信号量时,将创建计数信号量控制块。TX_SEMAPHORE的数据结构如下:
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 /\* Define the semaphore structure utilized by the application. \*/ typedef struct TX\_SEMAPHORE\_STRUCT { /\* Define the semaphore ID used for error checking. \*/ ULONG tx_semaphore_id; /\* Define the semaphore's name. \*/ CHAR \*tx_semaphore_name; /\* Define the actual semaphore count. A zero means that no semaphore instance is available. \*/ ULONG tx_semaphore_count; /\* Define the semaphore suspension list head along with a count of how many threads are suspended. \*/ struct TX\_THREAD\_STRUCT \*tx_semaphore_suspension_list; UINT tx_semaphore_suspended_count; /\* Define the created list next and previous pointers. \*/ struct TX\_SEMAPHORE\_STRUCT \*tx_semaphore_created_next, \*tx_semaphore_created_previous; #ifdef TX\_SEMAPHORE\_ENABLE\_PERFORMANCE\_INFO /\* Define the number of semaphore puts. \*/ ULONG tx_semaphore_performance_put_count; /\* Define the number of semaphore gets. \*/ ULONG tx_semaphore_performance_get_count; /\* Define the number of semaphore suspensions. \*/ ULONG tx_semaphore_performance_suspension_count; /\* Define the number of semaphore timeouts. \*/ ULONG tx_semaphore_performance_timeout_count; #endif #ifndef TX\_DISABLE\_NOTIFY\_CALLBACKS /\* Define the application callback routine used to notify the application when the a semaphore is put. \*/ VOID (\*tx_semaphore_put_notify)(struct TX\_SEMAPHORE\_STRUCT \*semaphore_ptr); #endif /\* Define the port extension in the semaphore control block. This is typically defined to whitespace in tx\_port.h. \*/ TX_SEMAPHORE_EXTENSION } TX_SEMAPHORE;
3、避免死锁 使用信号量进行互斥的最危险的陷阱之一是所谓的死锁。两个或多个线程在尝试获取信号量时无限期挂起的情况 由其他线程拥有。前面文章ThreadX中的互斥体(Mutex)服务 中介绍如何避免死锁的方法同样适用于计数信号量。
4、避免优先级反转(Priority Inversion) 与互斥信号量相关的另一个缺陷是优先级反转。当低优先级线程获得高优先级线程需要的互斥信号量时,就会为麻烦打下基础。 这种优先级倒置本身是正常的。 但是,如果具有中间优先级的线程获取信号量,则优先级反转可能会持续不确定的时间量。 可以通过仔细选择线程优先级、使用抢占阈值以及暂时将拥有资源的线程的优先级提高到高优先级线程的优先级来防止这种情况发生。 然而,与互斥体不同的是,计数信号量没有优先继承特性。
5、信号量服务 5.1 创建信号量 计数信号量使用 TX_SEMAPHORE 数据类型声明,并使用 tx_semaphore_create 服务定义。 定义计数信号量时,必须指定其控制块、计数信号量的名称以及信号量的初始计数。其原型如下:
UINT tx_semaphore_create(TX_SEMAPHORE *semaphore_ptr,CHAR *name_ptr, ULONG initial_count);
其中:
semaphore_ptr :指向信号量控制块的指针
name_ptr :指向信号量名称的指针
initial_count :初始化数量
当创建成功,则返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 TX_SEMAPHORE my_semaphore; UINT status; status = tx\_semaphore\_create(&my_semaphore,"my\_semaphore\_name", 1);
变量status的值为TX_SUCCESS时,表示创建信号量成功。
5.2 删除信号量 使用 tx_semaphore_delete 服务删除计数信号量。 所有因等待信号量实例而暂停的线程都将恢复并接收 TX_DELETED 返回状态。 确保不要尝试使用已删除的信号量。
UINT tx_semaphore_delete(TX_SEMAPHORE *semaphore_ptr);
示例如下:
1 2 3 4 5 6 7 TX_SEMAPHORE my_semaphore; UINT status; ... status = tx\_semaphore\_delete(&my_semaphore);
变量status的值为TX_SUCCESS时,表示创建信号量成功。
5.3 获取信号量 tx_semaphore_get 服务从指定的计数信号量中获取一个实例(单个计数)。 如果调用成功,则信号量计数减一。其原型如下:
UINT tx_semaphore_get(TX_SEMAPHORE *semaphore_ptr,ULONG wait_option);
其中,
semaphore_ptr :指向信号量控制块的指针
wait_option :获取信号量时的线程等待选项,取值可以为TX_NO_WAIT和TX_WAIT_FOREVER
获取成功,则返回TX_SUCCESS。
简单示例如下:
1 2 3 4 5 6 TX_SEMAPHORE my_semaphore; UINT status; … status = tx\_semaphore\_get(&my_semaphore);
变量status的值为TX_SUCCESS时,表示获取信号量成功。
5.4 释放信号量 tx_semaphore_put 服务将一个实例放入计数信号量中,即,它将计数增加一。 如果在执行 put 服务时有一个线程在此信号量上挂起,则挂起线程的 get 操作完成并恢复该线程。其原型如下:
UINT tx_semaphore_put(TX_SEMAPHORE *semaphore_ptr);
简单示例如下:
1 2 3 4 5 6 TX_SEMAPHORE my_semaphore; UINT status; … status = tx\_semaphore\_put(&my_semaphore);
如果status等于 TX_SUCCESS,则信号量计数已增加。 如果一个线程正在等待,它会被赋予信号量实例并恢复。
5.5 Ceiling方式释放信号量 tx_semaphore_put 服务将释放计数信号量,而不考虑信号量的当前计数。但是,如果想确保计数始终小于或等于某个值,请使用 tx_semaphore_ceiling_put 服务。 调用此服务时,仅当结果计数小于或等于上限时,信号量才会增加。其原型如下:
UINT tx_semaphore_ceiling_put(TX_SEMAPHORE *semaphore_ptr,ULONG ceiling);
其中:
semaphore_ptr :指向信号量控制块的指针
ceiling :计数截止值
如果服务返回TX_SUCCESS,则表示释放成功;如返回TX_CEILING_EXCEEDED,则表示信号量不会增加。
示例如下:
1 2 3 4 5 6 TX_SEMAPHORE my_semaphore; UINT status; … status = tx\_semaphore\_ceiling\_put(&my_semaphore,3);
如果status等于 TX_SUCCESS,则信号量计数已增加。 当然,如果一个线程正在等待,它会被赋予信号量实例并恢复。 如果status等于 TX_CEILING_EXCEEDED,则信号量不会增加,因为结果值将大于上限值 3。
5.5 信号量通知 tx_semaphore_put_notify 服务注册一个通知回调函数,只要将释放指定的信号量上就会调用该函数。 通知回调的处理由应用程序定义。 一个典型的使用场景是事件链(Event Chain),其中通知服务用于将各种同步事件链接在一起。当单个线程必须处理多个同步事件时,这通常很有用。其原型如下:
UINT tx_semaphore_put_notify(TX_SEMAPHORE *semaphore_ptr,VOID (*semaphore_put_notify)(TX_SEMAPHORE *));
其中,VOID (*semaphore_put_notify)(TX_SEMAPHORE *)为回调函数。
5.6 优先计算信号量挂起列表 当一个线程因为等待一个计数信号量而被挂起时,它以先进先出的方式被放入挂起列表中。 当计数信号量实例可用时,该挂起列表中的第一个线程(无论优先级如何)获得该实例的所有权。 tx_semaphore_prioritize 服务将最高优先级的线程挂在挂起列表前面的特定计数信号量上。 所有其他线程保持与它们被挂起时相同的 FIFO 顺序。其原型如下:
UINT tx_semaphore_prioritize(TX_SEMAPHORE *semaphore_ptr);
服务返回TX_SUCCESS,则表示操作成功。
简单示例如下:
1 2 3 4 5 6 7 TX_SEMAPHORE my_semaphore; UINT status; ... status = tx\_semaphore\_prioritize(&my_semaphore);
如果 status 等于 TX_SUCCESS,则最高优先级的挂起线程位于列表的前面。 对该信号量进行的下一次 tx_semaphore_put 调用将唤醒该线程。
5.7 信号量信息检索 有三种服务可以检索有关信号量的重要信息:
这些服务有助于分析系统的行为并确定是否存在潜在的问题区域。tx_semaphore_info_get 服务检索有关计数信号量的几条有用信息。检索到的信息包括计数信号量名称、当前计数、为此信号量挂起的线程数以及指向下一个创建的计数信号量的指针。
简单示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 TX_SEMAPHORE my_semaphore; CHAR \*name; ULONG current_value; TX_THREAD \*first_suspended; ULONG suspended_count; TX_SEMAPHORE \*next_semaphore; UINT status; … status = tx\_semaphore\_info\_get(&my_semaphore, &name, ¤t_value, &first_suspended, &suspended_count, &next_semaphore);
变量status的值为TX_SUCCESS时,表示检索成功。
6、完整示例 在本节中,将实现一个具有2个计数实例的信号量。一个生产者线程,两个消费者线程,一个程序计数器。其中生产者线程需要500ms才能让程序计数器的值增加2。每个消费者线程需要100ms将程序计数减1。通过实例,可以清楚比较信号量与互斥体的区别。
6.1 应用程序 1)应用程序基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 /\* \* app.h \* \* Created on: May 3, 2022 \* Author: jenson \*/ #ifndef \_\_APP\_H\_\_ #define \_\_APP\_H\_\_ #include "tx\_api.h" extern TX_SEMAPHORE app_producer_consumer_semaphore;; extern int32\_t app_counter; void app\_start(void); uint16\_t create\_task\_stack(void\*\* statck,uint32\_t size); #endif /\* \_\_APP\_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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 /\* \* 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" // 信号量 TX_SEMAPHORE app_producer_consumer_semaphore; // 程序计数器 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) ; } // 创建信号量 status = tx\_semaphore\_create(&app_producer_consumer_semaphore, "app\_producer\_consumer\_semphore", 2); if (status != TX_SUCCESS) { printf("create semaphore failed:code = %d\r\n", status); while (true) ; } create\_producer\_task(); create\_consumer\_task(); } void app\_start(void) { // 启用内核调度 tx\_kernel\_enter(); }
6.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 (;;) { // 获取信号量 tx\_semaphore\_get(&app_producer_consumer_semaphore, TX_WAIT_FOREVER); app_counter += 2; printf("producer:counter = %d\r\n",app_counter); tx\_thread\_sleep(500); // 释放信号量 tx\_semaphore\_put(&app_producer_consumer_semaphore); } } 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, /\*线程堆栈大小\*/ 2, /\*优先级\*/ 2, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); printf("created producer task\r\n"); }
6.3 消费者线程 1)基本定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 /\* \* 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 /\* \* consumer\_task.c \* \* Created on: May 3, 2022 \* Author: jenson \*/ /\* \* hello\_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 consumer1_thread_handle; uint8\_t \*consumer1_thread_stack = NULL; TX_THREAD consumer2_thread_handle; uint8\_t \*consumer2_thread_stack = NULL; void consumer1\_task\_entry(void \*params) { printf("[task]consumer 1 started\r\n"); for (;;) { // 获取信号量 tx\_semaphore\_get(&app_producer_consumer_semaphore, TX_WAIT_FOREVER); app_counter--; printf("consumer 1:counter = %d\r\n",app_counter); tx\_thread\_sleep(200); // 释放信号量 tx\_semaphore\_put(&app_producer_consumer_semaphore); } } void consumer2\_task\_entry(void \*params) { printf("[task]consumer 2 started\r\n"); for (;;) { // 获取信号量 tx\_semaphore\_get(&app_producer_consumer_semaphore, TX_WAIT_FOREVER); app_counter--; printf("consumer 2:counter = %d\r\n",app_counter); tx\_thread\_sleep(200); // 释放信号量 tx\_semaphore\_put(&app_producer_consumer_semaphore); } } void create\_consumer\_task(void) { uint16\_t status = create\_task\_stack(&consumer1_thread_stack, CONSUMER_THREAD_STACK_SIZE); if (status != TX_SUCCESS) { printf("cannot create consumer 1 task stack\r\n"); return; } status = create\_task\_stack(&consumer2_thread_stack, CONSUMER_THREAD_STACK_SIZE); if (status != TX_SUCCESS) { printf("cannot create consumer 2 task stack\r\n"); return; } tx\_thread\_create(/\*创建线程\*/ &consumer1_thread_handle, /\*线程句柄\*/ "consumer1\_task", /\*线程名称\*/ consumer1_task_entry, /\*线程执行函数\*/ NULL, /\*线程参数\*/ consumer1_thread_stack, /\*线程堆栈入口\*/ CONSUMER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/ 3, /\*优先级\*/ 3, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); tx\_thread\_create(/\*创建线程\*/ &consumer2_thread_handle, /\*线程句柄\*/ "consumer2\_task", /\*线程名称\*/ consumer2_task_entry, /\*线程执行函数\*/ NULL, /\*线程参数\*/ consumer2_thread_stack, /\*线程堆栈入口\*/ CONSUMER_THREAD_STACK_SIZE, /\*线程堆栈大小\*/ 3, /\*优先级\*/ 3, /\*抢占阈值\*/ TX_NO_TIME_SLICE, /\*线程时间切片类型\*/ TX_AUTO_START /\*线程启动类型:自动启动\*/ ); printf("created consumer task\r\n"); }
6.4 主程序 在main.c文件中添加如下代码:
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 \*/ }
6.5 程序运行结果
从运行结果可以看出,由于信号量没有对公共资源的排他性所有权,当线程取得可用的信号量实例后,即可对程序计数器进行操作,因此才出现程序计数器的值不断变化。
当更改成二值信号量时,
1 2 3 4 // 创建信号量 status = tx\_semaphore\_create(&app_producer_consumer_semaphore, "app\_producer\_consumer\_semphore", 1);
运行的结果如下:
在使用信号时,需要根据系统需要确定信号实例的数量以及线程的数量。
文章来源: https://iotsmart.blog.csdn.net/article/details/125131888