第 6 章 - Azure RTOS ThreadX SMP 的演示系统

本章介绍所有 Azure RTOS ThreadX SMP 处理器支持包随附的演示系统。

概述

每个 ThreadX SMP 产品分发均包含一个演示系统,该演示系统可在所有受支持的微处理器上运行。

此示例系统在分发文件 demo_threadx.c 中定义,旨在说明如何在嵌入式多线程环境中使用 ThreadX SMP。 演示包括初始化、八个线程、一个字节池、一个块池、一个队列、一个信号灯、一个互斥体和一个事件标志组。

重要

除线程的堆栈大小外,该演示应用程序在所有 ThreadX SMP 支持的处理器上均相同。

demo_threadx.c 的完整列表(包括在本章其余部分引用的行号)显示在第 324 页及其之后的页上。

应用程序定义

完成基本的 ThreadX SMP 初始化后,将执行 tx_application_define 函数。 该函数负责设置所有初始系统资源,包括线程、队列、信号灯、互斥体、事件标志和内存池。

演示系统的 tx_application_define(第 60-164 行)按以下顺序创建演示对象:

byte_pool_0
thread_0
thread_1
thread_2
thread_3
thread_4
thread_5
thread_6
thread_7
queue_0
semaphore_0
event_flags_0
mutex_0
block_pool_0

该演示系统不会创建任何其他的附加 ThreadX SMP 对象。 但是,实际应用程序可能会在运行时在执行线程内部创建系统对象。

初始执行

所有线程均使用 TX_AUTO_START 选项创建。 因此,这些线程初步准备就绪,可以执行。 tx_application_define 完成后,控制权将转移到线程计划程序,并从该处转移到各个线程。

线程执行顺序取决于它们的优先级和创建顺序。 在演示系统中,首先执行 thread_0,因为其具有最高优先级(创建时优先级为 1)。 thread_0 挂起后,将执行 thread_5,然后执行 thread_3、thread_4、thread_6、thread_7、thread_1,最后执行 thread_2

重要

尽管 thread_3 和 thread_4 具有相同的优先级(都是使用优先级 8 创建的),但 thread_3 先执行。 这是因为 thread_3 在 thread_4 之前创建并准备就绪。 相同优先级的线程以 FIFO 方式执行。

线程 0

函数 thread_0_entry 标记线程的入口点(第 167-190 行)。 Thread_0 是演示系统中要执行的第一个线程。 其处理方式很简单:递增其计数器,休眠 10 个计时器时钟周期,设置一个事件标志以唤醒 thread_5,然后重复该序列。

Thread_0 是系统中优先级最高的线程。 当其请求的休眠过期时,将抢占演示中任何其他正在执行的线程。

线程 1

函数 thread_1_entry 标记线程的入口点(第 193-216 行)。 Thread_1 是演示系统中要执行的倒数第二个线程。 其处理包括:递增其计数器,将消息发送到 thread_2(通过 queue_0),然后重复该序列。 请注意,只要 queue_0 已满,thread_1 就会挂起(第 207 行)。

线程 2

函数 thread_2_entry 标记线程的入口点(第 219-243 行)。 Thread_2 是演示系统中要执行的最后一个线程。 其处理包括:递增其计数器,从 thread_1(通过 queue_0)获取消息,然后重复该序列。 请注意,只要 queue_0 为空,thread_2 就会挂起(第 233 行)。

虽然 thread_1 和 thread_2 在演示系统中都具有最低优先级(优先级 16),但它们也是大多数时候准备好执行的唯一线程。 它们也是使用时间切片创建的唯一线程(第 87 行和第 93 行)。 在执行另一个线程之前,每个线程最多可执行 4 个计时器时钟周期。

线程 3 和 4

函数 thread_3_and_4_entry 标记 thread_3 和 thread_4 的入口点(第 246-280 行)。 这两个线程的优先级均为 8,这使得它们成为演示系统中要执行的第三个和第四个线程。 每个线程的处理方式均相同:递增其计数器,获取 semaphore_0,休眠 2 个计时器时钟周期,释放 semaphore_0,并重复该序列。 请注意,只要 semaphore_0 不可用,所有线程均会挂起(第 264 行)。

同时,两个线程在主处理中使用相同函数。 这不会带来任何问题,因为它们都有自己唯一的堆栈,并且 C 天生是可重入函数。 每个线程通过检查线程输入参数(第 258 行)来确定自己是哪一个线程,该参数可在创建线程时设置(第 102 行和第 109 行)。

重要

在线程执行过程中获取当前线程点,并将其与控制块的地址进行比较,以确定线程标识,这一操作也很合理。

线程 5

函数 thread_5_entry 标记线程的入口点(第 283-305 行)。 Thread_5 是演示系统中要执行的第二个线程。 其处理包括:递增其计数器,从 thread_0 获取事件标志(通过 event_flags_0),然后重复该序列。 请注意,只要 event_flags_0 中的事件标志不可用,thread_5 就会挂起(第 298 行)。

线程 6 和 7

函数 thread_6_and_7_entry 标记 thread_6 和 thread_7的入口点(第 307-358 行)。 这两个线程的优先级均为 8,这使得它们成为演示系统中要执行的第五个和第六个线程。 每个线程的处理方式均相同:递增其计数器,获取 mutex_0 两次,休眠 2 个计时器时钟周期,释放 mutex_0 两次,并重复该序列。 请注意,只要 mutex_0 不可用,所有线程均会挂起(第 325 行)。

同时,两个线程在主处理中使用相同函数。 这不会带来任何问题,因为它们都有自己唯一的堆栈,并且 C 天生是可重入函数。 每个线程通过检查线程输入参数(第 319 行)来确定自己是哪一个线程,该参数可在创建线程时设置(第 126 行和第 133 行)。

观看演示

每个演示线程均会递增自己的唯一计数器。 可以检查以下计数器以核实演示的操作:

thread_0_counter
thread_1_counter
thread_2_counter
thread_3_counter
thread_4_counter
thread_5_counter
thread_6_counter
thread_7_counter

在演示执行过程中,每个计数器都应继续增加,其中,thread_1_counter 和 thread_2_counter 的增加速度最快。

分发文件:demo_threadx.c

本部分显示了 demo_threadx.c 的完整列表,包括本章中引用的行号。

000 /* This is a small demo of the high-performance ThreadX SMP kernel. It includes examples of eight
001 threads of different priorities, using a message queue, semaphore, mutex, event flags group,
002 byte pool, and block pool. */
003
004 #include "tx_api.h"
005
006 #define DEMO_STACK_SIZE         1024
007 #define DEMO_BYTE_POOL_SIZE     9120
008 #define DEMO_BLOCK_POOL_SIZE    100
009 #define DEMO_QUEUE_SIZE         100
010
011 /* Define the ThreadX SMP object control blocks...  */
012
013 TX_THREAD               thread_0;
014 TX_THREAD               thread_1;
015 TX_THREAD               thread_2;
016 TX_THREAD               thread_3;
017 TX_THREAD               thread_4;
018 TX_THREAD               thread_5;
019 TX_THREAD               thread_6;
020 TX_THREAD               thread_7;
021 TX_QUEUE                queue_0;
022 TX_SEMAPHORE            semaphore_0;
023 TX_MUTEX                mutex_0;
024 TX_EVENT_FLAGS_GROUP    event_flags_0;
025 TX_BYTE_POOL            byte_pool_0;
026 TX_BLOCK_POOL           block_pool_0;
027
028 /* Define the counters used in the demo application...  */
029
030 ULONG                    thread_0_counter;
031 ULONG                    thread_1_counter;
032 ULONG                    thread_1_messages_sent;
033 ULONG                    thread_2_counter;
034 ULONG                    thread_2_messages_received;
035 ULONG                    thread_3_counter;
036 ULONG                    thread_4_counter;
037 ULONG                    thread_5_counter;
038 ULONG                    thread_6_counter;
039 ULONG                    thread_7_counter;
040
041 /* Define thread prototypes.  */
042
043 void    thread_0_entry(ULONG thread_input);
044 void    thread_1_entry(ULONG thread_input);
045 void    thread_2_entry(ULONG thread_input);
046 void    thread_3_and_4_entry(ULONG thread_input);
047 void    thread_5_entry(ULONG thread_input);
048 void    thread_6_and_7_entry(ULONG thread_input);
049
050
051 /* Define main entry point.  */
052
053 int main()
054 {
055
056     /* Enter the ThreadX SMP kernel. */
057     tx_kernel_enter();
058 }
059
060 /* Define what the initial system looks like. */
061 void    tx_application_define(void *first_unused_memory)
062 {
063
064 CHAR    *pointer;
065
066    /* Create a byte memory pool from which to allocate the thread stacks. */
067    tx_byte_pool_create(&byte_pool_0, "byte pool 0", first_unused_memory,
068                               DEMO_BYTE_POOL_SIZE);
069
070    /* Put system definition stuff in here, e.g., thread creates and other assorted
071       create information. */
072
073    /* Allocate the stack for thread 0. */
074    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
075
076    /* Create the main thread. */
077    tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
078                               pointer, DEMO_STACK_SIZE,
079                               1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
080
081    /* Allocate the stack for thread 1. */
082    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
083
084    /* Create threads 1 and 2. These threads pass information through a ThreadX SMP
085       message queue. It is also interesting to note that these threads have a time
086       slice. */
087    tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
088                              pointer, DEMO_STACK_SIZE,
089                              16, 16, 4, TX_AUTO_START);
090
091    /* Allocate the stack for thread 2. */
092    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
093    tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
094                              pointer, DEMO_STACK_SIZE,
095                              16, 16, 4, TX_AUTO_START);
096
097    /* Allocate the stack for thread 3. */
098    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
099
100    /* Create threads 3 and 4. These threads compete for a ThreadX SMP counting semaphore.
101       An interesting thing here is that both threads share the same instruction area. */
102    tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3,
103                              pointer, DEMO_STACK_SIZE,
104                              8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
105
106    /* Allocate the stack for thread 4. */
107    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
108
109    tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4,
110                              pointer, DEMO_STACK_SIZE,
111                              8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
112
113    /* Allocate the stack for thread 5. */
114    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
115
116    /* Create thread 5. This thread simply pends on an event flag, which will be set
117       by thread_0. */
118    tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,
119                              pointer, DEMO_STACK_SIZE,
120                              4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
121
122    /* Allocate the stack for thread 6. */
123    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
124
125    /* Create threads 6 and 7. These threads compete for a ThreadX SMP mutex. */
126    tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6,
127                              pointer, DEMO_STACK_SIZE,
128                              8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
129
130    /* Allocate the stack for thread 7. */
131    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
132
133    tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7,
134                              pointer, DEMO_STACK_SIZE,
135                              8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);
136
137    /* Allocate the message queue. */
138    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT);
139
140    /* Create the message queue shared by threads 1 and 2. */
141    tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG));
142
143    /* Create the semaphore used by threads 3 and 4. */
144    tx_semaphore_create(&semaphore_0, "semaphore 0", 1);
145
146    /* Create the event flags group used by threads 1 and 5. */
147    tx_event_flags_create(&event_flags_0, "event flags 0");
148
149    /* Create the mutex used by thread 6 and 7 without priority inheritance. */
150    tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT);
151
152    /* Allocate the memory for a small block pool. */
153    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT);
154
155    /* Create a block memory pool to allocate a message buffer from. */
156    tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer,
157                 DEMO_BLOCK_POOL_SIZE);
158
159    /* Allocate a block and release the block memory. */
160    tx_block_allocate(&block_pool_0, &pointer, TX_NO_WAIT);
161
162    /* Release the block back to the pool. */
163    tx_block_release(pointer);
164 }
165
166    /* Define the test threads. */
167    void thread_0_entry(ULONG thread_input)
168 {
169
170 UINT status;
171
172
173    /* This thread simply sits in while-forever-sleep loop. */
174     while(1)
175    {
176
177    /* Increment the thread counter. */
178    thread_0_counter++;
179
180    /* Sleep for 10 ticks. */
181    tx_thread_sleep(10);
182
183    /* Set event flag 0 to wakeup thread 5. */
184    status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
185
186    /* Check status. */
187    if (status != TX_SUCCESS)
188       break;
189    }
190 }
191
192
193 void    thread_1_entry(ULONG thread_input)
194 {
195
196 UINT    status;
197
198
199    /* This thread simply sends messages to a queue shared by thread 2. */
200    while(1)
201    {
202
203         /* Increment the thread counter. */
204         thread_1_counter++;
205
206         /* Send message to queue 0. */
207         status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);
208
209         /* Check completion status. */
210         if (status != TX_SUCCESS)
211            break;
212
213         /* Increment the message sent. */
214         thread_1_messages_sent++;
215    }
216 }
217
218
219 void     thread_2_entry(ULONG thread_input)
220 {
221
222 ULONG     received_message;
223 UINT      status;
224
225     /* This thread retrieves messages placed on the queue by thread 1. */
226     while(1)
227     {
228
229         /* Increment the thread counter. */
230         thread_2_counter++;
231
232         /* Retrieve a message from the queue. */
233         status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);
234
235         /* Check completion status and make sure the message is what we
236            expected. */
237         if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received))
238            break;
239
240         /* Otherwise, all is okay. Increment the received message count. */
241         thread_2_messages_received++;
242      }
243 }
244
245
246 void     thread_3_and_4_entry(ULONG thread_input)
247 {
248
249 UINT     status;
250
251
252     /* This function is executed from thread 3 and thread 4. As the loop
253        below shows, these function compete for ownership of semaphore_0. */
254     while(1)
255     {
256
257         /* Increment the thread counter. */
258         if (thread_input == 3)
259            thread_3_counter++;
260         else
261            thread_4_counter++;
262
263         /* Get the semaphore with suspension. */
264         status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);
265
266         /* Check status. */
267         if (status != TX_SUCCESS)
268            break;
269
270         /* Sleep for 2 ticks to hold the semaphore. */
271         tx_thread_sleep(2);
272
273         /* Release the semaphore. */
274         status = tx_semaphore_put(&semaphore_0);
275
276         /* Check status. */
277         if (status != TX_SUCCESS)
278             break;
279     }
280 }
281
282
283 void     thread_5_entry(ULONG thread_input)
284 {
285
286 UINT     status;
287 ULONG    actual_flags;
288
289
290     /* This thread simply waits for an event in a forever loop. */
291     while(1)
292     {
293
294         /* Increment the thread counter. */
295         thread_5_counter++;
296
297         /* Wait for event flag 0. */
298         status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
299                                &actual_flags, TX_WAIT_FOREVER);
300
301         /* Check status. */
302         if ((status != TX_SUCCESS) || (actual_flags != 0x1))
303            break;
304     }
305 }
306
307 void     thread_6_and_7_entry(ULONG thread_input)
308 {
309
310 UINT     status;
311
312
313     /* This function is executed from thread 6 and thread 7. As the loop
314         below shows, these function compete for ownership of mutex_0. */
315     while(1)
316     {
317
318         /* Increment the thread counter. */
319         if (thread_input == 6)
320            thread_6_counter++;
321         else
322            thread_7_counter++;
323
324         /* Get the mutex with suspension. */
325         status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
326
327         /* Check status. */
328         if (status != TX_SUCCESS)
329            break;
330
331         /* Get the mutex again with suspension. This shows
332            that an owning thread may retrieve the mutex it
333            owns multiple times. */
334         status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);
335
336         /* Check status. */
337         if (status != TX_SUCCESS)
338             break;
339
340         /* Sleep for 2 ticks to hold the mutex. */
341         tx_thread_sleep(2);
342
343         /* Release the mutex. */
344         status = tx_mutex_put(&mutex_0);
345
346         /* Check status. */
347         if (status != TX_SUCCESS)
348            break;
349
350         /* Release the mutex again. This will actually
351            release ownership since it was obtained twice. */
352         status = tx_mutex_put(&mutex_0);
353
354         /* Check status. */
355         if (status != TX_SUCCESS)
356            break;
357     }
358 }