我正在阅读allan cruse code
的smphello.s代码在接下来的部分中,他试图为每个处理器设置堆栈段。
关键是他在xadd的描述中使用了xadd而不使用锁定前缀,如here中所示。可能有一个锁定前缀。
这是一个错误还是没关系?为什么?
# setup an exclusive stack-area for this processor
mov $0x1000, %ax # paragraphs in segment
xadd %ax, newSS # 'atomic' xchg-and-add
mov %ax, %ss # segment-address in SS
xor %esp, %esp # top-of-stack into ESP
答案 0 :(得分:4)
xadd
的 lock
是原子wrt。在该内核上中断,但不中断。在其他内核(或DMA)上运行的代码。就像xchg
以外的所有其他内存目标RMW一样。请参见this answer,其中涉及cmpxchg
的相同问题。
如果此代码在多个内核上同时运行,则2个或更多内核可以读取newSS
的相同值,从而有效地失去增量并将相同的ss:esp
分配给两个内核。或者一个存储可能被恰好是顺序的其他内核延迟跨多个xadd进行存储,从而将计数器“倒回”到后来加载看到的某个先前值。或任何问题的组合。 Can num++ be atomic for 'int num'?
假设newSS
对齐,则负载和存储分别是原子的,但不是形成原子RMW。
如果同时唤醒多个内核(是否可以广播IPI?),这似乎是一个真正的问题。如果不是这样,则每个xadd
都有可能在下一个内核到达此代码之前完成。 (包括将存储提交到L1d缓存的存储;在全局范围内可见。)但这只是一种“工作要做”的行为,除非核心唤醒功能在唤醒另一个核心之前先等待一个核心的回音。
如果要与有关原子增量的注释匹配,肯定需要lock xadd
。是原子wrt。如果线程从不并发运行,则只能通过单个内核上的上下文切换来进行中断。 (例如,主线程与信号处理程序或同一内核上的中断处理程序之间的原子性)。但是,由于其标题为smphello.s
,因此单处理器假设似乎不太可能。
答案 1 :(得分:4)
仅为这些理论论证提供一些经验证据:
这是一个测试用例,其中多个线程使用xadd
来增加共享计数器。在具有4核的i7-8565U上,它输出
unlocked: counter = 1633267, expected 4000000
locked: counter = 4000000, expected 4000000
清楚地表明没有xadd
的{{1}}不是原子的。
代码:
lock
答案 2 :(得分:3)
经过另一次思考,我想起了另一种情况。
如果xadd
的微代码实现是这样的:
temp = ax + newSS
newSS = ax
ax = temp ; the last 2 are actual xchg
我们在这种情况下遇到了问题:
假设newSS
在两个线程之间共享。
第0号主题(t0
与ax
等于 5 )加载并newSS
添加ax
并将其放入temp register
。
假设此时我们有一个上下文切换。然后t1
使用ax
等于 5 尝试加载newSS
并将其添加到ax
并将结果放入temp register
}。然后上下文切换回t0
...
两个堆栈段寄存器都指向相同的地址。
显然我们这里有问题。除非微码实现是这样的:
tmp register = ax
xchg ax, newSS
ax = ax + tmpRegister
以任何其他方式变量newSS
被多次读取或读取和写入不同的指令,我们需要锁定。
答案 3 :(得分:3)
竞争条件不在“新SS”上。 正如教学解释所暗示的那样,
xadd %ax, newSS
表示:
temp = ax + newSS
newSS = ax
ax = temp ; the last 2 are actual xchg
因此竞争条件是在ax寄存器上。 但是如果我们仔细思考,我们知道每个线程都有自己的寄存器文件或上下文。所以没有问题。 (寄存器值不会在上下文切换时从线程的最后知识改变)
因此,在此示例中无需为xadd锁定。