IBM示例代码,不可重入函数在我的系统中不起作用

问题描述 投票:9回答:2
我正在研究编程的重入性。在IBM的site上(确实不错)。我建立了一个代码,复制到下面。这是在网站上滚动的第一个代码。

该代码通过打印在“危险上下文”中不断变化的两个值,试图显示涉及在文本程序的非线性开发(异步性)中共享访问变量的问题。

#include <signal.h> #include <stdio.h> struct two_int { int a, b; } data; void signal_handler(int signum){ printf ("%d, %d\n", data.a, data.b); alarm (1); } int main (void){ static struct two_int zeros = { 0, 0 }, ones = { 1, 1 }; signal (SIGALRM, signal_handler); data = zeros; alarm (1); while (1){ data = zeros; data = ones; } }

当我尝试运行代码时出现了问题(或者更好的是,没有出现)。我在默认配置中使用的是gcc版本6.3.0 20170516(Debian 6.3.0-18 + deb9u1)。不会产生误导的输出。获取“错误”对值的频率为0! 

到底发生了什么?为什么使用静态全局变量重新输入没有问题?

c gcc signals x86-64 data-race
2个回答
10
投票

那不是真的-entrancy;您不会在同一线程(或不同线程)中两次运行函数。您可以通过递归或将当前函数的地址作为回调函数指针arg传递给另一个函数来获得。 (它不会不安全,因为它是同步的。)

这只是信号处理程序和主线程之间的普通香草数据争用UB(未定义行为):为此,仅sig_atomic_t被保证是安全的。其他的可能会起作用,例如在x86-64上可以用一条指令加载或存储8字节对象的情况下,编译器恰好选择了该asm。 (如@icarus的答案所示)。

请参阅MCU programming - C++ O2 optimization breaks while loop-单核微控制器上的中断处理程序与单线程程序中的信号处理程序基本相同。在那种情况下,UB的结果是负载被吊起了回路。

您由于数据争用UB而实际发生的撕裂测试用例可能是在32位模式下开发的,或经过测试,或者是使用较旧的dudu编译器单独加载了结构成员。]

在您的情况下,编译器可以从无限循环中优化存储,因为没有UB-free程序无法观察到它们。 data不是_Atomicvolatile

,并且循环中没有其他副作用。因此,任何读者都无法与该作家进行同步。实际上,如果在启用优化的情况下进行编译,就会发生这种情况(Godbolt在main的底部显示一个空循环)。我还将结构更改为两个long long,并且gcc在循环之前使用了单个movdqa 16字节存储。 (这不是guaranteed原子,但实际上在几乎所有CPU上都是假定它是对齐的,或者在Intel上根本没有越过缓存行边界。Why is integer assignment on a naturally aligned variable atomic on x86?
因此启用优化的编译也会破坏您的测试,并每次都显示相同的值。 C不是可移植的汇编语言。

volatile struct two_int也将强制编译器不对其进行优化,但是

not

强制其以原子方式加载/存储整个结构。 (不过,它也不会[[stop
这样做。)请注意,volatile确实not避免了数据争用UB,但实际上,它足以进行线程间通信,这是人们的方式在C11 / C ++ 11之前,为常规CPU体系结构构建了手动滚动原子(以及内联asm)。它们是高速缓存一致的,因此对于纯负载和纯存储,volatilein practice mostly similar to _Atomic with memory_order_relaxed,如果用于的类型足够窄,则编译器将使用一条指令,因此您不会感到费解。当然,_Atomic对ISO C标准没有任何保证,与编写使用memory_order_relaxed和mo_relaxed编译为相同asm的代码相比。
如果您有一个函数对信号处理程序从主

异步运行的volatile_Atomic执行了global_var++;,那将是一种使用重入的方法创建数据竞赛UB。
取决于它是如何编译的(到内存目标inc或添加,或分开加载/ inc /存储),对于同一线程中的信号处理程序,它是否是原子的。有关x86和C ++中原子性的更多信息,请参见int。 (C11的long longCan num++ be atomic for 'int num'?属性提供与C ++ 11的stdatomic.h模板相同的功能)在指令中间不会发生中断或其他异常,因此内存目标地址是原子wrt。上下文在单核CPU上切换。在单核CPU上,只有(缓存相关的)DMA写入器可以“踩”从_Atomic开始的增量而没有std::atomic<T>前缀。没有其他线程可以在其上运行的任何其他核心。

因此,它类似于信号的情况:信号处理程序运行

代替

处理信号的线程的正常执行,因此不能在一条指令的中间对其进行处理。

14
投票
© www.soinside.com 2019 - 2024. All rights reserved.