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:
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:
If we use the atomic operation API provided by RT-Thread, we can do this:
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.h
to 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_ATOMIC
the macro. In this case, rtatomic.h
the macro provided in the call will actually call the API provided by the C11 standard. The configuration method of menuconfig is as follows:
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:
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.
The semantics of this operation function is: use atomic operation to write val into the 4-byte space pointed to by the ptr address.
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.
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.
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.
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.
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.
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
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.
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.
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.
Last updated