RT-Thread kernel-atomic operation ①

RT-Thread kernel-atomic operation ①

  • RT-Thread Kernel-Atomic Operations
    • Atomic operations
      • Introduction to atomic operations
      • Advantages of Atomic Operations
      • Implementation and usage of RT-Thread atomic operations
      • RT-Thread Atomic Operation API
      • Comprehensive example

RT-Thread kernel-atomic operation

Atomic operations

Introduction to atomic operations

Atomic operation refers to an indivisible operation that either executes completely successfully or does not execute at all. No interruption is allowed during the execution of atomic operations. If an interruption occurs, the result of the operation cannot be guaranteed. Atomic operations are usually used in multi-threaded programming to ensure that concurrent execution between multiple threads does not cause problems such as data competition. When implementing atomic operations, hardware instructions or atomic operation functions provided by the operating system are usually used to ensure the atomicity of the operation. At the application level, atomic operations can be used to implement some advanced synchronization and concurrency control mechanisms. For example, in multi-threaded programming, if multiple threads need to access the same shared variable, in order to avoid data competition problems, atomic operations can be used to ensure that the operation on the variable is atomic. Let’s take the ARM core performing an i++ operation as an example:

movl i, ?x //Memory access, read the i variable to the eax register of the cpu
addl $1, ?x //Modify the value of the register
movl ?x, i //Write the value in the register back to memory

We see that for coding engineers, we only need one line of code to perform an i++ operation. After compilation, i++ will be translated into three instructions, so there may be system scheduling, interruption, etc. between these three instructions. Interrupted by events, so in some scenarios we need to perform the above operations in one go. Atomic operations have such capabilities.

Advantages of atomic operations

In RT-Thread, we can protect critical section resources by switching global interrupts on and off, locking the scheduler, etc. Other OSs will also provide similar operations. If we use atomic operations, we can improve the execution efficiency of the critical section code, significantly Improving the operating efficiency of the system will also reduce the complexity of programming to a certain extent. The following is an example of a simple variable auto-increment:

The protection of critical areas is achieved by switching global interrupts:

 ...
    int a = 5;
    level = rt_hw_interrupt_disable();
    a + + ;
    rt_hw_interrupt_enable(level);
  ...

If we use the atomic operation API provided by RT-Thread, we can do this:

 ...
    int a = 5;
    rt_atomic_add( & amp;a,1);
  ...

Obviously, it is simpler to use atomic operations and avoid the performance loss caused by switching global interrupts.

Implementation and usage of RT-Thread atomic operations

RT-Thread provides atomic operation support for the kernels that support atomic operations in 32-bit ARM, 32-bit RISC-V and 64-bit RISC-V. It is implemented using the atomic operation instructions and related instructions of the corresponding platform. The default Supported, users do not need to care about implementation. Users only need to include rtatomic.h in their project to use the atomic operation API provided by this file. Detailed support is as follows:

Instruction architecture RT-Thread adaptation kernel’s atomic instruction support
Most of the 32-bit ARM cores that use the ARM instruction set support atomic instructions. The cores that do not support include cortex-m0, cortex-m0+, arm926, lpc214x, lpc24xx, s3c24x0, and AT91SAM7.
Most cores of 32-bit RISC-V using the RV32 instruction set support atomic operations. Some BSPs that do not support them include: core-v-mcu, rv32m1_vega.
Most of the 64-bit RISC-V kernels using the RV64 instruction set support atomic operations. Some BSPs that do not support them include: juicevm.

If the tool chain supports the C11 standard atomic operation API, you can also use menuconfig to configure the RT_USING_STDC_ATOMIC macro. In this case, calling the macro provided in rtatomic.h will actually eventually call the API provided by the C11 standard. The configuration method of menuconfig is as follows:

RT-Thread Kernel --->
   [*]Use atomic implemented in stdatomic.h

For the above kernels that do not support atomic operations, when the user includes rtatomic.h in the project and uses the API provided by this file, the atomic operation will be softly implemented by switching global interrupts on and off.

RT-Thread Atomic Operation API

RT-Thread provides 11 frequently used atomic operation APIs.

RT-Thread atomic operation API function
rt_atomic_t rt_hw_atomic_load(volatile rt_atomic_t *ptr) Atomicly loads a word from the ptr address
void rt_atomic_store(volatile rt_atomic_t *ptr, rt_atomic_t val) atomically writes val to the ptr address
rt_atomic_t rt_atomic_exchange(volatile rt_atomic_t *ptr, rt_atomic_t val) atomically replaces the value at the ptr address with val
rt_atomic_t rt_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val) Atomicly adds the value at the address of ptr to val
rt_atomic_t rt_atomic_sub(volatile rt_atomic_t *ptr, rt_atomic_t val) Atomicly subtracts the value at the ptr address from val
rt_atomic_t rt_atomic_xor(volatile rt_atomic_t *ptr, rt_atomic_t val) atomically performs bitwise XOR of the value at the ptr address and val
rt_atomic_t rt_atomic_and(volatile rt_atomic_t *ptr, rt_atomic_t val) Atomicly performs a bitwise AND of the value at the ptr address with val
rt_atomic_t rt_atomic_or(volatile rt_atomic_t *ptr, rt_atomic_t val) Atomicly OR the value at ptr address with val bitwise
rt_atomic_t rt_atomic_flag_test_and_set(volatile rt_atomic_t *ptr) Atomicly sets the value at the ptr address to 1
void rt_atomic_flag_clear(volatile rt_atomic_t *ptr) atomically clears the value at the ptr address to 0
rt_atomic_t rt_atomic_compare_exchange_strong(volatile rt_atomic_t *ptr, rt_atomic_t *old, rt_atomic_t new) Atomicly compares and exchanges the value at the ptr address with val and returns the comparison result

Detailed explanation of atomic operation function:

Atomic reading

rt_atomic_t rt_atomic_load(volatile rt_atomic_t *ptr);

The semantics of this operation function are: use atomic operation to load a word from the 4-byte space pointed to by the ptr address.

Parameter Description
ptr atomic object address
Return value Returns 4 bytes of data at the address of ptr

Atomic writing

void rt_atomic_store(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation to write val into the 4-byte space pointed to by the ptr address.

Parameter Description
ptr atomic object address
val expects to write the data at the address of ptr
Return value NULL

Atomic Data Exchange

rt_atomic_t rt_atomic_exchange(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation to exchange the data in the 4-byte space pointed to by the ptr address into val, and return the 4-byte data before modification at the ptr address.

Parameter Description
ptr atomic object address
val the data expected to be exchanged
Return value Returns the 4-byte data before swapping at the ptr address.

Atomic Plus

rt_atomic_t rt_atomic_add(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation to add the 4-byte data pointed to by the ptr address and val, write the result into the 4-byte space pointed by the ptr address, and return the 4-byte data before modification at the ptr address.

Parameter Description
ptr atomic object address
val the value to be added
Return value Returns the 4-byte data before modification at the ptr address.

Atomic subtraction

rt_atomic_t rt_atomic_sub(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation to subtract val from the 4-byte data pointed to by the ptr address, write the result into the 4-byte space pointed by the ptr address, and return the 4-byte data before modification at the ptr address.

Parameter Description
ptr atomic object address
val the value to be subtracted
Return value Returns the 4-byte data before modification at the ptr address.

Atomic XOR

rt_atomic_t rt_atomic_xor(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation mode to perform bitwise exclusive OR between the 4-byte data pointed to by the ptr address and val, write the result into the 4-byte space pointed by the ptr address, and return the 4 words before modification at the ptr address. section data.

Parameter Description
ptr atomic object address
val the value to expect XOR
Return value Returns the 4-byte data before modification at the ptr address.

Atomic and

rt_atomic_t rt_atomic_and(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation mode to perform bitwise AND between the 4-byte data pointed to by the ptr address and val, write the result into the 4-byte space pointed by the ptr address, and return the 4 bytes before modification at the ptr address. data.

Parameter Description
ptr atomic object address
val the value to be ANDed with
Return value Returns the 4-byte data before modification at the ptr address.

atomic or

rt_atomic_t rt_atomic_or(volatile rt_atomic_t *ptr, rt_atomic_t val);

The semantics of this operation function are: use atomic operation mode to perform bitwise OR between the 4-byte data pointed to by the ptr address and val, write the result into the 4-byte space pointed by the ptr address, and return the 4 bytes before modification at the ptr address. data.

Parameter Description
ptr atomic object address
val the value to be ORed with
Return value Returns the 4-byte data before modification at the ptr address.

Atomic Flag Checking and Setting

rt_atomic_t rt_atomic_flag_test_and_set(volatile rt_atomic_t *ptr);

The semantics of this operation function are: set the 4-byte atomic flag pointed to by the ptr address, and return the value of the atomic flag object before the setting operation. If the 4-byte data pointed to by the ptr address was previously in state 0, then after this operation, the state of the atomic flag object changes to state 1, and 0 is returned. If the 4-byte data pointed to by the ptr address was in state 1 before, it is still in state 1 after this operation, and 1 is returned. So if we use the atomic flag object as a “lock”, we can determine the return value of this function interface. If 0 is returned, it means the lock is successful, and relevant modification operations can be made to the multi-threaded shared object; if the return value is In state 1, the atomic flag has been occupied by other threads and needs to wait for release.

Parameter Description
ptr atomic object address, the 4-byte data pointed to by the address here can only be 0 or 1
Return value Set status

Atomic Flag Clear

void rt_atomic_flag_clear(volatile rt_atomic_t *ptr);

The semantics of this operation function are: clear the flag, clear the flag to 0, and clear the atomic flag pointed to by the ptr address. If we use the atomic flag object as a “lock”, then performing this operation is equivalent to releasing the lock.

Parameter Description
ptr atomic object address
Return value NULL

Atomic comparison and exchange

rt_atomic_t rt_atomic_compare_exchange_strong(volatile rt_atomic_t *ptr, rt_atomic_t *old, rt_atomic_t new);

The semantics of this operation function are: the first parameter points to the atomic type object; the second parameter points to the object to be compared, and if the comparison fails, the operation will copy the current value of the atomic object to the object pointed to by the parameter Medium; the third parameter specifies the value stored in the atomic object. If the comparison is successful, the new value will be stored in the atomic object and 1 will be returned; if the comparison fails, the value of the current atomic object will be copied to the object pointed to by old and 0 will be returned.

Parameter Description
ptr atomic object address
old the object being compared
new object that is expected to be updated
Return value comparison result

Comprehensive example

Include rtatomic.h in the project, and then add the example to the project to verify simple atomic operations.

/* Add this header file to the project */
#include <rtatomic.h>

rt_atomic_t value1 = 10;
rt_atomic_t value2 = 5;
int main(void)
{<!-- -->
    /* atomic add */
    result = rt_atomic_add( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic sub */
    result = rt_atomic_sub( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic xor */
    result = rt_atomic_xor( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic or */
    result = rt_atomic_or( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic and */
    result = rt_atomic_and( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic exchange */
    result = rt_atomic_exchange( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic compare and exchange */
    result = rt_atomic_compare_exchange_strong( & amp;value1, value2, 6);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    /* atomic load */
    result = rt_atomic_load( & amp;value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 10;
    value2 = 5;
    /* atomic store */
    result = rt_atomic_store( & amp;value1, value2);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    value1 = 0;
    /* atomic flag test and set */
    result = rt_atomic_flag_test_and_set( & amp;value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);

    /* atomic flag clear */
    result = rt_atomic_flag_clear( & amp;value1);
    rt_kprintf("result = %d value1 = %d value2 = %d\r\\
", result, value1, value2);
}

I have questions: [email protected]