diff options
| author | Chen Wang <[email protected]> | 2025-02-24 09:58:36 +0800 |
|---|---|---|
| committer | Rbb666 <[email protected]> | 2025-02-25 11:27:58 +0800 |
| commit | 75722c49981b43fa205989183f9958e199aaad2e (patch) | |
| tree | 893e7a66fa93a2b063a7594fe72881b21b1a241d /documentation/3.kernel | |
| parent | d9b36c8d181571922fd339ff2749c5eae62c6704 (diff) | |
doxygen: reorganize directory structure
The original directory structure was too flat and
inconvenient to view.
The directory is now reorganized according to the
first-level chapters, and some sub-chapters are
moved to the second-level directory.
Note: documentation/env is not moved to
documentation/4.tool because there are too many
external places that reference this path,
especially in bsp, so this directory path remains
as it is.
Signed-off-by: Chen Wang <[email protected]>
Diffstat (limited to 'documentation/3.kernel')
74 files changed, 6019 insertions, 0 deletions
diff --git a/documentation/3.kernel/basic/basic.md b/documentation/3.kernel/basic/basic.md new file mode 100644 index 0000000000..9ddd4860b7 --- /dev/null +++ b/documentation/3.kernel/basic/basic.md @@ -0,0 +1,736 @@ +@page page_kernel_basics Kernel Basics + +This chapter gives a brief introduction to the software architecture of the RT-Thread kernel, beginning with its composition and implementation. While also introducing RT-Thread kernel-related concepts for beginners. +After understanding this chapter, readers will have an elementary understanding of the RT Thread kernel and will be able to answer questions such as - + +- What are the constituents of the kernel? +- How does the system startup? +- How is the memory distributed? +- What are the methods of kernel configuration? + +In the nutshell, this is only a brief introduction to software architecture decomposition and implementation of the real-time kernel. This will give understanding and concepts of how RT-Thread kernel works togther. After learning from this chapter, readers will have basic knowledge of each kernel components, system booting up proccesses, memory allocation and distrubtion, and methods of kernel configuration. + +# Introduction to RT-Thread Kernel + +Kernel is the most basic and fundenmental part of an Operating System. Kernel service library and RT-Thread kernel libraries are interfacing between hardware and components/service layer. This includes the implementation of real-time kernel service library (rtservice.h/kservice.c) and other RT-Thread kernel libraries such as object management, thread management and scheduler, inter-thread communication management, clock management and memory management respectively. Below diagram is the core architecture diagram of the core kernel. + + + +Implementation of core kernel libraries are similar to a small set of standard C runtime library and it can run independently. For example, Standard C library (C runtime library) provides "strcpy", "memcpy", "printf", "scanf" and other function implementations. RT-Thread kernel libraries also provide the function implementations which are mainly used by Core Kernel. However, to avoid name conflicts, specifically functions' names are preceded with rt_. + + +The built of the Kernel will be vary depending on the complier. For example, using GNU GCC compiler, it will use more implementation from the standard C library. Last but not least, the minimum resource requirements of the Kernel is 3KB ROM and 1.2KB RAM. + + +## Thread Scheduling + +Thread is the smallest scheduling unit in the RT-Thread operating system. The thread scheduling algorithm is a **Priority-based Full Preemptive Multi-Thread** scheduling algorithm. +The system can support up to 256(0 - 255) thread priorities. For systems with tight resources, configurations with 8 or 32 thread priorities can be chosen(For example, STM32 has 32 thread priorities as per the default configuration). Lower numbers have a higher priority where 0 represents the highest priority furthermore the lowest priority(highest number) is reserved for idle threads. +RT-Thread supports the creation of multiple threads with the same priority. Threads having the same priority are scheduled with a Time Slice Rotation Scheduling algorithm so that each thread runs for the same amount of time. +The number of threads is bounded by the memory of the hardware platform and not the system. + +Thread management will be covered in detail in the "Thread Management" chapter. + +## Clock Management + +RT-Thread's Clock management is based upon a **clock beat**, which is the smallest clock unit in the RT-Thread operating system. +The RT-Thread timer provides two types of timer mechanisms: +- **One-Shot Timer** - Triggers only one timer event after startup and then stops automatically. +- **Periodic Trigger Timer** - Periodically triggers timer events until the user manually stops the timer or it will continue to operate. + +The RT-Thread timer can be set to the `HARD_TIMER` or the `SOFT_TIMER` mode depending on the context in which the timeout function is executed. + +The timer service is concluded using a timer timing callback i.e. a timeout function. The user can select the appropriate type of timer according to their real-time requirements for timing processing. + +Timer will be explained further in the "Clock Management" chapter. + +## Synchronization between Threads + +RT-Thread uses thread semaphores, mutexes, and event sets to achieve inter-thread synchronization. +Thread synchronizations happen through the acquisition and release of semaphore and mutexes. +The mutex uses priority inheritance to solve the common priority inversion problem in the real-time system. The thread synchronization mechanism allows threads to wait according to priorities or to acquire semaphores/mutexes following the First In First Out(FIFO) method. +Event sets are primarily used for synchronization between threads, they can achieve one-to-many and many-to-many synchronization. It allows "**OR** trigger"(*independent synchronization*) and "**AND** trigger"(*associative synchronization*) suitable for situations where threads are waiting for multiple events. + +The concepts of semaphores, mutexes, and event sets are detailed in the "Inter-Thread Synchronization" chapter. + +## Inter-Thread Communication + +RT-Thread supports communication mechanisms such as mailbox, message queue, etc. The mailbox's message length is fixed to 4 bytes. Whereas, message queue can receive messages in variable size and cache the messages in its own memory space. +Compared to a message queue, a mailbox is more efficient. The sending action of the mailbox and message queue can be safely used in an ISR (Interrupt Service Routine). The communication mechanism allows threads to wait by priority or to acquire by the First In First Out (FIFO) method. +The concept of mailbox and message queue will be explained in detail in the "Inter-Thread Communication" chapter. + +## Memory Management + +RT-Thread allows: +1. Static Memory Pool +2. Dynamic Memory Pool + +When the static memory pool has available memory, the time allocated to the memory block will be constant. +When the static memory pool is empty, the system will then request for suspending or blocking the thread of the memory block. The thread will abandon the request and return if the memory block is not obtained after waiting for a while, or the thread will abandon and return immediately. The waiting time depends on the waiting time parameter set when the memory block is applied. When other threads release the memory block to the memory pool, the system will wake up the thread if there are suspended threads waiting to be allocated of memory blocks. + +Under circumstances of different system resources, the dynamic memory heap management module respectively provides memory management algorithms for small memory systems and SLAB memory management algorithms for large memory systems. + +There is also a dynamic memory heap management called memheap, suitable for memory heaps in systems with multiple addresses that can be discontinuous. Using memheap, the user can "paste" multiple memory heaps together, letting them operate as if operating a memory heap. + +The concept of memory management will be explained in the "Memory Management" chapter. + +## I/O Device Management + +RT-Thread uses I2C, SPI, USB, UART, etc., as peripheral devices and is uniformly registered through the device. It realized a device management subsystem accessed by the name, and it can access hardware devices according to a unified API interface. On the device driver interface, depending on the characteristics of the embedded system, corresponding events can be attached to different devices. The driver notifies the upper application program when the device event is triggered. + +The concept of I/O device management will be explained in the "Device Model" and "General Equipment" chapters. + +# RT-Thread Startup Process + +The understanding of most codes usually starts from learning the startup process. We will firstly look for the source of the startup. Taking MDK-ARM as an example, the user program entry for MDK-ARM is the main() function located in the main.c file. The launching of the system starts from the assembly code startup_stm32f103xe.s, jumps to the C code, initializes the RT-Thread system function, and finally enters the user program entry main(). + +To complete the RT-Thread system function initialization before entering main(), we used the MDK extensions `$Sub$$` and `$Super$$`. Users can add the prefix of `$Sub$$` to main to make it a new function `$Sub$$main`. +`$Sub$$main` can call some functions to be added before main (here, RT-Thread system initialization function is added). Then, call `$Super$$main` to the main() function so that the user does not have to manage the system initialization before main(). + +For more information on the use of the `$Sub$$` and `$Super$$`extensions, see the ARM® Compiler v5.06 for μVision®armlink User Guide. + +Let's take a look at this code defined in components.c: + +```c +/* $Sub$$main Function */ +int $Sub$$main(void) +{ + rtthread_startup(); + return 0; +} +``` + +Here, the `$Sub$$main` function simply calls the rtthread_startup() function. RT-Thread allows multiple platforms and multiple compilers, and the rtthread_startup() function is a uniform entry point specified by RT-Thread, so the `$Sub$$main` function only needs to call the rtthread_startup() function (RT-Thread compiled using compiler GNU GCC is an example where it jumps directly from the assembly startup code section to the rtthread_startup() function and starts the execution of the first C code). +The rtthread_startup() function can be found in the code of components.c, the startup process of RT-Thread is as shown below: + + + +Code for the rtthread_startup() function is as follows: + +```c +int rtthread_startup(void) +{ + rt_hw_interrupt_disable(); + + /* Board level initialization: system heap initialization is required inside the function */ + rt_hw_board_init(); + + /* Print RT-Thread version information */ + rt_show_version(); + + /* Timer initialization */ + rt_system_timer_init(); + + /* Scheduler initialization */ + rt_system_scheduler_init(); + +#ifdef RT_USING_SIGNALS + /* Signal initialization */ + rt_system_signal_init(); +#endif + + /* Create a user main() thread here */ + rt_application_init(); + + /* Timer thread initialization */ + rt_system_timer_thread_init(); + + /* Idle thread initialization */ + rt_thread_idle_init(); + + /* Start scheduler */ + rt_system_scheduler_start(); + + /* Will not execute till here */ + return 0; +} +``` + +This part of the startup code can be roughly divided into four parts: + +1. Initialize hardware related to the system. +2. Initialize system kernel objects, such as timers, schedulers, and signals. +3. Create the main thread initialize various modules in the main thread one by one. +4. Initialize the timer thread, idle thread, and start the scheduler. + +Set the system clock in rt_hw_board_init() to provide heartbeat and serial port initialization for the system, bound to the system's input and output terminals to this serial port. Subsequent system operation information will be printed out from the serial port later. +The main() function is the user code entry for RT-Thread, and users can add their own applications to the main() function. + +```c +int main(void) +{ + /* user app entry */ + return 0; +} +``` + +# RT-Thread Program Memory Distribution + +The general MCU contains storage space that includes the on-chip Flash and the on-chip RAM. RAM is equivalent to memory, and Flash is comparable to a hard disk. The compiler classifies a program into several parts stored in different memory areas of the MCU. + +After the Keil project is compiled, there will be a prompt stating the occupied space by the corresponding program, for example: + +``` +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:07 +``` + +The Program Size mentioned above contains the following sections: + +1. **Code**: Code Segment, section of code that stores the program +2. **RO-data**: Read-Only data segment, stores the constants defined in the program +3. **RW-data**: Read and Write data segment, stores global variables initialized to non-zero values +4. **ZI-data**: 0 data segment, stores uninitialized global variables and initialized-to-0 variables + +After compiling the project, there will be a generated .map file that describes the size and address of each function. The last few lines of the file also illustrate 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) +``` + +1. RO Size contains Code and RO-data, indicating the size of the Flash occupied by the program +2. RW Size contains RW-data and ZI-data, indicating the size of the RAM occupied when operating +3. ROM Size contains Code, RO Data, and RW Data. Indicating the size of the Flash occupied by the programming system; + +Before the program runs, the file entity needs to be flashed into the STM32 Flash, usually a bin or hex file. The burned file is called an executable image file. The left part in the figure below shows the memory distribution after the executable image file is flashed into STM32 which includes the RO and RW segments. The RO segment stores data of Code and RO-data, and the RW segment holds the data of RW-data. Since ZI-data is 0, it is not included in the image file. + +STM32 is launched from Flash by default after power-on. After launching, RW-data (initialized global variable) the RW segment is transferred to RAM, but the RO segment is not transferred. Meaning the execution code of the CPU is read from the Flash. The ZI segment is allocated according to the ZI address and size is given by the compiler, and the RAM area is cleared. + + + +The dynamic memory heap is unused RAM space, and the memory blocks requested and released by the application come from this space. + +Like the following example: + +```c +rt_uint8_t* msg_ptr; +msg_ptr = (rt_uint8_t*) rt_malloc (128); +rt_memset(msg_ptr, 0, 128); +``` + +The 128-byte memory space pointed by the `msg_ptr` pointer in the code is in the dynamic memory heap space. + +Some global variables are stored in the RW segment and the ZI segment. The RW segment holds the global variable with the initial value (the global variable in the constant form is placed in the RO segment, which is a read-only property), and uninitialized global variable is stored in the ZI segment, as in the following example: + +```c +#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() +{ + /* ... */ +} +``` +The `sensor_value` is stored in the ZI segment and is automatically initialized to zero after system startup (some library functions provided by the user program or compiler are initialized to zero). The sensor_inited variable is stored in the RW segment, and the sensor_enable is stored in the RO segment. + +# RT-Thread Automatic Initialization Mechanism + +The automatic initialization mechanism means that the initialization function does not need to be called by explicit function. It only needs to be declared by macro definition at the function definition, and it will be executed during system startup. + +For example, calling a macro definition in the serial port driver to inform the function that needs to be called to initialize the system. The code is as follows: + +```c +int rt_hw_usart_init(void) /* Serial port initialization function */ +{ + ... ... + /* Register serial port 1 device */ + 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); /* Use component auto-initialization mechanism */ +``` + +The last part of the sample code INIT_BOARD_EXPORT(rt_hw_usart_init) indicates that the automatic initialization function is used. In this way, rt_hw_usart_init() function is 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(), subsequent functions inside the box with the underlying color represent functions that are automatically initialized, where: + +1. “board init functions” are all initialization functions declared by INIT_BOARD_EXPORT(fn). +2. “pre-initialization functions” are all initialization functions declared by INIT_PREV_EXPORT(fn). +3. “device init functions” are all initialization functions declared by INIT_DEVICE_EXPORT(fn). +4. “components init functions” are all initialization functions declared by INIT_COMPONENT_EXPORT(fn). +5. “enviroment init functions” are all initialization functions declared by INIT_ENV_EXPORT(fn). +6. “application init functions” are all initialization functions declared by INIT_APP_EXPORT(fn). + +The rt_components_board_init() function executes earlier, mainly to initialize the relevant hardware environment. When this function is executed, it will traverse the initialization function table declared by INIT_BOARD_EXPORT(fn) and call each function. + +The rt_components_init() function is 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 transverse through the remaining few initialization function tables declared by macros. + +RT-Thread's automatic initialization mechanism uses a custom RTI symbol segment, it puts the function pointer that needs to be initialized at startup into this segment and forms an initialization function table which will be traversed during system startup. It calls the functions in the table to achieve the purpose of automatic initialization. + +The macro interface definitions used to implement the automatic initialization function are described in the following table: + +|Initialization sequence|Macro Interface |Description | +|----------------|------------------------------------|----------------------------------------------| +| 1 | INIT_BOARD_EXPORT(fn) | Very early initialization, the scheduler has not started yet. | +| 2 | INIT_PREV_EXPORT(fn) | Mainly used for pure software initialization, functions without too many dependencies | +| 3 | INIT_DEVICE_EXPORT(fn) | Peripheral driver initialization related, such as network card devices | +| 4 | INIT_COMPONENT_EXPORT(fn) | Component initialization, such as file system or LWIP | +| 5 | INIT_ENV_EXPORT(fn) | System environment initialization, such as mounting file systems | +| 6 | INIT_APP_EXPORT(fn) | Application initialization, such as application GUI | + +Initialization function actively declares through these macro interfaces, such as INIT_BOARD_EXPORT (rt_hw_usart_init), the linker will automatically collect all the declared initialization functions, placed in the RTI symbol segment, the symbol segment is located in the RO segment of the memory distribution. All functions in this RTI symbol segment are automatically called when the system is initialized. + +# RT-Thread Kernel Object Model + +## Static and Dynamic Objects + +The RT-Thread kernel is designed with object-oriented method. The system-level infrastructures are all kernel objects such as threads, semaphores, mutexes, timers, and more. Kernel objects fall into two categories: static kernel objects and dynamic kernel objects. Static kernel objects are usually placed in RW and ZI segments, initialized in the program after system startup; dynamic kernel objects are created from the memory heap and then manually initialized. + +The following code is an example of static threads and dynamic threads: + + +```c +/* Thread 1 object and stack used while running */ +static struct rt_thread thread1; +static rt_uint8_t thread1_stack[512]; + +/* Thread 1 entry */ +void thread1_entry(void* parameter) +{ + int i; + + while (1) + { + for (i = 0; i < 10; i ++) + { + rt_kprintf("%d\n", i); + + /* Delay 100ms */ + rt_thread_mdelay(100); + } + } +} + +/* Thread 2 entry */ +void thread2_entry(void* parameter) +{ + int count = 0; + while (1) + { + rt_kprintf("Thread2 count:%d\n", ++count); + + /* Delay 50ms */ + rt_thread_mdelay(50); + } +} + +/* Thread routine initialization */ +int thread_sample_init() +{ + rt_thread_t thread2_ptr; + rt_err_t result; + + /* Initialize thread 1 */ + /* The thread entry is thread1_entry and the parameter is RT_NULL + * Thread stack is thread1_stack + * Priority is 200 and time slice is 10 OS Tick + */ + result = rt_thread_init(&thread1, + "thread1", + thread1_entry, RT_NULL, + &thread1_stack[0], sizeof(thread1_stack), + 200, 10); + + /* Start thread */ + if (result == RT_EOK) rt_thread_startup(&thread1); + + /* Create thread 2 */ + /* The thread entry is thread2_entry and the parameter is RT_NULL + * Stack space is 512, priority is 250, and time slice is 25 OS Tick + */ + thread2_ptr = rt_thread_create("thread2", + thread2_entry, RT_NULL, + 512, 250, 25); + + /* Start thread */ + if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr); + + return 0; +} +``` + +In this example, thread1 is a static thread object and thread2 is a dynamic thread object. The memory space of the thread1 object, including the thread control block thread1 and the stack space thread1_stack are all determined while compiling, because there is no initial value in the code and they are uniformly placed in the uninitialized data segment. The space used by thread2 is dynamically allocated includes the thread control block (the content pointed to by thread2_ptr) and the stack space. + +Static objects take up RAM space and is not depend on the memory heap manager. When allocating static objects, the time needed is determined. Dynamic objects depend on the memory heap manager. It requests RAM space while running. When the object is deleted, the occupied RAM space is released. These two methods have their own advantages and disadvantages, and can be selected according to actual needs. + +## Kernel Object Management Structure + +RT-Thread uses the 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 statically allocated static objects and dynamic objects allocated from the system memory heap. . + +Because of this design for kernel object, RT-Thread is able to not depend on 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, and more. The object container contains information about each type of kernel object, including object type, size, and so on. The object container assigns a linked list to each type of kernel object. All kernel objects are linked to the linked list. The kernel object container and linked list of RT-Thread are shown in the following figure: + + + +The following figure shows the derivation and inheritance relationships of various kernel objects in RT-Thread. For each specific kernel object and object control block, in addition to the basic structure, they have their own extended attributes (private attributes). Take thread control block for an example, the base object is extended, attributes like thread state, precedence and so on are added. These attributes are not used in the operation of the base class object and are only used in operations related to a specific thread. Therefore, from the object-oriented point of view, each concrete object can be considered as a derivative of an abstract object, inheriting the attributes of the base object and extending the attributes related to itself. + + + +In the object management module, a common data structure is defined to store the common attributes of various objects. Each specific object only needs to add some special attributes of its own and its own feature will be clearly expressed. + +The advantages of this design approach are: + +(1) Improve the reusability and scalability of the system. It is easy to add new object categories. It only needs to inherit the attributes of the general object and add a small amount of extension. + +(2) Provide a unified object operation mode, simplify the operation of various specific objects, and improve the reliability of the system. + +Derivations from object control block rt_object in the above figure includes: thread object, memory pool object, timer object, device object and IPC object (IPC: Inter-Process Communication. In RT-Thread real-time operating system, IPC objects is used for synchronization and communicate between threads); derivations from IPC objects includes: semaphores, mutexes, events, mailboxes, message queues, signals, etc. + +## Object Control Block + +Data structure of kernel object control block: + +```c +struct rt_object +{ + /* Kernel object name */ + char name[RT_NAME_MAX]; + /* Kernel object type */ + rt_uint8_t type; + /* Parameters to the kernel object */ + rt_uint8_t flag; + /* Kernel object management linked list */ + rt_list_t list; +}; +``` + +Types currently supported by kernel objects are as follows: + +```c +enum rt_object_class_type +{ + RT_Object_Class_Thread = 0, /* Object is thread type */ +#ifdef RT_USING_SEMAPHORE + RT_Object_Class_Semaphore, /* Object is semaphore type */ +#endif +#ifdef RT_USING_MUTEX + RT_Object_Class_Mutex, /* Object is mutex type */ +#endif +#ifdef RT_USING_EVENT + RT_Object_Class_Event, /* Object is event type */ +#endif +#ifdef RT_USING_MAILBOX + RT_Object_Class_MailBox, /* Object is mailbox type */ +#endif +#ifdef RT_USING_MESSAGEQUEUE + RT_Object_Class_MessageQueue, /* Object is message queue type */ +#endif +#ifdef RT_USING_MEMPOOL + RT_Object_Class_MemPool, /* Object is memory pool type */ +#endif +#ifdef RT_USING_DEVICE + RT_Object_Class_Device, /* Object is device type */ +#endif + RT_Object_Class_Timer, /* Object is timer type */ +#ifdef RT_USING_MODULE + RT_Object_Class_Module, /* Object is module */ +#endif + RT_Object_Class_Unknown, /* Object is unknown */ + RT_Object_Class_Static = 0x80 /* Object is a static object */ +}; +``` + +From the above type specification, we can see that if it is a static object, the highest bit of the object type will be 1 (which is the OR operation of RT_Object_Class_Static and other object types and operations). Otherwise it will be dynamic object, and the maximum number of object classes that the system can accommodate is 127. + +## Kernel Object Management + +Data structure of kernel object container: + +```c +struct rt_object_information +{ + /* Object type */ + enum rt_object_class_type type; + /* Object linked list */ + rt_list_t object_list; + /* Object size */ + rt_size_t object_size; +}; +``` + +A class of objects is managed by an rt_object_information structure, and each practical instance of such type of object is mounted to the object_list in the form of a linked list. The memory block size of this type of object is identified by object_size (the memory block each practical instance of each type of object is the same size). + +### Initialization Object + +An uninitialized static object must be initialized before it can be used. The initialization object uses the following interfaces: + +```c +void rt_object_init(struct rt_object* object , + enum rt_object_class_type type , + const char* name) +``` + +When this function is called to initialize the object, the system will place the object into the 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. Input parameters of the function is described in the following table: + + +|Parameters|Description | +| -------- | ------------------------------------------------------------ | +| object | The object pointer that needs to be initialized must point to a specific object memory block, not a null pointer or a wild pointer. | +| type | The type of the object must be a enumeration type listed in rt_object_class_type, RT_Object_Class_Static excluded. (For static objects, or objects initialized with the rt_object_init interface, the system identifies it as an RT_Object_Class_Static type) | +| name | Name of the object. Each object can be set to a name, and the maximum length for the name is specified by RT_NAME_MAX. The system does not care if it uses ’`\0`’as a terminal symbol. | + +### Detach Object + +Detach an object from the kernel object manager. The following interfaces are used to detach objects: + +```c +void rt_object_detach(rt_object_t object); +``` + +Calling this interface makes a static kernel object to be detached from the kernel object container, meaning the corresponding object node is deleted from the kernel object container linked list. After the object is detached, the memory occupied by the object will not be released. + +### Allocate object + +The above descriptions are interfaces of objects initialization and detachment, both of which are under circumstances that object-oriented memory blocks already exist. But dynamic objects can be requested when needed. The memory space is freed for other applications when not needed. To request assigning new objects, you can use the following interfaces: + +```c +rt_object_t rt_object_allocate(enum rt_object_class_typetype , + const char* name) +``` + +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 for the system to allocate the correct size of the memory data block), and then allocate memory space corresponding to the size of the object from the memory heap. Next, to start necessary initialization for the object, and finally insert it into the object container linked list in which it is located. The input parameters for this function are described in the following table: + + +|Parameters |Description | +| ------------------ | ------------------------------------------------------------ | +| type | The type of the allocated object can only be of type rt_object_class_type other than RT_Object_Class_Static. In addition, the type of object allocated through this interface is dynamic, not static. | +| name | Name of the object. Each object can be set to a name, and the maximum length for the name is specified by RT_NAME_MAX. The system does not care if it uses ’`\0`’as a terminal symbol. | +|**Return** | —— | +| object handle allocated successfully | Allocate successfully | +| RT_NULL | Fail to allocate | + +### Delete 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: + +```c +void rt_object_delete(rt_object_t object); +``` + +When the above interface is called, the object is first detached from the object container linked list, and then the memory occupied by the object is released. The following table describes the input parameters of the function: + + +|Parameter|Description | +|----------|------------| +| object | object handle | + +### Identify objects + +Identify whether the specified object is a system object (static kernel object). The following interface is used to identify the object: + +```c +rt_err_t rt_object_is_systemobject(rt_object_t object); +``` + +Calling the rt_object_is_systemobject interface can help to identify whether an object is a system object. In RT-Thread operating system, a system object is also a static object, RT_Object_Class_Static bit is set to 1 on the object type identifier. Usually, objects that are initialized using the rt_object_init() method are system objects. The input parameters for this function are described in the following table: + +Input parameter of rt_object_is_systemobject() + +|**Parameter**|Description | +|----------|------------| +| object | Object handle | + +RT-Thread Kernel Configuration Example +---------------------- + +An important feature of RT-Thread is its high degree of tailorability, which allows for fine-tuning of the kernel and flexible removal of components. + +Configuration is mainly done by modifying the file under project directory - rtconfig.h. User can conditionally compile the code by opening/closing the macro definition in the file, and finally achieve the purpos e of system configuration and cropping, as follows: + +(1)RT-Thread Kernel part + +```c +/* Indicates the maximum length of the name of the kernel object. If the maximum length of the name of the object in the code is greater than the length of the macro definition, + * the extra part will be cut off. */ +#define RT_NAME_MAX 8 + +/* Set the number of aligned bytes when bytes are aligned. Usually use ALIGN(RT_ALIGN_SIZE) for byte alignment.*/ +#define RT_ALIGN_SIZE 8 + +/* Define the number of system thread priorities; usually define the priority of idle threads with RT_THREAD_PRIORITY_MAX-1 */ +#define RT_THREAD_PRIORITY_MAX 32 + +/* Define the clock beat. When it is 100, it means 100 tick per second, and a tick is 10ms. */ +#define RT_TICK_PER_SECOND 100 + +/* Check if the stack overflows, if not defined, close. */ +#define RT_USING_OVERFLOW_CHECK + +/* Define this macro to enable debug mode, if not defined, close. */ +#define RT_USING_DEBUG +/* When debug mode is enabled: Define this macro to enable the print component initialization information, if not defined, close. */ +#define RT_DEBUGING_AUTO_INIT + +/* Defining this macro means the use of the hook function is started, if not defined, close. */ +#define RT_USING_HOOK + +/* Defines the stack size of idle threads. */ +#define IDLE_THREAD_STACK_SIZE 256 +``` + +(2)Inter-thread synchronization and communication part, the objects that will be used in this part are semaphores, mutexes, events, mailboxes, message queues, signals, and so on. + +```c +/* Define this macro to enable the use of semaphores, if not defined, close. */ +#define RT_USING_SEMAPHORE + +/* Define this macro to enable the use of mutexes, if not defined, close. */ +#define RT_USING_MUTEX + +/* Define this macro to enable the use of events, if not defined, close. */ +#define RT_USING_EVENT + +/* Define this macro to enable the use of mailboxes, if not defined, close. */ +#define RT_USING_MAILBOX + +/* Define this macro to enable the use of message queues, if not defined, close. */ +#define RT_USING_MESSAGEQUEUE + +/* Define this macro to enable the use of signals, if not defined, close. */ +#define RT_USING_SIGNALS +``` + +(3)Memory Management Part + +```c +/* Start the use of static memory pool */ +#define RT_USING_MEMPOOL + +/* Define this macro to start the concatenation of two or more memory heap , if not defined, close. */ +#define RT_USING_MEMHEAP + +/* Start algorithm for small memory management */ +#define RT_USING_SMALL_MEM + +/* Turn off SLAB memory management algorithm */ +/* #define RT_USING_SLAB */ + +/* Start the use of heap */ +#define RT_USING_HEAP +``` + +(4)Kernel Device Object + +```c +/* Indicates the start of useing system devices */ +#define RT_USING_DEVICE + +/* Define this macro to start the use of system console devices, if not defined, close. */ +#define RT_USING_CONSOLE +/* Define the buffer size of the console device. */ +#define RT_CONSOLEBUF_SIZE 128 +/* Name of the console device. */ +#define RT_CONSOLE_DEVICE_NAME "uart1" +``` + +(5)Automatic Initialization Method + +```c +/* Define this macro to enable automatic initialization mechanism, if not defined, close. */ +#define RT_USING_COMPONENTS_INIT + +/* Define this macro to set application entry as main function */ +#define RT_USING_USER_MAIN +/* Define the stack size of the main thread */ +#define RT_MAIN_THREAD_STACK_SIZE 2048 +``` + +(6)FinSH + +```c +/* Define this macro to start the use of the system FinSH debugging tool, if not defined, close. */ +#define RT_USING_FINSH + +/* While starting the system FinSH: the thread name is defined as tshell */ +#define FINSH_THREAD_NAME "tshell" + +/* While turning the system FinSH: use history commands. */ +#define FINSH_USING_HISTORY +/* While turning the system FinSH: define the number of historical command lines. */ +#define FINSH_HISTORY_LINES 5 + +/* While turning the system FinSH: define this macro to use symbol table in Finsh, if not defined, close. */ +#define FINSH_USING_SYMTAB + +/* While turning the system FinSH: define the priority of the thread. */ +#define FINSH_THREAD_PRIORITY 20 +/* While turning the system FinSH:define the stack size of the thread. */ +#define FINSH_THREAD_STACK_SIZE 4096 +/* While turning the system FinSH:define the length of command character. */ +#define FINSH_CMD_SIZE 80 + +/* While turning the system FinSH: define this macro to enable the MSH function. */ +#define FINSH_USING_MSH +/* While turning the system FinSH:when MSH function is enabled, macro is defined to use the MSH function by default. */ +#define FINSH_USING_MSH_DEFAULT +/* While turning the system FinSH:define this macro to use only the MSH function. */ +#define FINSH_USING_MSH_ONLY +``` + +(7)About MCU + +```c +/* Define the MCU used in this project is STM32F103ZE; the system defines the chip pins by defining the chip type. */ +#define STM32F103ZE + +/* Define the clock source frequency. */ +#define RT_HSE_VALUE 8000000 + +/* Define this macro to enable the use of UART1. */ +#define RT_USING_UART1 +``` + +>In practice, the system configuration file rtconfig.h is automatically generated by configuration tools and does not need to be changed manually. + +Common Macro Definition Description +-------------- + +Macro definitions are often used in RT-Thread. For example, some common macro definitions in the Keil compilation environment: + +1)rt_inline, definition is as follows, static keyword is to make the function only available for use in the current file; inline means inline, after modification using static, the compiler is recommended to perform inline expansion when calling the function. + +```c +#define rt_inline static __inline +``` + +2)rt_used,definition is as follows, the purpose of this macro is to explain to the compiler that this code is useful, compilation needs to be saved even if it is not called in the function. For example, RT-Thread auto-initialization uses custom segments, using rt_used will retain custom code snippets. + +```c +#define rt_used __attribute__((used)) +``` + +3)RT_UNUSED,definition is as follows, indicates that a function or variable may not be used. This attribute prevents the compiler from generating warnings. + +```c +#define RT_UNUSED __attribute__((unused)) +``` + +4)rt_weak,definition is as follows, often used to define functions, when linking the function, the compiler will link the function without the keyword prefix first and link the function modified by weak if it can't find those functions. + +```c +#define rt_weak __weak +``` + +5)ALIGN(n),definition is as follows, is used to align its stored address with n bytes when allocating an address space to an object. Here, n can be the power of 2. Byte alignment not only facilitates quick CPU access, but also save memory space if byte alignment is properly used. + +```c +#define ALIGN(n) __attribute__((aligned(n))) +``` + +6)RT_ALIGN(size,align),definition is as follows, to increase size to a multiple of an integer defined by align. For example, RT_ALIGN(13,4) will return to 16. + +```c +#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1)) +``` + diff --git a/documentation/3.kernel/basic/figures/03Memory_distribution.png b/documentation/3.kernel/basic/figures/03Memory_distribution.png Binary files differnew file mode 100644 index 0000000000..1dcd401648 --- /dev/null +++ b/documentation/3.kernel/basic/figures/03Memory_distribution.png diff --git a/documentation/3.kernel/basic/figures/03Startup_process.png b/documentation/3.kernel/basic/figures/03Startup_process.png Binary files differnew file mode 100644 index 0000000000..4128df8d94 --- /dev/null +++ b/documentation/3.kernel/basic/figures/03Startup_process.png diff --git a/documentation/3.kernel/basic/figures/03kernel_Framework.png b/documentation/3.kernel/basic/figures/03kernel_Framework.png Binary files differnew file mode 100644 index 0000000000..eb2c3059fe --- /dev/null +++ b/documentation/3.kernel/basic/figures/03kernel_Framework.png diff --git a/documentation/3.kernel/basic/figures/03kernel_object.png b/documentation/3.kernel/basic/figures/03kernel_object.png Binary files differnew file mode 100644 index 0000000000..85508581d3 --- /dev/null +++ b/documentation/3.kernel/basic/figures/03kernel_object.png diff --git a/documentation/3.kernel/basic/figures/03kernel_object2.png b/documentation/3.kernel/basic/figures/03kernel_object2.png Binary files differnew file mode 100644 index 0000000000..b1601b4868 --- /dev/null +++ b/documentation/3.kernel/basic/figures/03kernel_object2.png diff --git a/documentation/3.kernel/interrupt/figures/09fun1.png b/documentation/3.kernel/interrupt/figures/09fun1.png Binary files differnew file mode 100644 index 0000000000..10dc391162 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09fun1.png diff --git a/documentation/3.kernel/interrupt/figures/09fun2.png b/documentation/3.kernel/interrupt/figures/09fun2.png Binary files differnew file mode 100644 index 0000000000..bed6b18ea7 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09fun2.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_handle.png b/documentation/3.kernel/interrupt/figures/09interrupt_handle.png Binary files differnew file mode 100644 index 0000000000..9110afbcf5 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_handle.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_ops.png b/documentation/3.kernel/interrupt/figures/09interrupt_ops.png Binary files differnew file mode 100644 index 0000000000..96c9827ce0 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_ops.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_reque.png b/documentation/3.kernel/interrupt/figures/09interrupt_reque.png Binary files differnew file mode 100644 index 0000000000..cf9daaaf46 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_reque.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_table.png b/documentation/3.kernel/interrupt/figures/09interrupt_table.png Binary files differnew file mode 100644 index 0000000000..44ee881776 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_table.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_work.png b/documentation/3.kernel/interrupt/figures/09interrupt_work.png Binary files differnew file mode 100644 index 0000000000..69e1711e9b --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_work.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_work_process.png b/documentation/3.kernel/interrupt/figures/09interrupt_work_process.png Binary files differnew file mode 100644 index 0000000000..afe99425a9 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_work_process.png diff --git a/documentation/3.kernel/interrupt/figures/09interrupt_work_sta.png b/documentation/3.kernel/interrupt/figures/09interrupt_work_sta.png Binary files differnew file mode 100644 index 0000000000..f86bdd3d94 --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09interrupt_work_sta.png diff --git a/documentation/3.kernel/interrupt/figures/09relation.png b/documentation/3.kernel/interrupt/figures/09relation.png Binary files differnew file mode 100644 index 0000000000..b4094b4dec --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09relation.png diff --git a/documentation/3.kernel/interrupt/figures/09ths_switch.png b/documentation/3.kernel/interrupt/figures/09ths_switch.png Binary files differnew file mode 100644 index 0000000000..c3066302ac --- /dev/null +++ b/documentation/3.kernel/interrupt/figures/09ths_switch.png diff --git a/documentation/3.kernel/interrupt/interrupt.md b/documentation/3.kernel/interrupt/interrupt.md new file mode 100644 index 0000000000..1254213a15 --- /dev/null +++ b/documentation/3.kernel/interrupt/interrupt.md @@ -0,0 +1,562 @@ +@page page_interrupt_management Interrupt Management + +Interrupts often occur in embedded operating systems. When the CPU is processing a normal task, an external urgent event has occurred, requiring the CPU to suspend the current task to handle the asynchronous event. After the external event has been handled, CPU then returns to the interrupted address to continue working on the previous task. The system that implements this function is called the interrupt system, and the source of the request requesting for the CPU interrupt is called the interrupt source. An interrupt is an exception. An exception is any event that causes the processor to move away from normal operation and execute special code. If it is not processed in time, the system will either encounter an error or face a complete breakdown. So appropriately handling exceptions to avoid errors is a very important part of improving software robustness (stability). The following picture is a simple interrupt diagram. + + + +Interrupt processing is closely related to the CPU architecture. Therefore, this chapter first introduces the ARM Cortex-M CPU architecture, and then introduces the RT-Thread interrupt management mechanism in conjunction with the Cortex-M CPU architecture. After reading this chapter, you will learn more about the interrupt handling process of RT-Thread, how to add an interrupt service routine (ISR) and other matters related. + +# Cortex-M CPU Architecture Foundation + +Unlike older classic ARM processors (like ARM7, ARM9), the ARM Cortex-M processor has a very different architecture. Cortex-M is serious which Cortex M0/M3/M4/M7 models. There will be some differences between each model. For example, the Cortex-M4 has more floating point calculation functions than the Cortex-M3, but their programming models are basically the same, so the parts of the book that describe interrupt management and porting are not going to be too finely differentiated for the Cortex M0/M3/M4/M7. This section focuses on the architectural aspects related to RT-Thread interrupt management. + +## Introduction to Register + +The register set of Cortex-M series CPU has 16 general register sets and several special function registers from R0~R15, as shown in the figure below. + +R13 in the general register set is used as the stack pointer register (SP); R14 is used as the link register (LR), which is used to store the return address when the subroutine is called; R15 is used as the program counter (PC) , where the stack pointer register can be either the main stack pointer (MSP) or the process stack pointer (PSP). + + + +Special function registers include program status word register bank (PSRs), interrupt mask register banks (PRIMASK, FAULTMASK, BASEPRI), and control registers (CONTROL). Special function registers can be accessed through MSR/MRS instructions, such as: + +``` +MRS R0, CONTROL ; Read CONTROL to R0 +MSR CONTROL, R0 ; Write R0 to the CONTROL register +``` + +The program status word registers stores arithmetic and logic flags, such as negative flags, null result flags, overflow flags, and so on. The interrupt mask register bank controls the Cortex-M interrupt disable. The control registers are used to define the privilege level and decide which stack pointer is to be used. + +In the case of a Cortex-M4 or Cortex-M7 with a floating point unit, the control register is also used to indicate whether the floating point unit is currently in use. The floating point unit contains 32 floating point general-purpose registers S0~S31 and a special FPSCR register (Floating point status and control register). + +## Operating Scheme and Privilege Level + +Cortex-M introduces the concept of operation scheme and privilege level, which are thread mode and processing mode respectively. If it enters exception or interrupt processing, it enters processing mode, otherwise it is thread mode. + + + +Cortex-M has two running levels, privilege-level and user-level. Thread mode can work at both privilege-level and user-level, while processing mode always works at the privilege-level and can be controlled by the CONTROL special register. The switching of different working modes is as shown in the figure above. + +Cortex-M's stack register SP corresponds to two physical registers MSP and PSP, MSP is the main stack, PSP is the process stack. Processing mode always uses MSP as the stack; thread mode can choose to use MSP or PSP as the stack, also controlled through special register CONTROL. After reset, Cortex-M enters thread mode, privilege-level, and uses the MSP stack by default. + +## Nested Vector Interrupt Controller + +The Cortex-M interrupt controller is called NVIC (nested vectored interrupt controller) and supports interrupt nesting. When an interrupt is triggered and the system responds, the processor hardware automatically pushes the context register of the current location of running into the interrupt stack. The registers in this section include the PSR, PC, LR, R12, and R3-R0 registers. + + + +When the system is servicing an interrupt, if a higher priority interrupt is triggered, then the processor will also interrupt the currently running interrupt service routine, and then save the context of the interrupt service program register PSR, PC, LR, R12, R3-R0 to the interrupt stack. + +## PendSV System Call + +PendSV, also known as a suspendable system call, is an exception that can be suspended like a normal interrupt. It is specifically designed to assist the operating system in context switching. PendSV exceptions are initialized as lowest priority exceptions. Each time a context switch is required, the PendSV exception is triggered manually, and the context switch is performed in the PendSV exception handler. The detailed process of operating system context switching using the PendSV mechanism will be illustrated in the next chapter, *Kernel Porting*. + +# RT-Thread Interruption Mechanism + +## Interrupt Vector Table + +The interrupt vector table is the entry point for all interrupt handlers. The following figure shows the Cortex-M serious of interrupt handlers: linking a function (user interrupt service routine) to the interrupt vector in a virtual interrupt vector table. When the interrupt vector corresponds to an interrupt, the hooked user interrupt service routine is called. + + + +On the Cortex-M core, all interrupts are processed using the interrupt vector table which means when an interrupt is triggered, the processor will directly determine which interrupt source it is, and then jump directly to the corresponding fixed location for processing. The interrupt service routines must be placed together at a uniform address (this address must be set to the NVIC interrupt vector offset register). The interrupt vector table is generally defined by an array or given in the start code. Given by the start code is applied by default: + +```c + __Vectors DCD __initial_sp ; Top of Stack + DCD Reset_Handler ; Reset processing function + DCD NMI_Handler ; NMI processing function + DCD HardFault_Handler ; Hard Fault processing function + DCD MemManage_Handler ; MPU Fault processing function + DCD BusFault_Handler ; Bus Fault processing function + DCD UsageFault_Handler ; Usage Fault processing function + DCD 0 ; reserve + DCD 0 ; reserve + DCD 0 ; reserve + DCD 0 ; reserved + DCD SVC_Handler ; SVCall processing function + DCD DebugMon_Handler ; Debug Monitor processing function + DCD 0 ; reserve + DCD PendSV_Handler ; PendSV processing function + DCD SysTick_Handler ; SysTick processing function + +… … + +NMI_Handler PROC + EXPORT NMI_Handler [WEAK] + B . + ENDP +HardFault_Handler PROC + EXPORT HardFault_Handler [WEAK] + B . + ENDP +… … +``` + +Note the [WEAK] after the code, which is the symbol weakening identifier. The symbols before [WEAK] (such as NMI_Handler, HardFault_Handler) will be weakened if the entire code encounters symbols with the same names (for example, NMI_Handler function with the same name), then the code will use symbols that are not weakened (functions with the same name as NMI_Handler), and the code associated with the weakened symbols will be automatically discarded. + +Take the SysTick interrupt as an example. In the system startup code, you need to fill in the SysTick_Handler interrupt entry function, and then implement the function to respond to the SysTick interrupt. The interrupt handler function sample program is as follows: + +```c +void SysTick_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + rt_tick_increase(); + + /* leave interrupt */ + rt_interrupt_leave(); +} +``` + +## Interrupt Processing + +In RT-Thread interrupt management, interrupt handler is divided into three parts: interrupt preamble, user interrupt service routine, and interrupt follow-up procedure, as shown in the following figure: + + + +### Interrupt Preamble + +The main job of interrupt preamble is as follows: + +1) Save the CPU interrupt context. This part is related to the CPU architecture. Different CPU architectures are implemented differently. + +For Cortex-M, this part of work is done automatically by hardware. When an interrupt is triggered and the system responds, the processor hardware automatically pushes the context register of the currently running portion into the interrupt stack. The registers in this section include the PSR, PC, LR, R12, and R3-R0 registers. + +2) Inform the kernel to enter the interrupt state, call the rt_interrupt_enter() function, and add 1 to the global variable rt_interrupt_nest to record the number of levels of interrupt nesting. The code is as follows. + +```c +void rt_interrupt_enter(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + rt_interrupt_nest ++; + rt_hw_interrupt_enable(level); +} +``` + +### User Interrupt Service Routine + +In the user interrupt service routine (ISR), there are two cases. The first case is that no thread switching is performed. In this case, after user interrupt service routine and interrupt subsequent program finished running, it exits and return to the interrupted thread. . + +In another case, thread switching is required during interrupt processing. In this case, the rt_hw_context_switch_interrupt() function is called for context switching. This function is related to the CPU architecture, and different CPU architectures are implemented differently. + +In Cortex-M architecture, the function implementation of rt_hw_context_switch_interrupt() is shown in the following figure. It sets the thread rt_interrupt_to_thread variable that needs to be switched, and then triggers the PendSV exception (PendSV exception is specifically used to assist context switching and is initialized to the lowest level). After the PendSV exception is triggered, the PendSV exception interrupt handler will not be executed immediately as the interrupt processing is still in progress, the PendSV exception interrupt handler will be entered only after the interrupt subsequent program finishes running and exited the interrupt processing. + + + +### Interrupt Follow-up Procedure + +The main work done by interrupt follow-up procedure is: + +1) Inform the kernel to leave the interrupt state and reduce the global variable rt_interrupt_nest by 1 through calling the rt_interrupt_leave() function. The code is as follows. + +```c +void rt_interrupt_leave(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + rt_interrupt_nest --; + rt_hw_interrupt_enable(level); +} +``` + +2) Restore the CPU context before the interrupt. If thread is not switched during the interrupt processing, the CPU context of the *from* thread is restored. If the thread is switched during the interrupt, the CPU context of the *to* thread is restored. This part of the implementation is related to the CPU architecture. Different CPU architectures are implemented differently. The implementation process in the Cortex-M architecture is shown in the following figure. + + + +## Interrupt Nesting + +In the case of interrupt nesting, in the process of executing the interrupt service routine, if a high priority interrupt occurs, the execution of the current interrupt service routine will be interrupted to execute the interrupt service routine of the high priority interrupt. After the processing of the high priority interrupt is completed, the interrupted interrupt service routine is resumed. If thread scheduling is required, the thread context switch will occur when all interrupt handlers finish running, as shown in the following figure. + + + +## Interrupt Stack + +During the interrupt processing, before the system responds to the interrupt, the software code (or processor) needs to save the context of the current thread (usually stored in the thread stack of the current thread), and then call the interrupt service routine for interrupt response and processing. During interrupt processing (essentially calling the user's interrupt service routine function), the interrupt handler function is likely to have its own local variables, which require the corresponding stack space to save, so the interrupt response still needs a stack space as the context to run the interrupt handler. The interrupt stack can be saved in the stack of the interrupted thread. When exiting from the interrupt, the corresponding thread is resumed to be executed. + +The interrupt stack can also be completely separated from the thread stack, that is, when entering the interrupt each time, after the interrupt thread context is saved, it switches to the new interrupt stack and runs independently. When the interrupt exits, the corresponding context is resumed. Using an independent interrupt stack is relatively easy to implement, and it is easier to understand and grasp the thread stack usage (otherwise it must reserve space for the interrupt stack. If the system supports interrupt nesting, you should also consider how much space should be reserved for nested interrupt?). + +RT-Thread adopts interrupt stack that provides independence. When an interrupt occurs, the preprocessor of the interrupt will replace the user's stack pointer into the interrupt stack space reserved by the system in advance, and then restore the user's stack when the interrupt exits. This way, the interrupt does not occupy the stack space of the thread, thereby improving the utilization of the memory space, and as the number of threads increases, the effect of reducing the memory footprint is more obvious. + +There are two stack pointers in the Cortex-M processor core. One is the main stack pointer (MSP) which is the stack pointer by default. It is used before the first thread and in the interrupt and exception handlers. The other is the thread stack pointer (PSP), used in threads. When the interrupt and exception service routine exits, modify the value of the second bit of LR register as 1, and the SP of the thread is switched from MSP to PSP. + +## Processing of the Bottom Half of the Interruption + +RT-Thread does not make any assumptions or restrictions on the processing time required by the interrupt service routine, but like other real-time operating systems or non-real-time operating systems, users need to ensure that all interrupt service routines are completed in the shortest possible time (the interrupt service routine is equivalent to having the highest priority in the system and will preempt all threads to execute first). In the process of interrupt nesting or masking the corresponding interrupt source, the other nested interrupt processing and the next interrupt signal of its own interrupt source will not delayed. + +When an interrupt occurs, the interrupt service routine needs to obtain the corresponding hardware state or data. If the interrupt service routine is to perform simple processing on the state or data, such as a CPU clock interrupt, the interrupt service routine only needs to add one to the system clock variable and then terminate the interrupt service routine. Such interrupts often require relatively short running time. However, for other interrupts, the interrupt service routine needs to perform a series of more time-consuming processing after obtaining the hardware state or data. Usually, the interrupt is divided into two parts, the **top half** and the **bottom half**. In the top half, after getting the hardware state and data, open the blocked interrupt, send a notification to the relevant thread (which can be the semaphore, event, mailbox or message queue provided by RT-Thread), and then end the interrupt service program. Then, the relevant thread, after receiving the notification, further processes the state or data, this part of the processing is called bottom half processing. + +In order to illustrate the implementation of the bottom half processing in RT-Thread, we take a virtual network device receiving network data packets as an example, as shown in the following code. Assume that after receiving the data message, the system analyzes and processes the message is a relatively time consuming process that is much less important than an external interrupt source signal. It can also be processed without masking the interrupt source signal. + +The program in this example creates an nwt thread that will block on the nw_bh_sem signal after it starts to run. Once this semaphore is released, the next nw_packet_parser process will be executed to begin the *Bottom Half* event processing. + +```c +/* + * program list: interrupt bottom half processing example + */ + +/* semaphore used to wake up threads */ +rt_sem_t nw_bh_sem; + +/* thread for data reading and analysis */ +void demo_nw_thread(void *param) +{ + /*First, perform the necessary initialization work on the device. */ + device_init_setting(); + + /*.. other operations..*/ + + /* create a semaphore to respond to Bottom Half events */ + nw_bh_sem = rt_sem_create("bh_sem", 0, RT_IPC_FLAG_FIFO); + + while(1) + { + /* Finally, let demo_nw_thread wait on nw_bh_sem. */ + rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER); + + /* After receiving the semaphore signal, start the real Bottom Half processing. */ + nw_packet_parser (packet_buffer); + nw_packet_process(packet_buffer); + } +} + +int main(void) +{ + rt_thread_t thread; + + /* create processing thread */ + thread = rt_thread_create("nwt",demo_nw_thread, RT_NULL, 1024, 20, 5); + + if (thread != RT_NULL) + rt_thread_startup(thread); +} +``` + +Let's take a look at how Top Half is handled in demo_nw_isr and how Bottom Half is opened, as in the following example. + +```c +void demo_nw_isr(int vector, void *param) +{ + /* When the network device receives the data, it is met with an interrupt exception and starts executing this ISR. */ + /* Start the processing of the Top Half, such as reading the status of the hardware device to determine what kind of interruption occurred. */ + nw_device_status_read(); + + /*.. Some other data operations, etc. ..*/ + + /* Release nw_bh_sem, send a signal to demo_nw_thread, ready to start Bottom Half */ + rt_sem_release(nw_bh_sem); + + /* Then exit the interrupted Top Half section and end the device's ISR */ +} +``` + +As can be seen from the two code snippets of the above example, the interrupt service routine completes the start and end of the interrupt Bottom Half by waiting and releasing a semaphore object. Since the interrupt processing is divided into two parts, Top and Bottom, the interrupt processing becomes an asynchronous process. This part of the system overhead requires the user to seriously consider whether the interrupt service processing time is greater than the time to send notifications to Bottom Half and process when using RT-Thread. + +# RT-Thread Interrupt Management Interface + +In order to isolate the operating system from the underlying exceptions and interrupt hardware, RT-Thread encapsulates interrupts and exceptions into a set of abstract interfaces, as shown in the following figure: + + + +## Mount Interrupt Service Routine + +The system associates the user's interrupt handler with the specified interrupt number. You can call the following interface to mount a new interrupt service routine: + +```c +rt_isr_handler_t rt_hw_interrupt_install(int vector, + rt_isr_handler_t handler, + void *param, + char *name); +``` + +After calling rt_hw_interrupt_install(), when the interrupt source generates an interrupt, the system will automatically call the mounted interrupt service routine. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_hw_interrupt_install() + +|**Parameters **|**Description** | +|----------|--------------------------------------------------| +| vector | vector is the mounted interrupt number | +| handler | newly mounted interrupt service routine | +| param | param is passed as a parameter to the interrupt service routine | +| name | name of the interrupt | +|**Return**| —— | +| return | the handle of the interrupt service routine mounted before the interrupt service routine was mounted | + +>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4. + +The interrupt service routine is a kind of runtime environment that requires special attention. It runs in a non-threaded execution environment (generally a special operating mode of the chip (privileged mode)). In this runtime environment, the current thread cannot be suspended because the current thread does not exist. During the execution of related operations, information similar to print prompt information will appear, "Function [abc_func] shall not used in ISR", meaning a function that should not be called in the interrupt service routine. + +## Interrupt Source Management + +Usually before the ISR is ready to process an interrupt signal, we need to mask the interrupt source and open the previously blocked interrupt source in time after the ISR finishes processing the status or data. + +Masking the interrupt source ensures that the hardware state or data will not be disturbed during the following processing. The following function interface can be called: + +```c +void rt_hw_interrupt_mask(int vector); +``` + +After the rt_hw_interrupt_mask function interface is called, the corresponding interrupt will be masked (usually when this interrupt is triggered, the interrupt status register will change accordingly, but will not be sent to the processor for processing). The following table describes the input parameters for this function: + +Input parameters of rt_hw_interrupt_mask() + +|**Parameters**|**Description** | +|----------|----------------| +| vector | interrupt number to be masked | + +>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4. + +In order to avoid losing the hardware interrupt signal as much as possible, the following function interface can be called to enable the blocked interrupt source: + +```c +void rt_hw_interrupt_umask(int vector); +``` + +After the rt_hw_interrupt_umask function interface is called, if the interrupt (and corresponding peripheral) is configured correctly, after the interrupt is triggered, it will be sent to the processor for processing. The following table describes the input parameters for this function: + +Input parameters of rt_hw_interrupt_umask() + +|**Parameters**|**Description** | +|----------|--------------------| +| vector | enable the blocked interrupt number | + +>This API does not appear in every migration branch. For example, there is usually no such API in the migration branch of Cortex-M0/M3/M4. + +## Global Interrupt Switch + +The global interrupt switch, also known as the interrupt lock, is the easiest way to disable multi-threaded access to critical sections by shutting down the interrupts to ensure that the current thread is not interrupted by other events (because the entire system no longer responds to those external events that could trigger a thread rescheduling), that is, the current thread will not be preempted unless the thread voluntarily gives up control of the processor. When you need to shut off the interrupt of the entire system , you can call the following function interface: + +```c +rt_base_t rt_hw_interrupt_disable(void); +``` + +The following table describes the return values for this function: + + Return value of rt_hw_interrupt_disable() + +|**Return**|**Description** | +|----------|---------------------------------------------| +| Interrupt Status | interrupt status before the function rt_hw_interrupt_disable runs | + +To resume interrupt can also be understood as turn on an interrupt. The rt_hw_interrupt_enable() function is used to "enable" interrupts, which resumes the interrupt state before the rt_hw_interrupt_disable() function is called. If the interrupt state is turned off before the rt_hw_interrupt_disable() function is called, then the interrupt state is still turned off after calling this function. Resuming interrupts are often used in pairs with turning off interrupts. The function interface called is as follows: + +```c +void rt_hw_interrupt_enable(rt_base_t level); +``` + +The following table describes the input parameters for this function: + + Input parameters for rt_hw_interrupt_enable() + +|**Parameters**|**Description** | +|----------|---------------------------------------------| +| level | the interrupt status returned by the previous rt_hw_interrupt_disable | + +1) The method of using the interrupt lock to operate the critical section can be applied to any occasion, and other types of synchronization methods are implemented relying on the interrupt lock. It can be said that the interrupt lock is the most powerful and efficient synchronization method. The main problem with using interrupt locks is that the system will no longer respond to any interrupts during the interrupt shutdown and will not be able to respond to external events. Therefore, the impact of the interrupt lock on the real-time system is very large. When used improperly, the system will be completely non-real-time (may cause the system to completely deviate from the required time requirement); when used properly, it will become a fast, efficient synchronization. + +For example, to ensure that a line of code (such as assignments) is running mutually exclusively , the quickest way is to use interrupt locks instead of semaphores or mutexes: + +```c + /* turn off the interrupt */ + level = rt_hw_interrupt_disable(); + a = a + value; + /* resume interrupt */ + rt_hw_interrupt_enable(level); +``` + +When using an interrupt lock, you need to ensure that the interrupt is turned off for a very short time, such as a = a + value in the above code; you can also switch to another method, such as using semaphores: + +```c + /* get semaphore lock */ + rt_sem_take(sem_lock, RT_WAITING_FOREVER); + a = a + value; + /* release the semaphore lock */ + rt_sem_release(sem_lock); +``` + +In the implementation of rt_sem_take and rt_sem_release, this code already has the behavior of using interrupt locks to protect semaphore internal variables, so for operations such as a = a + value;, it is more concise and fast to use interrupt locks. + +2) The function rt_base_t rt_hw_interrupt_disable(void) and the function void rt_hw_interrupt_enable(rt_base_t level) generally need to be used in pairs to ensure correct interrupt status. + +In RT-Thread, the API for switching global interrupts supports multi-level nesting. The code for simple nested interrupts is shown in the following code: + +Simple nested interrupt use + +```c +#include <rthw.h> + +void global_interrupt_demo(void) +{ + rt_base_t level0; + rt_base_t level1; + + /* The global interrupt is turned off for the first time. The global interrupt status before being turned off may be turned on or off. */ + level0 = rt_hw_interrupt_disable(); + /* The global interrupt is turned off for the second time. The global interrupt status before being turned off may be turned on or off. */ + level1 = rt_hw_interrupt_disable(); + + do_something(); + + /* Resume the global interrupt to the state before the second turn-off, so the global interrupt is still turned off after this enable. */ + rt_hw_interrupt_enable(level1); + /* Resume the global interrupt to the state before the first turn-off, so the global interrupt status can be on or off. */ + rt_hw_interrupt_enable(level0); +} +``` + +This feature can bring great convenience to the development of the code. For example, if interrupt is turned off in a function, call some sub-functions and then turn on the interrupt. There may also be code for interrupt switch in these subfunctions. Since the API for global interrupts allows the use of nest, users do not need to do special processing for this code. + +## Interrupt Notification + +When the entire system is interrupted by an interrupt and enters the interrupt handler function, it needs to inform the kernel that it has entered the interrupt state. In this case, the following interfaces can be used: + +```c +void rt_interrupt_enter(void); +void rt_interrupt_leave(void); +``` + +These two interfaces are used respectively in the interrupt preamble and interrupt follow-up procedures, and will both modify the values of rt_interrupt_nest (interrupt nesting depth): + +Whenever an interrupt is entered, the rt_interrupt_enter() function can be called to notify the kernel that it has entered the interrupt state and increased the interrupt nesting depth (execute rt_interrupt_nest++); + +Whenever an interrupt is exited, the rt_interrupt_leave() function can be called to notify the kernel that it has exited the interrupt state and reduced the interrupt nesting depth (execute rt_interrupt_nest--). Be careful not to call these two interface functions in applications. + +The role of using rt_interrupt_enter/leave() is that in the interrupt service routine, if a kernel-related function (such as releasing a semaphore) is called, the kernel can be adjusted in time according to the current interrupt status. For example, if a semaphore is released in the interrupt and a thread is awakened, but the current system is found to be in the interrupt context, then Interrupt during thread switching should be implemented during the thread switching, instead of switching immediately . + +However, if the interrupt service routine does not call kernel-related functions (release semaphores, etc.), you may not call the rt_interrupt_enter/leave() function at this time. + +In the upper application, the rt_interrupt_get_nest() interface is called when the kernel needs to know that it has entered the interrupt state or the currently nested interrupt depth. It will return rt_interrupt_nest. as follows: + +```c +rt_uint8_t rt_interrupt_get_nest(void); +``` + +The following table describes the return value of rt_interrupt_get_nest() + +|**Return**|**Description** | +|----------|--------------------------------| +| 0 | the current system is not in an interrupt context | +| 1 | the current system is in an interrupt context | +| Bigger Than 1 | current interrupt nesting level | + +# Interrupt and Polling + +When the drive peripheral is working, whether the programming mode is triggered by interrupt mode or polling mode is often the first problem to be considered by the driver developer, and there is a difference between the real-time operating system and the time-sharing operating system when it comes to this problem. Because the polling mode itself adopts the sequential execution mode: corresponding processing is done after finding corresponding event. Therefore, the polling mode is relatively simple and clear in terms of implementation. For example, to write data to the serial port, the program code writes the next data only when the serial controller finishes writing a data (otherwise the data is discarded). The corresponding code can look like this: + +```c +/* polling mode writes data to the serial port */ + while (size) + { + /* Determine if the data in the UART peripheral is sent. */ + while (!(uart->uart_device->SR & USART_FLAG_TXE)); + /* Send the next data when all data has been sent. */ + uart->uart_device->DR = (*ptr & 0x1FF); + + ++ptr; --size; + } +``` + +In the real-time system, the polling mode may be very problematic, because in a real-time operating system, when a program is continuously executed (when polling), the thread it is running will always run, and the thread with lower priority will not be running. In a time-sharing system, it will be the opposite. There is almost no difference in priority. You can run this program in one time slice and then run another program on another slice. + +So generally, in real-time systems, interrupt mode is mostly used to drive peripherals. When the data has arrived, the relevant processing threads are awoken by the interrupt, and then the subsequent actions are executed . For example, some serial peripherals that carry FIFO (FIFO queue with a certain amount of data) can be written as shown below: + + + +The thread first writes data to the serial port's FIFO. When the FIFO is full, the thread actively suspends. The serial controller continuously fetches data from the FIFO and sends it out at a configured baud rate (for example, 115200 bps). When all data in the FIFO is sent, an interrupt is triggered to the processor; when the interrupt service routine is executed, the thread can be awaken. Here is an example of a FIFO type device. In reality, there are also DMA type devices with similar principles. + +For low-speed devices, this mode is very good because the processor can run other threads before the serial peripheral sends the data in the FIFO which improves the overall operating efficiency of the system. (Even for time-sharing systems, such a mode is very necessary.) But for some high-speed devices, such as when the transmission speed reaches 10Mbps, assuming that the amount of data sent at one time is 32 bytes, we can calculate the time required to send such a piece of data: (32 X 8) X 1/10Mbps = 25us. When data needs to be transmitted continuously, the system will trigger an interrupt after 25us to wake up the upper thread to continue the next transmission. Suppose the system's thread switching time is 8us, (usually the real-time operating system's thread context switching only takes a few us) then when the entire system is running, the data bandwidth utilization will be only 25/(25+8) = 75.8%. However, with polling mode, the data bandwidth utilization rate may reach 100%. This is also why people generally think that the data throughput in the real-time system is insufficient. The system overhead is consumed in the thread switching. (some real-time systems may even use the bottom half processing and hierarchical interrupt processing as described earlier in this chapter which means the time overhead of interrupting to the sending thread is lengthened and the efficiency will be further reduced). + +Through the above calculation process, we can see some of the key factors: the smaller the amount of transmitted data, the faster the transmission speed, and the greater the impact on data throughput. Ultimately, it depends on how often the system generates interrupts. When a real-time system wants to increase data throughput, there are several ways that can be considered: + +1) Increase the length of each data volume for each transmission, and try to let the peripherals send as much data as possible every time; + +2) Change the interrupt mode to polling mode if necessary. At the same time, in order to solve the problem that the processor is always preempted with polling mode and other low-priority threads cannot be operated, the priority of the polling thread can be lowered accordingly. + +# Global Interrupt Switch Usage Example + +This is an interrupted application routine: when multiple threads access the same variable, use the switch global interrupt to protect the variable, as shown in the following code: + +Use switch interrupts to access global variables + +```c +#include <rthw.h> +#include <rtthread.h> + +#define THREAD_PRIORITY 20 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* global variables accessed simultaneously */ +static rt_uint32_t cnt; +void thread_entry(void *parameter) +{ + rt_uint32_t no; + rt_uint32_t level; + + no = (rt_uint32_t) parameter; + while (1) + { + /* turn off glocal interrupt */ + level = rt_hw_interrupt_disable(); + cnt += no; + /* resume glocal interrupt */ + rt_hw_interrupt_enable(level); + + rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt); + rt_thread_mdelay(no * 10); + } +} + +/* user application entry */ +int interrupt_sample(void) +{ + rt_thread_t thread; + + /* create t1 thread */ + thread = rt_thread_create("thread1", thread_entry, (void *)10, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (thread != RT_NULL) + rt_thread_startup(thread); + + + /* create t2 thread */ + thread = rt_thread_create("thread2", thread_entry, (void *)20, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (thread != RT_NULL) + rt_thread_startup(thread); + + return 0; +} + +/* export to the msh command list */ +MSH_CMD_EXPORT(interrupt_sample, interrupt sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >interrupt_sample +msh >protect thread[10]'s counter is 10 +protect thread[20]'s counter is 30 +protect thread[10]'s counter is 40 +protect thread[20]'s counter is 60 +protect thread[10]'s counter is 70 +protect thread[10]'s counter is 80 +protect thread[20]'s counter is 100 +protect thread[10]'s counter is 110 +protect thread[10]'s counter is 120 +protect thread[20]'s counter is 140 +… +``` + +>Since shutting down the global interrupt will cause the entire system to fail to respond to the interrupt, when using the global interrupt as a means of exclusive access to the critical section, it is necessary to ensure that the global interrupt is very short, such as the time to run several machine instructions. + diff --git a/documentation/3.kernel/kernel-porting/figures/10pendsv.png b/documentation/3.kernel/kernel-porting/figures/10pendsv.png Binary files differnew file mode 100644 index 0000000000..95e02834b5 --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10pendsv.png diff --git a/documentation/3.kernel/kernel-porting/figures/10stack.png b/documentation/3.kernel/kernel-porting/figures/10stack.png Binary files differnew file mode 100644 index 0000000000..eb3e105156 --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10stack.png diff --git a/documentation/3.kernel/kernel-porting/figures/10switch.png b/documentation/3.kernel/kernel-porting/figures/10switch.png Binary files differnew file mode 100644 index 0000000000..b2cb9cc18c --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10switch.png diff --git a/documentation/3.kernel/kernel-porting/figures/10switch2.png b/documentation/3.kernel/kernel-porting/figures/10switch2.png Binary files differnew file mode 100644 index 0000000000..293a78ad15 --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10switch2.png diff --git a/documentation/3.kernel/kernel-porting/figures/10ths_env1.png b/documentation/3.kernel/kernel-porting/figures/10ths_env1.png Binary files differnew file mode 100644 index 0000000000..60438250aa --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10ths_env1.png diff --git a/documentation/3.kernel/kernel-porting/figures/10ths_env2.png b/documentation/3.kernel/kernel-porting/figures/10ths_env2.png Binary files differnew file mode 100644 index 0000000000..4c4ec3b1ef --- /dev/null +++ b/documentation/3.kernel/kernel-porting/figures/10ths_env2.png diff --git a/documentation/3.kernel/kernel-porting/kernel-porting.md b/documentation/3.kernel/kernel-porting/kernel-porting.md new file mode 100644 index 0000000000..66ba67870a --- /dev/null +++ b/documentation/3.kernel/kernel-porting/kernel-porting.md @@ -0,0 +1,380 @@ +@page page_kernel_porting Kernel Porting + +After learning the previous chapters, everyone has a better understanding of RT-Thread, but many people are not familiar with how to port the RT-Thread kernel to different hardware platforms. Kernel porting refers to the RT-Thread kernel running on different chip architectures and different boards. It can have functions such as thread management and scheduling, memory management, inter-thread synchronization and communication, and timer management. Porting can be divided into two parts: CPU architecture porting and BSP (Board support package) porting . + +This chapter will introduce CPU architecture porting and BSP porting. The CPU architecture porting part will be introduced in conjunction with the Cortex-M CPU architecture. Therefore, it is necessary to review "Cortex-M CPU Architecture Foundation" in the previous chapter @ref page_interrupt_management. After reading this chapter, how to complete the RT-Thread kernel porting will be learned. + +# CPU Architecture Porting + +There are many different CPU architectures in the embedded world, for example, Cortex-M, ARM920T, MIPS32, RISC-V, etc. In order to enable RT-Thread to run on different CPU architecture chips, RT-Thread provides a libcpu abstraction layer to adapt to different CPU architectures. The libcpu layer provides unified interfaces to the kernel, including global interrupt switches, thread stack initialization, context switching, and more. + +RT-Thread's libcpu abstraction layer provides a unified set of CPU architecture porting interfaces downwards. This part of the interface includes global interrupt switch functions, thread context switch functions, clock beat configuration and interrupt functions, Cache, and so on. The following table shows the interfaces and variables that the CPU architecture migration needs to implement. + +libcpu porting related API + +| **Functions and Variables** | **Description** | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| rt_base_t rt_hw_interrupt_disable(void); | disable global interrupt | +| void rt_hw_interrupt_enable(rt_base_t level); | enable global interrupt | +| rt_uint8_t \*rt_hw_stack_init(void \*tentry, void \*parameter, rt_uint8_t \*stack_addr, void \*texit); | The initialization of the thread stack, the kernel will call this function during thread creation and thread initialization. | +| void rt_hw_context_switch_to(rt_uint32 to); | Context switch without source thread, which is called when the scheduler starts the first thread, and is called in the signal. | +| void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); | Switch from *from* thread to *to* thread, used for switch between threads. | +| void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); | Switch from *from* thread to *to* thread, used for switch in interrupt. | +| rt_uint32_t rt_thread_switch_interrupt_flag; | A flag indicating that a switch is needed in the interrupt. | +| rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; | Used to save *from* and *to* threads when the thread is context switching. | + +## Implement Global Interrupt Enable/Disable + +Regardless of kernel code or user code, there may be some variables that need to be used in multiple threads or interrupts. If there is no corresponding protection mechanism, it may lead to critical section problems. In order to solve this problem, RT-Thread provides a series of inter-thread synchronization and communication mechanism. But these mechanisms require the global interrupt enable/disable function provided in libcpu. They are, respectively: + +```c +/* disable global interrupt */ +rt_base_t rt_hw_interrupt_disable(void); + +/* enable global interrupt */ +void rt_hw_interrupt_enable(rt_base_t level); +``` + +The following describes how to implement these two functions on the Cortex-M architecture. As mentioned earlier, the Cortex-M implements the CPS instruction in order to achieve fast switch interrupts, which can be used here. + +```c +CPSID I ;PRIMASK=1, ; disable global interrupt +CPSIE I ;PRIMASK=0, ; enable global interrupt +``` + +### Disable Global Interrupt + +The functions that need to be done in order in the rt_hw_interrupt_disable() function are: + +1). Save the current global interrupt status and use the status as the return value of the function. + +2). Disable the global interrupt. + +Based on MDK, the global interrupt disabled function on the Cortex-M core, is shown in the following code: + +Disable global interrupt + +```c +;/* +; * rt_base_t rt_hw_interrupt_disable(void); +; */ +rt_hw_interrupt_disable PROC ;PROC pseudoinstruction definition function + EXPORT rt_hw_interrupt_disable ;EXPORT output defined function, similar to C language extern + MRS r0, PRIMASK ;read the value of the PRIMASK register to the r0 register + CPSID I ;disable global interrupt + BX LR ;function renturn + ENDP ;ENDP end of function +``` + +The above code first uses the MRS instruction to save the value of the PRIMASK register to the r0 register, then disable the global interrupt with the "CPSID I" instruction, and finally returns with the BX instruction. The data stored by r0 is the return value of the function. Interrupts can occur between the “MRS r0, PRIMASK” instruction and “CPSID I”, which does not cause a global interrupt status disorder. + +There are different conventions for different CPU architectures regarding how registers are managed during function calls and in interrupt handlers. A more detailed introduction to the use of registers for Cortex-M can be found in the official ARM manual, "*Procedure Call Standard for the ARM ® Architecture*." + +### Enable Global Interrupt + +In `rt_hw_interrupt_enable(rt_base_t level)`, the variable *level* is used as the state to be restored, overriding the global interrupt status of the chip. + +Based on MDK, implementation on the Cortex-M core enables a global interrupt, as shown in the following code: + +Enable global interrupt + +```c +;/* +; * void rt_hw_interrupt_enable(rt_base_t level); +; */ +rt_hw_interrupt_enable PROC ; PROC pseudoinstruction definition function + EXPORT rt_hw_interrupt_enable ; EXPORT output defined function, similar to "extern" in C language + MSR PRIMASK, r0 ; write the value of the r0 register to the PRIMASK register + BX LR ; function renturn + ENDP ; ENDP end of function +``` + +The above code first uses the MSR instruction to write the value register of r0 to the PRIMASK register, thus restoring the previous interrupt status. + +## Implement Thread Stack Initialization + +When dynamically creating threads and initializing threads, the internal thread initialization function *_rt_thread_init()* is used. The _rt_thread_init() function calls the stack initialization function *rt_hw_stack_init()*, which manually constructs a context in the stack initialization function. The context will be used as the initial value for each thread's first execution. The layout of the context on the stack is shown below: + + + +The following code is the stack initialization code: + +Build a context on the stack + +```c +rt_uint8_t *rt_hw_stack_init(void *tentry, + void *parameter, + rt_uint8_t *stack_addr, + void *texit) +{ + struct stack_frame *stack_frame; + rt_uint8_t *stk; + unsigned long i; + + /* align the incoming stack pointer */ + stk = stack_addr + sizeof(rt_uint32_t); + stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); + stk -= sizeof(struct stack_frame); + + /* obtain the pointer to the stack frame of the context */ + stack_frame = (struct stack_frame *)stk; + + /* set the default value of all registers to 0xdeadbeef */ + for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++) + { + ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef; + } + + /* save the first parameter in the r0 register according to the ARM APCS calling standard */ + stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; + /* set the remaining parameter registers to 0 */ + stack_frame->exception_stack_frame.r1 = 0; /* r1 register */ + stack_frame->exception_stack_frame.r2 = 0; /* r2 register */ + stack_frame->exception_stack_frame.r3 = 0; /* r3 register */ + /* set IP (Intra-Procedure-call scratch register.) to 0 */ + stack_frame->exception_stack_frame.r12 = 0; /* r12 register */ + /* save the address of the thread exit function in the lr register */ + stack_frame->exception_stack_frame.lr = (unsigned long)texit; + /* save the address of the thread entry function in the pc register */ + stack_frame->exception_stack_frame.pc = (unsigned long)tentry; + /* Set the value of psr to 0x01000000L, which means that the default switch is Thumb mode. */ + stack_frame->exception_stack_frame.psr = 0x01000000L; + + /* return the stack address of the current thread */ + return stk; +} +``` + +## Implement Context Switching + +In different CPU architectures, context switches between threads and context switches from interrupts to context, the register portion of the context may be different or the same. In Cortex-M, context switching is done uniformly using PendSV exceptions, and there is no difference in the switching parts. However, in order to adapt to different CPU architectures, RT-Thread's libcpu abstraction layer still needs to implement three thread switching related functions: + +1) rt_hw_context_switch_to(): no source thread, switching to the target thread, which is called when the scheduler starts the first thread. + +2) rt_hw_context_switch():In a threaded environment, switch from the current thread to the target thread. + +3) rt_hw_context_switch_interrupt ():In the interrupt environment, switch from the current thread to the target thread. + +There are differences between switching in a threaded environment and switching in an interrupt environment. In the threaded environment, if the rt_hw_context_switch() function is called, the context switch can be performed immediately; in the interrupt environment, it needs to wait for the interrupt handler to complete processing the functions before switching. + +Due to this difference, the implementation of rt_hw_context_switch() and rt_hw_context_switch_interrupt() is not the same on platforms such as ARM9. If the thread's schedule is triggered in the interrupt handler, rt_hw_context_switch_interrupt() is called in the dispatch function to trigger the context switch. After the interrupt handler has processed the interrupt, check the rt_thread_switch_interrupt_flag variable before the schedule exits. If the value of the variable is 1, the context switch of the thread is completed according to the rt_interrupt_from_thread variable and the rt_interrupt_to_thread variable. + +In the Cortex-M processor architecture, context switching can be made more compact based on the features of automatic partial push and PendSV. + +Context switching between threads, as shown in the following figure: + + + +The hardware automatically saves the PSR, PC, LR, R12, R3-R0 registers of the *from* thread before entering the PendSV interrupt, then saves the R11~R4 registers of the *from* thread in PendSV, and restores the R4~R11 registers of the *to* thread, and finally the hardware automatically restores the R0~R3, R12, LR, PC, PSR registers of the *to* thread after exiting the PendSV interrupt. + +The context switch from interrupt to thread can be represented by the following figure: + + + +The hardware automatically saves the PSR, PC, LR, R12, R3-R0 registers of the *from* thread before entering the interrupt, and then triggers a PendSV exception. R11~R4 registers of the *from* thread are saved and R4~R11 registers of the *to* thread are restored in the PendSV exception handler. Finally, the hardware automatically restores the R0~R3, R12, PSR, PC, LR registers of the *to* thread after exiting the PendSV interrupt. + +Obviously, in the Cortex-M kernel, the rt_hw_context_switch() and rt_hw_context_switch_interrupt() have same functions, which is finishing saving and replying the remaining contexts in PendSV. So we just need to implement a piece of code to simplify the porting. + +### Implement rt_hw_context_switch_to() + +rt_hw_context_switch_to() has only the target thread and no source thread. This function implements the function of switching to the specified thread. The following figure is a flowchart: + + + +The rt_hw_context_switch_to() implementation on the Cortex-M3 kernel (based on MDK), as shown in the following code: + +MDK version rt_hw_context_switch_to() implementation + +```c +;/* +; * void rt_hw_context_switch_to(rt_uint32 to); +; * r0 --> to +; * this fucntion is used to perform the first thread switch +; */ +rt_hw_context_switch_to PROC + EXPORT rt_hw_context_switch_to + ; r0 is a pointer pointing to the SP member of the thread control block of the to thread + ; save the value of the r0 register to the rt_interrupt_to_thread variable + LDR r1, =rt_interrupt_to_thread + STR r0, [r1] + + ; set the from thread to empty, indicating that no context is needed to save from + LDR r1, =rt_interrupt_from_thread + MOV r0, #0x0 + STR r0, [r1] + + ; set the flag to 1, indicating that switching is required, this variable will be cleared when switching in the PendSV exception handler + LDR r1, =rt_thread_switch_interrupt_flag + MOV r0, #1 + STR r0, [r1] + + ; set PendSV exception priority to lowest priority + LDR r0, =NVIC_SYSPRI2 + LDR r1, =NVIC_PENDSV_PRI + LDR.W r2, [r0,#0x00] ; read + ORR r1,r1,r2 ; modify + STR r1, [r0] ; write-back + + ; trigger PendSV exception (PendSV exception handler will be executed) + LDR r0, =NVIC_INT_CTRL + LDR r1, =NVIC_PENDSVSET + STR r1, [r0] + + ; abandon the stack from chip startup to before the first context switch, set the value of the MSP as when it is started + LDR r0, =SCB_VTOR + LDR r0, [r0] + LDR r0, [r0] + MSR msp, r0 + + ; enable global interrupts and global exceptions. After enabling, the PendSV exception handler will be entered. + CPSIE F + CPSIE I + + ; will not execute to here + ENDP +``` + +### Implement rt_hw_context_switch()/ rt_hw_context_switch_interrupt() + +The function rt_hw_context_switch() and the function rt_hw_context_switch_interrupt() have two parameters, the *from* thread and the *to* thread. They implement the function to switch from the *from* thread to the *to* thread. The following figure is a specific flow chart: + + + + + +The rt_hw_context_switch() and rt_hw_context_switch_interrupt() implementations on the Cortex-M3 kernel (based on MDK) are shown in the following code: + +Implement rt_hw_context_switch()/rt_hw_context_switch_interrupt() + +```c +;/* +; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); +; * r0 --> from +; * r1 --> to +; */ +rt_hw_context_switch_interrupt + EXPORT rt_hw_context_switch_interrupt +rt_hw_context_switch PROC + EXPORT rt_hw_context_switch + + ; check if the rt_thread_switch_interrupt_flag variable is 1 + ; skip updating the contents of the thread from if the variable is 1 + LDR r2, =rt_thread_switch_interrupt_flag + LDR r3, [r2] + CMP r3, #1 + BEQ _reswitch + ; set the rt_thread_switch_interrupt_flag variable to 1 + MOV r3, #1 + STR r3, [r2] + + ; update the rt_interrupt_from_thread variable from parameter r0 + LDR r2, =rt_interrupt_from_thread + STR r0, [r2] + +_reswitch + ; update the rt_interrupt_to_thread variable from parameter r1 + LDR r2, =rt_interrupt_to_thread + STR r1, [r2] + + ; trigger PendSV exception, will enter the PendSV exception handler to complete the context switch + LDR r0, =NVIC_INT_CTRL + LDR r1, =NVIC_PENDSVSET + STR r1, [r0] + BX LR +``` + +### Implement PendSV Interrupt + +In Cortex-M3, the PendSV interrupt handler is PendSV_Handler(). The actual thread switching is done in PendSV_Handler(). The following figure is a specific flow chart: + + + +The following code is a PendSV_Handler implementation: + +```c +; r0 --> switch from thread stack +; r1 --> switch to thread stack +; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack +PendSV_Handler PROC + EXPORT PendSV_Handler + + ; disable global interrupt + MRS r2, PRIMASK + CPSID I + + ; check if the rt_thread_switch_interrupt_flag variable is 0 + ; if it is zero, jump to pendsv_exit + LDR r0, =rt_thread_switch_interrupt_flag + LDR r1, [r0] + CBZ r1, pendsv_exit ; pendsv already handled + + ; clear the rt_thread_switch_interrupt_flag variable + MOV r1, #0x00 + STR r1, [r0] + + ; check the rt_thread_switch_interrupt_flag variable + ; if it is 0, the context save of the from thread is not performed. + LDR r0, =rt_interrupt_from_thread + LDR r1, [r0] + CBZ r1, switch_to_thread + + ; save the context of the from thread + MRS r1, psp ; obtain the stack pointer of the thread from + STMFD r1!, {r4 - r11} ; save r4~r11 to the thread's stack + LDR r0, [r0] + STR r1, [r0] ; update the SP pointer of the thread's control block + +switch_to_thread + LDR r1, =rt_interrupt_to_thread + LDR r1, [r1] + LDR r1, [r1] ; obtain the stack pointer of the thread to + + LDMFD r1!, {r4 - r11} ; restore the register value of the thread to in the stack of the thread + MSR psp, r1 ; update the value of r1 to psp + +pendsv_exit + ; restore global interrupt status + MSR PRIMASK, r2 + + ; modify bit 2 of the lr register to ensure that the process uses the PSP stack pointer + ORR lr, lr, #0x04 + ; exit interrupt function + BX lr + ENDP +``` + +## Impalement OS Tick + +With the basics of switching global interrupts and context switching, RTOS can perform functions such as creating, running, and scheduling threads. With OS Tick, RT-Thread can schedule time-to-roll rotations for threads of the same priority, implementing timer functions, implementing rt_thread_delay() delay functions, and so on. + +In oder to finish the porting of libcpu, we need to ensure that the rt_tick_increase() function is called periodically during the clock tick interrupt. The call cycle depends on the value of the RT_TICK_PER_SECOND macro of rtconfig.h. + +In Cortex M, the OS tick can be implemented by SysTick's interrupt handler. + +```c +void SysTick_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + rt_tick_increase(); + + /* leave interrupt */ + rt_interrupt_leave(); +} +``` + +# BSP Porting + +In a practical project, for the same CPU architecture, different boards may use the same CPU architecture, carry different peripheral resources, and complete different products, so we also need to adapt to the board. RT-Thread provides a BSP abstraction layer to fit boards commonly seen. If you want to use the RT-Thread kernel on a board, in addition to the need to have the corresponding chip architecture porting, you need to port the corresponding board, that is, implement a basic BSP. The job is to establish a basic environment for the operating system to run. The main tasks that need to be completed are: + +1) Initialize the CPU internal registers and set the RAM operation timing. + +2) Implement clock driver and interrupt controller driver and interrupt management. + +3) Implement UART and GPIO driver. + +4) Initialize the dynamic memory heap to implement dynamic heap memory management. + + + diff --git a/documentation/3.kernel/memory/figures/08Memory_distribution.png b/documentation/3.kernel/memory/figures/08Memory_distribution.png Binary files differnew file mode 100644 index 0000000000..580095fbf8 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08Memory_distribution.png diff --git a/documentation/3.kernel/memory/figures/08heap_ops.png b/documentation/3.kernel/memory/figures/08heap_ops.png Binary files differnew file mode 100644 index 0000000000..0cabb23884 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08heap_ops.png diff --git a/documentation/3.kernel/memory/figures/08memheap.png b/documentation/3.kernel/memory/figures/08memheap.png Binary files differnew file mode 100644 index 0000000000..b27c818733 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08memheap.png diff --git a/documentation/3.kernel/memory/figures/08mempool.png b/documentation/3.kernel/memory/figures/08mempool.png Binary files differnew file mode 100644 index 0000000000..c045d1acda --- /dev/null +++ b/documentation/3.kernel/memory/figures/08mempool.png diff --git a/documentation/3.kernel/memory/figures/08mempool_ops.png b/documentation/3.kernel/memory/figures/08mempool_ops.png Binary files differnew file mode 100644 index 0000000000..4e73043558 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08mempool_ops.png diff --git a/documentation/3.kernel/memory/figures/08mempool_work.png b/documentation/3.kernel/memory/figures/08mempool_work.png Binary files differnew file mode 100644 index 0000000000..ee9d1ab39f --- /dev/null +++ b/documentation/3.kernel/memory/figures/08mempool_work.png diff --git a/documentation/3.kernel/memory/figures/08slab.png b/documentation/3.kernel/memory/figures/08slab.png Binary files differnew file mode 100644 index 0000000000..1b2a28dd19 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08slab.png diff --git a/documentation/3.kernel/memory/figures/08smem_work.png b/documentation/3.kernel/memory/figures/08smem_work.png Binary files differnew file mode 100644 index 0000000000..397ddf070c --- /dev/null +++ b/documentation/3.kernel/memory/figures/08smem_work.png diff --git a/documentation/3.kernel/memory/figures/08smem_work2.png b/documentation/3.kernel/memory/figures/08smem_work2.png Binary files differnew file mode 100644 index 0000000000..533db94719 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08smem_work2.png diff --git a/documentation/3.kernel/memory/figures/08smem_work3.png b/documentation/3.kernel/memory/figures/08smem_work3.png Binary files differnew file mode 100644 index 0000000000..4933c69ce3 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08smem_work3.png diff --git a/documentation/3.kernel/memory/figures/08smem_work4.png b/documentation/3.kernel/memory/figures/08smem_work4.png Binary files differnew file mode 100644 index 0000000000..92544169d0 --- /dev/null +++ b/documentation/3.kernel/memory/figures/08smem_work4.png diff --git a/documentation/3.kernel/memory/memory.md b/documentation/3.kernel/memory/memory.md new file mode 100644 index 0000000000..bebe283616 --- /dev/null +++ b/documentation/3.kernel/memory/memory.md @@ -0,0 +1,684 @@ +@page page_memory_management Memory Management + +In a computing system, there are usually two types of memory space: internal memory space and external memory space. Internal memory can be quickly accessed, its contents can be read to and changed and only an address is required. The contents of internal memory are deleted after each power off. It is what would usually be called RAM (Random Access Memory) and is analogous to the RAM in a desktop computer. On the other hand, external memory has relatively fixed contents, retains data even after power off. It is usually called ROM (Read-Only Memory) and is analogous to the hard disk in a desktop computer. + +In a computer system, variables and intermediate data are generally stored in RAM, and they are only transferred from RAM to CPU for calculation when actually used. The memory size required by some data needs to be determined according to the actual situation during the running of the program, which requires the system to have the ability to dynamically manage the memory space.The user applies to the system when he needs a block of memory space, then the system selects a suitable memory space to allocate to the user. After the user finishes using it, the memory space is released back to the system which enables the system to recycle the memory space. + +This chapter introduces two kinds of memory management methods in RT-Thread, namely dynamic memory heap management and static memory pool management. After studying this chapter, readers will understand the memory management principle and usage of RT-Thread. + +# Memory Management Functional Features + +Because time requirements are very strict in real-time systems, memory management is often much more demanding than in general-purpose operating systems: + +1) Time for allocating memory must be deterministic. The general memory management algorithm needs to find a free memory block that is compatible with the data according to the length of the data to be stored, and then store the data therein. The time it takes to find such a free block of memory is uncertain, but for real-time systems, this is unacceptable. Real-time systems require that the allocation process of the memory block is completed within a predictable certain time, otherwise the response of a real-time task to an external event will become indeterminable. + +2) As memory is constantly being allocated and released, the entire memory area will fragment. This means that instead of one large continuous block, the memory area will have small memory blocks taken up inbetween, limiting the largest possible contiguous block which the code can ask for. For general-purpose systems, this issue can be solved by rebooting the system (once every month or once per few months). However, this solution is unacceptable for embedded systems that need to work continuously in the field work all the year round. + +3) The resource environment of embedded system is also different. Some systems have relatively tight resources, only tens of kilobytes of memory are available for allocation, while some systems have several megabytes of memory. This makes choosing an efficient memory allocation algorithm for these different systems more complicated. + +The RT-Thread operating system provides different memory allocation management algorithms for memory management according to different upper layer applications and system resources. Generally, it can be divided into two categories: memory heap management and memory pool management. Memory heap management is divided into three cases according to specific memory devices: + +The first is allocation management for small memory blocks (small memory management algorithm); + +The second is allocation management for large memory blocks (slab management algorithm); + +The third is allocation management for multiple memory heaps (memheap management algorithm) + +# Memory Heap Management + +Memory heap management is used to manage a contiguous memory space. We introduced the memory distribution of RT-Thread in chapter "Kernel Basics". As shown in the following figure, RT-Thread uses the space at "the end of the ZI segment" to the end of the memory as the memory heap. + + + +If current resources allow it, the memory heap can allocate memory blocks of any size according to the needs of the user. When the user does not need to use these memory blocks, they can be released back to the heap for other applications to allocate and use. In order to meet different needs, the RT-Thread system provides different memory management algorithms, namely small memory management algorithm, slab management algorithm and memheap management algorithm. + +The small memory management algorithm is mainly for system with less resources and with less than 2MB of memory. The slab memory management algorithm mainly provides a fast algorithm similar to multiple memory pool management algorithms when the system resources are rich. In addition to the above, RT-Thread also has a management algorithm for a multi-memory heap, namely the memheap management algorithm. The memheap management algorithm is suitable for where there are multiple memory heaps in the system. It can “paste” multiple memories together to form a large memory heap, which is very easy to use for users. + +Any or none of these memory heap management algorithms can be chosen when the system is running and all algorithms provide the same API interface to the application. + +>Because the memory heap manager needs to meet the security allocation in multi-threaded conditions, which means mutual exclusion between multiple threads needs to be taken into consideration, please do not allocate or release dynamic memory blocks in interrupt service routines, as that may result in the current context being suspended. + +## Small Memory Management Algorithm + +The small memory management algorithm is a simple memory allocation algorithm. Initially, it is a large piece of memory. When a memory block needs to be allocated, the matching memory block is segmented from the large memory block, and then this matching free memory block is returned to the heap management system. Each memory block contains data head for management use through which the used block and the free block are linked by a doubly linked list. + + + +**Use this implementation before 4.1.0** + +As shown in the following figure: + + + +Each memory block (whether it is an allocated memory block or a free memory block) contains a data head, including: + +**1) magic**: Variable (also called magic number). It will be initialized to 0x1ea0 (that is, the English word heap) which is used to mark this memory block as a memory data block for memory management. The variable is not only used to identify that the data block is a memory data block for memory management. It is also a memory protection word: if this area is overridden, it means that the memory block is illegally overridden (normally only the memory manager will operate on this memory). + +**2)used**: Indicates whether the current memory block has been allocated. + +The performance of memory management is mainly reflected in the allocation and release of memory. The small memory management algorithm can be embodied by the following examples. + + + +**4.1.0 and later use this implementation** + +As shown in the following figure: + + + +**Heap Start**: The heap head address stores memory usage information, the heap head address pointer, the heap end address pointer, the minimum heap free address pointer, and the memory size. + +Each memory block (whether it is an allocated memory block or a free memory block) contains a data head, including: + +**pool_ptr**: Small memory object address. If the last bit of the memory block is 1, it is used. If the last bit of the memory block is 0, it is not used. When used, the structure members of the small memory algorithm can be quickly obtained by calc. + + + +As shown in the following figure, the free list pointer lfree initially points to a 32-byte block of memory. When the user thread wants to allocate a 64-byte memory block, since the memory block pointed to by this lfree pointer is only 32 bytes and does not meet the requirements, the memory manager will continue to search for the next memory block. When the next memory block with 128 bytes is found, it meets the requirements of the allocation. Because this memory block is large, the allocator will split the memory block, and the remaining memory block (52 bytes) will remain in the lfree linked list, as shown in the following table which is after 64 bytes is allocated. + + + + + +In addition, a 12-byte data head is reserved for `magic, used` information, and linked list nodes before each memory block are allocated. The address returned to the application is actually the address after 12 bytes of this memory block. The 12-byte data head is the part that the user should never use. (Note: The length of the 12-byte data head will be different depending on the alignment of the system). + +As for releasing, it is the reverse process, but the allocator will check if the adjacent memory blocks are free, and if they are free, the allocator will merge them into one large free memory block. + +## Slab Management Algorithm + +RT-Thread's slab allocator is an optimized memory allocation algorithm for embedded systems based on the slab allocator implemented by DragonFly BSD founder Matthew Dillon. The most primitive slab algorithm is Jeff Bonwick's efficient kernel memory allocation algorithm introduced for the Solaris operating system. + +RT-Thread's slab allocator implementation mainly removes the object construction and destruction process, and only retains the pure buffered memory pool algorithm. The slab allocator is divided into multiple zones according to the size of the object which can also be seen as having a memory pool for each type of object, as shown in the following figure: + + + + + +A zone is between 32K and 128K bytes in size, and the allocator will automatically adjust based on the heap size when the heap is initialized. The zone in the system includes up to 72 objects, which can allocate up to 16K of memory at a time. It will directly allocate from the page allocator if it exceeds 16K. The size of the memory block allocated on each zone is fixed. Zones that can allocate blocks of the same size are linked in a linked list. The zone linked lists of the 72 objects are managed in an array. (zone_array[]). + +Here are the two main operations for the memory allocator: + +**(1) Memory Allocation** + +Assuming a 32-byte memory is allocated, the slab memory allocator first finds the corresponding zone linked list from the linked list head of zone array in accordance with the 32-byte value. If the linked list is empty, assign a new zone to the page allocator and return the first free block of memory from the zone. If the linked list is not empty, a free block must exist in the first zone node in the zone linked list (otherwise it would not have been placed in the linked list), so then take the corresponding free block. If all free memory blocks in the zone are used after the allocation, the allocator needs to remove this zone node from the linked list. + +**(2)Memory Release** + +The allocator needs to find the zone node where the memory block is located, and then link the memory block to the zone's free memory block linked list. If the free linked list of the zone indicates that all the memory blocks of the zone have been released, it means that the zone is completely free. The system will release the fully free zone to the page allocator when the number of free zones in the zone linked list reaches a certain number. + +## memheap Management Algorithm + +The memheap management algorithm is suitable for systems with multiple memory heaps that are not contiguous. Using memheap memory management can simplify the use of multiple memory heaps in the system: when there are multiple memory heaps in the system, the user only needs to initialize multiple needed memheaps during system initialization and turn on the memheap function to attach multiple memheaps (addresses can be discontinuous) for the system's heap allocation. + +>The original heap function will be turned off after memheap is turned on. Both can only be selected by turning RT_USING_MEMHEAP_AS_HEAP on or off. + +The working mechanism of memheap is shown in the figure below. First, add multiple blocks of memory to the memheap_item linked list to attach. The allocation of a memory block starts with allocating memory from default memory heap. When it can not be allocated, memheap_item linked list is looked up, and an attempt is made to allocate a memory block from another memory heap. This process is opaque to the user, who only sees one memory heap. + + + +## Memory Heap Configuration and Initialization + +When using the memory heap, heap initialization must be done at system initialization, which can be done through the following function interface: + +```c +void rt_system_heap_init(void* begin_addr, void* end_addr); +``` + +This function will use the memory space of the parameters begin_addr, end_addr as a memory heap. The following table describes the input parameters for this function: + +Input parameter for rt_system_heap_init() + +|**Parameters** |**Description** | +|------------|--------------------| +| begin_addr | Start address for heap memory area | +| end_addr | End address for heap memory area | + +When using memheap heap memory, you must initialize the heap memory at system initialization, which can be done through the following function interface: + +```c +rt_err_t rt_memheap_init(struct rt_memheap *memheap, + const char *name, + void *start_addr, + rt_uint32_t size) +``` + +If there are multiple non-contiguous memheaps, the function can be called multiple times to initialize it and join the memheap_item linked list. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_memheap_init() + +|**Parameters** |**Description** | +|------------|--------------------| +| memheap | memheap control block | +| name | The name of the memory heap | +| start_addr | Heap memory area start address | +| size | Heap memory size | +|**Return** | —— | +| RT_EOK | Successful | + +## Memory Heap Management + +Operations of the memory heap are as shown in the following figure, including: initialization, application for memory blocks, release of memory. After use, all dynamic memory should be released for future use by other programs. + + + +### Allocate and Release Memory Block + +Allocate a memory block of user-specified size from the memory heap. The function interface is as follows: + +```c +void *rt_malloc(rt_size_t nbytes); +``` + +The rt_malloc function finds a memory block of the appropriate size from the system heap space and returns the available address of the memory block to the user. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_malloc() + +|**Parameters** |**Description** | +|------------------|------------------------------------| +| nbytes | The size of the memory block to be allocated, in bytes | +|**Return** | —— | +| Allocated memory block address | Successful | +| RT_NULL | Fail | + +After the application uses the memory applied from the memory allocator, it must be released in time, otherwise it will cause a memory leak. The function interface for releasing the memory block is as follows: + +```c +void rt_free (void *ptr); +``` + +The rt_free function will return the to-be-released memory back to the heap manager. When calling this function, user needs to pass the to-be-released pointer of the memory block. If it is a null pointer, it returns directly. The following table describes the input parameters for this function: + +Input parameters of rt_free() + +|**Parameters**|**Description** | +|----------|--------------------| +| ptr | to-be-released memory block pointer | + +### Re-allocate Memory Block + +Re-allocating the size of the memory block (increase or decrease) based on the allocated memory block can be done through the following function interface: + +```c +void *rt_realloc(void *rmem, rt_size_t newsize); +``` + +When the memory block is re-allocated, the original memory block data remains the same (in the case of reduction, the subsequent data is automatically truncated). The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_realloc() + +|**Parameters** |**Description** | +|----------------------|--------------------| +| rmem | Point to the allocated memory block | +| newsize | Re-allocated memory size | +|**Return** | —— | +| Re-allocated memory block address | Successful | + +### Allocate Multiple Memory Blocks + +Allocating multiple memory blocks with contiguous memory addresses from the memory heap can be done through the following function interface: + +```c + void *rt_calloc(rt_size_t count, rt_size_t size); +``` + +The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_calloc() + +|**Parameters** |**Description** | +|----------------------------|---------------------------------------------| +| count | Number of memory block | +| size | Size of memory block | +|**Return** | —— | +| Pointer pointing to the first memory block address | Successful, all allocated memory blocks are initialized to zero. | +| RT_NULL | Allocation failed | + +### Set Memory Hook Function + +When allocating memory blocks, user can set a hook function. The function interface called is as follows: + +```c +void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size)); +``` + +The hook function set will callback after the memory allocation. During the callback, the allocated memory block address and size are passed as input parameters. The following table describes the input parameters for this function: + +Input parameters for rt_malloc_sethook() + +|**Parameters**|**Description** | +|----------|--------------| +| hook | Hook function pointer | + +The hook function interface is as follows: + +```c +void hook(void *ptr, rt_size_t size); +``` + +The following table describes the input parameters for the hook function: + +Allocate hook function interface parameters + +|**Parameters**|**Description** | +|----------|----------------------| +| ptr | The allocated memory block pointer | +| size | The size of the allocated memory block | + +When releasing memory, user can set a hook function, the function interface called is as follows: + +```c +void rt_free_sethook(void (*hook)(void *ptr)); +``` + +The hook function set will callback before the memory release is completed. During the callback, the released memory block address is passed in as the entry parameter (the memory block is not released at this time). The following table describes the input parameters for this function: + +Input parameters for rt_free_sethook() + +|**Parameters**|**Description** | +|----------|--------------| +| hook | Hook function pointer | + +The hook function interface is as follows: + +```c +void hook(void *ptr); +``` + +The following table describes the input parameters for the hook function: + +Input parameters of the hook function + +|**Parameters**|**Description** | +|----------|--------------------| +| ptr | Memory block pointer to be released | + +## Memory Heap Management Application Example + +This is an example of a memory heap application. This program creates a dynamic thread that dynamically requests memory and releases it. Each time it apples for more memory, it ends when it can't apply for it, as shown in the following code: + +Memory heap management + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* thread entry */ +void thread1_entry(void *parameter) +{ + int i; + char *ptr = RT_NULL; /* memory block pointer */ + + for (i = 0; ; i++) + { + /* memory space for allocating (1 << i) bytes each time */ + ptr = rt_malloc(1 << i); + + /* if allocated successfully */ + if (ptr != RT_NULL) + { + rt_kprintf("get memory :%d byte\n", (1 << i)); + /* release memory block */ + rt_free(ptr); + rt_kprintf("free memory :%d byte\n", (1 << i)); + ptr = RT_NULL; + } + else + { + rt_kprintf("try to get %d byte memory failed!\n", (1 << i)); + return; + } + } +} + +int dynmem_sample(void) +{ + rt_thread_t tid = RT_NULL; + + /* create thread 1 */ + tid = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + return 0; +} +/* Export to the msh command list */ +MSH_CMD_EXPORT(dynmem_sample, dynmem sample); +``` + +The simulation results are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >dynmem_sample +msh >get memory :1 byte +free memory :1 byte +get memory :2 byte +free memory :2 byte +… +get memory :16384 byte +free memory :16384 byte +get memory :32768 byte +free memory :32768 byte +try to get 65536 byte memory failed! +``` + +The memory is successfully allocated in the routine and the information is printed; when trying to apply 65536 byte, 64KB, of memory, the allocation fails because the total RAM size is only 64K and the available RAM is less than 64K. + +# Memory Pool + +The memory heap manager can allocate blocks of any size, which is very flexible and convenient. However, it also has obvious shortcomings. Firstly, the allocation efficiency is not high because free memory blocks need to be looked up for each allocation. Secondly, it is easy to generate memory fragmentation. In order to improve the memory allocation efficiency and avoid memory fragmentation, RT-Thread provides another method of memory management: Memory Pool. + +Memory pool is a memory allocation method for allocating a large number of small memory blocks of the same size. It can greatly speed up memory allocation and release, and can avoid memory fragmentation as much as possible. In addition, RT-Thread's memory pool allows thread suspend function. When there is no free memory block in the memory pool, the application thread will be suspended until there is a new available memory block in the memory pool, and then the suspended application thread will be awakened. + +The thread suspend function of the memory pool is very suitable for scenes that need to be synchronized by memory resources. For example, when playing music, the player thread decodes the music file and then sends it to the sound card driver to drive the hardware to play music. + + + +As shown in the figure above, when the player thread needs to decode the data, it will request the memory block from the memory pool. If there is no memory block available, the thread will be suspended, otherwise it will obtain the memory block to place the decoded data. + +The player thread then writes the memory block containing the decoded data to the sound card abstraction device (the thread will return immediately and continue to decode more data). + +After the sound card device is written, the callback function set by the player thread is called to release the written memory block. If the player thread is suspended because there is no memory block in the memory pool available, then it will be awakened to continue to decode. + +## Memory Pool Working Mechanism + +### Memory Pool Control Block + +The memory pool control block is a data structure used by the operating system to manage the memory pool. It stores some information about the memory pool, such as the start address of the data area in the memory pool, the memory block size and the memory block list. It also includes memory blocks, a linked list structure used for the connection between memory blocks, event set of the thread suspended due to the memory block being unavailable, and so on. + +In the RT-Thread real-time operating system, the memory pool control block is represented by the structure `struct rt_mempool`. Another C expression, `rt_mp_t`, represents the memory block handle. The implementation in C language is a pointer pointing to the memory pool control block. For details, see the following code: + +```c +struct rt_mempool +{ + struct rt_object parent; + + void *start_address; /* start address of memory pool data area */ + rt_size_t size; /* size of memory pool data area */ + + rt_size_t block_size; /* size of memory block */ + rt_uint8_t *block_list; /* list of memory block */ + + /* maximum number of memory blocks that can be accommodated in the memory pool data area */ + rt_size_t block_total_count; + /* number of free memory blocks in the memory pool */ + rt_size_t block_free_count; + /* list of threads suspended because memory blocks are unavailable */ + rt_list_t suspend_thread; + /* number of threads suspended because memory blocks are unavailable */ + rt_size_t suspend_thread_count; +}; +typedef struct rt_mempool* rt_mp_t; +``` + +### Memory Block Allocation Mechanism + +When the memory pool is created, it first asks for a large amount of memory from the system. Then it divides the memory into multiple small memory blocks of the same size. The small memory blocks are directly connected by a linked list (this linked list is also called a free linked list). At each allocation, the first memory block is taken from the head of the free linked list and provided to the applicant. As you can see from the figure below, there are multiple memory pools of different sizes allowed in physical memory. Each memory pool is composed of multiple free memory blocks, which are used by the kernel for memory management. When a memory pool object is created, the memory pool object is assigned to a memory pool control block. The parameters of the memory control block include the memory pool name, memory buffer, memory block size, number of blocks, and a queue of threads waiting. + + + +The kernel is responsible for allocating memory pool control blocks to the memory pool. It also receives the request for allocation of memory blocks from the user thread. When this information is obtained, the kernel can allocate memory for the memory pool from available memory. Once the memory pool is initialized, the size of the memory blocks inside will no longer be available for adjustment. + +Each memory pool object consists of the above structure, where suspend_thread forms a list for thread waiting for memory blocks, that is, when there is no memory block available in the memory pool, and the request thread allows waiting, the thread applying for the memory block will suspend on the suspend_thread linked list. + +## Memory Pool Management + +The memory pool control block is a structure that contains important parameters related to the memory pool and acts as a link between various states of the memory pool. The related interfaces of the memory pool are as shown in the following figure. The operation of the memory pool includes: creating/initializing the memory pool, appling for memory blocks, releasing memory blocks and deleting/detaching memory pools. It needs to noted that not all memory pools will be deleted. The deletion is relegated to the user, but the used memory blocks should be released. + + + +### Create and Delete Memory Pool + +To create a memory pool, a memory pool object is created first and then a memory heap is allocated from the heap. Creating a memory pool is a prerequisite for allocating and releasing memory blocks from the corresponding memory pool. After the memory pool is created, a thread can perform operations like application, release and so on. To create a memory pool, use the following function interface. This function returns a created memory pool object. + +```c +rt_mp_t rt_mp_create(const char* name, + rt_size_t block_count, + rt_size_t block_size); +``` + +Using this function interface can create a memory pool that matches the size and number of memory blocks required. The creation will be successful if system resources allow it (most importantly memory heap resources). When you create a memory pool, you need to give the memory pool a name. The kernel then requests a memory pool object from the system. Next, a memory buffer calculated from the number and sizes of blocks will be allocated from the memory heap. Then the memory pool object is initialized. Afterwards, the successfully applied memory block buffer is organized into the idle linked list used for allocation. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_mp_create() + +|**Parameters** |**Description** | +|--------------|--------------------| +| name | Name of the memory pool | +| block_count | Number of memory blocks | +| block_size | Size of memory block | +|**Return** | —— | +| Handle of memory pool | Creation of memory pool object successful | +| RT_NULL | Creation of memory pool failed | + +Deleting the memory pool will delete the memory pool object and release the applied memory. Use the following function interface: + +```c +rt_err_t rt_mp_delete(rt_mp_t mp); +``` + +When a memory pool is deleted, all threads waiting on the memory pool object will be awakened (return -RT_ERROR). Then the memory pool data storage area allocated from the memory heap is released, and the memory pool object is deleted. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mp_delete() + +|**Parameters**|**Description** | +|----------|-----------------------------------| +| mp | memory pool object handle | +|**Return**| —— | +| RT_EOK | Deletion successful | + +### Initialize and Detach Memory Pool + +Memory pool initialization is similar to memory pool creation, except that the memory pool initialization is used for static memory management, and the memory pool control block is derived from static objects that the user applies in the system. In addition, unlike memory pool creation, the memory space used by the memory pool object here is a buffer space specified by user. The user passes the pointer of the buffer to the memory pool control block, the rest of the initialization is the same as the creation of the memory pool. The function interface is as follows: + +```c +rt_err_t rt_mp_init(rt_mp_t mp, + const char* name, + void *start, + rt_size_t size, + rt_size_t block size); +``` + +When initializing the memory pool, the following arguments need to be passed: the memory pool object that needs to be initialized, the memory space used by the memory pool, the number and sizes of memory blocks managed by the memory pool and a name to the memory pool. This way, the kernel can initialize the memory pool and organize the memory space used by the memory pool into a free block linked list for allocation. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mp_init() + +|**Parameters** |**Description** | +|-------------|--------------------| +| mp | memory pool object | +| name | memory pool name | +| start | starting address of memory pool | +| size | memory pool data area size | +| block_size | memory pool size | +|**Return** | —— | +| RT_EOK | initialization successful | +| \- RT_ERROR | Fail | + +The number of memory pool blocks = size / (block_size + 4-byte, linked list pointer size), the calculation result needs to be rounded (an integer). + +For example, if the size of the memory pool data area is set to 4096 bytes, and the memory block size block_size is set to 80 bytes, then the number of memory blocks applied is 4096/(80+4)=48. + +Detaching the memory pool means the memory pool object will be detached from the kernel object manager. Use the following function interface to detach the memory pool: + +```c +rt_err_t rt_mp_detach(rt_mp_t mp); +``` + +After using this function interface, the kernel wakes up all threads waiting on the memory pool object and then detaches the memory pool object from the kernel object manager. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_mp_detach() + +|**Parameters**|**Description** | +|----------|------------| +| mp | memory pool object | +|**Return**| —— | +| RT_EOK | Successful | + +### Allocate and Release Memory Block + +To allocate a memory block from the specified memory pool, use the following interface: + +```c +void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time); +``` + +The time parameter means the timeout period for applying for allocation of memory blocks. If there is a memory block available in the memory pool, remove a memory block from the free linked list of the memory pool, reduce the number of free blocks and return this memory block; if there are no free memory blocks in the memory pool, determine the timeout time setting: if the timeout period is set to zero, the empty memory block is immediately returned; if the waiting time is greater than zero, the current thread is suspended on the memory pool object until there is free memory block available in the memory pool, or the waiting time elapses. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mp_alloc() + +|**Parameters** |**Description** | +|------------------|------------| +| mp | Memory pool object | +| time | Timeout | +|**Return** | —— | +| Allocated memory block address | Successful | +| RT_NULL | Fail | + +Any memory block must be released after it has been used. Otherwise, memory leaks will occur. The memory block is released using the following interface: + +```c +void rt_mp_free (void *block); +``` + +When using the function interface, firstly, the memory pool object of (or belongs to) the memory block will be calculated by the pointer of the memory block that needs to be released. Secondly, the number of available memory blocks of the memory pool object will be increased. Thirdly, the released memory block to the linked list of free memory blocks will be added. Then, it will be determined whether there is a suspended thread on the memory pool object, if so, the first thread on the suspended thread linked list will be awakened. The following table describes the input parameters for this function: + +Input parameters of rt_mp_free() + +|**Parameters**|**Description** | +|----------|------------| +| block | memory block pointer | + +## Memory Pool Application Example + +This is a static internal memory pool application routine that creates a static memory pool object and 2 dynamic threads. One thread will try to get the memory block from the memory pool, and the other thread will release the memory block, as shown in the following code: + + Memory pool usage example + +```c +#include <rtthread.h> + +static rt_uint8_t *ptr[50]; +static rt_uint8_t mempool[4096]; +static struct rt_mempool mp; + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* pointer pointing to the thread control block */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; + +/* thread 1 entry */ +static void thread1_mp_alloc(void *parameter) +{ + int i; + for (i = 0 ; i < 50 ; i++) + { + if (ptr[i] == RT_NULL) + { + /* Trying to apply for a memory block 50 times, when no memory block is available, + thread 1 suspends, thread 2 runs */ + ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER); + if (ptr[i] != RT_NULL) + rt_kprintf("allocate No.%d\n", i); + } + } +} + +/* thread 2 entry, thread 2 has a lower priority than Thread 1, so thread 1 should be executed first. */ +static void thread2_mp_release(void *parameter) +{ + int i; + + rt_kprintf("thread2 try to release block\n"); + for (i = 0; i < 50 ; i++) + { + /* release all successfully allocated memory blocks */ + if (ptr[i] != RT_NULL) + { + rt_kprintf("release block %d\n", i); + rt_mp_free(ptr[i]); + ptr[i] = RT_NULL; + } + } +} + +int mempool_sample(void) +{ + int i; + for (i = 0; i < 50; i ++) ptr[i] = RT_NULL; + + /* initialize the memory pool object */ + rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80); + + /* create thread 1: applying for memory pool */ + tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + + /* create thread 2: release memory pool */ + tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + return 0; +} + +/* export to the msh command list */ +MSH_CMD_EXPORT(mempool_sample, mempool sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mempool_sample +msh >allocate No.0 +allocate No.1 +allocate No.2 +allocate No.3 +allocate No.4 +… +allocate No.46 +allocate No.47 +thread2 try to release block +release block 0 +allocate No.48 +release block 1 +allocate No.49 +release block 2 +release block 3 +release block 4 +release block 5 +… +release block 47 +release block 48 +release block 49 +``` + +This routine initializes 4096/(80+4) = 48 memory blocks when initializing the memory pool object. + +1) After thread 1 applies for 48 memory blocks, the memory block has been used up and needs to be released elsewhere to be applied again; but at this time, thread 1 has applied for another one in the same way, because it cannot be allocated, so thread 1 suspends; + +2) Thread 2 starts to execute the operation of releasing the memory. When thread 2 releases a memory block, it means there is a memory block that is free. Wake up thread 1 to apply for memory, and then apply again after the application is successful, thread 1 suspends again, and repeats this process; + +3) Thread 2 continues to release the remaining memory blocks, release is complete. + diff --git a/documentation/3.kernel/thread-comm/figures/07mb_ops.png b/documentation/3.kernel/thread-comm/figures/07mb_ops.png Binary files differnew file mode 100644 index 0000000000..f8abd19148 --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07mb_ops.png diff --git a/documentation/3.kernel/thread-comm/figures/07mb_work.png b/documentation/3.kernel/thread-comm/figures/07mb_work.png Binary files differnew file mode 100644 index 0000000000..9c014afbda --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07mb_work.png diff --git a/documentation/3.kernel/thread-comm/figures/07msg_ops.png b/documentation/3.kernel/thread-comm/figures/07msg_ops.png Binary files differnew file mode 100644 index 0000000000..a0163ec39e --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07msg_ops.png diff --git a/documentation/3.kernel/thread-comm/figures/07msg_syn.png b/documentation/3.kernel/thread-comm/figures/07msg_syn.png Binary files differnew file mode 100644 index 0000000000..70b0bd164a --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07msg_syn.png diff --git a/documentation/3.kernel/thread-comm/figures/07msg_work.png b/documentation/3.kernel/thread-comm/figures/07msg_work.png Binary files differnew file mode 100644 index 0000000000..01569817bb --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07msg_work.png diff --git a/documentation/3.kernel/thread-comm/figures/07signal_ops.png b/documentation/3.kernel/thread-comm/figures/07signal_ops.png Binary files differnew file mode 100644 index 0000000000..e408e9f6b5 --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07signal_ops.png diff --git a/documentation/3.kernel/thread-comm/figures/07signal_work.png b/documentation/3.kernel/thread-comm/figures/07signal_work.png Binary files differnew file mode 100644 index 0000000000..e08adc669d --- /dev/null +++ b/documentation/3.kernel/thread-comm/figures/07signal_work.png diff --git a/documentation/3.kernel/thread-comm/thread-comm.md b/documentation/3.kernel/thread-comm/thread-comm.md new file mode 100644 index 0000000000..143fdf96ac --- /dev/null +++ b/documentation/3.kernel/thread-comm/thread-comm.md @@ -0,0 +1,1038 @@ +@page page_thread_comm Inter-thread Communication + +In the last chapter, we talked about inter-thread synchronization, concepts such as semaphores, mutexes, and event sets were mentioned. Following the last chapter, this chapter is going to explain inter-thread communication. In bare-metal programming, global variables are often used for communication between functions. For example, some functions may change the value of a global variable due to some operations. Another function reads the global variable and will perform corresponding actions to achieve communication and collaboration according to the global variable values it read. More tools are available in RT-Thread to help pass information between different threads. These tools are covered in more detail in this chapter. After reading this chapter, you will learn how to use mailboxes, message queues, and signals for communication between threads. + +# Mailbox + +Mailbox service is a typical inter-thread communication method in real-time operating systems.For example, there are two threads, thread 1 detects the state of the button and sends it's state, and thread 2 reads the state of the button and turns on or off the LED according to the state of the button. Here, a mailbox can be used to communicate. Thread 1 sends the status of the button as an email to the mailbox. Thread 2 reads the message in the mailbox to get the button status and turn on or off the LED accordingly. + +Thread 1 here can also be extended to multiple threads. For example, there are three threads, thread 1 detects and sends the button state, thread 2 detects and sends the ADC information, and thread 3 performs different operations according to the type of information received. + +## Mailbox Working Mechanism + +The mailbox of the RT-Thread operating system is used for inter-thread communication, which is characterized by low overhead and high efficiency. Each message in the mailbox can only hold a fixed 4 bytes(for a 32-bit processing system, the pointer is 4 bytes in size, so an email can hold only one pointer). A typical mailbox is also called message exchange. As shown in the following figure, a thread or an interrupt service routine sends a 4-byte message to a mailbox, and one or more threads can receive and process the message from the mailbox. + + + +The sending process of the non-blocking mode mails can be safely used in ISR. It is an effective way for the thread, the interrupt service, and the timer to send a message to the thread. In general, the receiving of the mails can be a blocked process, depending on whether there is a message in the mailbox and the timeout set when the message was received. When there is no mail in the mailbox and the set timeout is not 0, the receive of the mails will become blocked. In such cases, the mails can only be received by threads. + +When a thread sends a message to a mailbox, if the mailbox is not full, the message will be copied to the mailbox. If the mailbox is full, the thread sending the message can set a timeout, and choose to wait and suspend or return directly - RT_EFULL. If the thread sending the message chooses to suspend and wait, then when the mails in the mailbox are received and space is left open again, the thread sending the message will be awaken and will continue to send. + +When a thread receives a message from a mailbox, if the mailbox is empty, the thread receiving the message can choose whether to set a timeout or wait and suspend until a new message is received to be awaken. When the set timeout is up and the mailbox still hasn't received the message, the thread that chose to wait till timeout will be awaken and return -RT_ETIMEOUT. If there are messages in the mailbox, then the thread receiving the message will copy the 4-byte message in the mailbox to the receiving cache. + +## Mailbox Control Block + +In RT-Thread, the mailbox control block is a data structure used by the operating system to manage mailboxes, represented by the structure `struct rt_mailbox`. Another C expression, `rt_mailbox_t`, represents the handle of the mailbox, and the implementation in C language is a pointer to the mailbox control block. See the following code for a detailed definition of the mailbox control block structure: + + +```c +struct rt_mailbox +{ + struct rt_ipc_object parent; + + rt_uint32_t* msg_pool; /* the start address of the mailbox buffer */ + rt_uint16_t size; /* the size of the mailbox buffer */ + + rt_uint16_t entry; /* the number of messages in the mailbox */ + rt_uint16_t in_offset, out_offset; /* the entry and exit pointer of the mailbox buffer */ + rt_list_t suspend_sender_thread; /* send the suspend and wait queue of the thread */ +}; +typedef struct rt_mailbox* rt_mailbox_t; +``` + +The `rt_mailbox` object is derived from `rt_ipc_object` and is managed by the IPC container. + +## Management of Mailbox + +The mailbox control block is a structure that contains important parameters related to mailbox and it plays an important role in the function implementation of the mailbox. The relevant interfaces of the mailbox are as shown in the following figure. The operation on a mailbox includes: create/initiate a mailbox, send a mail, receive a mail, and delete/detach a mailbox. + + + +### Create and Delete Mailbox + +To dynamically create a mailbox object, call the following function interface: + +```c +rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag); +``` + +When a mailbox object is created, a mailbox object is first allocated from the object manager, and then a memory space is dynamically allocated to the mailbox for storing the mail. The size of the memory is equal to the product of the message size (4 bytes) and the mailbox capacity. Then initialize the number of incoming messages and the offset of the outgoing message in the mailbox. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_create() + +|**Parameters** |**Description** | +|----------------|------------------------------------------------------------------| +| name | The name of the mailbox | +| size | Mailbox capacity | +| flag | The mailbox flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| RT_NULL | Creation failed | +| The handle of the mailbox object | Creation successful | + +When a mailbox created with rt_mb_create() is no longer used, it should be deleted to release the corresponding system resources. Once the operation is completed, the mailbox will be permanently deleted. The function interface for deleting a mailbox is as follows: + +```c +rt_err_t rt_mb_delete (rt_mailbox_t mb); +``` + +When deleting a mailbox, if a thread is suspended on the mailbox object, the kernel first wakes up all threads suspended on the mailbox (the thread return value is -RT_ERROR), then releases the memory used by the mailbox, and finally deletes the mailbox object. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_delete() + +|**Parameters**|**Description** | +|----------|----------------| +| mb | The handle of the mailbox object | +|**Return**| —— | +| RT_EOK | Successful | + +### Initialize and Detach Mailbox + +Initializing a mailbox is similar to creating a mailbox, except that the mailbox initialized is for static mailbox objects. Different from creating a mailbox, the memory of a static mailbox object is allocated by the compiler during system compilation which is usually placed in a read-write data segment or an uninitialized data segment. The rest of the initialization is the same as the creation of a mailbox. The function interface is as follows: + +```c + rt_err_t rt_mb_init(rt_mailbox_t mb, + const char* name, + void* msgpool, + rt_size_t size, + rt_uint8_t flag) +``` + +When the mailbox is initialized, the function interface needs to obtain the mailbox object control block that the user has applied for, the pointer of the buffer, the mailbox name and mailbox capacity (the number of messages that can be stored). The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_init() + +|**Parameters**|**Description** | +|----------|-----------------------------------------------------------------| +| mb | The handle of the mailbox object | +| name | Mailbox name | +| msgpool | Buffer pointer | +| size | Mailbox capacity | +| flag | The mailbox flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return**| —— | +| RT_EOK | Successful | + +The size parameter here specifies the capacity of the mailbox, if the number of bytes in the buffer pointed to by msgpool is N, then the mailbox capacity should be N/4. + +Detaching the mailbox means to detach the statically initialized mailbox objects from the kernel object manager. Use the following interface to detach the mailbox: + +```c +rt_err_t rt_mb_detach(rt_mailbox_t mb); +``` + +After using this function interface, the kernel wakes up all the threads suspended on the mailbox (the threads return -RT_ERROR), and then detaches the mailbox objects from the kernel object manager. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_mb_detach() + +|**Parameters**|**Description** | +|----------|----------------| +| mb | The handle of the mailbox object | +|**Return**| —— | +| RT_EOK | Successful | + +### Send Mail + +The thread or ISR can send mail to other threads through the mailbox. The function interface of sending mails is as follows: + +```c +rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value); +``` + +The message sent can be any data 32-bit formatted, an integer value or a pointer pointing to the buffer. When the mailbox is fully filled with mails, the thread or ISR that sent the mail will receive a return value of -RT_EFULL. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_send() + +|**Parameters** |**Description** | +|------------|----------------| +| mb | The handle of the mailbox object | +| value | Content of email | +|**Return** | —— | +| RT_EOK | Sent successfully | +| \-RT_EFULL | The mailbox is filled | + +### Send Mails with Waiting + +Users can also send mails to specified mailbox through the following function interface: + +```c +rt_err_t rt_mb_send_wait (rt_mailbox_t mb, + rt_uint32_t value, + rt_int32_t timeout); +``` + +The difference between rt_mb_send_wait() and rt_mb_send() is that there is waiting time. If the mailbox is full, the thread sending the message will wait for the mailbox to release space as mails are received according to the set timeout parameter. If by the set timeout, there is still no available space, then the thread sending the message will wake up and return an error code. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_send_wait() + +|**Parameters** |**Description** | +|---------------|----------------| +| mb | The handle of the mailbox object | +| value | Mail content | +| timeout | Timeout | +|**Return** | —— | +| RT_EOK | Sent successfully | +| \-RT_ETIMEOUT | Timeout | +| \-RT_ERROR | Failed, return error | + +### Receive Mails + +Only when there is a mail in the mailbox, the recipient can receive the mail immediately and return RT_EOK. Otherwise, the thread receiving the message will suspend on the waiting thread queue of the mailbox or return directly according to the set timeout. The receiving mail function interface is as follows: + +```c +rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout); +``` + +When receiving a mail, the recipient needs to specify the mailbox handle, and specify the location to store the received message and the maximum timeout that it can wait. If a timeout is set at the time of receiving, -RT_ETIMEOUT will be returned when the message has not been received within the specified time. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mb_recv() + +|**Parameters** |**Description** | +|---------------|----------------| +| mb | The handle of the mailbox object | +| value | Mail content | +| timeout | Timeout | +|**Return** | —— | +| RT_EOK | Sent successfully | +| \-RT_ETIMEOUT | Timeout | +| \-RT_ERROR | Failed, return error | + +## Mail Usage Sample + +This is a mailbox application routine that initializes 2 static threads, 1 static mailbox object, one of the threads sends mail to the mailbox, and one thread receives mail from the mailbox. As shown in the following code: + + Mailbox usage routine + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 10 +#define THREAD_TIMESLICE 5 + +/* Mailbox control block */ +static struct rt_mailbox mb; +/* Memory pool for mails storage */ +static char mb_pool[128]; + +static char mb_str1[] = "I'm a mail!"; +static char mb_str2[] = "this is another mail!"; +static char mb_str3[] = "over"; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* Thread 1 entry */ +static void thread1_entry(void *parameter) +{ + char *str; + + while (1) + { + rt_kprintf("thread1: try to recv a mail\n"); + + /* Receive mail from the mailbox */ + if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str); + if(!strcmp(str, mb_str3)) + break; + + /* Delay 100ms */ + rt_thread_mdelay(100); + } + } + /* Executing the mailbox object detachment */ + rt_mb_detach(&mb); +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* Thread 2 entry*/ +static void thread2_entry(void *parameter) +{ + rt_uint8_t count; + + count = 0; + while (count < 10) + { + count ++; + if (count & 0x1) + { + /* Send the mb_str1 address to the mailbox */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + } + else + { + /* Send the mb_str2 address to the mailbox */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + } + + /* Delay 200ms */ + rt_thread_mdelay(200); + } + + /* Send mail to inform thread 1 that thread 2 has finished running */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); +} + +int mailbox_sample(void) +{ + rt_err_t result; + + /* Initialize a mailbox */ + result = rt_mb_init(&mb, + "mbt", /* Name is mbt */ + &mb_pool[0], /* The memory pool used by the mailbox is mb_pool */ + sizeof(mb_pool) / 4, /* The number of messages in the mailbox because a message occupies 4 bytes */ + RT_IPC_FLAG_FIFO); /* Thread waiting in FIFO approach */ + if (result != RT_EOK) + { + rt_kprintf("init mailbox failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +/* Export to the msh command list */ +MSH_CMD_EXPORT(mailbox_sample, mailbox sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mailbox_sample +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:I'm a mail! +msh >thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +… +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:over +``` + +The routine demonstrates how to use the mailbox. Thread 2 sends the mails, for a total of 11 times; thread 1 receives the mails, for a total of 11 mails, prints the contents of the mail, and determines end. + +## Occasions to Use Mails + +Mailbox is a simple inter-thread messaging method, which is characterized by low overhead and high efficiency. In the implementation of the RT-Thread operating system, a 4-byte message can be delivered at a time, and the mailbox has certain storage capabilities, which can cache a certain number of messages (the number of messages is determined by the capacity specified when creating and initializing the mailbox). The maximum length of a message in a mailbox is 4 bytes, so the mailbox can be used for messages less than 4 bytes. Since on the 32 system, 4 bytes can be placed right on a pointer, when a larger message needs to be transferred between threads, a pointer pointing to a buffer can be sent as an mail to the mailbox, which means the mailbox can also delivery a pointer. For example, + +```c +struct msg +{ + rt_uint8_t *data_ptr; + rt_uint32_t data_size; +}; +``` + +For such a message structure, it contains a pointer pointing to data `data_ptr` and a variable `data_size` of the length of the data block. When a thread needs to send this message to another thread, the following operations can be used: + +```c +struct msg* msg_ptr; + +msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg)); +msg_ptr->data_ptr = ...; /* Point to the corresponding data block address */ +msg_ptr->data_size = len; /* Length of data block */ +/* Send this message pointer to the mb mailbox */ +rt_mb_send(mb, (rt_uint32_t)msg_ptr); +``` + +When receiving the thread, because the pointer is what's being received, and msg_ptr is a newly allocated memory block, so after the thread receiving the message finishes processing, the corresponding memory block needs to be released: + +```c +struct msg* msg_ptr; +if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK) +{ + /* After the thread receiving the message finishes processing, the corresponding memory block needs to be released: */ + rt_free(msg_ptr); +} +``` + +# Message Queue + +Message Queuing is another commonly used inter-thread communication method, which is an extension of the mailbox. Can be used in a variety of occasions, like message exchange between threads, use the serial port to receive variable length data, etc. + +## Message Queue Working Mechanism + +Message queue can receive messages with unfixed length from threads or ISR and cache messages in their own memory space. Other threads can also read the corresponding message from the message queue, and when the message queue is empty, the thread reading the messages can be suspended. When a new message arrives, the suspended thread will be awaken to receive and process the message. Message queue is an asynchronous way of communication. + +As shown in the following figure, a thread or interrupt service routine can put one or more messages into the message queue. Similarly, one or more threads can also get messages from the message queue. When multiple messages are sent to the message queue, the message that first enters the message queue is first passed to the thread. That is, the thread first gets the message that first enters the message queue, in other words, it follows the first in first out principle (FIFO). + + + +The message queue object of the RT-Thread operating system consists of multiple elements. When a message queue is created, it is assigned a message queue control block: name, memory buffer, message size, and queue length. At the same time, each message queue object contains multiple message boxes, and each message box can store one message. The first and last message boxes in the message queue are respectively called the message linked list header and the message linked list tail, corresponding to `msg_queue_head` and `msg_queue_tail` in the queue control block. Some message boxes may be empty, they form a linked list of idle message boxes via `msg_queue_free`. The total number of message boxes in all message queues is the length of the message queue, which can be specified when creating the message queue. + +## Message Queue Control Block + +In RT-Thread, a message queue control block is a data structure used by the operating system to manage message queues, represented by the structure `struct rt_messagequeue`. Another C expression, `rt_mq_t`, represents the handle of the message queue. The implementation in C language is a pointer to the message queue control block. See the following code for a detailed definition of the message queue control block structure: + +```c +struct rt_messagequeue +{ + struct rt_ipc_object parent; + + void* msg_pool; /* Pointer pointing to the buffer storing the messages */ + + rt_uint16_t msg_size; /* The length of each message */ + rt_uint16_t max_msgs; /* Maximum number of messages that can be stored */ + + rt_uint16_t entry; /* Number of messages already in the queue */ + + void* msg_queue_head; /* Message linked list header */ + void* msg_queue_tail; /* Message linked list tail */ + void* msg_queue_free; /* Idle message linked list */ +}; +typedef struct rt_messagequeue* rt_mq_t; +``` + +rt_messagequeue object is derived from rt_ipc_object and is managed by the IPC container. + +## Management of Message Queue + +The message queue control block is a structure that contains important parameters related to the message queue and plays an important role in the implementation of functions of the message queue. The relevant interfaces of the message queue are shown in the figure below. Operations on a message queue include: create a message queue - send a message - receive a message - delete a message queue. + + + +### Create and Delete Message Queue + +The message queue should be created before it is used, or the existing static message queue objects should be initialized. The function interface for creating the message queue is as follows: + +```c +rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, + rt_size_t max_msgs, rt_uint8_t flag); +``` + +When creating a message queue, first allocate a message queue object from the object manager, then allocate a memory space to the message queue object forming an idle message linked list. The size of this memory = [message size + message header (for linked list connection) Size] X the number of messages in the message queue. Then initialize the message queue, at which point the message queue is empty. The following table describes the input parameters and return values for this function: + + Input parameters and return values for rt_mq_create() + +|**Parameters** |**Description** | +|--------------------|-------------------------------------------------------------------------------| +| name | The name of the message queue | +| msg_size | The maximum length of a message in the message queue, in bytes | +| max_msgs | The number of messages in the message queue | +| flag | The waiting method took by the message queue, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| RT_EOK | Sent successfully | +| The handle of the message queue object | Successful | +| RT_NULL | Fail | + +When the message queue is no longer in use, it should be deleted to free up system resources, and once the operation is complete, the message queue will be permanently deleted. The function interface for deleting the message queue is as follows: + +```c +rt_err_t rt_mq_delete(rt_mq_t mq); +``` + +When deleting a message queue, if a thread is suspended on the message queue's waiting queue, the kernel wakes up all the threads suspended on the message waiting queue (the thread returns the value is -RT_ERROR), and then releases the memory used by the message queue. Finally, delete the message queue object. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mq_delete() + +|**Parameters**|**Description** | +|----------|--------------------| +| mq | The handle of the message queue object | +|**Return**| —— | +| RT_EOK | Successful | + +### Initialize and Detach Message Queue + +Initializing a static message queue object is similar to creating a message queue object, except that the memory of a static message queue object is allocated by the compiler during system compilation and is typically placed in a read data segment or an uninitialized data segment. Initialization is required before using such static message queue objects. The function interface for initializing a message queue object is as follows: + +```c +rt_err_t rt_mq_init(rt_mq_t mq, const char* name, + void *msgpool, rt_size_t msg_size, + rt_size_t pool_size, rt_uint8_t flag); +``` + +When the message queue is initialized, the interface requires the handle to the message queue object that the user has requested (that is, a pointer pointing to the message queue object control block), the message queue name, the message buffer pointer, the message size, and the message queue buffer size. As shown in the following figure, all messages after the message queue is initialized are suspended on the idle message list, and the message queue is empty. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mq_init() + +|**Parameters** |**Description** | +|-----------|-------------------------------------------------------------------------------| +| mq | The handle of the message queue object | +| name | The name of the message queue | +| msgpool | Pointer pointing to the buffer storing the messages | +| msg_size | The maximum length of a message in the message queue, in bytes | +| pool_size | The buffer size for storing messages | +| flag | The waiting method took by the message queue, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| RT_EOK | Successful | + +Detaching the message queue will cause the message queue object to be detached from the kernel object manager. The following interface is used to detach from the message queue: + +```c +rt_err_t rt_mq_detach(rt_mq_t mq); +``` + +After using this function interface, the kernel wakes up all threads suspended on the message waiting queue object (the thread return value is -RT_ERROR) and then detaches the message queue object from the kernel object manager. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_mq_detach() + +|**Parameters**|**Description** | +|----------|--------------------| +| mq | The handle of the message queue object | +|**Return**| —— | +| RT_EOK | Successful | + +### Send Message + +A thread or ISR can send a message to the message queue. When sending a message, the message queue object first takes an idle message block from the idle message list, then copies the content of the message sent by the thread or the interrupt service program to the message block, and then suspends the message block to the end of the message queue. The sender can successfully send a message if and only if there is an idle message block available on the idle message list; when there is no message block available on the idle message list, it means that the message queue is full, at this time, the thread or interrupt program that sent the message will receive an error code (-RT_EFULL). The function interface for sending messages is as follows: + +```c +rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size); +``` + +When sending a message, the sender specifies the object handle of the sent message queue (that is, a pointer pointing to the message queue control block) and specifies the content of the message being sent and the size of the message. As shown in the figure below, after sending a normal message, the first message on the idle message list is transferred to the end of the message queue. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mq_send() + +|**Parameter** |**Description** | +|------------|------------------------------------------------------| +| mq | The handle of the message queue object | +| buffer | Message content | +| size | Message size | +|**Return** | —— | +| RT_EOK | Successful | +| \-RT_EFULL | Message queue is full | +| \-RT_ERROR | Failed, indicating that the length of the sent message is greater than the maximum length of the message in the message queue. | + +### Send an Emergency Message + +The process of sending an emergency message is almost the same as sending a message. The only difference is that when an emergency message is sent, the message block taken from the idle message list is not put in the end of the message queue, but the head of the queue. The recipient can receive the emergency message preferentially, so that the message can be processed in time. The function interface for sending an emergency message is as follows: + +```c +rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size); +``` + +The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mq_urgent() + +|**Parameters** |**Description** | +|------------|--------------------| +| mq | The handle of the message queue object | +| buffer | Message content | +| size | Message size | +|**Return** | —— | +| RT_EOK | Successful | +| \-RT_EFULL | Message queue is full | +| \-RT_ERROR | Fail | + +### Receive Message + +Only when there is a message in the message queue, can the receiver receive message, otherwise the receiver will set according to the timeout, or suspend on the waiting queue of the message queue, or return directly. The function interface to receive message is as follows: + +```c +rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer, + rt_size_t size, rt_int32_t timeout); +``` + +When receiving a message, the receiver needs to specify the message queue object handle that stores the message and specify a memory buffer into which the contents of the received message will be copied. In addition, the timeout when the message is not received in time needs to be set. As shown in the figure below, the message on the front of the message queue is transferred to the end of the idle message linked list after receiving a message. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mq_recv() + +|**Parameters** |**Description** | +|---------------|--------------------| +| mq | The handle of the message queue object | +| buffer | Message content | +| size | Message size | +| timeout | Specified timeout | +|**Return** | —— | +| RT_EOK | Received successfully | +| \-RT_ETIMEOUT | Timeout | +| \-RT_ERROR | Fail, return error | + +## Message Queue Application Example + +This is a message queue application routine. Two static threads are initialized in the routine, one thread will receive messages from the message queue; and another thread will periodically send regular messages and emergency messages to the message queue, as shown in the following code: + +Message queue usage routine + +```c +#include <rtthread.h> + +/* Message queue control block */ +static struct rt_messagequeue mq; +/* The memory pool used to place messages in the message queue */ +static rt_uint8_t msg_pool[2048]; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +/* Thread 1 entry function */ +static void thread1_entry(void *parameter) +{ + char buf = 0; + rt_uint8_t cnt = 0; + + while (1) + { + /* Receive messages from the message queue */ + if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) >= 0) + { + rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); + if (cnt == 19) + { + break; + } + } + /* Delay 50ms */ + cnt++; + rt_thread_mdelay(50); + } + rt_kprintf("thread1: detach mq \n"); + rt_mq_detach(&mq); +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +/* Thread 2 entry */ +static void thread2_entry(void *parameter) +{ + int result; + char buf = 'A'; + rt_uint8_t cnt = 0; + + while (1) + { + if (cnt == 8) + { + /* Send emergency message to the message queue */ + result = rt_mq_urgent(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_urgent ERR\n"); + } + else + { + rt_kprintf("thread2: send urgent message - %c\n", buf); + } + } + else if (cnt>= 20)/* Exit after sending 20 messages */ + { + rt_kprintf("message queue stop send, thread2 quit\n"); + break; + } + else + { + /* Send a message to the message queue */ + result = rt_mq_send(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_send ERR\n"); + } + + rt_kprintf("thread2: send message - %c\n", buf); + } + buf++; + cnt++; + /* Delay 5ms */ + rt_thread_mdelay(5); + } +} + +/* Initialization of the message queue example */ +int msgq_sample(void) +{ + rt_err_t result; + + /* Initialize the message queue */ + result = rt_mq_init(&mq, + "mqt", + &msg_pool[0], /* Memory pool points to msg_pool */ + 1, /* The size of each message is 1 byte */ + sizeof(msg_pool), /* The size of the memory pool is the size of msg_pool */ + RT_IPC_FLAG_FIFO); /* If there are multiple threads waiting, assign messages in first come first get mode. */ + + if (result != RT_EOK) + { + rt_kprintf("init message queue failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), 25, 5); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), 25, 5); + rt_thread_startup(&thread2); + + return 0; +} + +/* Export to the msh command list */ +MSH_CMD_EXPORT(msgq_sample, msgq sample); +``` + +The simulation results are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh > msgq_sample +msh >thread2: send message - A +thread1: recv msg from msg queue, the content:A +thread2: send message - B +thread2: send message - C +thread2: send message - D +thread2: send message - E +thread1: recv msg from msg queue, the content:B +thread2: send message - F +thread2: send message - G +thread2: send message - H +thread2: send urgent message - I +thread2: send message - J +thread1: recv msg from msg queue, the content:I +thread2: send message - K +thread2: send message - L +thread2: send message - M +thread2: send message - N +thread2: send message - O +thread1: recv msg from msg queue, the content:C +thread2: send message - P +thread2: send message - Q +thread2: send message - R +thread2: send message - S +thread2: send message - T +thread1: recv msg from msg queue, the content:D +message queue stop send, thread2 quit +thread1: recv msg from msg queue, the content:E +thread1: recv msg from msg queue, the content:F +thread1: recv msg from msg queue, the content:G +… +thread1: recv msg from msg queue, the content:T +thread1: detach mq +``` + +The routine demonstrates how to use message queue. Thread 1 receives messages from the message queue; thread 2 periodically sends regular and emergency messages to the message queue. Since the message "I" that thread 2 sent is an emergency message, it will be inserted directly into the front of the message queue. So after receiving the message "B", thread 1 receives the emergency message and then receives the message "C". + +## Occasions to Use Message Queue + +Message Queue can be used where occasional long messages are sent, including exchanging message between threads and threads and sending message to threads in interrupt service routines (interrupt service routines cannot receive messages). The following sections describe the use of message queues from two perspectives, sending messages and synchronizing messages. + +### Sending Messages + +The obvious difference between message queue and mailbox is that the length of the message is not limited to 4 bytes. In addition, message queue also includes a function interface for sending emergency messages. But when you create a message queue with a maximum length of 4 bytes for all messages, the message queue object will be reduced to a mailbox. This unrestricted message is also reflected in the case of code writing which is also a mailbox-like code: + +```c +struct msg +{ + rt_uint8_t *data_ptr; /* Data block starting address */ + rt_uint32_t data_size; /* Data block size */ +}; +``` + +Similar to the mailbox example is the message structure definition. Now, let's assume that such a message needs to be sent to thread receiving messages. In the mailbox example, this structure can only send pointers pointing to this structure (after the function pointer is sent, the thread receiving messages can access the content pointing to this address, usually this piece of data needs to be left to the thread receiving the messages to release). How message queues is used is quite different: + +```c +void send_op(void *data, rt_size_t length) +{ + struct msg msg_ptr; + + msg_ptr.data_ptr = data; /* Point to the corresponding data block address */ + msg_ptr.data_size = length; /* Datablock length */ + + /* Send this message pointer to the mq message queue */ + rt_mq_send(mq, (void*)&msg_ptr, sizeof(struct msg)); +} +``` + +Note that in the above code, the data content of a local variable is sent to the message queue. In the thread that receives message, the same structure that receives messages using local variables is used: + +```c +void message_handler() +{ + struct msg msg_ptr; /* Local variable used to place the message */ + + /* Receive messages from the message queue into msg_ptr */ + if (rt_mq_recv(mq, (void*)&msg_ptr, sizeof(struct msg)) >= 0) + { + /* Successfully received the message, corresponding data processing is performed */ + } +} +``` + +Because message queue is a direct copy of data content, so in the above example, local structure is used to save the message structure, which eliminates the trouble of dynamic memory allocation (and no need to worry, because message memory space has already been released when the thread receives the message). + +### Synchronizing Messages + +In general system designs, the problem of sending synchronous messages is often encountered. At this time, corresponding implementations can be selected according to the state of the time: two threads can be implemented in the form of [message queue + semaphore or mailbox]. The thread sending messages sends the corresponding message to the message queue in the form of message sending. After the message is sent, it is hoping to receive the confirmation from the threads receiving messages. The working diagram is as shown below: + + + +Depending on the message confirmation, the message structure can be defined as: + +```c +struct msg +{ + /* Other members of the message structure */ + struct rt_mailbox ack; +}; +/* or */ +struct msg +{ + /* Other members of the message structure */ + struct rt_semaphore ack; +}; +``` + +The first type of message uses a mailbox as a confirmation flag, while the second type of message uses a semaphore as a confirmation flag. The mailbox is used as a confirmation flag, which means that the receiving thread can notify some status values to the thread sending messages; and the semaphore is used as a confirmation flag that can only notify the thread sending messages in a single way, and the message has been confirmed to be received. + +# Signal + +A signal (also known as a soft interrupt signal), from a software perspective, is a simulation of interrupt mechanism. When it comes to its principle, thread receiving a signal is similar to processor receiving an interrupt request. + +## Signal Working Mechanism + +Signals are used for asynchronous communication in RT-Thread. The POSIX standard defines that sigset_t type defines a signal set. However, the sigset_t type may be defined differently in different systems. In RT-Thread, sigset_t is defined as unsigned long and named rt_sigset_t, the signals that the application can use are SIGUSR1(10) and SIGUSR2(12). + +The essence of signal is soft interrupt, which is used to notify the thread that an asynchronous event has occurred. It is used to notify abnormality and handle emergency between threads. No operation is needed for a thread to wait for the signal's arrival. In fact, the thread does not know when the signal will arrive. Threads can send soft interrupt signals by calling `rt_thread_kill()` between each other. + +The thread that receives the signals has different processing methods for various signals, and these methods can be divided into three categories: + +The first one is an interrupt-like processing program. For signals that need to be processed, the thread can specify a function to process. + +The second one is to ignore a signal and do nothing about it, as if it had not happened. + +The third one is to reserve the default value of the system for processing the signal. + +As shown in the following figure, suppose that thread 1 needs to process the signal. First, thread 1 installs a signal and unmasks it. At the same time, it sets the approach of how to process abnormality of signals. Then other threads can send a signal to thread 1, triggering thread 1 to process the signal. + + + +When the signal is passed to thread 1, if it is suspended, it will be switched to ready to process the corresponding signal. If it is running, it will create a new stack frame space on its current thread stack to process the corresponding signal. It should be noted that the thread stack size used will also increase accordingly. + +## Management of Signals + +Operations on signals include: install signal, block signal, unblock signal, send signal, and wait for signal. The interfaces of the signal are shown in the following figure: + + + +### Install Signal + +If the thread is to process a signal, then the signal needs to be installed in the thread. Signal installation is primarily used to determine the mapping relation between the signal value and the actions of the thread on that signal value, that is what signal is to be processed, what actions will be taken when the signal is passed to the thread. See the following code for a detailed definition: + +```c +rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler); +``` + +`rt_sighandler_t` is the type of function pointer that defines the signal process function. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_signal_install() + +|**Parameters** |**Description** | +|-----------------------|--------------------------------------------------------| +| signo | Signal value (only SIGUSR1 and SIGUSR2 are open to the user, the same applies below) | +| handler | Set the approach to process signal values | +|**Return** | —— | +| SIG_ERR | Wrong signal | +| The handler value before the signal is installed | Successful | + +Setting the handler parameter during signal installation determines the different processing methods for this signal. Processing methods can be divided into three types: + +1) Similar to the interrupt processing method, the parameter points to the user-defined processing function when the signal occurs, and is processed by the function. + +2) The parameter is set to SIG_IGN. Ignore a signal, and do nothing on the signal, as if it had not happened. + +3) The parameter is set to SIG_DFL, and the system will call the default function _signal_default_handler() to process. + +### Block Signal + +Blocking signal can also be understood as shielding signals. If the signal is blocked, the signal will not be delivered to the thread that installed the signal and will not cause soft interrupt processing. Call rt_signal_mask() to block the signal: + +```c +void rt_signal_mask(int signo); +``` + +The following table describes the input parameters for this function: + +rt_signal_mask() function parameters + +|**Parameters**|**Description**| +|----------|----------| +| signo | Signal value | + +### Unblock Signal + +Several signals can be installed in the thread. Using this function can give some "attention" to some of the signals, then sending these signals will cause a soft interrupt for the thread. Calling rt_signal_unmask() to unblock the signal: + +```c +void rt_signal_unmask(int signo); +``` + +The following table describes the input parameters for this function: + +rt_signal_unmask() function parameters + +|**Parameters**|**Description**| +|----------|----------| +| signo | Signal value | + +### Send Signals + +When we need to process abnormality, a signal can be sent to the thread that has been set to process abnormality. Call rt_thread_kill() to send a signal to any thread: + +```c +int rt_thread_kill(rt_thread_t tid, int sig); +``` + +The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_thread_kill() + +|**Parameters** |**Description** | +|-------------|----------------| +| tid | The thread receiving signal | +| sig | Signal value | +|**Return** | —— | +| RT_EOK | Sent successfully | +| \-RT_EINVAL | Parameter error | + +### Wait for Signal + +Wait for the arrival of the set signal. If this signal did not arrive, suspend the thread until the signal arrival or the wait until time exceeds the specified timeout. If the signal arrived, the pointer pointing to the signal body is stored in si, as follows is the function to wait for signal. + +```c +int rt_signal_wait(const rt_sigset_t *set, + rt_siginfo_t[] *si, rt_int32_t timeout); +``` + +rt_siginfo_t is the data type that defines the signal information, and the following table describes the input parameters and return values of the function: + +Input parameters and return values for rt_signal_wait() + +|**Parameters** |**Description** | +|---------------|----------------------------| +| set | Specify the signal to wait | +| si | Pointer pointing to signal information | +| timeout | Waiting time | +|**Return** | —— | +| RT_EOK | Signal arrives | +| \-RT_ETIMEOUT | Timeout | +| \-RT_EINVAL | Parameter error | + +## Signal Application Example + +This is a signal application routine, as shown in the following code. This routine creates one thread. When the signal is installed, the signal processing mode is set to custom processing. The processing function of the signal is defined to be thread1_signal_handler(). After the thread is running and the signal is installed, a signal is sent to this thread. This thread will receive the signal and print the message. + +Signal usage routine + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +/* Signal process function for thread 1 signal handler */ +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +/* Entry function for thread 1 */ +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + /* Install signal */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + /* Run for 10 times */ + while (cnt < 10) + { + /* Thread 1 runs with low-priority and prints the count value all through*/ + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} + +/* Initialization of the signal example */ +int signal_sample(void) +{ + /* Create thread 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + rt_thread_mdelay(300); + + /* Send signal SIGUSR1 to thread 1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* Export to the msh command list */ +MSH_CMD_EXPORT(signal_sample, signal sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >signal_sample +thread1 count : 0 +thread1 count : 1 +thread1 count : 2 +msh >thread1 received signal 10 +thread1 count : 3 +thread1 count : 4 +thread1 count : 5 +thread1 count : 6 +thread1 count : 7 +thread1 count : 8 +thread1 count : 9 +``` + +In the routine, the thread first installs the signal and unblocks it, then sends a signal to the thread. The thread receives the signal and prints out the signal received: SIGUSR1 (10). + + diff --git a/documentation/3.kernel/thread-sync/figures/06event_ops.png b/documentation/3.kernel/thread-sync/figures/06event_ops.png Binary files differnew file mode 100644 index 0000000000..8b6691632d --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06event_ops.png diff --git a/documentation/3.kernel/thread-sync/figures/06event_use.png b/documentation/3.kernel/thread-sync/figures/06event_use.png Binary files differnew file mode 100644 index 0000000000..e590b5165c --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06event_use.png diff --git a/documentation/3.kernel/thread-sync/figures/06event_work.png b/documentation/3.kernel/thread-sync/figures/06event_work.png Binary files differnew file mode 100644 index 0000000000..bf2cb9f658 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06event_work.png diff --git a/documentation/3.kernel/thread-sync/figures/06inter_ths_commu1.png b/documentation/3.kernel/thread-sync/figures/06inter_ths_commu1.png Binary files differnew file mode 100644 index 0000000000..240a0d9d48 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06inter_ths_commu1.png diff --git a/documentation/3.kernel/thread-sync/figures/06inter_ths_commu2.png b/documentation/3.kernel/thread-sync/figures/06inter_ths_commu2.png Binary files differnew file mode 100644 index 0000000000..774d4d61ce --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06inter_ths_commu2.png diff --git a/documentation/3.kernel/thread-sync/figures/06mutex_ops.png b/documentation/3.kernel/thread-sync/figures/06mutex_ops.png Binary files differnew file mode 100644 index 0000000000..7c004b9622 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06mutex_ops.png diff --git a/documentation/3.kernel/thread-sync/figures/06mutex_work.png b/documentation/3.kernel/thread-sync/figures/06mutex_work.png Binary files differnew file mode 100644 index 0000000000..a9f1f23463 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06mutex_work.png diff --git a/documentation/3.kernel/thread-sync/figures/06priority_inherit.png b/documentation/3.kernel/thread-sync/figures/06priority_inherit.png Binary files differnew file mode 100644 index 0000000000..0229975046 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06priority_inherit.png diff --git a/documentation/3.kernel/thread-sync/figures/06priority_inversion.png b/documentation/3.kernel/thread-sync/figures/06priority_inversion.png Binary files differnew file mode 100644 index 0000000000..20fc8bab54 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06priority_inversion.png diff --git a/documentation/3.kernel/thread-sync/figures/06sem_lock.png b/documentation/3.kernel/thread-sync/figures/06sem_lock.png Binary files differnew file mode 100644 index 0000000000..07e79caf0d --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06sem_lock.png diff --git a/documentation/3.kernel/thread-sync/figures/06sem_ops.png b/documentation/3.kernel/thread-sync/figures/06sem_ops.png Binary files differnew file mode 100644 index 0000000000..ae1f163958 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06sem_ops.png diff --git a/documentation/3.kernel/thread-sync/figures/06sem_work.png b/documentation/3.kernel/thread-sync/figures/06sem_work.png Binary files differnew file mode 100644 index 0000000000..8c34715a41 --- /dev/null +++ b/documentation/3.kernel/thread-sync/figures/06sem_work.png diff --git a/documentation/3.kernel/thread-sync/thread-sync.md b/documentation/3.kernel/thread-sync/thread-sync.md new file mode 100644 index 0000000000..6284ea4752 --- /dev/null +++ b/documentation/3.kernel/thread-sync/thread-sync.md @@ -0,0 +1,1330 @@ +@page page_thread_sync Inter-thread Synchronization + +In a multi-threaded real-time system, the completion of a task can often be accomplished through coordination of multiple threads, so how do these multiple threads collaborate well with each other to perform without errors? Here is an example. + +For example, two threads in one task: one thread receives data from the sensor and writes the data to shared memory, while another thread periodically reads data from the shared memory and sends it to display. The following figure depicts data transfer between two threads: + + + +If access to shared memory is not exclusive, then it may be accessed simultaneously by each thread, which causes data consistency issues. For example, before thread #2 (thread that can display data) attempts to display data, thread #1 (thread that can receive data) has not yet completed the writing in of data, then the display will contain data sampled at different times, causing the display data to be disordered. + +Thread #1 that writes the sensor data to the shared memory block and thread #2 that reads the sensor data from the shared memory block access the same memory block. In order to prevent data errors, the actions of the two threads must be mutually exclusive. One of threads should only be allowed after another thread completes its operation on the shared memory block. This way, thread #1 and thread #2 can work properly to execute this task correctly. + +Synchronization refers to running in a predetermined order. Thread synchronization refers to multiple threads controlling the execution order between threads through specific mechanisms (such as mutex, event object, critical section). In other words, establish a relationship of execution order by synchronization between threads and if there is no synchronization, the threads will be out-of-order. + +Multiple threads operate / access the same area (code), this block of code is called the critical section, the shared memory block in the above example is the critical section. Thread mutual exclusion refers to the exclusiveness of access to critical section resources. When multiple threads use critical section resources, only one thread is allowed each time. Other threads that want to use the resource must wait until the resource occupant releases the resource. Thread mutex can be seen as a special kind of thread synchronization. + +There are many ways to synchronize threads. The core idea is that **only one (or one kind of) thread is allowed to run when accessing the critical section.** There are many ways to enter/exit the critical section: + +1) Call rt_hw_interrupt_disable() to enter the critical section and call rt_hw_interrupt_enable() to exit the critical section; see the *Global Interrupt Switch* in *Interrupt Management* for details. + +2)Call rt_enter_critical() to enter the critical section and call rt_exit_critical() to exit the critical section. + +This chapter introduces several synchronization methods: **semaphores,** **mutex**, and **event**. After learning this chapter, you will learn how to use semaphores, mutex, and event to synchronize threads. + +# Semaphores + +Take parking lot as an example to understand the concept of semaphore: + +①When the parking lot is empty, the administrator of the parking lot finds that there are a lot of empty parking spaces. And then, cars outside will enter the parking lot and get parking spaces. + +②When the parking space of the parking lot is full, the administrator finds that there is no empty parking space. As a result, cars outside will be prohibited from entering the parking lot, and they will be waiting in line; + +③When cars are leaving the parking lot, the administrator finds that there are empty parking spaces for cars outside to enter the parking lot; after the empty parking spaces are taken, cars outside are prohibited from entering. + +In this example, the administrator is equivalent to the semaphore. The number of empty parking spaces that the administrator is in charge of is the value of the semaphore (non-negative, dynamic change); the parking space is equivalent to the common resource (critical section), and the cars are equivalent to the threads. Cars access the parking spaces by obtaining permission from the administrator, which is similar to thread accessing public resource by obtaining the semaphore. + +## Semaphore Working Mechanism + +Semaphore is a light-duty kernel object that can solve the problems of synchronization between threads. By obtaining or releasing semaphore, a thread can achieve synchronization or mutual exclusion. + +Schematic diagram of semaphore is shown in the figure below. Each semaphore object has a semaphore value and a thread waiting queue. Semaphore value corresponds to the actual number of instances of semaphore object and the number of resources. If the semaphore value is 5, it means that there are 5 semaphore instances (resources) that can be used. If the number of semaphore instances is zero, the thread that is applying for the semaphore will be suspended on the waiting queue of the semaphore, waiting for available semaphore instances (resources). + + + +## Semaphore Control Block + +In RT-Thread, semaphore control block is a data structure used by the operating system to manage semaphores, represented by struct rt rt_semaphore. Another C expression is rt_sem_t, which represents the handle of the semaphore, and the implementation in C language is a pointer to the semaphore control block. The detailed definition of semaphore control block structure is as follows: + +```c +struct rt_semaphore +{ + struct rt_ipc_object parent; /* Inherited from the ipc_object class */ + rt_uint16_t value; /* Semaphore Value */ +}; +/* rt_sem_t is the type of pointer pointing to semaphore structure */ +typedef struct rt_semaphore* rt_sem_t; +``` + +rt_semaphore object is derived from rt_ipc_object and is managed by the IPC container. The maximum semaphore is 65535. + +## Semaphore Management + +Semaphore control block contains important parameters related to the semaphore and acts as a link between various states of the semaphore. Interfaces related to semaphore are shown in the figure below. Operations on a semaphore includes: creating/initializing the semaphore, obtaining the semaphore, releasing the semaphore, and deleting/detaching the semaphore. + + + +### Create and Delete Semaphore + +When creating a semaphore, the kernel first creates a semaphore control block, then performs basic initialization on the control block. The following function interface is used to create a semaphore: + +```c + rt_sem_t rt_sem_create(const char *name, + rt_uint32_t value, + rt_uint8_t flag); +``` + +When this function is called, the system will first allocate a semaphore object from the object manager and initialize the object, and then initialize the parent class IPC object and semaphore-related parts. Among parameters specified in the creation of semaphore, semaphore flag parameter determines the queuing way of how multiple threads wait when the semaphore is not available. When the RT_IPC_FLAG_FIFO (first in, first out) mode is selected, the waiting thread queue will be queued in a first in first out manner. The first thread that goes in will firstly obtain the waiting semaphore. When the RT_IPC_FLAG_PRIO (priority waiting) mode is selected, the waiting threads will be queued in order of priority. Threads waiting with the highest priority will get the wait semaphore first. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_sem_create() + +|Parameters |Description | +|--------------------|-------------------------------------------------------------------| +| name | Semaphore Name | +| value | Semaphore Initial Value | +| flag | Semaphore flag, which can be the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| RT_NULL | Creation failed | +| semaphore control block pointer | Creation successful | + +For dynamically created semaphores, when the system no longer uses semaphore, they can be removed to release system resources. To delete a semaphore, use the following function interface: + +```c +rt_err_t rt_sem_delete(rt_sem_t sem); +``` + +When this function is called, the system will delete this semaphore. If there is a thread waiting for this semaphore when it is being deleted, the delete operation will first wake up the thread waiting on the semaphore (return value of the waiting thread is - RT_ERROR), then release the semaphore's memory resources. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_sem_delete() + +|Parameters|Description | +|----------|----------------------------------| +| sem | Semaphore object created by rt_sem_create() | +|**Return**| —— | +| RT_EOK | Successfully deleted | + +### Initialize and Detach Semaphore + +For a static semaphore object, its memory space is allocated by the compiler during compiling and placed on the read-write data segment or on the uninitialized data segment. In this case,rt_sem_create interface is no longer needed to create the semaphore to use it, just initialize it before using it. To initialize the semaphore object, use the following function interface: + +```c +rt_err_t rt_sem_init(rt_sem_t sem, + const char *name, + rt_uint32_t value, + rt_uint8_t flag) +``` + +When this function is called, the system will initialize the semaphore object, then initialize the IPC object and parts related to the semaphore. The flag mentioned above in semaphore function creation can be used as the semaphore flag here. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_sem_init() + +|**Parameters**|**Description** | +|----------|-------------------------------------------------------------------| +| sem | Semaphore object handle | +| name | Semaphore name | +| value | Semaphore initial value | +| flag | Semaphore flag, which can be the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return**| —— | +| RT_EOK | Initialization successful | + +For statically initialized semaphore, detaching the semaphore is letting the semaphore object detach from the kernel object manager. To detach the semaphore, use the following function interface: + +```c +rt_err_t rt_sem_detach(rt_sem_t sem); +``` + +After using this function, the kernel wakes up all threads suspended in the semaphore wait queue and then detaches the semaphore from the kernel object manager. The waiting thread that was originally suspended on the semaphore will get the return value of -RT_ERROR. The following table describes the input parameters and return values for this function: + + Input parameters and return values of rt_sem_detach() + +|Parameters|**Description** | +|----------|------------------| +| sem | Semaphore object handle | +|**Return**| —— | +| RT_EOK | Successfully detached | + +### Obtain Semaphore + +Thread obtains semaphore resource instances by obtaining semaphores. When the semaphore value is greater than zero, the thread will get the semaphore, and the corresponding semaphore value will be reduced by 1. The semaphore is obtained using the following function interface: + +```c +rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time); +``` + +When calling this function, if the value of the semaphore is zero, it means the current semaphore resource instance is not available, and the thread applying for the semaphore will choose according to the time parameters to either return directly, or suspend for a period of time, or wait forever. While waiting, if other threads or ISR released the semaphore, then the thread will stop the waiting. If the semaphore is still not available after parameter specified time, the thread will time out and return, return value is -RT_ETIMEOUT. The following table describes the input parameters and return values for this function: + + Input parameters and return values of rt_sem_take() + +|Parameters |Description | +|---------------|---------------------------------------------------| +| sem | Semaphore object handle | +| time | Specified wait time, unit is operating system clock tick (OS Tick) | +|**Return** | —— | +| RT_EOK | Semaphore obtained successfully | +| \-RT_ETIMEOUT | Did not received semaphore after timeout | +| \-RT_ERROR | Other errors | + +### Obtain Semaphore without Waiting + +When user does not want to suspend thread on the applied semaphore and wait, the semaphore can be obtained using the wait-free mode , and the following function interface is used for obtaining the semaphore without waiting: + +```c +rt_err_t rt_sem_trytake(rt_sem_t sem); +``` + +This function has the same effect as rt_sem_take(sem, 0), which means when the semaphore resource instance requested by the thread is not available, it will not wait on the semaphore, instead it returns to -RT_ETIMEOUT directly. The following table describes the input parameters and return values for this function: + + Input parameters and return values for rt_sem_trytake() + +|**Parameter** |Description | +|---------------|------------------| +| sem | Semaphore object handle | +|**Return** | —— | +| RT_EOK | Semaphore successfully obtained | +| \-RT_ETIMEOUT | Semaphore obtainment failed | + +### Semaphore Release + +Semaphore is released to wake up the thread that suspends on the semaphore. To release the semaphore, use the following function interface: + +```c +rt_err_t rt_sem_release(rt_sem_t sem); +``` + +For example, when semaphore value is zero and a thread is waiting for this semaphore, releasing the semaphore will wake up the first thread waiting in the thread queue of the semaphore, and this thread will obtain the semaphore; otherwise value of the semaphore will plus 1. The following table describes the input parameters and return values of the function: + + Input parameters and return values of rt_sem_release() + +|**Parameters**|Description | +|----------|------------------| +| sem | Semaphore object handle | +|**Return**| —— | +| RT_EOK | Semaphore successfully released | + +## Semaphore Application Sample + +This is a sample of semaphore usage routine. This routine creates a dynamic semaphore and initializes two threads, one thread sends the semaphore, and one thread receives the semaphore and performs the corresponding operations. As shown in the following code: + +Use of semaphore + + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 + +/* pointer to semaphore */ +static rt_sem_t dynamic_sem = RT_NULL; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +static void rt_thread1_entry(void *parameter) +{ + static rt_uint8_t count = 0; + + while(1) + { + if(count <= 100) + { + count++; + } + else + return; + + /* count release semaphore every 10 counts */ + if(0 == (count % 10)) + { + rt_kprintf("t1 release a dynamic semaphore.\n"); + rt_sem_release(dynamic_sem); + } + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +static void rt_thread2_entry(void *parameter) +{ + static rt_err_t result; + static rt_uint8_t number = 0; + while(1) + { + /* permanently wait for the semaphore; once obtain the semaphore, perform the number self-add operation */ + result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("t2 take a dynamic semaphore, failed.\n"); + rt_sem_delete(dynamic_sem); + return; + } + else + { + number++; + rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number); + } + } +} + +/* initialization of the semaphore sample */ +int semaphore_sample(void) +{ + /* create a dynamic semaphore with an initial value of 0 */ + dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO); + if (dynamic_sem == RT_NULL) + { + rt_kprintf("create dynamic semaphore failed.\n"); + return -1; + } + else + { + rt_kprintf("create done. dynamic semaphore value = 0.\n"); + } + + rt_thread_init(&thread1, + "thread1", + rt_thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + rt_thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY-1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} +/* export to msh command list */ +MSH_CMD_EXPORT(semaphore_sample, semaphore sample); +``` + +Simulation results: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >semaphore_sample +create done. dynamic semaphore value = 0. +msh >t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 1 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 2 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 3 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 4 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 5 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 6 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 7 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 8 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 9 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 10 +``` + +As the result of the operation above: Thread 1 sends a semaphore when the count is multiple of 10 (the thread exits after the count reaches 100), and thread 2 adds one on top of the number after receiving the semaphore. + +Another semaphore application routine is shown below. This sample will use two threads and three semaphores to implement an example of producers and consumers. Among them: + +Another application routine for semaphores is as follows: + +To be more accurate, the producer consumer model is actually a "producer-consumer-warehouse" model. We call the available spots in the warehouse "empty seats", and once an available spot ("empty seat") is taken, we then call it a "full seat". For this model, the following points should be clarified: +1、The producer only produces when the warehouse is not full; the producer will stop production when the warehouse is full. +2、The consumer can consume only when there are products in the warehouse; consumers will wait if the warehouse is empty. +3、When the consumer consumes, the warehouse is not full anymore, and then the producer will be notified to produce again. +4、The producer should notify the consumer to consumer after it produces consumable products. + + +This routine will use two threads and three semaphores to implement examples of producers and consumers. Among them +The three semaphores in the example are: +①sem_lock: This semaphore acts as a lock because both threads operate on the same array array which means this array is a shared resource and sem_lock is used to protect this shared resource. +②sem_empty: Its value is used to indicate the number of "warehouse" available seats , and the value of sem_empty is initialized to 5, indicating that there are 5 "empty seats" . +③sem_full:Its value is used to indicate the number of "full seats" in the "warehouse", and the value of sem_full is initialized to 0, indicating that there are 0 "full seats". + +The 2 threads in the example are: +①Producer thread: After obtaining the available seat (sem_empty value minus 1), generate a number, loop into the array, and then release a "full seat"(sem_full value plus 1). +②Consumer thread: After getting the "full seat" (the value of sem_full is decremented by 1), the contents of the array are read and added, and then an "empty seat" is released (the value of sem_empty is increased by 1). + +Producer consumer routine + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 6 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* Define a maximum of 5 elements to be generated */ +#define MAXSEM 5 + +/* An array of integers used to place production */ +rt_uint32_t array[MAXSEM]; + +/* Point to the producer and consumer's read-write position in the array */ +static rt_uint32_t set, get; + +/* Pointer to the thread control block */ +static rt_thread_t producer_tid = RT_NULL; +static rt_thread_t consumer_tid = RT_NULL; + +struct rt_semaphore sem_lock; +struct rt_semaphore sem_empty, sem_full; + +/* Pointer to the thread control block */ +void producer_thread_entry(void *parameter) +{ + int cnt = 0; + + /* Run for 10 times*/ + while (cnt < 10) + { + /* Obtain one vacancy */ + rt_sem_take(&sem_empty, RT_WAITING_FOREVER); + + /* Modify array content, lock */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + array[set % MAXSEM] = cnt + 1; + rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]); + set++; + rt_sem_release(&sem_lock); + + /* 发布一个满位 */ + rt_sem_release(&sem_full); + cnt++; + + /* Pause for a while */ + rt_thread_mdelay(20); + } + + rt_kprintf("the producer exit!\n"); +} + +/* Consumer thread entry */ +void consumer_thread_entry(void *parameter) +{ + rt_uint32_t sum = 0; + + while (1) + { + /* obtain a "full seat" */ + rt_sem_take(&sem_full, RT_WAITING_FOREVER); + + /* Critical region, locked for operation */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + sum += array[get % MAXSEM]; + rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]); + get++; + rt_sem_release(&sem_lock); + + /* Release one vacancy */ + rt_sem_release(&sem_empty); + + /* The producer produces up to 10 numbers, stops, and the consumer thread stops accordingly */ + if (get == 10) break; + + /* Pause for a while */ + rt_thread_mdelay(50); + } + + rt_kprintf("the consumer sum is: %d\n", sum); + rt_kprintf("the consumer exit!\n"); +} + +int producer_consumer(void) +{ + set = 0; + get = 0; + + /* Initialize 3 semaphores */ + rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_full, "full", 0, RT_IPC_FLAG_FIFO); + + /* Create producer thread */ + producer_tid = rt_thread_create("producer", + producer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, + THREAD_TIMESLICE); + if (producer_tid != RT_NULL) + { + rt_thread_startup(producer_tid); + } + else + { + rt_kprintf("create thread producer failed"); + return -1; + } + + /* Create consumer thread */ + consumer_tid = rt_thread_create("consumer", + consumer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, + THREAD_TIMESLICE); + if (consumer_tid != RT_NULL) + { + rt_thread_startup(consumer_tid); + } + else + { + rt_kprintf("create thread consumer failed"); + return -1; + } + + return 0; +} + +/* Export to msh command list */ +MSH_CMD_EXPORT(producer_consumer, producer_consumer sample); +``` + +The simulation results for this routine are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >producer_consumer +the producer generates a number: 1 +the consumer[0] get a number: 1 +msh >the producer generates a number: 2 +the producer generates a number: 3 +the consumer[1] get a number: 2 +the producer generates a number: 4 +the producer generates a number: 5 +the producer generates a number: 6 +the consumer[2] get a number: 3 +the producer generates a number: 7 +the producer generates a number: 8 +the consumer[3] get a number: 4 +the producer generates a number: 9 +the consumer[4] get a number: 5 +the producer generates a number: 10 +the producer exit! +the consumer[0] get a number: 6 +the consumer[1] get a number: 7 +the consumer[2] get a number: 8 +the consumer[3] get a number: 9 +the consumer[4] get a number: 10 +the consumer sum is: 55 +the consumer exit! +``` + +This routine can be understood as the process of producers producing products and putting them into the warehouse and the consumer taking the products from the warehouse. + +(1) Producer thread: + +1) Obtain 1 "empty seat" (put product number), now the number of "empty seats" is decremented by 1; + +2) Lock protection; the generated number value is cnt+1, and the value is looped into the array; then unlocked; + +3) Release 1 "full seat" (put one product into the warehouse, there will be one more "full seat" in the warehouse), add 1 to the number "full seats". + +(2) Consumer thread: + +1)Obtain 1 "full seat" (take product number), then the the number of "full seats" is decremented by 1; + +2)Lock protection; read the number produced by the producer from array and add it to the last number; then unlock it; + +3)Release 1 "empty seat" (take one product from the warehouse, then there is one more "empty seat" in the warehouse), add 1 to the number of "empty seats". + +The producer generates 10 numbers in turn, and the consumers take them away in turn and sum the values of the 10 numbers. Semaphore lock protects array critical region resources, ensuring the exclusivity of number taking for the consumers each time and achieving inter-thread synchronization. + +## Semaphore Usage Occasion + +Semaphores is a very flexible way to synchronize and can be used in a variety of situations, like forming locks, synchronization, resource counts, etc. It can also be conveniently used for synchronization between threads and threads, interrupts and threads. + +### Thread Synchronization + +Thread synchronization is one of the simplest types of semaphore applications. For example, using semaphores to synchronize between two threads, the value of the semaphore is initialized to 0, indicating that there are 0 semaphore resource instances; and the thread attempting to acquire the semaphore will wait directly on this semaphore. + +When the thread holding the semaphore completes the work it is processing, it will release this semaphore. Thread waiting on this semaphore can be awaken, and it can then perform the next part of the work. This occasion can also be seen as using the semaphore for the work completion flag: the thread holding the semaphore completes its own work, and then notifies the thread waiting for the semaphore to continue the next part of the work. + +### Lock + +Locks, a single lock is often applied to multiple threads accessing the same shared resource (in other words, critical region). When semaphore is used as a lock, the semaphore resource instance should normally be initialized to 1, indicating that the system has one resource available by default. Because the semaphore value always varies between 1 and 0, so this type of lock is also called binary semaphore. As shown in the following figure, when a thread needs to access shared resource, it needs to obtain the resource lock first. When this thread successfully obtains the resource lock, other threads that intend to access the shared resource will suspend because they cannot obtain the resource. This is because it is already locked (semaphore value is 0) when other threads are trying to obtain the lock. When thread holding the semaphore is processed and exiting the critical region, it will release the semaphore and unlock the lock, and the first waiting thread that is suspending on the lock will be awaken to gain access to the critical region. + + + +### Interrupt Synchronization between Threads + +Semaphore can also be easily applied to interrupting synchronization between threads, such as an interrupt trigger. When interrupting service routine, thread needs to be notifies to perform corresponding data processing. At this time, the initial value of the semaphore can be set to 0. When the thread tries to hold this semaphore, since the initial value of the semaphore is 0, the thread will then suspends on this semaphore until the semaphore is released. When the interrupt is triggered, hardware-related actions are firstly performed, such as reading corresponding data from the hardware I/O port, and confirming the interrupt to clear interrupt source, and then releasing a semaphore to wake up the corresponding thread for subsequent data processing. For example, the processing of FinSH threads is as shown in the following figure. + + + +The value of the semaphore is initially 0. When the FinSH thread attempts to obtain the semaphore, it will be suspended because the semaphore value is 0. When the console device has data input, an interrupt is generated to enter the interrupt service routine. In the interrupt service routine, it reads the data of the console device, puts the read data into the UART buffer for buffering, and then releases the semaphore. The semaphore release will wake up the shell thread. After the interrupt service routine has finished, if there are no ready threads with higher priority than the shell thread in the system, the shell thread will hold the semaphore and run, obtaining the input data from the UART buffer. + +>The mutual exclusion between interrupts and threads cannot be done by means of semaphores (locks), but by means of switch interrupts. + +### Resource Count + +Semaphore can also be considered as an incrementing or decrementing counter. It should be noted that the semaphore value is non-negative. For example, if the value of a semaphore is initialized to 5, then the semaphore can be reduced by a maximum of 5 consecutive times until the counter is reduced to zero. Resource count is suitable for occasions where the processing speeds between threads do not match. At this time, the semaphore can be counted as the number of completed tasks of the previous thread, and when dispatched to the next thread, it can also be used in a continuous manner handling multiple events each time. For example, in the producer and consumer problem, the producer can release the semaphore multiple times, and then the consumer can process multiple semaphore resources each time when dispatched. + +>Generally, resource count is mostly inter-thread synchronization in a hybrid mode, because there are still multiple accesses from threads for a single resource processing, which requires accessing and processing for a single resource, and operate lock mutex operation. + +# Mutex + +Mutexes, also known as mutually exclusive semaphores, are a special binary semaphore. Mutex is similar to a parking lot with only one parking space: when one car enters, the parking lot gate is locked and other vehicles are waiting outside. When the car inside comes out, parking lot gate will open and the next car can enter. + +## Mutex Working Mechanism + +The difference between a mutex and a semaphore is that the thread with a mutex has ownership of the mutex, mutex supports recursive access and prevents thread priority from reversing; and mutex can only be released by the thread holding it, whereas semaphore can be released by any thread. + +There are only two states of mutex, unlocked and locked (two state values). When a thread holds it, then the mutex is locked and its ownership is obtained by this thread. Conversely, when this thread releases it, it unlocks the mutex and loses its ownership. When a thread is holding a mutex, other threads will not be able to unlock this mutex or hold it. The thread holding the mutex can also acquire the lock again without being suspended, as shown in the following figure. This feature is quite different from the general binary semaphore: in semaphore, because there is no instance, the thread will suspend if the thread recursively holds the semaphore (which eventually leads to a deadlock). + + + +Another potential problem with using semaphores is thread priority inversion. The so-called priority inversion is when a high-priority thread attempts to access shared resource through the semaphore mechanism, if the semaphore is already held by a low-priority thread which may happen to be preempted by other medium-priority threads while running, this leads to high-priority threads being blocked by many lower-priority threads which means instantaneity is difficult to guarantee. As shown in the following figure: There are three threads with the priority levels A, B, and C, priority A> B > C. Threads A and B are in suspended state, waiting for an event to trigger; thread C is running, and thread C starts using a shared resource M. While using the resource, the event thread A is waiting for occurs and thread A switches to ready state because it has higher priority than thread C, so it executes immediately. But when thread A wants to use shared resource M, because it is being used by thread C, thread A is suspended and thread C is running. If the event thread B is waiting for occurs, thread B switches to ready state. Since thread B has a higher priority than thread C, thread B starts running, thread C won't run until thread B finishes. Thread A is only executed when thread C releases the shared resource M. In this case, the priority has been reversed: thread B runs before thread A. This does not guarantee the response time for high priority threads. + + + +In the RT-Thread operating system, mutex can solve the priority inversion problem and implement the priority inheritance algorithm. Priority inheritance solves the problems caused by priority inversion by raising the priority of thread C to the priority of thread A during the period during when thread A is suspended trying to access the shared resource. This prevents C (indirectly preventing A) from being preempted by B, as shown in the following figure. Priority inheritance refers to raising the priority of a low-priority thread that occupies a certain resource, making the priority level of the low-priority thread to be equal to the priority of the thread with the highest priority level among all threads waiting for the resource, and then executes. When this is low-priority releases the resource, the priority level returns to the initial setting. Therefore, threads that inherit priority help to prevent the system resources from being preempted by any intermediate-priority thread. + + + +>After the mutex is obtained, release the mutex as soon as possible. During the time when holding the mutex, you must not change the priority of the thread holding the mutex. + +## Mutex Control Block + +In RT-Thread, mutex control block is a data structure used by the operating system to manage mutexes, represented by the struct rt rt_mutex. Another C expression, rt_mutex_t, represents the handle of the mutex, and the implementation in C language refers to the pointer of the mutex control block. See the following code for a detailed definition of the mutex control block structure: + +```c +struct rt_mutex + { + struct rt_ipc_object parent; /* inherited from the ipc_object class */ + + rt_uint16_t value; /* mutex value */ + rt_uint8_t original_priority; /* hold the original priority of the thread */ + rt_uint8_t hold; /* number of times holding the threads */ + struct rt_thread *owner; /* thread that currently owns the mutex */ + }; + /* rt_mutext_t pointer type of the one poniter pointing to the mutex structure */ + typedef struct rt_mutex* rt_mutex_t; +``` + +The rt_mutex object is derived from rt_ipc_object and is managed by the IPC container. + +## Mutex Management + +The mutex control block contains important parameters related to mutex and it plays an important role in the implementation of the mutex function. The mutex-related interface is as shown in the following figure. The operation of a mutex includes: creating/initiating a mutex, obtaining a mutex, releasing a mutex, and deleting/detaching a mutex. + + + +### Create and Delete Mutex + +When creating a mutex, the kernel first creates a mutex control block and then completes the initialization of the control block. Create a mutex using the following function interface: + +```c +rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag); +``` + +You can call the rt_mutex_create function to create a mutex whose name is designated by name. When this function is called, the system will first allocate a mutex object from the object manager, initialize the object, and then initialize the parent class IPC object and the mutex-related part. The flag of the mutex is set to RT_IPC_FLAG_PRIO, which means that when multiple threads are waiting for resources, the resources will be accessed by the thread with higher priority. The flag is set to RT_IPC_FLAG_FIFO, which means that when multiple threads are waiting for resources, resources are being accesses in a first-come-first-served order. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mutex_create() + +|Parameters |**Description** | +|------------|-------------------------------------------------------------------| +| name | Mutex name | +| flag | Mutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| Mutex handle | Created successfully | +| RT_NULL | Creation failed | + +For dynamically created mutex, when the mutex is no longer used, the system resource is released by removing the mutex. To remove a mutex, use the following function interface: + +```c +rt_err_t rt_mutex_delete (rt_mutex_t mutex); +``` + +When a mutex is deleted, all threads waiting for this mutex will be woken up, return value for the waiting threads is - RT_ERROR. The system then removes the mutex from the kernel object manager linked list and releases the memory space occupied by the mutex. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mutex_delete() + +|Parameters|Description | +|----------|------------------| +| mutex | The handle of the mutex object | +|**Return**| —— | +| RT_EOK | Deleted successfully | + +### Initialize and Detach Mutex + +The memory of a static mutex object is allocated by the compiler during system compilation, and is usually placed in a read-write data segment or an uninitialized data segment. Before using such static mutex objects, you need to initialize them first. To initialize the mutex, use the following function interface: + +```c +rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag); +``` + +When using this function interface, you need to specify the handle of the mutex object (that is, the pointer to the mutex control block), the mutex name, and the mutex flag. The mutex flag can be the flags mentioned in the creation of mutex function above. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mutex_init() + +|Parameters|Description | +|----------|-------------------------------------------------------------------| +| mutex | The handle of the mutex object, which is provided by the user and points to the memory block of the mutex object | +| name | Mutex name | +| flag | Mutex flag, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return**| —— | +| RT_EOK | Initialization successful | + +For statically initialized muex, detaching mutex means to remove the mutex object from the kernel object manager. To detach the mutex, use the following function interface: + +```c +rt_err_t rt_mutex_detach (rt_mutex_t mutex); +``` + +After using this function interface, the kernel wakes up all threads suspended on the mutex (the return value of the thread is -RT_ERROR), and then the system detaches the mutex from the kernel object manager. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_mutex_detach() + +|Parameters|Description | +|----------|------------------| +| mutex | The handle of the mutex object | +|**Return**| —— | +| RT_EOK | Successful | + +### Obtain Mutex + +Once the thread obtains the mutex, the thread has ownership of the mutex, that is, a mutex can only be held by one thread at a time. To obtain the mutex, use the following function interface: + +```c +rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time); +``` + +If the mutex is not controlled by another thread, the thread requesting the mutex will successfully obtain the mutex. If the mutex is already controlled by the current thread, then add one 1 to the number of holds for the mutex, and the current thread will not be suspended to wait. If the mutex is already occupied by another thread, the current thread suspends and waits on the mutex until another thread releases it or until the specified timeout elapses. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mutex_take() + +|**Parameters** |Description | +|---------------|------------------| +| mutex | The handle of the mutex object | +| time | Specified waiting time | +|**Return** | —— | +| RT_EOK | Successfully obtained mutex | +| \-RT_ETIMEOUT | Timeout | +| \-RT_ERROR | Failed to obtain | + +### Release Mutex + +When a thread completes the access to a mutually exclusive resource, it should release the mutex it occupies as soon as possible, so that other threads can obtain the mutex in time. To release the mutex, use the following function interface: + +```c +rt_err_t rt_mutex_release(rt_mutex_t mutex); +``` + +When using this function interface, only threads that already have control of the mutex can release it. Each time the mutex is released, its holding count is reduced by one. When the mutex's holding count is zero (that is, the holding thread has released all holding operations), it becomes available, threads waiting on the semaphore is awaken. If the thread's priority is increased by the mutex, then when the mutex is released, the thread reverts to the priority level before holding the mutex. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_mutex_release() + +|**Parameters**|**Description** | +|----------|------------------| +| mutex | The handle of the mutex object | +|**Return**| —— | +| RT_EOK | Success | + +## Mutex Application Sample + +This is a mutex application routine, and a mutex lock is a way to protect shared resources. When a thread has the mutex lock, it can protect shared resources from being destroyed by other threads. The following example can be used to illustrate. There are two threads: thread 1 and thread 2, thread 1 adds 1 to each of the two numbers; thread 2 also adds 1 to each of the two numbers. mutex is used to ensure that the operation of the thread changing the values of the 2 numbers is not interrupted. As shown in the following code: + +Mutex routine + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 8 +#define THREAD_TIMESLICE 5 + +/* Pointer to the mutex */ +static rt_mutex_t dynamic_mutex = RT_NULL; +static rt_uint8_t number1,number2 = 0; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +static void rt_thread_entry1(void *parameter) +{ + while(1) + { + /* After thread 1 obtains the mutex, it adds 1 to number1 and number2, and then releases the mutex. */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + number1++; + rt_thread_mdelay(10); + number2++; + rt_mutex_release(dynamic_mutex); + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +static void rt_thread_entry2(void *parameter) +{ + while(1) + { + /* After thread 2 obtains the mutex, check whether the values of number1 and number2 are the same. If they are the same, it means the mutex succeesfully played the role of a lock. */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + if(number1 != number2) + { + rt_kprintf("not protect.number1 = %d, number2 = %d \n",number1 ,number2); + } + else + { + rt_kprintf("mutex protect ,number1 = number2 is %d\n",number1); + } + + number1++; + number2++; + rt_mutex_release(dynamic_mutex); + + if(number1>=50) + return; + } +} + +/* Initialization of the mutex sample */ +int mutex_sample(void) +{ + /* Create a dynamic mutex */ + dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO); + if (dynamic_mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + rt_thread_entry1, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + rt_thread_entry2, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY-1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +/* Export to the MSH command list */ +MSH_CMD_EXPORT(mutex_sample, mutex sample); +``` + +Both thread 1 and thread 2 use mutexes to protect the operation on the 2 numbers (if the obtain and release mutex statements in thread 1 are commented out, thread 1 will no longer protect number), the simulation results are as follows : + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mutex_sample +msh >mutex protect ,number1 = number2 is 1 +mutex protect ,number1 = number2 is 2 +mutex protect ,number1 = number2 is 3 +mutex protect ,number1 = number2 is 4 +… +mutex protect ,number1 = number2 is 48 +mutex protect ,number1 = number2 is 49 +``` + +Threads use mutexes to protect the operation on the two numbers, keeping the number values consistent. + +Another example of a mutex is shown in the following code. This example creates three dynamic threads to check if the priority level of the thread holding the mutex is adjusted to the highest priority level among the waiting threads. + +Prevent priority inversion routine + +```c +#include <rtthread.h> + +/* Pointer to the thread control block */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; +static rt_thread_t tid3 = RT_NULL; +static rt_mutex_t mutex = RT_NULL; + + +#define THREAD_PRIORITY 10 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* Thread 1 Entry */ +static void thread1_entry(void *parameter) +{ + /* Let the low priority thread run first */ + rt_thread_mdelay(100); + + /* At this point, thread3 holds the mutex and thread2 is waiting to hold the mutex */ + + /* Check the priority level of thread2 and thread3 */ + if (tid2->current_priority != tid3->current_priority) + { + /* The priority is different, the test fails */ + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test failed.\n"); + return; + } + else + { + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test OK.\n"); + } +} + +/* Thread 2 Entry */ +static void thread2_entry(void *parameter) +{ + rt_err_t result; + + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + + /* Let the low-priority thread run first */ + rt_thread_mdelay(50); + + /* + * Trying to hold a mutex lock. At this point, thread 3 has the mutex lock, so the priority level of thread 3 should be raised + * to the same level of priority as thread 2 + */ + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + + if (result == RT_EOK) + { + /* Release mutex lock */ + rt_mutex_release(mutex); + } +} + +/* Thread 3 Entry */ +static void thread3_entry(void *parameter) +{ + rt_tick_t tick; + rt_err_t result; + + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("thread3 take a mutex, failed.\n"); + } + + /* Operate a long cycle, 500ms */ + tick = rt_tick_get(); + while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ; + + rt_mutex_release(mutex); +} + +int pri_inversion(void) +{ + /* Created a mutex lock */ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO); + if (mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + /* Create thread 1*/ + tid1 = rt_thread_create("thread1", + thread1_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* Create thread 2 */ + tid2 = rt_thread_create("thread2", + thread2_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + /* Create thread 3 */ + tid3 = rt_thread_create("thread3", + thread3_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid3 != RT_NULL) + rt_thread_startup(tid3); + + return 0; +} + +/* Export to the msh command list */ +MSH_CMD_EXPORT(pri_inversion, prio_inversion sample); +``` + +The simulation results are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >pri_inversion +the priority of thread2 is: 10 +the priority of thread3 is: 11 +the priority of thread2 is: 10 +the priority of thread3 is: 10 +test OK. +``` + +The routine demonstrates how to use the mutex. Thread 3 holds the mutex first, and then thread 2 tries to hold the mutex, at which point thread 3's priority is raised to the same level as thread 2. + +>It is important to remember that mutexes cannot be used in interrupt service routines. + +## Occasions to Use Mutex + +The use of a mutex is relatively simple because it is a type of semaphore and it exists in the form of a lock. At the time of initialization, the mutex is always unlocked, and when it is held by the thread, it immediately becomes locked. Mutex is more suitable for: + +(1) When a thread holds a mutex multiple times. This can avoid the problem of deadlock caused by multiple recursive holdings of the same thread. + +(2) A situation in which priority inversion may occur due to multi-thread synchronization. + +# Event + +Event set is also one of the mechanisms for synchronization between threads. An event set can contain multiple events. Event set can be used to complete one-to-many, many-to-many thread synchronization. Let's take taking bus as an example to illustrate event. There may be the following situations when waiting for a bus at a bus stop: + +①P1 is taking a bus to a certain place, only one type of bus can reach the destination. P1 can leave for the destination once that bus arrives. + +②P1 is taking a bus to a certain place, 3 types of buses can reach the destination. P1 can leave for the destination once any 1 of the 3 types of bus arrives. + +③P1 is traveling with P2 to a certain place together, P1 can't leave for the destination unless two conditions are met. These two conditions are “P2 arrives at the bus stop” and “bus arrives at the bus stop”. + +Here, P1 leaving for a certain place can be regarded as a thread, and “bus arrives at the bus stop” and “P2 arrives at the bus stop” are regarded as the occurrence of events. Situation ① is a specific event to wakes up the thread; situation ② is any single event to wake up the thread; situation ③ is when multiple events must occur simultaneously to wake up the thread. + +## Event Set Working Mechanism + +The event set is mainly used for synchronization between threads. Unlike the semaphore, it can achieve one-to-many, many-to-many synchronization. That is, the relationship between a thread and multiple events can be set as follows: any one of the events wakes up the thread, or several events wake up the thread for subsequent processing; likewise, the event can be multiple threads to synchronize multiple events. This collection of multiple events can be represented by a 32-bit unsigned integer variable, each bit of the variable representing an event, and the thread associates one or more events by "logical AND" or "logical OR" to form event combination. The "logical OR" of an event is also called independent synchronization, which means that the thread is synchronized with one of the events; the event "logical AND" is also called associative synchronization, which means that the thread is synchronized with several events. + +The event set defined by RT-Thread has the following characteristics: + +1) Events are related to threads only, and events are independent of each other: each thread can have 32 event flags, recorded with a 32-bit unsigned integer, each bit representing an event; + +2) Event is only used for synchronization and does not provide data transfer functionality; + +3) Events are not queuing, that is, sending the same event to the thread multiple times (if the thread has not had time read it), the effect is equivalent to sending only once. + +In RT-Thread, each thread has an event information tag with three attributes. They are RT_EVENT_FLAG_AND (logical AND), RT_EVENT_FLAG_OR (logical OR), and RT_EVENT_FLAG_CLEAR (clear flag). When the thread waits for event synchronization, it can determine whether the currently received event satisfies the synchronization condition by 32 event flags and this event information flag. + + + +As shown in the figure above, the first and 30th bits of the event flag of thread #1 are set. If the event information flag is set to logical AND, it means that thread #1 will be triggered to wake up only after both event 1 and event 30 occur. If the event information flag is set to logical OR, the occurrence of either event 1 or event 30 will trigger to wake up thread #1. If the message flag also sets the clear flag bit, this means event 1 and event 30 will be automatically cleared to zero when thread #1 wakes up, otherwise the event flag will still be present (set to 1). + +## Event Set Control Block + +In RT-Thread, event set control block is a data structure used by the operating system to manage events, represented by the structure struct rt_event. Another C expression, rt_event_t, represents the handle of the event set, and the implementation in C language is a pointer to the event set control block. See the following code for a detailed definition of the event set control block structure: + +```c +struct rt_event +{ + struct rt_ipc_object parent; /* Inherited from the ipc_object class */ + + /* The set of events, each bit represents 1 event, the value of the bit can mark whether an event occurs */ + rt_uint32_t set; +}; +/* rt_event_t is the pointer type poniting to the event structure */ +typedef struct rt_event* rt_event_t; +``` + +rt_event object is derived from rt_ipc_object and is managed by the IPC container. + +## Management of Event Sets + +Event set control block contains important parameters related to the event set and plays an important role in the implementation of the event set function. The event set related interfaces are as shown in the following figure. The operations on an event set include: create/initiate event sets, send events, receive events, and delete/detach event sets. + + + +### Create and Delete Event Set + +When creating an event set, the kernel first creates an event set control block, and then performs basic initialization on the event set control block. The event set is created using the following function interface: + +```c +rt_event_t rt_event_create(const char* name, rt_uint8_t flag); +``` + +When the function interface is called, the system allocates the event set object from the object manager, initializes the object, and then initializes the parent class IPC object. The following table describes the input parameters and return values for this function: + + Input parameters and return values for rt_event_create() + +|**Parameters** |**Description** | +|----------------|---------------------------------------------------------------------| +| name | Name of the event set. | +| flag | The flag of the event set, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return** | —— | +| RT_NULL | Creation failed. | +| Handle of the event object | Creation successful | + +When the system no longer uses the event set object created by rt_event_create(), the system resource is released by deleting the event set object control block. To delete an event set you can use the following function interface: + +```c +rt_err_t rt_event_delete(rt_event_t event); +``` + +When you call rt_event_delete function to delete an event set object, you should ensure that the event set is no longer used. All threads that are suspended on the event set will be awaken before the deletion (the return value of the thread is -RT_ERROR), and then the memory block occupied by the event set object is released. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_event_delete() + +|**Parameters**|**Description** | +|----------|------------------| +| event | The handle of the event set object | +|**Return**| —— | +| RT_EOK | Success | + +### Initialize and Detach Event Set + +The memory of a static event set object is allocated by the compiler during system compilation, and is usually placed in a read-write data segment or an uninitialized data segment. Before using a static event set object, you need to initialize it first. The initialization event set uses the following function interface: + +```c +rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag); +``` + +When the interface is called, you need to specify the handle of the static event set object (that is, the pointer pointing to the event set control block), and then the system will initialize the event set object and add it to the system object container for management. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_event_init() + +|**Parameters**|**Description** | +|----------|---------------------------------------------------------------------| +| event | The handle of the event set object | +| name | The name of the event set | +| flag | The flag of the event set, which can take the following values: RT_IPC_FLAG_FIFO or RT_IPC_FLAG_PRIO | +|**Return**| —— | +| RT_EOK | Success | + +When the system no longer uses the event set object initialized by rt_event_init(), the system resources are released by detaching the event set object control block. Detaching event set means to detach the event set object from the kernel object manager. To detach an event set, use the following function interface: + +```c +rt_err_t rt_event_detach(rt_event_t event); +``` + +When the user calls this function, the system first wakes up all the threads suspended on the event set wait queue (the return value of the thread is -RT_ERROR), and then detaches the event set from the kernel object manager. The following table describes the input parameters and return values for this function: + +Input parameters and return values for rt_event_detach() + +|**Parameters**|**Description** | +|----------|------------------| +| event | The handle of the event set object | +|**Return**| —— | +| RT_EOK | Success | + +### Send Event + +The send event function can send one or more events in the event set as follows: + +```c +rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); +``` + +When using the function interface, the event flag value of the event set object is set by the event flag specified by the parameter set, and then the waiting thread linked list transversely waiting for the event set objects is used to determine whether there is a thread has the event activation requirement that matches the current event flag value. If there is, wake up the thread. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_event_send() + +|**Parameters**|**Description** | +|----------|------------------------------| +| event | The handle of the event set object | +| set | The flag value of one or more events sent | +|**Return**| —— | +| RT_EOK | Success | + +### Receive Event + +The kernel uses a 32-bit unsigned integer to identify the event set, each bit represents an event, so an event set object can wait to receive 32 events at the same time, and the kernel can decide how to activate the thread by specifying the parameter "logical AND" or "logical OR". Using the "logical AND" parameter indicates that the thread is only activated when all waiting events occur, and using the "logical OR" parameter means that the thread is activated as soon as one waiting event occurs. To receive events, use the following function interface: + +```c +rt_err_t rt_event_recv(rt_event_t event, + rt_uint32_t set, + rt_uint8_t option, + rt_int32_t timeout, + rt_uint32_t* recved); +``` + +If it has already occurred, it determines whether to reset the corresponding flag of the event according to whether RT_EVENT_FLAG_CLEAR is set on the parameter option. If it has already occurred, it determines whether to reset the corresponding flag of the event according to whether RT_EVENT_FLAG_CLEAR is set on the parameter option, then return (where the recved parameter returns to the received event); if it has not occurred, fill the waiting set and option parameters into the structure of the thread itself, then suspend the thread on this event until its waiting event satisfies the condition or until the specified timeout elapses. If the timeout is set to zero, it means that when the event to be accepted by the thread does not meet the requirements, it does not wait, but returns directly-RT_ETIMEOUT. The following table describes the input parameters and return values for this function: + +Input parameters and return values of rt_event_recv() + +|**Parameters** |**Description** | +|---------------|----------------------| +| event | The handle of the event set object | +| set | Receive events of interest to the thread | +| option | Receive options | +| timeout | Timeout | +| recved | Point to the received event | +|**Return** | —— | +| RT_EOK | Successful | +| \-RT_ETIMEOUT | Timeout | +| \-RT_ERROR | Error | + +The value of option can be: + +```c +/* Select AND or OR to receive events */ +RT_EVENT_FLAG_OR +RT_EVENT_FLAG_AND + +/* Choose to clear reset event flag */ +RT_EVENT_FLAG_CLEAR +``` + +## Event Set Application Sample + +This is the application routine for the event set, which initializes an event set, two threads. One thread waits for an event of interest to it, and another thread sends an event, as shown in code listing 6-5: + +Event set usage routine + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 + +#define EVENT_FLAG3 (1 << 3) +#define EVENT_FLAG5 (1 << 5) + +/* Event control block */ +static struct rt_event event; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* Thread 1 entry function*/ +static void thread1_recv_event(void *param) +{ + rt_uint32_t e; + + /* The first time the event is received, either event 3 or event 5 can trigger thread 1, clearing the event flag after receiving */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: OR recv event 0x%x\n", e); + } + + rt_kprintf("thread1: delay 1s to prepare the second event\n"); + rt_thread_mdelay(1000); + + /* The second time the event is received, both event 3 and event 5 can trigger thread 1, clearing the event flag after receiving */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: AND recv event 0x%x\n", e); + } + rt_kprintf("thread1 leave.\n"); +} + + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* Thread 2 Entry */ +static void thread2_send_event(void *param) +{ + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event5\n"); + rt_event_send(&event, EVENT_FLAG5); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_kprintf("thread2 leave.\n"); +} + +int event_sample(void) +{ + rt_err_t result; + + /* Initialize event object */ + result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("init event failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_recv_event, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_send_event, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* Export to the msh command list */ +MSH_CMD_EXPORT(event_sample, event sample); +``` + +The simulation results are as follows: + +```c + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >event_sample +thread2: send event3 +thread1: OR recv event 0x8 +thread1: delay 1s to prepare the second event +msh >thread2: send event5 +thread2: send event3 +thread2 leave. +thread1: AND recv event 0x28 +thread1 leave. +``` + +The routine demonstrates how to use the event set. Thread 1 receives events twice before and after, using the "logical OR" and "logical AND" respectively. + +## Occasions to Use Event Set + +Event sets can be used in a variety of situations, and it can replace semaphores to some extent for inter-thread synchronization. A thread or interrupt service routine sends an event to the event set object, and the waiting thread is awaken and the corresponding event is processed. However, unlike semaphore, the event transmission operation is not cumulative until the event is cleared, and the release actions of semaphore are cumulative. Another feature of the event is that the receiving thread can wait for multiple events, meaning multiple events correspond to one thread or multiple threads. At the same time, according to thread waiting parameters, you can choose between a "logical OR" trigger or a "logical AND" trigger. This feature is not available for semaphores, etc. The semaphore can only recognize a single release action, and cannot wait for multiple types of release at the same time. The following figure shows the multi-event receiving diagram: + + + +An event set contains 32 events, and a particular thread only waits for and receives events it is interested in. It can be a thread waiting for the arrival of multiple events (threads 1, 2 are waiting for multiple events, logical "and" or logical "or" can be used to trigger the thread in events), or multiple threads waiting for an event to arrive (event 25). When there are events of interest to them occur, the thread will be awaken and subsequent processing actions will be taken. + + diff --git a/documentation/3.kernel/thread/figures/04Object_container.png b/documentation/3.kernel/thread/figures/04Object_container.png Binary files differnew file mode 100644 index 0000000000..d79943ae6e --- /dev/null +++ b/documentation/3.kernel/thread/figures/04Object_container.png diff --git a/documentation/3.kernel/thread/figures/04Task_switching.png b/documentation/3.kernel/thread/figures/04Task_switching.png Binary files differnew file mode 100644 index 0000000000..4ec4d438ad --- /dev/null +++ b/documentation/3.kernel/thread/figures/04Task_switching.png diff --git a/documentation/3.kernel/thread/figures/04main_thread.png b/documentation/3.kernel/thread/figures/04main_thread.png Binary files differnew file mode 100644 index 0000000000..7e86b22aa6 --- /dev/null +++ b/documentation/3.kernel/thread/figures/04main_thread.png diff --git a/documentation/3.kernel/thread/figures/04thread_ops.png b/documentation/3.kernel/thread/figures/04thread_ops.png Binary files differnew file mode 100644 index 0000000000..29395f1613 --- /dev/null +++ b/documentation/3.kernel/thread/figures/04thread_ops.png diff --git a/documentation/3.kernel/thread/figures/04thread_sta.png b/documentation/3.kernel/thread/figures/04thread_sta.png Binary files differnew file mode 100644 index 0000000000..3157b70dfc --- /dev/null +++ b/documentation/3.kernel/thread/figures/04thread_sta.png diff --git a/documentation/3.kernel/thread/figures/04thread_stack.png b/documentation/3.kernel/thread/figures/04thread_stack.png Binary files differnew file mode 100644 index 0000000000..9e12945c52 --- /dev/null +++ b/documentation/3.kernel/thread/figures/04thread_stack.png diff --git a/documentation/3.kernel/thread/figures/04time_slience.png b/documentation/3.kernel/thread/figures/04time_slience.png Binary files differnew file mode 100644 index 0000000000..6cd9c4575a --- /dev/null +++ b/documentation/3.kernel/thread/figures/04time_slience.png diff --git a/documentation/3.kernel/thread/thread.md b/documentation/3.kernel/thread/thread.md new file mode 100644 index 0000000000..65a95ad439 --- /dev/null +++ b/documentation/3.kernel/thread/thread.md @@ -0,0 +1,769 @@ +@page page_thread_management Thread Management + +When we are facing a big task in our daily life, we usually break it down into a number of simple, easy-to-manage smaller tasks. Then, we would deal with these smaller tasks one by one, gradually, the big task is worked out. In a multi-threaded operating system, developers also need to break down a complex application into multiple small, schedulable, and serialized program units. When tasks are reasonably divided and properly executed, this design allows the system to meet the capacity and time requirements of the real-time system. For example, to have the embedded system to perform such tasks, the system would collect data through sensors and display the data on the screen. In a multi-threaded real-time system, the task can be decomposed into two subtasks. The subtask, as shown in the following figure, reads the sensor data continuously and writes the data into the shared memory. The other subtask periodically reads the data from the shared memory and outputs the sensor data onto the screen. + + + +In RT-Thread, the application entity corresponding to the above subtask is the thread. The thread is the carrier of the task. It is the most basic scheduling unit in RT-Thread. It describes the running environment of a task execution. It also describes the priority level of the task - an important task can be set a relatively high priority, while a non-important task can be set a lower priority, and different tasks can also be set to the same priority and take turns while running. + +When a thread runs, it thinks it is has sole access to the CPU as it is running. The runtime environment when the thread executes is called the context, specifically each variables and data, including all register variables, stacks, memory information, and so on. + +This chapter will be divided into five sections to introduce thread management in RT-Thread. After reading this chapter, readers will have a deeper understanding of the thread management mechanism of RT-Thread. They will have clear answers to questions like what states does a thread have, how to create a thread, why do idle threads exist, etc. + +# Thread Management Features + +The main function of RT-Thread's thread management is to manage and schedule threads. There are two types of threads in the system, namely system threads and user threads. System threads are threads created by the RT-Thread kernel. User threads are the thread created by the application. Both types of threads will allocate thread objects from the kernel object container. When a thread is deleted, it will also be deleted from the object container. As shown in the following figure, each thread has important attributes, such as a thread control block, thread stack, entry function, and so on. + + + +The thread scheduler of RT-Thread is preemptive and its main job is to find the highest priority thread from the list of ready threads so as to ensure that the highest priority thread can be run. Once the highest priority task is ready, it can always get the CPU usage it needs. + +When a running thread makes the running condition ready for another thread with a higher priority, then the current running thread's CPU usage right is deprived, or in other words, released, and the high-priority thread immediately gets CPU access. + +If it is the interrupt service routine that makes the running condition ready for the thread with a higher priority, when the interrupt is completed, the interrupted thread is suspended, the thread with the higher priority starts running. + +When the scheduler schedules threads and switches them, the current thread context is first saved. When it is switched back to this thread, the scheduler restores the context information of the thread. + +# Working Mechanism of Thread + +## Thread Control Block + +In RT-Thread, the thread control block is represented by structure `struct rt_thread`, which is a data structure used by the operating system to manage threads. It stores information about the thread, such as priority, thread name, thread status, etc. It also includes a linked list structure for connecting threads, event collection of thread waiting, etc., which is defined as follows: + +```c +/* Thread Control Block */ +struct rt_thread +{ + /* rt Object */ + char name[RT_NAME_MAX]; /* Thread Name */ + rt_uint8_t type; /* Object Type */ + rt_uint8_t flags; /* Flag Position */ + + rt_list_t list; /* Object List */ + rt_list_t tlist; /* Thread List */ + + /* Stack Pointer and Entry pointer */ + void *sp; /* Stack Pointer */ + void *entry; /* Entry Function Pointer */ + void *parameter; /* Parameter */ + void *stack_addr; /* Stack Address Pointer */ + rt_uint32_t stack_size; /* Stack Size */ + + /* Error Code */ + rt_err_t error; /* Thread Error Code */ + rt_uint8_t stat; /* Thread State */ + + /* Priority */ + rt_uint8_t current_priority; /* Current Priority */ + rt_uint8_t init_priority; /* Initial Priority */ + rt_uint32_t number_mask; + + ...... + + rt_ubase_t init_tick; /* Thread Initialization Count Value */ + rt_ubase_t remaining_tick; /* Thread Remaining Count Value */ + + struct rt_timer thread_timer; /* Built-in Thread Timer */ + + void (*cleanup)(struct rt_thread *tid); /* Thread Exit Clear Function */ + rt_uint32_t user_data; /* User Data */ +}; +``` + +`init_priority` is the thread priority specified when the thread was created, and will not be changed while the thread is running (unless the user executes the thread control function to manually adjust the thread priority). `cleanup` will be called back by the idle thread when the thread exits to perform the user-setup cleanup site and so on. The last member, `user_data`, can be used by the user to send some data into the thread control block to provide an implementation similar to thread private data. + +## Thread Important Attributes + +### Thread Stack + +RT-Thread's threads have a dependent stack. When the thread is switched, the context of the current thread is stored in the stack. When the thread is about to resume operation, the context information is read from the stack and recovered. + +The thread stack is also used to store local variables in functions: local variables in functions are applied from the thread stack space; local variables in functions are initially allocated from registers (ARM architecture), when this function calls another function, these local variables will be placed on the stack. + +For the first run of thread, the context can be constructed manually to set initial environment like: entry function (PC register), entry parameter (R0 register), return position (LR register), current machine operating status (CPSR register). + +The growth direction of the thread stack is closely related to the CPU architecture. Versions before RT-Thread 3.1.0 only allow the stack to grow from high address to low address. For the ARM Cortex-M architecture, the thread stack can be constructed as shown below. + + + +When setting the size for thread stack, a larger thread stack can be designed for an MCU with a relatively large resource pool; or a larger stack can be set initially, for example, a size of 1K or 2K bytes, then in FinSH, use `list_thread` command to check the size of the stack used by the thread during the running of the thread. With this command, you can see the maximum stack depth used by the thread from the start of the thread to the current point in time, and then add the appropriate margin to form the final thread stack size, and finally modify the size of the stack space. + +### Thread State + +Only one thread is allowed to run on a processor at any time. Apart from the running process, threads have various different operating states, such as initial state, suspended state, ready state, etc. In RT-Thread, a thread can have five states, and the operating system would automatically adjust its state based on its running condition. + +The five states of a thread in RT-Thread are shown in the following table: + +| **States** | **Description** | +| --------------- | ------------------------------------------------------------ | +| Initial State | Thread is in initial state when it has just been created but has not started running; in initial state, the thread does not participate in scheduling. In RT-Thread, the macro definition of this state is RT_THREAD_INIT | +| Ready State | In ready state, the thread is queued according to priority, waiting to be executed; the processor is available again once the current thread is finished, and the operating system will then immediately find the ready thread with the highest priority to run. In RT-Thread, the macro definition of this state is RT_THREAD_READY | +| Running State | Thread is currently running. In a single-core system, only the thread returned from the rt_thread_self() function is running; in a multi-core system, more than one thread may be running. In RT-Thread, the macro definition of this state is RT_THREAD_RUNNING | +| Suspended State | Also known as the blocking state. It may be suspended and paused because the resource is unavailable, or the thread is suspended because it is voluntarily delayed. In suspended state, threads do not participate in scheduling. In RT-Thread, the macro definition of this state is RT_THREAD_SUSPEND | +| Closed State | It will be turned to closed state when the thread finishes running. The thread in closed state does not participate in the thread's scheduling. In RT-Thread, the macro definition of this state is RT_THREAD_CLOSE | + +### Thread Priority + +The priority of the RT-Thread thread indicates the thread's priority of being scheduled. Each thread has its priority. The more important the thread, the higher priority it should be given, resulting in a higher chance of being scheduled. + +RT-Thread supports a maximum of 256 thread priorities (0~255). The lower the number, the higher the priority, with 0 being the highest priority. In some systems with tight resources, you can choose system configurations that only support 8 or 32 priorities according to the actual situation; for the ARM Cortex-M series, 32 priorities are commonly used. The lowest priority is assigned to idle threads by default and is not used by users. In the system, when a thread with a higher priority is ready, the current thread with the lower priority will be swapped out immediately, and the high-priority thread will preempt the processor. + +### Time Slice + +Each thread has a time slice parameter, but time slice is only valid for ready-state threads of the same priority. The system schedules the ready-state threads with the same priority using time slice rotation scheduling method. In this case, time slice plays the role of constraining thread's single running time and the unit is a system tick (OS Tick). Suppose there are 2 ready-state threads, A and B with the same priority. Time slice of A thread is set to 10, and time slice of B thread is set to 5. When there is no ready-state thread with higher priority than A in the system, the system will switch back and forth between thread A and B. Each time the system performs 10 OS Ticks on thread A, and 5 OS Ticks on thread B, as shown below. + + + +### Thread Entry Function + +"Entry" in Thread Control Block is the thread's entry function, which is the thread's main function. The thread's entry function is created by the user. There are generally two forms of code: + +**Infinite Loop Mode**: + +In real-time systems, threads are usually passive: this is determined by the characteristics of real-time systems, which usually wait for external events to occur, and then perform the appropriate services: + +```c +void thread_entry(void* parameter) +{ + while (1) + { + /* Wait for an event to occur */ + + /* Serve and process events */ + } +} +``` + +It may seem that threads have no restrictions on program execution and that all operations can be performed. But as a real-time system, with clear priorities, if a program in a thread is stuck in an infinite loop, then threads with a lower priorities will not be executed. Therefore, one thing that must be noted in the real-time operating system is that the thread cannot be stuck in an infinite loop operation, and there must be an action to relinquish the use of the CPU, such as calling a delay function in the loop or actively suspending. The purpose of user designing a infinite loop thread is to let this thread be continuously scheduled and run by the system and never be deleted. + +**Sequential Execution or Finite-Cycle Mode**: + +With simple sequential statements, such as `while()` or `for()` loop, etc., these threads will not cycle or permanently loop. They can be described as a "one-off" threads and will surely be executed. After it finished executing, the thread will be automatically deleted by the system. + +```c +static void thread_entry(void* parameter) +{ + /* Processing Transaction #1 */ + … + /* Processing Transaction #2 */ + … + /* Processing Transaction #3 */ +} +``` + +### Thread Error Code + +Each thread is one execution scenario. Error codes are closely related to the execution environment, so each thread is equipped with a variable to store the error code. These error codes are as follows: + +```c +#define RT_EOK 0 /* No error */ +#define RT_ERROR 1 /* Regular error */ +#define RT_ETIMEOUT 2 /* Timeout error */ +#define RT_EFULL 3 /* Resource is full */ +#define RT_EEMPTY 4 /* No resource */ +#define RT_ENOMEM 5 /* No memory */ +#define RT_ENOSYS 6 /* System does not support */ +#define RT_EBUSY 7 /* System busy */ +#define RT_EIO 8 /* IO error */ +#define RT_EINTR 9 /* Interrupt system call */ +#define RT_EINVAL 10 /* Invalid Parameter */ +``` + +## Switching Thread State + +RT-Thread provides a set of operating system call interfaces that make the state of a thread to switch back and forth between these five states. The conversion relationship between these states is shown in the following figure: + + + +Threads enter the initial state (RT_THREAD_INIT) by calling the functions `rt_thread_create()` or `rt_thread_init()`; threads in the initial state enter the ready state (RT_THREAD_READY) by calling the function `rt_thread_startup()`; threads in the ready state are scheduled by the scheduler and enter the running state (RT_THREAD_RUNNING). When a running thread calls a function such as `rt_thread_delay()`, `rt_sem_take()`, `rt_mutex_take()`, `rt_mb_recv()` or fails to get resources, it will enter the suspended state (RT_THREAD_SUSPEND); if a thread in suspended state waits until timeout and still doesn't acquire the resources, or other threads release the resources, it will return to the ready state. + +If a thread in suspended state calls the functions `rt_thread_delete()` or `rt_thread_detach()`, it will switch to the closed state (RT_THREAD_CLOSE); as for thread in the running state, if the operation is completed, the function `rt_thread_exit()` will be executed at the last part of the thread to change its state into the closed state. + +>In RT-Thread, a thread does not actually have a running state; the ready state and the running state are equivalent. + +## System thread + +As mentioned previously, system threads refers to threads created by the system and user threads are threads created by the user program calling the thread management interface. System threads In RT-Thread kernel include the idle thread and the main thread. + +### Idle Thread + +An idle thread is the lowest priority thread created by the system, and its thread state is always ready. When no other ready thread exists in the system, the scheduler will schedule the idle thread, which is usually an infinite loop and can never be suspended. In addition, idle threads have special functions in RT-Thread: + +If a thread finishes running, the system will automatically delete the thread by executing the function `rt_thread_exit()`, first removing the thread from the system ready queue, then changing the state of the thread to the closed state which means it no longer participates in system scheduling, and then suspend it into the `rt_thread_defunct` queue (a thread queue that is not reclaimed and in a closed state). Lastly, the idle thread reclaims the resources of the deleted thread. + +The idle thread also provides an interface to run a hook function set by the user. The hook function is called when idle thread is running, which is suitable for operations like hooking into power management, watchdog feeding, etc. + +### Main Thread + +When the system starts, it will create the main thread. Its entry function is `main_thread_entry()`. User's application entry function `main()` starts from here. After the system scheduler starts, the main thread starts running. The process is as follows. Users can add their own application initialization code to the `main()` function. + + + +# Thread Management + +The first two sections of this chapter conceptually explain the function and working mechanism of threads. This section will delve into the various interfaces of threads and give some source code to help the reader understand threads. + +The following figure depicts related operations to threads, including creating / initializing threads, starting threads, running threads, deleting / detaching threads. You can use `rt_thread_create()` to create a dynamic thread and `rt_thread_init()` to initialize a static thread. The difference between a dynamic thread and a static thread is that for a dynamic thread, the system automatically allocates stack space and thread handles from the dynamic memory heap (only after initializing the heap can the system create a dynamic thread). As for static threads, it is the user that allocates the stack space and the thread handle. + + + +## Create and Delete Thread + +To become an executable object, a thread must be created by the kernel of the operating system. You can create a dynamic thread through the following interface: + +```c +rt_thread_t rt_thread_create(const char* name, + void (*entry)(void* parameter), + void* parameter, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick); +``` + +When this function is called, the system will allocate a thread handle from the dynamic heap memory and allocate the corresponding space from the dynamic heap memory according to the stack size specified in the parameter. The allocated stack space is aligned in RT_ALIGN_SIZE mode configured in rtconfig.h. The parameters and return values of the thread creation `rt_thread_create()` are as follows: + +|Parameters |Description | +|------------|----------------------------------------------------------------------------------------| +| name | The name of the thread; the maximum length of the thread name is specified by macro RT_NAME_MAX in rtconfig.h, and the extra part is automatically truncated. | +| entry | Thread entry function. | +| parameter | Thread entry function's parameter. | +| stack_size | Thread stack size in bytes. | +| priority | Priority of the thread. The priority range is based on the system configuration (macro definition RT_THREAD_PRIORITY_MAX in rtconfig.h). If 256-level priority is supported, then the range is from 0 to 255. The smaller the value, the higher the priority, and 0 is the highest priority. | +| tick | The time slice size of the thread. The unit of the time slice (tick) is the tick of the operating system. When there are threads with the same priority in the system, this parameter specifies the maximum length of time of a thread for one schedule. At the end of this time slice run, the scheduler automatically selects the next ready state of the same priority thread to run. | +|**Return** | —— | +| thread | Thread creation succeeds, return thread handle. | +| RT_NULL | Failed to create thread. | + +For some threads created with `rt_thread_create()`, when not needed or when an error occurs, one can use the following function interface to completely remove the thread from the system: + +```c +rt_err_t rt_thread_delete(rt_thread_t thread); +``` + +After calling this function, the thread object will be moved out of the thread list and removed from the kernel object manager. Consequently, the stack space occupied by the thread will also be freed, and the reclaimed space will be reused for other memory allocations. In fact, using the `rt_thread_delete()` function to delete the thread interface is just changing the corresponding thread state to RT_THREAD_CLOSE state and then putting it into rt_thread_defunct queue; the actual delete action (releasing the thread control block and releasing the thread stack) needs to be completed later by an idle thread when it is being executed. + +The parameters and return values of thread deleting `rt_thread_delete()` interface are shown in the following table: + +|**Parameter** |**Description** | +|------------|------------------| +| thread | Thread handles to delete | +|**Return** | —— | +| RT_EOK | Delete thread successfully. | +| \-RT_ERROR | Failed to delete thread. | + +This function is only valid when the system dynamic heap is enabled (meaning RT_USING_HEAP macro definition is already defined). + +## Initialize and Detach Thread + +The initialization of a thread can be done using the following function interface, to initialize a static thread object: + +```c +rt_err_t rt_thread_init(struct rt_thread* thread, + const char* name, + void (*entry)(void* parameter), void* parameter, + void* stack_start, rt_uint32_t stack_size, + rt_uint8_t priority, rt_uint32_t tick); +``` + +The thread handle of the static thread (in other words, the thread control block pointer) and the thread stack are provided by the user. A static thread means that the thread control block and the thread running stack are generally set to global variables, which are determined and allocated when compiling. The kernel is not responsible for dynamically allocating memory space. It should be noted that the user-provided stack starting address needs to be system aligned (for example, 4-byte alignment is required on ARM). The parameters and return values of the thread initialization interface `rt_thread_init()` are as follows: + +|**Parameter** |**Description** | +|-----------------|---------------------------------------------------------------------------| +| thread | Thread handle. Thread handle is provided by the user and points to the corresponding thread control block memory address. | +| name | Name of the thread; the maximum length of the thread name is specified by the RT_NAME_MAX macro defined in rtconfig.h, and the extra part is automatically truncated. | +| entry | Thread entry function. | +| parameter | Thread entry function parameter. | +| stack_start | Thread stack start address | +| stack_size | Thread stack size in bytes. Stack space address alignment is required in most systems (for example, alignment to 4-byte addresses in the ARM architecture) | +| priority | The priority of the thread. The priority range is based on the system configuration (macro definition RT_THREAD_PRIORITY_MAX in rtconfig.h). If 256 levels of priority are supported, the range is from 0 to 255. The smaller the value, the higher the priority, and 0 is the highest priority. | +| tick | The time slice size of the thread. The unit of the time slice (tick) is the tick of the operating system. The unit of the time slice (tick) is the tick of the operating system. When there are threads with the same priority in the system, this parameter specifies the maximum length of time of a thread for one schedule. At the end of this time slice run, the scheduler automatically selects the next ready state of the same priority thread to run. | +|**Return** | —— | +| RT_EOK | Thread creation succeeds. | +| \-RT_ERROR | Failed to create thread. | + +For threads initialized with `rt_thread_init()`, using `rt_thread_detach()` will cause the thread object to be detached from the thread queue and kernel object manager. The detach thread function is as follows: + +```c +rt_err_t rt_thread_detach (rt_thread_t thread); +``` + +Parameters and return values of the thread detached from the interface `rt_thread_detach()` are as follows: + +|**Parameters** |**Description** | +|------------|------------------------------------------------------------| +| thread | Thread handle, which should be the thread handle initialized by rt_thread_init. | +|**Return** | —— | +| RT_EOK | Thread detached successfully. | +| \-RT_ERROR | Thread detachment failed. | + +This function interface corresponds to the `rt_thread_delete()` function. The object operated by the rt_thread_delete() function is the handle created by rt_thread_create(), and the object operated by the rt_thread_detach() function is the thread control block initialized with the rt_thread_init() function. Again, the thread itself should not call this interface to detach thread itself. + +## Start Thread + +The thread created (initialized) is in initial state and does not enter the scheduling queue of the ready thread. We can call the following function interface to make the thread enter the ready state after the thread is initialized/created successfully: + +```c +rt_err_t rt_thread_startup(rt_thread_t thread); +``` + +When this function is called, the state of the thread is changed to the ready state and placed in the corresponding priority queue for scheduling. If the newly started thread has a higher priority than the current thread, it will immediately switch to the new thread. The parameters and return values of the thread start interface `rt_thread_startup()` are as follows: + +|**Parameter** |**Description** | +|------------|--------------| +| thread | Thread handle. | +|**Return** | —— | +| RT_EOK | Thread started successfully. | +| \-RT_ERROR | Thread start failed. | + +## Obtaining Current Thread + +During the running of the program, the same piece of code may be executed by multiple threads. At the time of execution, the currently executed thread handle can be obtained through the following function interface: + +```c +rt_thread_t rt_thread_self(void); +``` + +The return value of this interface is shown in the following table: + +|**Return**|**Description** | +|----------|----------------------| +| thread | The currently running thread handle. | +| RT_NULL | Failed, the scheduler has not started yet. | + +## Making Thread Release Processor Resources + +When the current thread's time slice runs out or the thread actively requests to release the processor resource, it will no longer occupy the processor, and the scheduler will select the next thread of the same priority to execute. After the thread calls this interface, the thread is still in the ready queue. The thread gives up the processor using the following function interface: + +```c +rt_err_t rt_thread_yield(void); +``` + +After calling this function, the current thread first removes itself from its ready priority thread queue, then suspends itself to the end of the priority queue list, and then activates the scheduler for thread context switching (if there is no other thread with the same priority, then this thread continues to execute without context switching action). + +The `rt_thread_yield()` function is similar to the `rt_schedule()` function, but the behavior of the system is completely different when other ready-state threads of the same priority exist. After executing the `rt_thread_yield()` function, the current thread is swapped out and the next ready thread of the same priority will be executed. After the `rt_schedule()` function is executed, the current thread is not necessarily swapped out. Even if it is swapped out, it will not be placed at the end of the ready thread list. Instead, the thread with the highest priority is selected in the system and executed. If there is no thread in the system with a higher priority than the current thread, the system will continue to execute the current thread after the `rt_schedule()` function is executed. + +## Thread Sleep + +In practical applications, we sometimes need to delay the current thread running for a period of time and re-run at a specified time. This is called "thread sleep". Thread sleep can use the following three function interfaces: + +```c +rt_err_t rt_thread_delay(rt_tick_t tick); +rt_err_t rt_thread_mdelay(rt_int32_t ms); +``` + +These three function interfaces have the same effect. Calling them can cause the current thread to suspend for a specified period of time. After, the thread will wake up and enter the ready state again. This function accepts a parameter that specifies the sleep time of the thread. The parameters and return values of the thread sleep interface rt_thread_delay/mdelay() are as follows: + +|**Parameters**|Description | +| -------- | ------------------------------------------------------------ | +| tick/ms | Thread sleep time:<br>The input parameter tick of rt_thread_delay is in units of 1 OS Tick; <br>The input parameter ms of rt_thread_mdelay is in units of 1ms; | +|**Return**| —— | +| RT_EOK | Successful operation. | + +## Suspend and Resume Thread + +When a thread calls `rt_thread_delay()`, the thread will voluntarily suspend; when a function such as `rt_sem_take()`, `rt_mb_recv()` is called, the resource is not available for use and will cause the thread to suspend. A thread in a suspended state, if it waits for resources longer than the set time, then the thread will no longer wait for these resources and will return to the ready state; or, when other threads release the resource the thread is waiting for, the thread will also return to the ready state. + +The thread suspends using the following function interface: + +```c +rt_err_t rt_thread_suspend (rt_thread_t thread); +``` + +The parameters and return values of the thread suspend interface `rt_thread_suspend()` are shown in the following table: + +|**Parameters** |Description | +|------------|----------------------------------------------| +| thread | Thread handle. | +|**Return** | —— | +| RT_EOK | Thread suspends successfully | +| \-RT_ERROR | Thread suspension failed because the thread is not in ready state. | + +>Generally, you should not use this function to suspend the thread itself, if you really need to use rt_thread_suspend() to suspend the current task, immediately after calling function rt_thread_suspend(), rt_schedule() needs to be called. + + Functions' context switch is achieved manually. User only needs to understand the role of the interface, which is not recommended. + +To resume a thread is to let the suspended thread re-enter the ready state and put the thread into the system's ready queue. Thread resuming uses the following function interface: + +```c +rt_err_t rt_thread_resume (rt_thread_t thread); +``` + +The parameters and return values of the thread recovery interface `rt_thread_resume()` are as follows: + +|Parameter |**Description** | +|------------|---------------------------------------------------------------| +| thread | Thread handle. | +|**Return** | —— | +| RT_EOK | Thread resumed successfully. | +| \-RT_ERROR | Thread recovery failed because the state of the thread is not RT_THREAD_SUSPEND state | + +## Control Thread + +When you need other control over a thread, such as dynamically changing the priority of a thread, you can call the following function interface: + +```c +rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg); +``` + +The parameters and return values of the thread control interface rt_thread_control() are as follows: + +|Function Parameters|**Description** | +|--------------|--------------| +| thread | Thread handle. | +| cmd | Control command demand. | +| arg | Control parameter. | +|**Return** | —— | +| RT_EOK | Control execution is correct. | +| \-RT_ERROR | Failure. | + +Demands supported by control command demand cmd include: + +•RT_THREAD_CTRL_CHANGE_PRIORITY:dynamically change the priority of a thread; + +•RT_THREAD_CTRL_STARTUP:Start running a thread, equivalent to the `rt_thread_startup()` function call; + +•RT_THREAD_CTRL_CLOSE:Close a thread, equivalent to the `rt_thread_delete()` function call. + +## Set and Delete Idle Hooks + +The idle hook function is a hook function of the idle thread. If the idle hook function is set, the idle hook function can be automatically executed to perform other things, such as the LED of system indicator , when the system executes the idle thread. The interface for setting/deleting idle hooks is as follows: + +```c +rt_err_t rt_thread_idle_sethook(void (*hook)(void)); +rt_err_t rt_thread_idle_delhook(void (*hook)(void)); +``` + +Input parameters and return values of setting idle hook function `rt_thread_idle_sethook()` are as shown in the following table: + +|**Function Parameters**|Description | +|--------------|----------------| +| hook | Set hook function. | +|**Return** | —— | +| RT_EOK | Set Successfully. | +| \-RT_EFULL | Set fail. | + +Input parameters and return values of deleting the idle hook function `rt_thread_idle_delhook()` are as shown in the following table: + +|Function Parameters|Description | +|--------------|----------------| +| hook | Deleted hook function. | +|**Return** | —— | +| RT_EOK | Successfully deleted. | +| \-RT_ENOSYS | Failed to delete. | + +>An idle thread is a thread whose state is always ready. Therefore, hook function must ensure that idle threads will not be suspended at any time. Functions like rt_thread_delay(), rt_sem_take(), etc can't be used because they may cause the thread to suspend. + +## Set the Scheduler Hook + +During the time when the system is running, it is in the process of thread running, interrupt triggering, responding to interrupts, switching to other threads, and switching between threads. In other words, context switching is the most common event in the system. Sometimes the user may want to know what kind of thread switch has occurred at times, you can set a corresponding hook function by calling the following function interface. This hook function will be called when the system thread switches: + +```c +void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to)); +``` + +Input parameters for setting the scheduler hook function are shown in the following table: + +|**Function Parameters**|Description | +|--------------|----------------------------| +| hook | Represents a user-defined hook function pointer | + +Hook function `hook()` is declared as follows: + +```c +void hook(struct rt_thread* from, struct rt_thread* to); +``` + +Input parameters for the scheduler hook function hook() are shown in the following table: + +|Function Parameters|**Description** | +|--------------|------------------------------------| +| from | Indicates the thread control block pointer that the system wants to switch out | +| to | Indicates the thread control block pointer that the system wants to switch out | + +>Please carefully compile your hook function, any carelessness is likely to cause the entire system to run abnormally (in this hook function, it is basically not allowed to call the system API, and should not cause the current running context to suspend). + +# Thread Application Sample + +An application example in Keil simulator environment is given below. + +## Create Thread Sample + +This sample is creating a dynamic thread and initializing a static thread. A thread is automatically deleted by the system after it has finished running. The other thread is always printing the counts, as follows: + +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +/* Entry Function for Thread 1 */ +static void thread1_entry(void *parameter) +{ + rt_uint32_t count = 0; + + while (1) + { + /* Thread 1 runs with low priority and prints the count value all the time */ + rt_kprintf("thread1 count: %d\n", count ++); + rt_thread_mdelay(500); + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +/* Entry for Thread 2 */ +static void thread2_entry(void *param) +{ + rt_uint32_t count = 0; + + /* Thread 2 has a higher priority to preempt thread 1 and get executed */ + for (count = 0; count < 10 ; count++) + { + /* Thread 2 prints count value */ + rt_kprintf("thread2 count: %d\n", count); + } + rt_kprintf("thread2 exit\n"); + /* Thread 2 will also be automatically detached from the system after it finishes running. */ +} + +/* Thread Sample */ +int thread_sample(void) +{ + /* Creat thread 1, Name is thread1,Entry is thread1_entry */ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + /* Start this thread if you get the thread control block */ + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* Creat thread 2, Name is thread2,Entry is thread2_entry */ + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* Export to msh command list */ +MSH_CMD_EXPORT(thread_sample, thread sample); +``` + +The simulation results are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >thread_sample +msh >thread2 count: 0 +thread2 count: 1 +thread2 count: 2 +thread2 count: 3 +thread2 count: 4 +thread2 count: 5 +thread2 count: 6 +thread2 count: 7 +thread2 count: 8 +thread2 count: 9 +thread2 exit +thread1 count: 0 +thread1 count: 1 +thread1 count: 2 +thread1 count: 3 +… +``` + +When thread 2 counts to a certain value, it will stop running. Then thread 2 is automatically deleted by the system, and therefore the counting stops. Thread 1 prints the count all the time. + +>About deleting threads: Most threads are executed cyclically without needing to be deleted. For thread that can finish running, RT-Thread automatically deletes the thread after the thread finishes running, and deletes it in rt_thread_exit(). User only needs to understand the role of the interface. It is not recommended to use this interface (this interface can be called by other threads or call this interface in the timer timeout function to delete a thread which is not used very often). + +## Thread Time Slice Round-Robin Scheduling Sample + +This sample is creating two threads that will always print counts when executing, as follows: + +```c +#include <rtthread.h> + +#define THREAD_STACK_SIZE 1024 +#define THREAD_PRIORITY 20 +#define THREAD_TIMESLICE 10 + +/* Thread Entry */ +static void thread_entry(void* parameter) +{ + rt_uint32_t value; + rt_uint32_t count = 0; + + value = (rt_uint32_t)parameter; + while (1) + { + if(0 == (count % 5)) + { + rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count); + + if(count> 200) + return; + } + count++; + } +} + +int timeslice_sample(void) +{ + rt_thread_t tid = RT_NULL; + /* Create Thread 1 */ + tid = rt_thread_create("thread1", + thread_entry, (void*)1, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + + /* Create Thread 2 */ + tid = rt_thread_create("thread2", + thread_entry, (void*)2, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE-5); + if (tid != RT_NULL) + rt_thread_startup(tid); + return 0; +} + +/* Export to msh command list */ +MSH_CMD_EXPORT(timeslice_sample, timeslice sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timeslice_sample +msh >thread 1 is running ,thread 1 count = 0 +thread 1 is running ,thread 1 count = 5 +thread 1 is running ,thread 1 count = 10 +thread 1 is running ,thread 1 count = 15 +… +thread 1 is running ,thread 1 count = 125 +thread 1 is rthread 2 is running ,thread 2 count = 0 +thread 2 is running ,thread 2 count = 5 +thread 2 is running ,thread 2 count = 10 +thread 2 is running ,thread 2 count = 15 +thread 2 is running ,thread 2 count = 20 +thread 2 is running ,thread 2 count = 25 +thread 2 is running ,thread 2 count = 30 +thread 2 is running ,thread 2 count = 35 +thread 2 is running ,thread 2 count = 40 +thread 2 is running ,thread 2 count = 45 +thread 2 is running ,thread 2 count = 50 +thread 2 is running ,thread 2 count = 55 +thread 2 is running ,thread 2 count = 60 +thread 2 is running ,thread 2 cunning ,thread 2 count = 65 +thread 1 is running ,thread 1 count = 135 +… +thread 2 is running ,thread 2 count = 205 +``` + +As can be seen from the running count results, thread 2 runs half the time of thread 1. + +## Thread Scheduler Hook Sample + +When thread is scheduling switch, it executes the schedule. We can set a scheduler hook so that we can do other things when the thread is being switched. This sample is printing switch information between the threads in the scheduler hook function, as shown in the following code. + +```c +#include <rtthread.h> + +#define THREAD_STACK_SIZE 1024 +#define THREAD_PRIORITY 20 +#define THREAD_TIMESLICE 10 + +/* Counter for each thread */ +volatile rt_uint32_t count[2]; + +/* Threads 1, 2 share an entry, but the entry parameters are different */ +static void thread_entry(void* parameter) +{ + rt_uint32_t value; + + value = (rt_uint32_t)parameter; + while (1) + { + rt_kprintf("thread %d is running\n", value); + rt_thread_mdelay(1000); // Delay for a while + } +} + +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; + +static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to) +{ + rt_kprintf("from: %s --> to: %s \n", from->name , to->name); +} + +int scheduler_hook(void) +{ + /* Set the scheduler hook */ + rt_scheduler_sethook(hook_of_scheduler); + + /* Create Thread 1 */ + tid1 = rt_thread_create("thread1", + thread_entry, (void*)1, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* Create Thread 2 */ + tid2 = rt_thread_create("thread2", + thread_entry, (void*)2, + THREAD_STACK_SIZE, + THREAD_PRIORITY,THREAD_TIMESLICE - 5); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + return 0; +} + +/* Export to msh command list */ +MSH_CMD_EXPORT(scheduler_hook, scheduler_hook sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh > scheduler_hook +msh >from: tshell --> to: thread1 +thread 1 is running +from: thread1 --> to: thread2 +thread 2 is running +from: thread2 --> to: tidle +from: tidle --> to: thread1 +thread 1 is running +from: thread1 --> to: tidle +from: tidle --> to: thread2 +thread 2 is running +from: thread2 --> to: tidle +… +``` + +It can be seen from the simulation results that when the threads are being switched, the set scheduler hook function is working normally and printing the information of the thread switching, including switching to the idle thread. diff --git a/documentation/3.kernel/timer/figures/05timer_env.png b/documentation/3.kernel/timer/figures/05timer_env.png Binary files differnew file mode 100644 index 0000000000..f11edc7aa6 --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_env.png diff --git a/documentation/3.kernel/timer/figures/05timer_linked_list.png b/documentation/3.kernel/timer/figures/05timer_linked_list.png Binary files differnew file mode 100644 index 0000000000..434e46165f --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_linked_list.png diff --git a/documentation/3.kernel/timer/figures/05timer_linked_list2.png b/documentation/3.kernel/timer/figures/05timer_linked_list2.png Binary files differnew file mode 100644 index 0000000000..06e7282f74 --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_linked_list2.png diff --git a/documentation/3.kernel/timer/figures/05timer_ops.png b/documentation/3.kernel/timer/figures/05timer_ops.png Binary files differnew file mode 100644 index 0000000000..383a7af750 --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_ops.png diff --git a/documentation/3.kernel/timer/figures/05timer_skip_list.png b/documentation/3.kernel/timer/figures/05timer_skip_list.png Binary files differnew file mode 100644 index 0000000000..eb0b54bfd3 --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_skip_list.png diff --git a/documentation/3.kernel/timer/figures/05timer_skip_list2.png b/documentation/3.kernel/timer/figures/05timer_skip_list2.png Binary files differnew file mode 100644 index 0000000000..b16117c97d --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_skip_list2.png diff --git a/documentation/3.kernel/timer/figures/05timer_skip_list3.png b/documentation/3.kernel/timer/figures/05timer_skip_list3.png Binary files differnew file mode 100644 index 0000000000..d5550cc3e5 --- /dev/null +++ b/documentation/3.kernel/timer/figures/05timer_skip_list3.png diff --git a/documentation/3.kernel/timer/timer.md b/documentation/3.kernel/timer/timer.md new file mode 100644 index 0000000000..4b17728f8d --- /dev/null +++ b/documentation/3.kernel/timer/timer.md @@ -0,0 +1,520 @@ +@page page_clock_management Clock & Timer Management + +The concept of time is very important. You need to set a time to go out with friends and it takes time to complete tasks. Life is inseparable from time. The same is true for operating systems, which require time to regulate the execution of their tasks. The smallest time unit in operating system is clock tick (OS Tick). This chapter focuses on introduction of clock ticks and clock-based timers. After reading this chapter, we will learn how clock ticks are generated and how to use RT-Thread timers. + +# Clock Tick(OS Tick) + +Any operating system needs to provide a clock tick for the system to handle all time-related events, such as thread latency, thread time slice rotation scheduling, timer timeout, etc. Clock tick is a specific periodic interrupt. This interrupt can be regarded as the system heartbeat. The time interval between interrupts depends on different applications, generally it is 1ms–100ms. The faster the clock tick rate, the greater the overhead of the system. The number of clock ticks counted from the start of the system is called the system time. + +In RT-Thread, the length of clock tick can be adjusted according to the value of RT_TICK_PER_SECOND, which is equal to 1/RT_TICK_PER_SECOND second. + +## Clock Tick Implementation + +Clock tick is generated by a hardware timer configured in interrupt trigger mode. `void rt_tick_increase(void)` will be called when an interrupt occurs,notifying the operating system that a system clock has passed; different hardware driver have different timer interrupt implementations. Here is an example of using STM32 `SysTick_Handler` to achieve clock Tick. + +```c +void SysTick_Handler(void) +{ + /* Entry Interrupt*/ + rt_interrupt_enter(); + …… + rt_tick_increase(); + /* Leave Interrupt */ + rt_interrupt_leave(); +} +``` + +Call `rt_tick_increase()` in interrupt function to self-add global variable rt_tick. The code is as follows: + +```c +void rt_tick_increase(void) +{ + struct rt_thread *thread; + + /* Global variable rt_tick self-add */ + ++ rt_tick; + + /* Check time slice */ + thread = rt_thread_self(); + + -- thread->remaining_tick; + if (thread->remaining_tick == 0) + { + /* Re-assign initial value */ + thread->remaining_tick = thread->init_tick; + + /* Thread suspends */ + rt_thread_yield(); + } + + /* Check time slice */ + rt_timer_check(); +} +``` + +You can see that global variable rt_tick is incremented by one on every clock tick. The value of rt_tick indicates the total number of clock ticks that the system has elapsed since it started, that is, system time. In addition, on every clock tick, whether the current thread's time slice is exhausted and whether there is a timer timeout will be checked. + +>In interrupt, rt_timer_check() is used to check the system hardware timer linked list. If there is a timer timeout, the corresponding timeout function will be called. All timers are removed from the timer linked list if it timed out, and periodic timer is added to the timer linked list when it is started again. + +## Obtain Clock Tick + +Since global variable rt_tick is incremented by one on every clock tick, the value of the current rt_tick will be returned by calling `rt_tick_get`, which is the current clock tick value. This interface can be used to record the length of time a system is running, or to measure the time it takes for a task to run. The interface function is as follows: + +```c +rt_tick_t rt_tick_get(void); +``` + +The following table describes the return values of `rt_tick_get()` function: + +|**R**eturn|Description | +|----------|----------------| +| rt_tick | Current clock tick value | + +# Timer Management + +Timer refers to triggering an event after a certain specified time from a specified moment, for example, setting a timer to wake up yourself the next morning. Timer includes hardware timer and software timer: + +1) **Hardware timer** is the timing function provided by the chip itself. The hardware timer can be used by configuring the timer module into a timer mode and setting the time. Hardware timer is accurate to nanosecond precision, and is interrupt trigger mode. + +2) **Software timer** is a type of system interface provided by the operating system. It is built on the basis of the hardware timer to enable the system to provide a timer service with no constraint on numbers. + +RT-Thread operating system provides software-implemented timers in units of clock tick (OS Tick), that is, the timing value must be an integer multiple of OS Tick. For example, an OS Tick is 10ms, then the software timer can only be timed 10ms, 20ms, 100ms, etc., but not 15ms. RT-Thread timer is also based on the clock tick, providing timing capabilities based on integral multiples of the clock tick. + +## RT-Thread Timer Introduction + +RT-Thread timer provides two types of timer mechanisms: the first type is a one-shot timer, which only triggers a timer event for onetime after startup, and then the timer stops automatically. The second type is a periodic trigger timer, which periodically triggers a timer event until the user manually stops it, otherwise it will continue to execute forever. + +In addition, according to the context in which the timeout function is executed, RT-Thread timer can be divided into HARD_TIMER mode and SOFT_TIMER mode, as shown in the following figure. + + + +### HARD_TIMER Mode + +The timer timeout function of HARD_TIMER mode is executed in the interrupt context and can be specified with the parameter RT_TIMER_FLAG_HARD_TIMER when initializing/creating the timer. + +When executed in interrupt context, the requirements for timeout function are the same as those for the interrupt service routine: execution time should be as short as possible, and the execution should not cause the current context to suspend and wait. For example, a timeout function executed in an interrupt context should not attempt to apply for dynamic memory, free dynamic memory, etc. + +The default mode of RT-Thread timer is HARD_TIMER mode which means after the timer timeout, the timeout function runs in the context of the system clock interrupt. The execution mode in the interrupt context determines that the timer's timeout function should not call any system function that will cause the current context to suspend; nor can it be executing for a very long time, otherwise the response time of other interrupts will be lengthened or the running time of other threads will be preempted. + +### SOFT_TIMER Mode + +The SOFT_TIMER mode is configurable and macro definition RT_USING_TIMER_SOFT is used to determine whether the mode should be enabled. When this mode is enabled, the system will create a timer thread at initialization, and then the timer timeout function of SOFT_TIMER mode will be executed in the context of the timer thread. SOFT_TIMER mode can be specified using the parameter RT_TIMER_FLAG_SOFT_TIMER when initializing/creating the timer. + +## Timer Working Mechanism + +The following is an example to illustrate the working mechanism of RT-Thread timer. Two important global variables are maintained in the RT-Thread timer module: + + (1) Elapsed tick time of the current system rt_tick (when hardware timer interrupt comes, it will add 1); + + (2) Timer linked list `rt_timer_list`. Newly created and activated timers of the system are inputted into the `rt_timer_list` linked list in a timeout-ordered manner. + +As shown in the figure below, the current tick value of the system is 20. In the current system, three timers have been created and started, which are Timer1 with 50 ticks set time, Timer2 with 100 ticks, and Timer3 with 500 ticks. Current time of the system rt_tick=20 is added respectively on these three timers and they are linked from small to large in the rt_timer_list linked list, forming a timer linked list structure as shown in the figure. + + + +Along with the trigger of the hardware timer, `rt_tick` has been increasing (rt_tick variable is incremented by 1 every time the hardware timer is interrupted). After 50 tick, rt_tick is increased from 20 to 70, which is equal to the timeout value of Timer1. Then, timeout functions associated with the Timer1 timer will be triggered and Timer1 from the rt_timer_list linked list will be removed. Similarly, after 100 ticks and 500 ticks, timeout functions associated with Timer2 and Timer3 are triggered, and timers of Time2 and Timer3 will be removed from the rt_timer_list linked list. + +If after system's 10 ticks (current rt_tick=30), a new task has created Timer4 with 300 ticks, so the Timer4's timeout is equal to rt_tick add 300, that is 330. Timer4 will be inserted in between Timer2 and Timer3, forming a linked list structure shown below: + + + +### Timer Control Block + +In RT-Thread operating system, timer control block is defined by structure `struct rt_timer` and forms a timer kernel object, which is then linked to the kernel object container for management. It is a data structure used by the operating system to manage timers. It stores information about timers, such as the initial number of ticks, the number of timeout ticks, the linked list structure used to connect timers, timeout callback functions, etc. + +```c +struct rt_timer +{ + struct rt_object parent; + rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; /* Timer Linked List Node */ + + void (*timeout_func)(void *parameter); /* Timeout Function */ + void *parameter; /* Parameters of Timeout Function */ + rt_tick_t init_tick; /* Timer Initial Timeout Ticks */ + rt_tick_t timeout_tick; /* Number of ticks when the timer actually times out */ +}; +typedef struct rt_timer *rt_timer_t; +``` + +Timer control block is defined by structure `struct rt_timer` and forms a timer kernel object, which is then linked to the kernel object container for management. The `list` member is used to link an active (already started) timer to the `rt_timer_list` linked list. + +### Timer Skip List Algorithm + +In the introduction of working mechanics of the timer above, we have talked about that the newly created and activated timers are inserted into the rt_timer_list linked list in the order of the timeout, that is, rt_timer_list linked list is an ordered list. RT-Thread uses a skip list algorithm to speed up the search for linked list elements. + +Skip list is a data structure based on a parallel linked list, which is simple to implement, and the time complexity of insertion, deletion, and search is O(log n). Skip list is a kind of linked list, but it adds a "skip" function to the linked list. It is this function that enables the skip list to have the time complexity of O(log n) when looking for elements, for example: + +An ordered list, as shown in the following figure, searches for elements {13, 39} from the ordered list. The number of comparisons is {3, 5} and the total number of comparisons is 3 + 5 = 8 times. + + + +After using the skip list algorithm, a method similar to binary search tree can be used to extract some nodes as indexes, and then the structure shown in the following figure is obtained: + + + +In this structure, {3, 18, 77} is extracted as first-level index, so that the number of comparisons can be reduced when searching. For example, there are only 3 times of comparisons when searching 39 (by comparing 3, 18, 39). Of course, we can also extract some elements from the first-level index, as a secondary index, which can speed up the element search. + + + +Therefore, the timer skip list can pass the index of the upper layer, reducing the number of comparisons during the search and improving the efficiency of the search. This is an algorithm of "space in exchange of time", macro definition RT_TIMER_SKIP_LIST_LEVEL is used to configure the number of layers in skip list. The default value is 1, which means that ordered linked list algorithm for first-order ordered list graph is used. Each additional one means that another level of index is added to the original list. + +## Timer Management + +RT-Thread timer is introduced in the previous sections and the working mechanism of the timer is conceptually explained. This section will go deep into the various interfaces of the timer to help the reader understand the RT-Thread timer at the code level. + +Timer management system needs to be initialized at system startup. This can be done through the following function interface: + +```c +void rt_system_timer_init(void); +``` + +If you need to use SOFT_TIMER, the following function interface should be called when the system is initialized: + +```c +void rt_system_timer_thread_init(void); +``` + +Timer control block contains important parameters related to the timer and acts as a link between various states of the timer. Relevant operations of the timer are as shown in the following figure. Relevant operations of the timer includes: creating/initializing the timer, starting the timer, running the timer, and deleting/detaching the timer. All the timers will be moved from the timer linked list after their timings expire. However, periodic timer is added back to the timer linked list when it is started again, which is related to timer parameter settings. Each time an operating system clock interrupt occurs, a change is made to the timer status parameter that has timed out. + + + +### Create and Delete Timer + +When dynamically creating a timer, the following function interface can be used: + +```c +rt_timer_t rt_timer_create(const char* name, + void (*timeout)(void* parameter), + void* parameter, + rt_tick_t time, + rt_uint8_t flag); +``` + +After calling the function interface, kernel first allocates a timer control block from the dynamic memory heap and then performs basic initialization on the control block. The description of each parameter and return value is detailed in the following table: + + Input parameters and return values of `rt_timer_create()` + +|**Parameters** |**Description** | +|---------------------------------|--------------------------------------------------------------------------| +| name | Name of the timer | +| void (timeout) (void parameter) | Timer timeout function pointer (this function is called when the timer expires) | +| parameter | Entry parameter of the timer timeout function (when the timer expires, calling timeout callback function will pass this parameters as the entry parameter to the timeout function) | +| time | Timeout of the timer, the unit is the clock tick | +| flag | Parameters when the timer is created. The supported values include one-shot timing, periodic timing, hardware timer, software timer, etc. (You can use multiple values with "OR") | +|**Return** | —— | +| RT_NULL | Creation failed (usually returning RT_NULL due to insufficient system memory) | +| Timer Handle | Timer was created successfully. | + +In include/rtdef.h, some timer related macros are defined, as follows: + +```c +#define RT_TIMER_FLAG_ONE_SHOT 0x0 /* One shot timing */ +#define RT_TIMER_FLAG_PERIODIC 0x2 /* Periodic timing */ + +#define RT_TIMER_FLAG_HARD_TIMER 0x0 /* Hardware timer */ +#define RT_TIMER_FLAG_SOFT_TIMER 0x4 /* Software timer */ +``` + +The above two sets of values can be assigned to the flag in an "or" logical manner. When the specified flag is RT_TIMER_FLAG_HARD_TIMER, if the timer expires, the timer's callback function will be called in the context of the service routine of the clock interrupt; when the specified flag is RT_TIMER_FLAG_SOFT_TIMER, if the timer expires, the timer's callback function will be called in the context of the system clock timer thread. + +When the system no longer uses dynamic timers, the following function interface can be used: + +```c +rt_err_t rt_timer_delete(rt_timer_t timer); +``` + +After calling this function interface, the system will remove this timer from the rt_timer_list linked list, and then release the memory occupied by the corresponding timer control block. The parameters and return values are detailed in the following table: + +Input parameters and return values of rt_timer_delete() + +|**Parameters**|**Description** | +|----------|-------------------------------------------------------------------------| +| timer | Timer handle, pointing at timer needs to be deleted | +|**Return**| —— | +| RT_EOK | Deletion is successful (if the parameter timer handle is RT_NULL, it will result in an ASSERT assertion) | + +### Initialize and Detach Timer + +When creating a timer statically , the timer can be initialized by using `rt_timer_init` interface. The function interface is as follows: + +```c +void rt_timer_init(rt_timer_t timer, + const char* name, + void (*timeout)(void* parameter), + void* parameter, + rt_tick_t time, rt_uint8_t flag); +``` + +When using this function interface, the corresponding timer control block, the corresponding timer name, timer timeout function, etc will be initialized. The description of each parameter and return value is shown in the following table: + +Input parameters and return values of `rt_timer_init()` + +|Parameters |**Description** | +|---------------------------------|-------------------------------------------------------------------------------------| +| timer | Timer handle, pointing to the to-be-initialized timer control block | +| name | Name of the timer | +| void (timeout) (void parameter) | Timer timeout function pointer (this function is called when the timer expires) | +| parameter | Entry parameter of the timer timeout function (when the timer expires, the call timeout callback function will pass this parameter as the entry parameter to the timeout function) | +| time | Timeout of the timer, the unit is clock tick | +| flag | Parameters of when the timer is created. The supported values include one shot timing, periodic timing, hardware timer, and software timer (multiple values can be taken with OR). For details, see Creating a Timer. | + +When a static timer does not need to be used again, you can use the following function interface: + +```c +rt_err_t rt_timer_detach(rt_timer_t timer); +``` + +When detaching a timer, the system will detach the timer object from the kernel object container, but the memory occupied by the timer object will not be released. The parameters and return values are detailed in the following table: + +Input parameters and return values for `rt_timer_detach()` + +|**Parameters**|Description | +|----------|--------------------------------------| +| timer | Timer handle, pointing to the to-be-detached timer control block | +|**Return **| —— | +| RT_EOK | Successfully detached | + +### Start and Stop Timer + +When the timer is created or initialized, it will not be started immediately. It will start after timer function interface is called. The timer function interface is started as follows: + +```c +rt_err_t rt_timer_start(rt_timer_t timer); +``` + +After the timer start function interface is called, the state of the timer is changed to activated state (RT_TIMER_FLAG_ACTIVATED) and inserted into rt_timer_list queue linked list, the parameters and return values are detailed in the following table: + + Input parameters and return values of rt_timer_start() + +|Parameters|Description | +|----------|--------------------------------------| +| timer | Timer handle, pointing to the to-be-initialized timer control block | +|**Return**| —— | +| RT_EOK | Successful startup | + +For an example of starting the timer, please refer to the sample code below. + +After starting the timer, if you want to stop it, you can use the following function interface: + +```c +rt_err_t rt_timer_stop(rt_timer_t timer); +``` + +After the timer stop function interface is called, the timer state will change to the stop state and will be detached from the rt_timer_list linked list without participating in the timer timeout check. When a (periodic) timer expires, this function interface can also be called to stop the (periodic) timer itself. The parameters and return values are detailed in the following table: + + Input parameters and return values of rt_timer_stop() + +|**Parameters** |Description | +|-------------|--------------------------------------| +| timer | Timer handle, pointing to the to-be-stopped timer control block | +|**Return** | —— | +| RT_EOK | Timer successfully stopped | +| \- RT_ERROR | timer is in stopped state | + +### Control Timer + +In addition to some of the programming interfaces provided above, RT-Thread additionally provides a timer control function interface to obtain or set more timer information. The control timer function interface is as follows: + +```c +rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg); +``` + +The control timer function interface can view or change the setting of the timer according to the parameters of the command type. The description of each parameter and return value is as follows: + +Input parameters and return values of rt_timer_control() + +|Parameters|**Description** | +|----------|----------------------------------------------------------------------------------------------------------| +| timer | Timer handle, pointing to the to-be-stopped timer control block | +| cmd | The command for controlling the timer currently supports four command interfaces, which are setting timing, viewing the timing time, setting a one shot trigger, and setting the periodic trigger. | +| arg | Control command parameters corresponding to cmd. For example, when cmd is the set timeout time, the timeout time parameter can be set by arg. | +|**Return**| —— | +| RT_EOK | Successful | + +Commands supported by function parameters cmd: + +```c +#define RT_TIMER_CTRL_SET_TIME 0x0 /* Set Timeout value */ +#define RT_TIMER_CTRL_GET_TIME 0x1 /* Obtain Timer Timeout Time */ +#define RT_TIMER_CTRL_SET_ONESHOT 0x2 /* Set the timer as a oneshot timer. */ +#define RT_TIMER_CTRL_SET_PERIODIC 0x3 /* Set the timer as a periodic timer */ +``` + +See "dynamic timer routine" for code that uses the timer control interface. + +# Timer Application Sample + +This is an example of creating a timer that creates two dynamic timers, one for one shot timing, another one for periodic timing and for the periodic timer to run for a while and then stop running, as shown below: + +```c +#include <rtthread.h> + +/* Timer Control Block */ +static rt_timer_t timer1; +static rt_timer_t timer2; +static int cnt = 0; + +/* Timer 1 Timeout Function */ +static void timeout1(void *parameter) +{ + rt_kprintf("periodic timer is timeout %d\n", cnt); + + /* On the 10th time, stops perodic timer */ + if (cnt++>= 9) + { + rt_timer_stop(timer1); + rt_kprintf("periodic timer was stopped! \n"); + } +} + +/* Timer 2 Timeout Function */ +static void timeout2(void *parameter) +{ + rt_kprintf("one shot timer is timeout\n"); +} + +int timer_sample(void) +{ + /* Create Timer 1 Periodic Timer */ + timer1 = rt_timer_create("timer1", timeout1, + RT_NULL, 10, + RT_TIMER_FLAG_PERIODIC); + + /* Start Timer 1*/ + if (timer1 != RT_NULL) rt_timer_start(timer1); + + /* Create Timer 2 One Shot Timer */ + timer2 = rt_timer_create("timer2", timeout2, + RT_NULL, 30, + RT_TIMER_FLAG_ONE_SHOT); + + /* Start Timer 2 */ + if (timer2 != RT_NULL) rt_timer_start(timer2); + return 0; +} + +/* Export to msh command list */ +MSH_CMD_EXPORT(timer_sample, timer sample); +``` + +The simulation results are as follows: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timer_sample +msh >periodic timer is timeout 0 +periodic timer is timeout 1 +one shot timer is timeout +periodic timer is timeout 2 +periodic timer is timeout 3 +periodic timer is timeout 4 +periodic timer is timeout 5 +periodic timer is timeout 6 +periodic timer is timeout 7 +periodic timer is timeout 8 +periodic timer is timeout 9 +periodic timer was stopped! +``` + +Timeout function of periodic timer 1 runs once every 10 OS Ticks for 10 times (after 10 times, rt_timer_stop is called to stop timer 1); timeout function of one-shot timer 2 runs once on the 30th OS Tick. + +The example of initializing timer is similar to the example of creating a timer. This program initializes two static timers, one is one-shot timing and one is periodic timing, as shown in the following code: + +```c +#include <rtthread.h> + +/* Timer Control Block */ +static struct rt_timer timer1; +static struct rt_timer timer2; +static int cnt = 0; + +/* Timer 1 Timeout Function */ +static void timeout1(void* parameter) +{ + rt_kprintf("periodic timer is timeout\n"); + /* Run for 10 times */ + if (cnt++>= 9) + { + rt_timer_stop(&timer1); + } +} + +/* Timer 2 Timeout Function */ +static void timeout2(void* parameter) +{ + rt_kprintf("one shot timer is timeout\n"); +} + +int timer_static_sample(void) +{ + /* Initialize Timer */ + rt_timer_init(&timer1, "timer1", /* Timer name is timer1 */ + timeout1, /* Callback handler for timeout */ + RT_NULL, /* Entry parameter of the timeout function */ + 10, /* Timing length in OS Tick, 10 OS Tick */ + RT_TIMER_FLAG_PERIODIC); /* Periodic timer */ + rt_timer_init(&timer2, "timer2", /* Timer name is timer2 */ + timeout2, /* Callback handler for timeout */ + RT_NULL, /* Entry parameter of the timeout function */ + 30, /* Timing length is 30 OS Tick */ + RT_TIMER_FLAG_ONE_SHOT); /* One-shot timer */ + + /* Start Timer */ + rt_timer_start(&timer1); + rt_timer_start(&timer2); + return 0; +} +/* Export to msh command list */ +MSH_CMD_EXPORT(timer_static_sample, timer_static sample); +``` + +The simulation results are as follows: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timer_static_sample +msh >periodic timer is timeout +periodic timer is timeout +one shot timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +``` + +The timeout function of periodic timer1 runs once every 10 OS Ticks for 10 times (After 10 times rt_timer_stop is called to stop timer1); the timeout function of one-shot timer2 runs once on the 30th OS Tick. + +# High Precision Delay + +The minimum precision of the RT-Thread timer is determined by the system clock tick (1 OS Tick = 1/RT_TICK_PER_SECOND second, RT_TICK_PER_SECOND value is defined in the rtconfig.h file), and the timer must be set to an integer multiple of the OS Tick. When it is necessary to implement system timing for a shorter time length. For example, the OS Tick is 10ms but the program needs to implement a timing or delay of 1ms. In this case, the operating system timer can't meet the requirements. This problem can be solved by reading the counter of a hardware timer of the system or using hardware timer directly. + +In Cortex-M series, SysTick has been used by RT-Thread as an OS Tick. It is configured to trigger an interrupt after 1/RT_TICK_PER_SECOND seconds. The interrupt handler uses the Cortex-M3 default name `SysTick_Handler`. In Cortex-M3 CMSIS (Cortex Microcontroller Software Interface Standard) specification, SystemCoreClock represents the dominant frequency of the chip, so based on SysTick and SystemCoreClock, we can use SysTick to obtain an accurate delay function, as shown in the following example, Cortex-M3 SysTick-based precision delay (requires the system to enable SysTick): + +The high-precision delay routine is as follows: + +```c +#include <board.h> +void rt_hw_us_delay(rt_uint32_t us) +{ + rt_uint32_t delta; + /* Obtain the number of ticks of the delay */ + us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND)); + /* Obtain current time */ + delta = SysTick->VAL; + /* Loop to obtain the current time until the specified time elapses and exits the loop */ + while (delta - SysTick->VAL< us); +} +``` + +The entry parameter us indicates the number of microseconds that need to be delayed. This function can only support delays shorter than 1 OS Tick, otherwise the SysTick will overflow and not be able to obtain the specified delay time. + |
