【发布时间】:2014-07-24 10:39:25
【问题描述】:
编译器或操作系统如何区分 sig_atomic_t 类型和普通的 int 类型变量,并确保操作是原子的?使用两者的程序具有相同的汇编代码。使操作原子化需要多加注意?
【问题讨论】:
编译器或操作系统如何区分 sig_atomic_t 类型和普通的 int 类型变量,并确保操作是原子的?使用两者的程序具有相同的汇编代码。使操作原子化需要多加注意?
【问题讨论】:
sig_atomic_t 不是原子数据类型。它只是允许您在信号处理程序的上下文中使用的数据类型,仅此而已。所以最好把这个名字读成“相对于信号处理的原子”。
为了保证与信号处理程序的通信,只需要原子数据类型的一个属性,即读取和更新将始终看到一致的值这一事实。其他数据类型(可能是long long)可以用几个汇编指令来编写较低和较高的部分,例如sig_atomic_t保证一次读写。
因此,平台可以选择任何整数基类型作为sig_atomic_t,它可以保证volatile sig_atomic_t 可以安全地用于信号处理程序。许多平台为此选择了int,因为他们知道int 是用一条指令编写的。
最新的 C 标准 C11 具有原子类型,但它们是完全不同的东西。其中一些(“无锁”)也可以用于信号处理程序,但这又是完全不同的故事了。
【讨论】:
int 的平台可以保证volatile int 足以用于信号处理程序。这通常不是您作为用户应该或可能知道的,但编译器实现者必须查找并为此选择正确的基类型。再说一次 sig_atomic_t 不是原子类型。
sig_atomic_t 类型根本没有假设这是否适合多线程。是的,通常它不适合线程间通信,请勿将其用于此目的。 volatile 也与线程安全没有太大关系。正如我所说,引入线程的 C11 也引入了适当的原子数据类型。
sig_atomic_t 不是原子数据类型。 sig_atomic_t 的定义是:“定义的类型是 sig_atomic_t,它是可以作为原子实体访问的对象的(可能是 volatile 限定的)整数类型,即使存在异步中断也是如此。”该标准明确规定“sig_atomic_t ... 是 ... 一个可以作为原子实体访问的对象。”在多线程上下文中仍然存在可见性问题(volatile 对此无济于事),但访问本身似乎需要是原子的。
请注意,sig_atomic_t 不是线程安全的,只有异步信号安全。
原子涉及两种类型的障碍:
volatile 关键字的作用。对于综合治疗的主题观看atomic Weapons: The C++ Memory Model and Modern Hardware。
【讨论】:
sig_atomic_t 的名称中有 atomic,它也不是 atomic 数据类型。
sig_atomic_t 不是原子数据类型。它只是为信号处理程序提供了某些保证。请看我的回答。
sig_atomic_t 定义为原子对象:“定义的类型是 sig_atomic_t,它是(可能是 volatile 限定的)整数类型的对象,可以即使存在异步中断,也可以作为原子实体访问。”
sig_atomic_t始终是原子的。定义的“即使存在异步中断”部分并没有将原子性限制为异步中断,它只是强调原子性的要求包括它们。请注意,我并不是说sig_atomic_t 是线程安全的——这确实需要使用屏障。但是,原子性确实意味着 entire 值可以在没有互斥锁之类的东西的情况下访问以保证完整性。仍然需要解决线程安全所需更改的可见性。
【讨论】:
_Atomic & C11 需要 compiler (不是 IDE) 支持。而且 Pelles C 似乎无法在 Linux 上运行。
gcc)。您可以使用vim 或emacs;顺便说一句,您到 Pelles 的链接说只支持 Windows(不支持 Linux),并且他们使用 lcc 的某些变体
使用两者的程序具有相同的汇编代码。使操作原子化需要多加注意?
虽然这是一个老问题,但我认为仍然值得专门解决这部分问题。在 Linux 上,sig_atomic_t 由 glibc 提供。 glibc 中的sig_atomic_t 是int 的类型定义,没有特殊处理(截至本文)。 glibc docs 地址:
在实践中,您可以假设 int 是原子的。你也可以假设 指针类型是原子的;这很方便。这两个 假设在 GNU C 库的所有机器上都是正确的 支持我们知道的所有 POSIX 系统。
也就是说,在 glibc 支持的所有平台上,普通的int 已经满足了sig_atomic_t 的要求,不需要特殊支持。尽管如此,C 和 POSIX 标准还是强制要求 sig_atomic_t,因为我们可能希望在某些特殊机器上实现 C 和 POSIX,而 int 不满足 sig_atomic_t 的要求。
【讨论】:
这种数据类型似乎是原子的。
来自here:
24.4.7.2 原子类型 为了避免中断对变量的访问的不确定性,您可以使用特定的数据类型来访问 总是原子的:sig_atomic_t。读写这种数据类型是 保证在一条指令中发生,所以没有办法 处理程序在访问的“中间”运行。
类型 sig_atomic_t 始终是整数数据类型,但它是哪一种 是,它包含多少位,可能因机器而异。
数据类型:sig_atomic_t 这是一个整数数据类型。这个的对象 类型总是以原子方式访问。
在实践中,您可以假设 int 是原子的。你也可以假设 指针类型是原子的;这很方便。这两个 假设在 GNU C 库的所有机器上都是正确的 支持我们知道的所有 POSIX 系统。
【讨论】:
研究一些内核开发级别的内存模型是值得的……
无论如何,sig_atomic_t 是原子的。 atomic 的正常定义是您无法获得“部分”结果,例如由于并发写入,或并发读写。将任何其他属性附加到“原子”是危险的,并且会导致此处看到的混淆类型。
因此,当您执行任何类型的 sig_atomic_t 存储时,您可以保证获得旧值,或者在某些内容回读时获得新值——无论是在该存储之前、期间或之后。
回答您关于“它是如何工作的”的直接问题:编译器将使用基础类型大小并在需要时发出额外的机器指令,以向 CPU 发出它必须执行原子存储和原子读取的信号。
说了这么多,重要的是要注意,当您尝试读取像 sig_atomic_t 这样的原子变量时,您真的不能说您会得到旧值还是新值。您所知道的是,您不会得到两个相互竞争的不同存储的混合,也不会在存储与您的读取同时发生时混合旧值和新值。
在 C 语言中,您通常还需要将变量声明为“volatile sig_atomic_t”,否则编译器没有理由不缓存它,并且您使用旧值的时间可能比预期的要长:编译器没有理由强制如果它已经在寄存器中具有先前读取的旧值,则重新读取内存。 “volatile”告诉编译器在需要获取变量的值时总是执行新的内存读取。
请注意,“volatile”和“sig_atomic_t”都不是足够强大的“编译器屏障”,以确保它不会被编译器优化器重新排序,更不用说 CPU 本身了(这需要内存屏障,而不仅仅是编译器屏障)。如果您需要任何可见性约束。在执行 MMIO 时,其他线程、处理器甚至硬件,您需要“额外的东西”(编译器屏障和内存屏障)。
这就是 C11 _Atomic 和 C11 内存模型发挥作用的地方。它们不仅仅是关于“原子”读取和存储,它们还包括许多可见性规则和约束。其他实体(MMIO 设备、其他执行线程、其他处理器)。
【讨论】: