Atomic Operations

An atomic operation refers to an indivisible operation that is either executed completely successfully or not executed at all. No interruptions are allowed during the execution of an atomic operation. If an interruption occurs, the result of the operation cannot be guaranteed. Atomic operations are often used in multi-threaded programming to ensure that concurrent execution between multiple threads does not cause problems such as data contention. 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 contention problems, atomic operations can be used to ensure that operations on the variable are atomic. Let's take the ARM kernel executing an i++ operation as an example:

movl i, %eax                            //内存访问,读取 i 变量到 cpu 的 eax 寄存器
addl $1, %eax                           //修改寄存器的值
movl %eax, i                            //将寄存器中的值写回内存copymistakeCopy Success

We see that for coding engineers, we only need one line of code to execute an i++ operation. After compilation, i++ will be translated into three instructions. Therefore, these three instructions may be interrupted by system scheduling, interrupts and other events. Therefore, in some scenarios, we need to execute the above operations in one go. Atomic operations have this capability.

In RT-Thread, we can protect critical section resources by switching global interrupts, locking the scheduler, etc. Other OSes also provide similar operations. If atomic operations are used, we can improve the execution efficiency of critical section codes and greatly improve the operating efficiency of the system. At the same time, it will also reduce the complexity of programming to a certain extent. The following is an example of a simple variable increment:

The protection of critical area is realized by switching global interrupt:

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

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

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

Obviously, the atomic operation method is simpler and avoids the performance loss caused by switching global interrupts.

RT-Thread provides atomic operation support for 32-bit ARM, 32-bit RISC-V and 64-bit RISC-V cores that support atomic operations. It uses the atomic operation instructions and related instructions of the corresponding platform to implement it. It is supported by default and users do not need to worry about the implementation. When using it, users only need to include it in the project rtatomic.hto use the atomic operation API provided by the file. The detailed support is as follows:

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

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

For the above kernels that do not support atomic operations, when users include and use the API provided by this file in their projects rtatomic.h, atomic operations will be soft-implemented by switching global interrupts.

RT-Thread provides 11 frequently used atomic operation APIs.

Detailed explanation of atomic operation functions:

Atomic read

rt_atomic_t rt_atomic_load(volatile rt_atomic_t *ptr);copymistakeCopy Success

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

Atomic Write

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

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

Atomic Data Exchange

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

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

Atomic Add

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

The semantics of this operation function is: use atomic operation to add the 4-byte data pointed to by the ptr address to val, write the result to the 4-byte space pointed to by the ptr address, and return 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);copymistakeCopy Success

The semantics of this operation function is: use atomic operation to subtract val from the 4-byte data pointed to by the ptr address, write the result to the 4-byte space pointed to by the ptr address, and return 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);copymistakeCopy Success

The semantics of this operation function is: use atomic operation to perform bitwise XOR on the 4-byte data pointed to by the ptr address and val, write the result to the 4-byte space pointed to by the ptr address, and return the 4-byte data before modification at the ptr address.

Atom and

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

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

Atom or

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

The semantics of this operation function is: use atomic operation to perform bitwise OR of the 4-byte data pointed to by the ptr address and val, write the result to the 4-byte space pointed to by the ptr address, and return 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);copymistakeCopy Success

The semantics of this operation function is: 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 returns 0. If the 4-byte data pointed to by the ptr address was previously in state 1, then after this operation, it is still in state 1, and returns 1. So if we use the atomic flag object as a "lock", we can judge the return value of this function interface. If it returns 0, it means that the lock is successful, and related modification operations can be performed on the multi-threaded shared object; if the return is state 1, the atomic flag has been occupied by other threads and needs to wait for release.

Atomic flag clearing

void rt_atomic_flag_clear(volatile rt_atomic_t *ptr);copymistakeCopy Success

The semantics of this operation function is: 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.

Atomic compare and swap

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

The semantics of this operation function is: the first parameter points to an 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; the third parameter specifies the value stored in the atomic object. If the comparison succeeds, 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.

Include it in your project rtatomic.h, and then add the example to your project for simple atomic operation verification.

/* 在工程中添加该头文件 */
#include <rtatomic.h>

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

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

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

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

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

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

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

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

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

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

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

Last updated

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