GCC-1——内嵌原子操作和内核原子操作
一、GCC内嵌原子操作翻译
5.44 用于原子内存访问的内置函数
以下内置函数旨在与英特尔安腾处理器特定应用程序二进制接口第 7.4 节中描述的函数兼容。 因此,它们偏离了使用“__builtin_”前缀的正常 GCC 实践,并且它们被重载以便它们可以在多种类型上工作。
英特尔文档中给出的定义只允许使用 int、long、long long 类型以及它们的无符号对应物。 GCC 将允许长度为 1、2、4 或 8 个字节的任何整数标量或指针类型。
并非所有目标处理器都支持所有操作。 如果无法在目标处理器上执行特定操作,则会生成警告并调用外部函数。 外部函数将与内置函数具有相同的名称,并带有一个附加后缀“_n”,其中 n 是数据类型的大小。
在大多数情况下,这些内置函数被认为是一个完整的屏障。 也就是说,无论是向前还是向后,都不会在操作中移动内存操作数。 此外,将根据需要发出指令,以防止处理器推测操作中的加载以及操作后对存储进行排队。
所有例程都在英特尔文档中进行了描述,以采用“受内存屏障保护的变量的可选列表”。 目前尚不清楚这是什么意思。 这可能意味着只有以下变量受到保护,或者可能意味着这些变量还应该受到保护。 目前 GCC 忽略此
列表并保护所有可全局访问的变量。 如果将来我们使用这个列表,一个空列表将继续表示所有全局可访问的变量。
(1) 先fetch后op原子操作
type __sync_fetch_and_add (type *ptr, type value, ...) type __sync_fetch_and_sub (type *ptr, type value, ...) type __sync_fetch_and_or (type *ptr, type value, ...) type __sync_fetch_and_and (type *ptr, type value, ...) type __sync_fetch_and_xor (type *ptr, type value, ...) type __sync_fetch_and_nand (type *ptr, type value, ...)
这些内置函数执行名称所建议的操作,并返回以前在内存中的值。 那是:
{ tmp = *ptr; *ptr op= value; return tmp; } { tmp = *ptr; *ptr = ~tmp & value; return tmp; } //nand
(2) 先op然后再fetch原子操作
type __sync_add_and_fetch (type *ptr, type value, ...) type __sync_sub_and_fetch (type *ptr, type value, ...) type __sync_or_and_fetch (type *ptr, type value, ...) type __sync_and_and_fetch (type *ptr, type value, ...) type __sync_xor_and_fetch (type *ptr, type value, ...) type __sync_nand_and_fetch (type *ptr, type value, ...)
这些内置函数执行名称建议的操作,并返回新值。 那是:
{ *ptr op= value; return *ptr; } { *ptr = ~*ptr & value; return *ptr; } //nand
(3) 比较和交换原子操作
bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...) type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)
这些内置函数执行原子比较和交换。 即如果 *ptr 的当前值为 oldval,则将 newval 写入 *ptr。如果比较成功并且写入了 new val,则 “bool” 版本返回 true。 “val” 版本返回操作之前的 *ptr 的内容。
(4) 产生一个内存屏障
__sync_synchronize (...)
这个内置产生一个完整的内存屏障。
(5) 原子交换操作(上锁)
type __sync_lock_test_and_set (type *ptr, type value, ...)
正如 Intel 所描述的,这个内置函数不是传统的测试和设置操作,而是原子交换操作。 它将值写入 *ptr,并返回 *ptr 之前的内容。
许多目标仅对此类锁提供最低限度的支持,并且不支持完整的交换操作。 在这种情况下,目标可能支持减少的功能,其中唯一要存储的有效值是立即常数 1。实际存储在 *ptr 中的确切值是实现定义的。
这个内置不是一个完整的障碍,而是一个获取障碍。 这意味着内置函数之后的引用不能移动到(或推测到)内置函数之前,但是以前的内存存储可能还不是全局可见的,并且以前的内存加载可能还没有得到满足。
(6) 原子的设为0(释放锁)
void __sync_lock_release (type *ptr, ...)
该内置函数释放 __sync_lock_test_and_set 获取的锁。 通常这意味着将常量 0 写入 *ptr。
这个内置不是一个完整的屏障,而是一个释放屏障。 这意味着所有先前的内存存储都是全局可见的,并且所有先前的内存负载都已得到满足,但不会阻止随后的内存读取被推测到屏障之前。
来自GCC手册:https://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html
二、相关实验
1. __sync_fetch_and_add() 操作
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #define WORK_SIZE 5000000 #define WORKER_COUNT 10 pthread_t g_tWorkerID[WORKER_COUNT]; int g_iFlagAtom = 0; //是否使用原子指令 int g_iSum = 0; void *thread_worker(void *arg) { int i = 0; printf ("worker thread %08X startup.\n", (unsigned int)pthread_self()); for (i = 0; i < WORK_SIZE; i++) { if (g_iFlagAtom) { __sync_fetch_and_add(&g_iSum, 1); //load --> add 1 --> store, is atomic } else { g_iSum++; //load --> add 1 --> store, not atomic } } return NULL; } void *thread_management(void *arg) { int i; printf ("management thread %08X startup.\n", (unsigned int)pthread_self()); for (i = 0; i < WORKER_COUNT; i++) { pthread_join(g_tWorkerID[i], NULL); } printf ("all worker thread finished.\n"); return NULL; } int main(int argc, const char *argv[]) { int i = 0; if (argc == 2) { g_iFlagAtom = atoi(argv[1]); } for (i = 0; i < WORKER_COUNT; i++) { pthread_create(&g_tWorkerID[i], NULL, thread_worker, NULL); } for (i = 0; i < WORKER_COUNT; i++) { pthread_join(g_tWorkerID[i], NULL); } printf ("sum: %d\n", g_iSum); return 0; }
实验结果:
lvm:~/origin_tmp/5.gcc_atomic_test$ gcc gcc_atomic_test.c -o pp -lpthread lvm:~/origin_tmp/5.gcc_atomic_test$ ./pp worker thread D83DB700 startup. worker thread D7BDA700 startup. worker thread CFFFF700 startup. worker thread D73D9700 startup. worker thread D6BD8700 startup. worker thread D63D7700 startup. worker thread D5BD6700 startup. worker thread D53D5700 startup. worker thread D4BD4700 startup. worker thread CF7FE700 startup. sum: 9625705 //不符合预期 lvm:~/origin_tmp/5.gcc_atomic_test$ ./pp 1 worker thread 6C534700 startup. worker thread 6A530700 startup. worker thread 6B532700 startup. worker thread 69D2F700 startup. worker thread 6AD31700 startup. worker thread 6BD33700 startup. worker thread 68D2D700 startup. worker thread 6952E700 startup. worker thread 63FFF700 startup. worker thread 637FE700 startup. sum: 50000000 //符合预期
三、内核中的原子操作
1. 有32位和64位的原子变量
typedef struct { int counter; } atomic_t; typedef struct { s64 counter; } atomic64_t; typedef atomic64_t atomic_long_t;
内核原子操作含义解释见:https://www.kernel.org/doc/html/next/driver-api/basics.html
参考:
GCC手册:https://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/
posted on 2022-11-11 15:22 Hello-World3 阅读(583) 评论(0) 编辑 收藏 举报