Kernel Basics

This chapter introduces the basics of the RT-Thread kernel, including: an introduction to the kernel, the system startup process, and some contents of the kernel configuration, laying the foundation for the following chapters.

A brief introduction to the RT-Thread kernel, starting from the software architecture to explain the composition and implementation of the real-time kernel. This part introduces some concepts and basic knowledge related to the RT-Thread kernel to beginners, so that beginners can have a preliminary understanding of the kernel, know the components of the kernel, how to start the system, memory distribution, and kernel configuration methods.

The kernel is the core of an operating system and is the most basic and important part of an operating system. It is responsible for managing system threads, inter-thread communication, system clocks, interrupts, and memory. The following figure is the RT-Thread kernel architecture diagram. You can see that the kernel is above the hardware layer. The kernel part includes the kernel library and real-time kernel implementation.

The kernel library is a small set of C library-like function implementations that ensures the kernel can run independently. The C library included in this part varies depending on the compiler. When using the GNU GCC compiler, it will carry more standard C library implementations.

Tip: C library: also called C Runtime Library, it provides functions like "strcpy", "memcpy", and some also include the implementation of "printf" and "scanf". RT-Thread Kernel Service Library only provides a small part of the C library function implementation used by the kernel. In order to avoid duplication with the standard C library, the rt_ prefix is ​​added before these functions.

The implementation of the real-time kernel includes: object management, thread management and scheduler, inter-thread communication management, clock management and memory management, etc. The minimum resource usage of the kernel is 3KB ROM and 1.2KB RAM.

Thread is the smallest scheduling unit in RT-Thread operating system. The thread scheduling algorithm is a priority-based fully preemptive multithread scheduling algorithm. That is, except for the interrupt handling function, the code of the locked part of the scheduler and the code that prohibits interrupts, the rest of the system can be preempted, including the thread scheduler itself. It supports 256 thread priorities (it can also be changed to support a maximum of 32 or 8 thread priorities through the configuration file. The default configuration for STM32 is 32 thread priorities). 0 priority represents the highest priority, and the lowest priority is reserved for idle threads. At the same time, it also supports the creation of multiple threads with the same priority. The threads of the same priority are scheduled using the round-robin scheduling algorithm of the time slice, so that each thread runs for a corresponding time. In addition, when the scheduler is looking for the highest priority threads in the ready state, the time it takes is constant. The system does not limit the number of threads. The number of threads is only related to the specific memory of the hardware platform.

Thread management will be introduced in detail in the "Thread Management" chapter.

RT-Thread's clock management is based on the clock beat, which is the smallest clock unit in the RT-Thread operating system. RT-Thread's timer provides two types of timer mechanisms: the first type is a single-trigger timer, which will only trigger a timer event once after it is started, and then the timer will stop automatically. The second type is a periodic trigger timer, which will periodically trigger a timer event until the user manually stops the timer, otherwise it will continue to execute forever.

In addition, depending on the context in which the timeout function is executed, the RT-Thread timer can be set to HARD_TIMER mode or SOFT_TIMER mode.

Usually, a timer callback function (i.e., timeout function) is used to complete the timing service. Users can select the appropriate type of timer based on their real-time requirements for timing processing.

Timers will be explained in detail in the "Clock Management" chapter.

RT-Thread uses semaphores, mutexes and event sets to achieve synchronization between threads. Threads synchronize by acquiring and releasing semaphores and mutexes; mutexes use priority inheritance to solve the priority flipping problem common in real-time systems. The thread synchronization mechanism supports threads to acquire semaphores or mutexes by priority waiting. Threads synchronize by sending and receiving events; event sets support "or triggering" and "and triggering" of multiple events, which is suitable for situations where threads are waiting for multiple events.

The concepts of semaphores, mutexes, and event sets will be introduced in detail in the "Inter-thread Synchronization" chapter.

RT-Thread supports communication mechanisms such as mailboxes and message queues. The length of an email in a mailbox is fixed at 4 bytes; a message queue can receive messages of variable length and cache them in its own memory space. Mailboxes are more efficient than message queues. The sending actions of mailboxes and message queues can be safely used in interrupt service routines. The communication mechanism supports threads to obtain by priority waiting.

The concepts of mailboxes and message queues will be introduced in detail in the "Inter-thread Communication" chapter.

RT-Thread supports static memory pool management and dynamic memory heap management. When the static memory pool has available memory, the system will allocate memory blocks at a constant time; when the static memory pool is empty, the system will suspend or block the thread that applied for the memory block (that is, the thread will give up the application and return if it has not obtained the memory block after waiting for a period of time, or return immediately. The waiting time depends on the waiting time parameter set when applying for the memory block). When other threads release memory blocks to the memory pool, if there is a thread that is suspended to allocate memory blocks, the system will wake up this thread.

The dynamic memory heap management module provides a memory management algorithm for a small memory system and a SLAB memory management algorithm for a large memory system under different system resources.

There is also a dynamic memory heap management called memheap, which is suitable for systems with multiple addresses and non-contiguous memory heaps. Using memheap, multiple memory heaps can be "pasted" together, allowing users to operate as if they were operating one memory heap.

The concept of memory management will be explained in the "Memory Management" chapter.

RT-Thread uses PIN, I2C, SPI, USB, UART, etc. as peripheral devices, which are uniformly registered through devices. It implements a device management subsystem that can be accessed by name, and can access hardware devices according to a unified API interface. On the device driver interface, according to the characteristics of the embedded system, corresponding events can be attached to different devices. When a device event is triggered, the driver notifies the upper-level application.

The concept of I/O device management will be explained in the "Device Model" and "General Device" chapters.

Generally, understanding a code mostly starts from the startup part. This method is also used here to find the source of startup first. RT-Thread supports multiple platforms and multiple compilers, and the rtthread_startup() function is the unified startup entry specified by RT-Thread. The general execution order is: the system starts running from the startup file, then enters the startup function rtthread_startup() of RT-Thread, and finally enters the user entry function main(), as shown in the following figure:

Taking MDK-ARM as an example, the user program entry is the main() function, which is located in the main.c file. After the system starts, it starts running from the assembly code startup_stm32f103xe.s, then jumps to the C code to start the RT-Thread system, and finally enters the user program entry function main().

In order to complete the RT-Thread system function initialization before entering main(), we use the MDK extension functions $Sub$$and $Super$$. You can add $Sub$$the prefix symbol to main as a new function $Sub$$main. This $Sub$$maincan first call some function functions to be added before main (here add RT-Thread system startup, perform a series of system initialization), and then call $Super$$mainto go to main() function execution, so that users do not need to care about the system initialization operations before main().

For details on how to use the $Sub$$and $Super$$extended functions, see the ARM® Compiler v5.06 for µVision®armlink User Guide .

Let's take a look at this code defined in components.c:

/* $Sub$$main 函数 */
int $Sub$$main(void)
{
  rtthread_startup();
  return 0;
}copymistakeCopy Success

Here $Sub$$mainthe function calls the rtthread_startup() function, where the code of the rtthread_startup() function is as follows:

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* 板级初始化:需在该函数内部进行系统堆的初始化 */
    rt_hw_board_init();

    /* 打印 RT-Thread 版本信息 */
    rt_show_version();

    /* 定时器初始化 */
    rt_system_timer_init();

    /* 调度器初始化 */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* 信号初始化 */
    rt_system_signal_init();
#endif

    /* 由此创建一个用户 main 线程 */
    rt_application_init();

    /* 定时器线程初始化 */
    rt_system_timer_thread_init();

    /* 空闲线程初始化 */
    rt_thread_idle_init();

    /* 启动调度器 */
    rt_system_scheduler_start();

    /* 不会执行至此 */
    return 0;
}copymistakeCopy Success

This part of the startup code can be roughly divided into four parts:

(1) Initialize system-related hardware;

(2) Initialize system kernel objects, such as timers, schedulers, and signals;

(3) Create the main thread and initialize various modules in the main thread in turn;

(4) Initialize the timer thread, idle thread, and start the scheduler.

Before starting the scheduler, the threads created by the system will not run immediately after executing rt_thread_startup(). They will be in the ready state waiting for system scheduling. After starting the scheduler, the system will switch to the first thread to start running. According to the scheduling rules, the thread with the highest priority in the ready queue is selected.

In rt_hw_board_init(), the system clock is set, heartbeat and serial port initialization are provided for the system, and the system input and output terminals are bound to this serial port. Subsequent system operation information will be printed out from the serial port.

The main() function is the user code entry of RT-Thread. Users can add their own applications in the main() function.

int main(void)
{
  /* user app entry */
  return 0;
}copymistakeCopy Success

Generally, the storage space contained in MCU includes: on-chip Flash and on-chip RAM. RAM is equivalent to internal memory, and Flash is equivalent to hard disk. The compiler will classify a program into several parts and store them in different storage areas of MCU.

After the Keil project is compiled, there will be a prompt message about the space occupied by the corresponding program, as shown below:

linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07copymistakeCopy Success

The Program Size mentioned above contains the following parts:

1) Code: Code segment, which stores the code part of the program;

2) RO-data: read-only data segment, storing constants defined in the program;

3) RW-data: read-write data segment, storing global variables initialized to non-zero values;

4) ZI-data: 0 data segment, storing uninitialized global variables and variables initialized to 0;

After compiling the project, a .mapfile will be generated, which describes the size and address occupied by each function. The last few lines of the file also explain the relationship between the above fields:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)copymistakeCopy Success

1) RO Size includes Code and RO-data, indicating the size of the Flash space occupied by the program;

2) RW Size includes RW-data and ZI-data, indicating the size of RAM occupied during runtime;

3) ROM Size includes Code, RO-data and RW-data, indicating the size of the Flash space occupied by the burning program;

Before the program runs, a file entity needs to be burned into the STM32 Flash, usually a bin or hex file. The burned file is called an executable image file. As shown in the left part of the figure below, it is the memory distribution after the executable image file is burned into the STM32. It contains two parts: RO segment and RW segment: the RO segment stores the Code and RO-data data, and the RW segment stores the RW-data data. Since ZI-data is 0, it is not included in the image file.

After power-on, STM32 starts from Flash by default. After startup, the RW-data (initialized global variables) in the RW segment will be moved to RAM, but the RO segment will not be moved. That is, the CPU execution code is read from Flash. In addition, the ZI segment is allocated according to the ZI address and size given by the compiler, and this RAM area is cleared.

The dynamic memory heap is the unused RAM space, and the memory blocks requested and released by the application all come from this space.

As in the following example:

rt_uint8_t* msg_ptr;
msg_ptr = (rt_uint8_t*) rt_malloc (128);
rt_memset(msg_ptr, 0, 128);copymistakeCopy Success

The 128-byte memory space pointed to by the msg_ptr pointer in the code is located in the dynamic memory heap space.

Some global variables are stored in the RW segment and the ZI segment. The RW segment stores global variables with initial values ​​(global variables in the form of constants are placed in the RO segment and are read-only). The ZI segment stores uninitialized global variables of the system, as shown in the following example:

#include <rtthread.h>

const static rt_uint32_t sensor_enable = 0x000000FE;
rt_uint32_t sensor_value;
rt_bool_t sensor_inited = RT_FALSE;

void sensor_init()
{
     /* ... */
}copymistakeCopy Success

The sensor_value is stored in the ZI segment and is automatically initialized to zero after the system starts (initialized to zero by the user program or some library functions provided by the compiler). The sensor_inited variable is stored in the RW segment, and sensor_enable is stored in the RO segment.

The automatic initialization mechanism means that the initialization function does not need to be called explicitly. It only needs to be declared through macro definition at the function definition, and it will be executed during the system startup process.

For example, in the serial port driver, a macro definition is called to inform the system to initialize the function that needs to be called. The code is as follows:

int rt_hw_usart_init(void)  /* 串口初始化函数 */
{
     ... ...
     /* 注册串口 1 设备 */
     rt_hw_serial_register(&serial1, "uart1",
                        RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                        uart);
     return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init);    /* 使用组件自动初始化机制 */copymistakeCopy Success

The INIT_BOARD_EXPORT(rt_hw_usart_init) at the end of the sample code indicates the use of the automatic initialization function. In this way, the rt_hw_usart_init() function will be automatically called by the system, so where is it called?

In the system startup flowchart, there are two functions: rt_components_board_init() and rt_components_init(). The functions in the background box after them are automatically initialized functions, among which:

  1. “Board init functions” are all initialization functions declared via INIT_BOARD_EXPORT(fn).

  2. “Pre-initialization functions” are all initialization functions declared via INIT_PREV_EXPORT(fn).

  3. “Device init functions” are all initialization functions declared via INIT_DEVICE_EXPORT(fn).

  4. “components init functions” are all initialization functions declared via INIT_COMPONENT_EXPORT(fn).

  5. “Environment init functions” are all initialization functions declared via INIT_ENV_EXPORT(fn).

  6. “Application init functions” are all initialization functions declared via INIT_APP_EXPORT(fn).

The rt_components_board_init() function is executed relatively early, mainly to initialize the relevant hardware environment. When executing this function, it will traverse the initialization function table declared by INIT_BOARD_EXPORT(fn) and call each function.

The rt_components_init() function will be called and executed in the main thread created after the operating system is running. At this time, the hardware environment and the operating system have been initialized and the application-related code can be executed. The rt_components_init() function will traverse the initialization function table declared by the remaining macros.

RT-Thread's automatic initialization mechanism uses a custom RTI symbol segment, and places the function pointers that need to be initialized at startup in this segment, forming an initialization function table. During the system startup process, the table will be traversed and the functions in the table will be called to achieve the purpose of automatic initialization.

The macro interface definition used to implement the automatic initialization function is described in detail in the following table:

The initialization function is actively declared through these macro interfaces, such as INIT_BOARD_EXPORT(rt_hw_usart_init). The linker will automatically collect all declared initialization functions and put them into the RTI symbol segment, which is located in the RO segment of the memory distribution. All functions in the RTI symbol segment will be automatically called when the system is initialized.

The RT-Thread kernel is designed with object-oriented design ideas. System-level infrastructure is a kernel object, such as threads, semaphores, mutexes, timers, etc. Kernel objects are divided into two categories: static kernel objects and dynamic kernel objects. Static kernel objects are usually placed in the RW segment and ZI segment and initialized in the program after the system starts; dynamic kernel objects are created from the memory heap and then initialized manually.

The following code is an example of static thread and dynamic thread:

/* 线程 1 的对象和运行时用到的栈 */
static struct rt_thread thread1;
static rt_uint8_t thread1_stack[512];

/* 线程 1 入口 */
void thread1_entry(void* parameter)
{
     int i;

    while (1)
    {
        for (i = 0; i < 10; i ++)
        {
            rt_kprintf("%d\n", i);

            /* 延时 100ms */
            rt_thread_mdelay(100);
        }
    }
}

/* 线程 2 入口 */
void thread2_entry(void* parameter)
{
     int count = 0;
     while (1)
     {
         rt_kprintf("Thread2 count:%d\n", ++count);

        /* 延时 50ms */
        rt_thread_mdelay(50);
    }
}

/* 线程例程初始化 */
int thread_sample_init()
{
     rt_thread_t thread2_ptr;
     rt_err_t result;

    /* 初始化线程 1 */
    /* 线程的入口是 thread1_entry,参数是 RT_NULL
     * 线程栈是 thread1_stack
     * 优先级是 200,时间片是 10 个 OS Tick
     */
    result = rt_thread_init(&thread1,
                            "thread1",
                            thread1_entry, RT_NULL,
                            &thread1_stack[0], sizeof(thread1_stack),
                            200, 10);

    /* 启动线程 */
    if (result == RT_EOK) rt_thread_startup(&thread1);

    /* 创建线程 2 */
    /* 线程的入口是 thread2_entry, 参数是 RT_NULL
     * 栈空间是 512,优先级是 250,时间片是 25 个 OS Tick
     */
    thread2_ptr = rt_thread_create("thread2",
                                thread2_entry, RT_NULL,
                                512, 250, 25);

    /* 启动线程 */
    if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr);

    return 0;
}copymistakeCopy Success

In this example, thread1 is a static thread object, and thread2 is a dynamic thread object. The memory space of thread1 object, including thread control block thread1 and stack space thread1_stack, is determined at compile time. Since there is no initial value in the code, they are all placed in the uninitialized data segment. The space used by thread2 during operation is dynamically allocated, including thread control block (the content pointed to by thread2_ptr) and stack space.

Static objects occupy RAM space, do not rely on the memory heap manager, and the memory allocation time is determined. Dynamic objects rely on the memory heap manager, apply for RAM space at runtime, and when the object is deleted, the occupied RAM space is released. Both methods have their own advantages and disadvantages, and you can choose the specific usage method according to the actual environment requirements.

RT-Thread uses a kernel object management system to access/manage all kernel objects. Kernel objects contain most of the facilities in the kernel. These kernel objects can be static objects allocated statically or dynamic objects allocated from the system memory heap.

Through this kernel object design, RT-Thread is independent of the specific memory allocation method, and the flexibility of the system is greatly improved.

RT-Thread kernel objects include: threads, semaphores, mutexes, events, mailboxes, message queues and timers, memory pools, device drivers, etc. The object container contains information about each type of kernel object, including object type, size, etc. The object container assigns a linked list to each type of kernel object, and all kernel objects are linked to the linked list. The kernel object container and linked list of RT-Thread are shown in the figure below:

The following figure shows the derivation and inheritance relationship of various kernel objects in RT-Thread. For each specific kernel object and object control block, in addition to the basic structure, there are also its own extended attributes (private attributes). For example, for the thread control block, it is extended on the basis of the base class object, adding attributes such as thread state and priority. These attributes will not be used in the operation of the base class object, but only in the operation related to the specific thread. Therefore, from the object-oriented point of view, it can be considered that each specific object is derived from the abstract object, inheriting the attributes of the basic object and extending the attributes related to itself on this basis.

In the object management module, a general data structure is defined to store the common attributes of various objects. Various specific objects only need to add some special attributes of their own on this basis to clearly express their own characteristics.

The advantages of this design approach are:

(1) The reusability and extensibility of the system are improved. It is easy to add new object categories by simply inheriting the properties of common objects and adding a small amount of extensions.

(2) Providing a unified object operation method simplifies the operation of various specific objects and improves the reliability of the system.

In the figure above, objects derived from the object control block rt_object include: thread objects, memory pool objects, timer objects, device objects, and IPC objects (IPC: Inter-Process Communication. In the RT-Thread real-time operating system, the function of the IPC object is to synchronize and communicate between threads); objects such as semaphores, mutexes, events, mailboxes and message queues, and signals are derived from the IPC object.

The data structure of the kernel object control block:

struct rt_object
{
     /* 内核对象名称     */
     char      name[RT_NAME_MAX];
     /* 内核对象类型     */
     rt_uint8_t  type;
     /* 内核对象的参数   */
     rt_uint8_t  flag;
     /* 内核对象管理链表 */
     rt_list_t   list;
};copymistakeCopy Success

The types currently supported by kernel objects are as follows:

enum rt_object_class_type
{
     RT_Object_Class_Thread = 0,             /* 对象为线程类型      */
#ifdef RT_USING_SEMAPHORE
    RT_Object_Class_Semaphore,              /* 对象为信号量类型    */
#endif
#ifdef RT_USING_MUTEX
    RT_Object_Class_Mutex,                  /* 对象为互斥量类型    */
#endif
#ifdef RT_USING_EVENT
    RT_Object_Class_Event,                  /* 对象为事件类型      */
#endif
#ifdef RT_USING_MAILBOX
    RT_Object_Class_MailBox,                /* 对象为邮箱类型      */
#endif
#ifdef RT_USING_MESSAGEQUEUE
    RT_Object_Class_MessageQueue,           /* 对象为消息队列类型   */
#endif
#ifdef RT_USING_MEMPOOL
    RT_Object_Class_MemPool,                /* 对象为内存池类型     */
#endif
#ifdef RT_USING_DEVICE
    RT_Object_Class_Device,                 /* 对象为设备类型       */
#endif
    RT_Object_Class_Timer,                  /* 对象为定时器类型     */
#ifdef RT_USING_MODULE
    RT_Object_Class_Module,                 /* 对象为模块          */
#endif
    RT_Object_Class_Unknown,                /* 对象类型未知        */
    RT_Object_Class_Static = 0x80           /* 对象为静态对象      */
};copymistakeCopy Success

From the above type description, we can see that if it is a static object, the highest bit of the object type will be 1 (RT_Object_Class_Static and other object types are ORed together), otherwise it is a dynamic object. The maximum number of object classes that the system can accommodate is 127.

The data structure of the kernel object container:

struct rt_object_information
{
     /* 对象类型 */
     enum rt_object_class_type type;
     /* 对象链表 */
     rt_list_t object_list;
     /* 对象大小 */
     rt_size_t object_size;
};copymistakeCopy Success

A class of objects is managed by an rt_object_information structure, and each specific instance of this class of objects is attached to object_list in the form of a linked list. The memory block size of this class of objects is identified by object_size (the specific instances of each class of objects occupy the same memory block size).

Initializing an object

Before using an uninitialized static object, you must initialize it. The initialization object uses the following interface:

void rt_object_init(struct  rt_object*  object ,
                    enum rt_object_class_type  type ,
                    const char* name)copymistakeCopy Success

When this function is called to initialize an object, the system will place the object in an object container for management, that is, initialize some parameters of the object, and then insert the object node into the object linked list of the object container. The description of the input parameters of this function is as follows:

Departure from the object

Detaching an object from the kernel object manager. Detaching an object uses the following interface:

void rt_object_detach(rt_object_t object);copymistakeCopy Success

Calling this interface can make a static kernel object detach from the kernel object container, that is, delete the corresponding object node from the kernel object container linked list. After the object is detached, the memory occupied by the object will not be released.

Assignment Object

The above descriptions are all interfaces for object initialization and detachment, which are all based on the situation where the object-oriented memory block already exists. Dynamic objects can be applied for when needed, and the memory space can be released for other applications when not needed. The following interfaces can be used to apply for the allocation of new objects:

rt_object_t rt_object_allocate(enum  rt_object_class_type type ,
                               const  char*  name)copymistakeCopy Success

When calling the above interface, the system first needs to obtain object information according to the object type (especially the size information of the object type so that the system can allocate a memory data block of the correct size), and then allocate the memory space of the corresponding size of the object from the memory heap, and then perform necessary initialization on the object, and finally insert it into the object container linked list where it is located. The description of the input parameters of this function is as follows:

Deleting an object

For a dynamic object, when it is no longer used, you can call the following interface to delete the object and release the corresponding system resources:

void rt_object_delete(rt_object_t object);copymistakeCopy Success

When the above interface is called, the object is first detached from the object container list, and then the memory occupied by the object is released. The following table describes the input parameters of this function:

Identify the object

Determine whether the specified object is a system object (static kernel object). The following interface is used to identify the object:

rt_err_t rt_object_is_systemobject(rt_object_t object);copymistakeCopy Success

Calling the rt_object_is_systemobject interface can determine whether an object is a system object. In the RT-Thread operating system, a system object is also a static object, and the RT_Object_Class_Static bit on the object type identifier is set. Objects that are usually initialized using the rt_object_init() method are system objects. The input parameters of this function are described in the following table:

Input parameters for rt_object_is_systemobject()

Take traversing all threads as an example:

Note: RT-Thread5.0 and higher versions struct rt_threadmove the name member of the structure to parent. When using it, the code needs to be thread->namechanged from thread->parent.name, otherwise the compilation will report an error!

rt_thread_t thread = RT_NULL;
struct rt_list_node *node = RT_NULL;
struct rt_object_information *information = RT_NULL;

information = rt_object_get_information(RT_Object_Class_Thread);

rt_list_for_each(node, &(information->object_list))
{
    thread = (rt_thread_t)rt_list_entry(node, struct rt_object, list);
    /* 比如打印所有thread的名字 */
    rt_kprintf("name:%s\n", thread->name);
}copymistakeCopy Success

Let's take traversing all mutexes as an example:

rt_mutex_t mutex = RT_NULL;
struct rt_list_node *node = RT_NULL;
struct rt_object_information *information = RT_NULL;

information = rt_object_get_information(RT_Object_Class_Mutex);

rt_list_for_each(node, &(information->object_list))
{
    mutex = (rt_mutex_t)rt_list_entry(node, struct rt_object, list);
    /* 比如打印所有mutex的名字 */
    rt_kprintf("name:%s\n", mutex->parent.parent.name);
}copymistakeCopy Success

An important feature of RT-Thread is its high scalability, which supports fine-tuning of the kernel and flexible disassembly of components.

Configuration is mainly performed by modifying the rtconfig.h file in the project directory. Users can conditionally compile the code by turning on/off the macro definitions in the file, ultimately achieving the purpose of system configuration and tailoring, as follows:

(1) RT-Thread kernel

/* 表示内核对象的名称的最大长度,若代码中对象名称的最大长度大于宏定义的长度,
 * 多余的部分将被截掉。*/
#define RT_NAME_MAX 8

/* 字节对齐时设定对齐的字节个数。常使用 ALIGN(RT_ALIGN_SIZE) 进行字节对齐。*/
#define RT_ALIGN_SIZE 4

/* 定义系统线程优先级数;通常用 RT_THREAD_PRIORITY_MAX-1 定义空闲线程的优先级 */
#define RT_THREAD_PRIORITY_MAX 32

/* 定义时钟节拍,为 100 时表示 100 个 tick 每秒,一个 tick 为 10ms */
#define RT_TICK_PER_SECOND 100

/* 检查栈是否溢出,未定义则关闭 */
#define RT_USING_OVERFLOW_CHECK

/* 定义该宏开启 debug 模式,未定义则关闭 */
#define RT_DEBUG
/* 开启 debug 模式时:该宏定义为 0 时表示关闭打印组件初始化信息,定义为 1 时表示启用 */
#define RT_DEBUG_INIT 0
/* 开启 debug 模式时:该宏定义为 0 时表示关闭打印线程切换信息,定义为 1 时表示启用 */
#define RT_DEBUG_THREAD 0

/* 定义该宏表示开启钩子函数的使用,未定义则关闭 */
#define RT_USING_HOOK

/* 定义了空闲线程的栈大小 */
#define IDLE_THREAD_STACK_SIZE 256copymistakeCopy Success

(2) Inter-thread synchronization and communication part. The objects used in this part include semaphores, mutexes, events, mailboxes, message queues, signals, etc.

/* 定义该宏可开启信号量的使用,未定义则关闭 */
#define RT_USING_SEMAPHORE

/* 定义该宏可开启互斥量的使用,未定义则关闭 */
#define RT_USING_MUTEX

/* 定义该宏可开启事件集的使用,未定义则关闭 */
#define RT_USING_EVENT

/* 定义该宏可开启邮箱的使用,未定义则关闭 */
#define RT_USING_MAILBOX

/* 定义该宏可开启消息队列的使用,未定义则关闭 */
#define RT_USING_MESSAGEQUEUE

/* 定义该宏可开启信号的使用,未定义则关闭 */
#define RT_USING_SIGNALScopymistakeCopy Success

(3) Memory management part

/* 开启静态内存池的使用 */
#define RT_USING_MEMPOOL

/* 定义该宏可开启两个或以上内存堆拼接的使用,未定义则关闭 */
#define RT_USING_MEMHEAP

/* 开启小内存管理算法 */
#define RT_USING_SMALL_MEM

/* 关闭 SLAB 内存管理算法 */
/* #define RT_USING_SLAB */

/* 开启堆的使用 */
#define RT_USING_HEAPcopymistakeCopy Success

(4) Kernel device objects

/* 表示开启了系统设备的使用 */
#define RT_USING_DEVICE

/* 定义该宏可开启系统控制台设备的使用,未定义则关闭 */
#define RT_USING_CONSOLE
/* 定义控制台设备的缓冲区大小 */
#define RT_CONSOLEBUF_SIZE 128
/* 控制台设备的名称 */
#define RT_CONSOLE_DEVICE_NAME "uart1"copymistakeCopy Success

(5) Automatic initialization method

/* 定义该宏开启自动初始化机制,未定义则关闭 */
#define RT_USING_COMPONENTS_INIT

/* 定义该宏开启设置应用入口为 main 函数 */
#define RT_USING_USER_MAIN
/* 定义 main 线程的栈大小 */
#define RT_MAIN_THREAD_STACK_SIZE 2048copymistakeCopy Success

(6)FinSH

/* 定义该宏可开启系统 FinSH 调试工具的使用,未定义则关闭 */
#define RT_USING_FINSH

/* 开启系统 FinSH 时:将该线程名称定义为 tshell */
#define FINSH_THREAD_NAME "tshell"

/* 开启系统 FinSH 时:使用历史命令 */
#define FINSH_USING_HISTORY
/* 开启系统 FinSH 时:对历史命令行数的定义 */
#define FINSH_HISTORY_LINES 5

/* 开启系统 FinSH 时:定义该宏开启使用 Tab 键,未定义则关闭 */
#define FINSH_USING_SYMTAB

/* 开启系统 FinSH 时:定义该线程的优先级 */
#define FINSH_THREAD_PRIORITY 20
/* 开启系统 FinSH 时:定义该线程的栈大小 */
#define FINSH_THREAD_STACK_SIZE 4096
/* 开启系统 FinSH 时:定义命令字符长度 */
#define FINSH_CMD_SIZE 80

/* 开启系统 FinSH 时:定义该宏开启 MSH 功能 */
#define FINSH_USING_MSH
/* 开启系统 FinSH 时:开启 MSH 功能时,定义该宏默认使用 MSH 功能 */
#define FINSH_USING_MSH_DEFAULT
/* 开启系统 FinSH 时:定义该宏,仅使用 MSH 功能 */
#define FINSH_USING_MSH_ONLYcopymistakeCopy Success

(7) About MCU

/* 定义该工程使用的 MCU 为 STM32F103ZE;系统通过对芯片类型的定义,来定义芯片的管脚 */
#define STM32F103ZE

/* 定义时钟源频率 */
#define RT_HSE_VALUE 8000000

/* 定义该宏开启 UART1 的使用 */
#define RT_USING_UART1copymistakeCopy Success

Note

Note: In actual applications, the system configuration file rtconfig.h is automatically generated by the configuration tool and does not need to be changed manually.

Some macro definitions are often used in RT-Thread. Here are some common macro definitions in the Keil compilation environment:

1) rt_inline, defined as follows, the static keyword is used to make the function only available in the current file; inline means inline, and after being modified with static, the compiler will be advised to perform inline expansion when calling the function.

#define rt_inline                   static __inlinecopymistakeCopy Success

2) RT_USED, defined as follows, is used to tell the compiler that this code is useful and should be compiled even if it is not called in the function. For example, the RT-Thread automatic initialization function uses a custom segment, and using RT_USED will keep the custom code segment.

Note: RT-Thread 5.0 and later versions have RT_USEDchanged the keyword to rt_used, so please pay attention to the modification when using it.

#define RT_USED                     __attribute__((used))copymistakeCopy Success

3) RT_UNUSED, defined as follows, indicates that the function or variable may not be used. This attribute can prevent the compiler from generating warning messages.

#define RT_UNUSED                   ((void)x)copymistakeCopy Success

4) RT_WEAK, defined as follows, is often used to define functions. When linking functions, the compiler will give priority to linking functions without the keyword prefix. If it cannot find the function, it will link the function modified by weak.

Note: RT-Thread 5.0 and later versions have RT_WEAKchanged the keyword to rt_weak, so please pay attention to the modification when using it.

#define RT_WEAK                     __weakcopymistakeCopy Success

5) ALIGN(n), defined as follows, is used to align the address stored in an object to n bytes when allocating address space to an object, where n can be a power of 2. Byte alignment is not only for fast CPU access, but also can effectively save storage space if used properly.

Note: RT-Thread 5.0 and later versions have ALIGNchanged the keyword to rt_align, so please pay attention to the modification when using it.

#define ALIGN(n)                    __attribute__((aligned(n)))copymistakeCopy Success

6) RT_ALIGN(size,align), defined as follows, raises size to a multiple of the integer defined by align. For example, RT_ALIGN(13,4) returns 16.

#define RT_ALIGN(size, align)      (((size) + (align) - 1) & ~((align) - 1))copymistakeCopy Success

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University