谷歌数据竞争检测工具ThreadSanitizer代码阅读记录——atomic相关文件
gcc-9.1.0\libsanitizer\sanitizer_common\sanitizer_atomic.h
- 文件有一个内存序列的枚举类型
enum memory_order {
memory_order_relaxed = 1 << 0,
memory_order_consume = 1 << 1,
memory_order_acquire = 1 << 2,
memory_order_release = 1 << 3,
memory_order_acq_rel = 1 << 4,
memory_order_seq_cst = 1 << 5
};
- 定义了多个不同位数的原子性结构体,如atomic_uint32_t表示原子性无符号32位整形。
- 定义了原子性的指针,指针大小由系统的位数决定。
- 使用预编译选择性包含了头文件
#if defined(__clang__) || defined(__GNUC__)
# include "sanitizer_atomic_clang.h"
#elif defined(_MSC_VER)
# include "sanitizer_atomic_msvc.h"
#else
# error "Unsupported compiler"
#endif
- 定义了两个模板函数atomic_load_relaxed和atomic_store_relaxed,具体实现与前面预编译包含的头文件有关
gcc-9.1.0\libsanitizer\sanitizer_common\sanitizer_atomic_clang.h
这个文件也用预编译指令来选择要包含的文件,主要是区分了x86和其他平台
#if defined(__i386__) || defined(__x86_64__)
# include "sanitizer_atomic_clang_x86.h"
#else
# include "sanitizer_atomic_clang_other.h"
#endif
sanitizer本想要直接使用编译器提供的原子操作,但是这些操作在clang中存在许多问题,如lead to vastly inefficient code generation、64-bit atomic operations are not implemented on x86_32、they are not implemented on ARM等,所以重新实现了这些操作。本头文件主要使用到的一些内建的函数为:
- __sync_synchronize()
- __sync_fetch_and_add()
- __sync_lock_test_and_set()
- __sync_val_compare_and_swap()
我查了一下这些函数,它们最主要的特点是full barrier,我个人理解就是在完成这些函数之前,整个处理器是不可能被打断的。另外,处理器对这些函数涉及到的各种内存操作也是立即执行的,不会为了效率采用一些推迟写的策略。
- atomic_thread_fence(memory_order)
INLINE void atomic_thread_fence(memory_order) {
__sync_synchronize();
}
该函数只有一句代码,__sync_synchronize的作用就是同步所有线程之间的数据,同步期间是有full barrier的,主要的原子操作函数还有:
- atomic_fetch_add()
- atomic_fetch_sub()
- atomic_exchange()
- atomic_compare_exchange_strong()
- atomic_compare_exchange_weak()
值得注意得是这个文件实现了两个compare exchange函数,分别对应后缀strong和weak.但是函数atomic_compare_exchange_weak()中其实只是单纯调用了atomic_compare_exchange_strong(),如下:
template<typename T>
INLINE bool atomic_compare_exchange_weak(volatile T *a,
typename T::Type *cmp,
typename T::Type xchg,
memory_order mo) {
return atomic_compare_exchange_strong(a, cmp, xchg, mo);
}
所以可能这两个函数命名是为了兼容某些调用但实际上效果相同。
DCHECK():这是一个代码中反复出现的函数,在谷歌上查到了他们对开源软件Chromium的一些编码规范,里面提到了DCHECK:
DCHECK() is like CHECK() but is only compiled in when DCHECK_IS_ON is true (debug builds and some bot configurations, but not end-user builds). NOTREACHED() is equivalent to DCHECK(false),Here are some rules for using these:
- Use DCHECK() or NOTREACHED() as assertions, e.g. to document pre- and post-conditions. A DCHECK() means “this condition must always be true”, not “this condition is normally true, but perhaps not in exceptional cases.” Things like disk corruption or strange network errors are examples of exceptional circumstances that nevertheless should not result in DCHECK() failure.
- A consequence of this is that you should not handle DCHECK() failures, even if failure would result in a crash. Attempting to handle a DCHECK() failure is a statement that the DCHECK() can fail, which contradicts the point of writing the DCHECK().
总结来说,这是一个用于确保条件为真的函数,并且只在调试阶段启用,这个函数代表了一种断言,传入该函数的参数应该永远为真,而不是通常为真,即使发生硬盘崩溃或网络错误也不能为假,也不应该有任何当该函数失败时的处理操作。
sanitizer_atomic_clang_x86.h & sanitizer_atomic_clang_other.h
这两个文件就涉及到对原子操作的实现,一共就三个函数:
- proc_yield
- atomic_load
- atomic_store
对应基本的进程挂起、原子读和原子写操作,具体全都通过内联汇编完成。比如:
INLINE void proc_yield(int cnt) {
__asm__ __volatile__("" ::: "memory");
for (int i = 0; i < cnt; i++)
__asm__ __volatile__("pause");
__asm__ __volatile__("" ::: "memory");
}
__asm__ __volatile__ ("":::"memory")
是一句值得关注的代码,整句代码被称为编译器内存屏障,用于保证该语句前后的其他语句不会被重排序
- 首先_asm_用于内联汇编
- volatile修饰符保证了这一句内联汇编不会被编译器优化,不管内容如何都会照代码指定的方式执行
- memory强制GCC编译器假设RAM中的数据已经被修改,这样CPU寄存器和缓存的数据都相当于已经作废,需要时会重新从内存中读取。
- “”:::表示这是一个空指令,barrier()不用在此插入一条串行化指令。示例这篇文章给出了一个简单示例
for (int i = 0; i < cnt; i++) __asm__ __volatile__("pause");
中的pause是一种常用于防止编译器优化的语句,常见于自旋锁。
最后,sanitizer_atomic_msvc.h和sanitizer_atomic_clang_mips.h这两个文件涉及到的平台暂时不关注,就先不看了。