第 6 章 - Azure RTOS ThreadX 演示系统

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

概述

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

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

注意

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

demo_threadx.c 的完整列表,包括在本章其余部分引用的行号

应用程序定义

完成基本的 ThreadX 初始化后,将执行 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 对象。 但是,实际应用程序可能会在运行时在执行线程内部创建系统对象。

初始执行

所有线程均使用 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),但它们是线程 3 和 4

也是在大多数时候都可以执行的唯一线程。 它们也是使用时间切片创建的唯一线程(第 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 的完整列表,包括本章中引用的行号。

/* This is a small demo of the high-performance ThreadX kernel. It includes examples of eight
threads of different priorities, using a message queue, semaphore, mutex, event flags group,
byte pool, and block pool. */

#include "tx_api.h"

#define DEMO_STACK_SIZE 1024
#define DEMO_BYTE_POOL_SIZE 9120
#define DEMO_BLOCK_POOL_SIZE 100
#define DEMO_QUEUE_SIZE 100

/* Define the ThreadX object control blocks... */

TX_THREAD thread_0;
TX_THREAD thread_1;
TX_THREAD thread_2;
TX_THREAD thread_3;
TX_THREAD thread_4;
TX_THREAD thread_5;
TX_THREAD thread_6;
TX_THREAD thread_7;
TX_QUEUE queue_0;
TX_SEMAPHORE semaphore_0;
TX_MUTEX mutex_0;
TX_EVENT_FLAGS_GROUP event_flags_0;
TX_BYTE_POOL byte_pool_0;
TX_BLOCK_POOL block_pool_0;

/* Define the counters used in the demo application... */

ULONG thread_0_counter;
ULONG thread_1_counter;
ULONG thread_1_messages_sent;
ULONG thread_2_counter;
ULONG thread_2_messages_received;
ULONG thread_3_counter;
ULONG thread_4_counter;
ULONG thread_5_counter;
ULONG thread_6_counter;
ULONG thread_7_counter;

/* Define thread prototypes. */

void thread_0_entry(ULONG thread_input);
void thread_1_entry(ULONG thread_input);
void thread_2_entry(ULONG thread_input);
void thread_3_and_4_entry(ULONG thread_input);
void thread_5_entry(ULONG thread_input);
void thread_6_and_7_entry(ULONG thread_input);


/* Define main entry point. */

int main()
{
    /* Enter the ThreadX kernel. */
    tx_kernel_enter();
}

/* Define what the initial system looks like. */
void tx_application_define(void *first_unused_memory)
{

    CHAR *pointer;

    /* Create a byte memory pool from which to allocate the thread stacks. */
    tx_byte_pool_create(&byte_pool_0, "byte pool 0", first_unused_memory,
        DEMO_BYTE_POOL_SIZE);

    /* Put system definition stuff in here, e.g., thread creates and other assorted
        create information. */

    /* Allocate the stack for thread 0. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    /* Create the main thread. */
    tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
        pointer, DEMO_STACK_SIZE,
        1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the stack for thread 1. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    /* Create threads 1 and 2. These threads pass information through a ThreadX
        message queue. It is also interesting to note that these threads have a time
        slice. */
    tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1,
        pointer, DEMO_STACK_SIZE,
        16, 16, 4, TX_AUTO_START);

    /* Allocate the stack for thread 2. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);
        tx_thread_create(&thread_2, "thread 2", thread_2_entry, 2,
        pointer, DEMO_STACK_SIZE,
        16, 16, 4, TX_AUTO_START);

    /* Allocate the stack for thread 3. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    /* Create threads 3 and 4. These threads compete for a ThreadX counting semaphore.
        An interesting thing here is that both threads share the same instruction area. */
    tx_thread_create(&thread_3, "thread 3", thread_3_and_4_entry, 3,
        pointer, DEMO_STACK_SIZE,
        8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the stack for thread 4. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    tx_thread_create(&thread_4, "thread 4", thread_3_and_4_entry, 4,
        pointer, DEMO_STACK_SIZE,
        8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the stack for thread 5. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    /* Create thread 5. This thread simply pends on an event flag, which will be set
        by thread_0. */
    tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,
        pointer, DEMO_STACK_SIZE,
        4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the stack for thread 6. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    /* Create threads 6 and 7. These threads compete for a ThreadX mutex. */
    tx_thread_create(&thread_6, "thread 6", thread_6_and_7_entry, 6,
        pointer, DEMO_STACK_SIZE,
        8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the stack for thread 7. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_STACK_SIZE, TX_NO_WAIT);

    tx_thread_create(&thread_7, "thread 7", thread_6_and_7_entry, 7,
        pointer, DEMO_STACK_SIZE,
        8, 8, TX_NO_TIME_SLICE, TX_AUTO_START);

    /* Allocate the message queue. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_QUEUE_SIZE*sizeof(ULONG), TX_NO_WAIT);

    /* Create the message queue shared by threads 1 and 2. */
    tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, pointer, DEMO_QUEUE_SIZE*sizeof(ULONG));

    /* Create the semaphore used by threads 3 and 4. */
    tx_semaphore_create(&semaphore_0, "semaphore 0", 1);

    /* Create the event flags group used by threads 1 and 5. */
    tx_event_flags_create(&event_flags_0, "event flags 0");

    /* Create the mutex used by thread 6 and 7 without priority inheritance. */
    tx_mutex_create(&mutex_0, "mutex 0", TX_NO_INHERIT);

    /* Allocate the memory for a small block pool. */
    tx_byte_allocate(&byte_pool_0, &pointer, DEMO_BLOCK_POOL_SIZE, TX_NO_WAIT);

    /* Create a block memory pool to allocate a message buffer from. */
    tx_block_pool_create(&block_pool_0, "block pool 0", sizeof(ULONG), pointer,
        DEMO_BLOCK_POOL_SIZE);

    /* Allocate a block and release the block memory. */
    tx_block_allocate(&block_pool_0, &pointer, TX_NO_WAIT);

    /* Release the block back to the pool. */
    tx_block_release(pointer);
}

/* Define the test threads. */
void thread_0_entry(ULONG thread_input)
{
    UINT status;


    /* This thread simply sits in while-forever-sleep loop. */
    while(1)
    {

        /* Increment the thread counter. */
        thread_0_counter++;

        /* Sleep for 10 ticks. */
        tx_thread_sleep(10);

        /* Set event flag 0 to wakeup thread 5. */
        status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;
    }
}


void thread_1_entry(ULONG thread_input)
{
    UINT status;


    /* This thread simply sends messages to a queue shared by thread 2. */
    while(1)
    {
        /* Increment the thread counter. */
        thread_1_counter++;

        /* Send message to queue 0. */
        status = tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);

        /* Check completion status. */
        if (status != TX_SUCCESS)
            break;

        /* Increment the message sent. */
        thread_1_messages_sent++;
    }
}


void thread_2_entry(ULONG thread_input)
{
    ULONG received_message;
    UINT status;

    /* This thread retrieves messages placed on the queue by thread 1. */
    while(1)
    {
        /* Increment the thread counter. */
        thread_2_counter++;

        /* Retrieve a message from the queue. */
        status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);

        /* Check completion status and make sure the message is what we
        expected. */
        if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received))
            break;

        /* Otherwise, all is okay. Increment the received message count. */
        thread_2_messages_received++;
    }
}


void thread_3_and_4_entry(ULONG thread_input)
{
    UINT status;


    /* This function is executed from thread 3 and thread 4. As the loop
    below shows, these function compete for ownership of semaphore_0. */
    while(1)
    {
        /* Increment the thread counter. */
        if (thread_input == 3)
            thread_3_counter++;
        else
            thread_4_counter++;

        /* Get the semaphore with suspension. */
        status = tx_semaphore_get(&semaphore_0, TX_WAIT_FOREVER);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;

        /* Sleep for 2 ticks to hold the semaphore. */
        tx_thread_sleep(2);

        /* Release the semaphore. */
        status = tx_semaphore_put(&semaphore_0);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;
    }
}


void thread_5_entry(ULONG thread_input)
{
    UINT status;
    ULONG actual_flags;


    /* This thread simply waits for an event in a forever loop. */
    while(1)
    {
        /* Increment the thread counter. */
        thread_5_counter++;

        /* Wait for event flag 0. */
        status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
            &actual_flags, TX_WAIT_FOREVER);

        /* Check status. */
        if ((status != TX_SUCCESS) || (actual_flags != 0x1))
            break;
    }
}

void thread_6_and_7_entry(ULONG thread_input)
{
    UINT status;

    /* This function is executed from thread 6 and thread 7. As the loop
        below shows, these function compete for ownership of mutex_0. */
    while(1)
    {
        /* Increment the thread counter. */
        if (thread_input == 6)
            thread_6_counter++;
        else
            thread_7_counter++;

        /* Get the mutex with suspension. */
        status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;

        /* Get the mutex again with suspension. This shows
            that an owning thread may retrieve the mutex it
            owns multiple times. */
        status = tx_mutex_get(&mutex_0, TX_WAIT_FOREVER);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;

        /* Sleep for 2 ticks to hold the mutex. */
        tx_thread_sleep(2);

        /* Release the mutex. */
        status = tx_mutex_put(&mutex_0);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;

        /* Release the mutex again. This will actually
            release ownership since it was obtained twice. */
        status = tx_mutex_put(&mutex_0);

        /* Check status. */
        if (status != TX_SUCCESS)
            break;
    }
}