路径://alps/kernel-5.10/tools/include/linux/compiler.h
相关源码:
#define READ_ONCE(x) \
({ \
union { typeof(x) __val; char __c[1]; } __u = \
{ .__c = { 0 } }; \
__read_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})
void __read_once_size(const volatile void *p, void *res, int size)
{
switch (size) { \
case 1: *(unsigned char *)res = *(volatile unsigned char *)p; break; \
case 2: *(unsigned short *)res = *(volatile unsigned short *)p; break; \
case 4: *(unsigned int *)res = *(volatile unsigned int *)p; break; \
case 8: *(unsigned long long *)res = *(volatile unsigned long long *)p; break; \
default: \
barrier(); \
__builtin_memcpy((void *)res, (const void *)p, size); \
barrier(); \
} \
}
#define barrier() asm volatile("" ::: "memory")
源码分析:
READ_ONCE(x)
是一个宏,用于读取变量 x
的值,并确保这个读取操作在多线程环境中是原子的。该宏的实现如下:
- 它创建了一个联合体
__u
,它有两个成员:__val
和__c。
创建联合体的目的是通过将变量x
当作字符数组来解释,以便读取其值。 - 初始化
__u.__c
数组的所有元素为零,以确保初始状态下__u.__val
的值为零。 - 调用函数
__read_once_size
来实际执行读取操作。它将x
的地址、__u.__c
数组的地址以及x
的大小传递给函数。 - 最后,返回
__u.__val
,即读取到的值。
__read_once_size
函数根据传入的大小参数 size
执行不同的读取操作。
- 如果
size
是 1、2、4 或 8 中的一个,表示要读取的数据类型是 unsigned char、unsigned short、unsigned int 或 unsigned long long。在这种情况下,函数会将p
指向的内容读取到res
指向的内存位置。 - 如果
size
不满足上述条件,即要读取的数据类型大小不固定,那么函数会执行一对内存屏障(barrier)。内存屏障用于确保在它们之前和之后的内存访问操作按照预期顺序执行,并且不会被重排序。然后,函数使用内置函数__builtin_memcpy
将p
指向的数据复制到res
指向的内存位置。
宏定义 barrier()
是一条汇编指令,用于创建一个内存屏障。它在需要确保内存访问顺序的地方使用。内存屏障可以防止编译器和处理器对内存访问进行重排序,以确保操作的正确性和一致性。
为什么不是直接读取变量值,而是如此大费周折的去读取一个变量呢,目的是为了能够安全地读取变量值,特别是在多线程环境中,确保读取操作是原子的,并且使在读取大小不固定的情况下也能保持正确性。