13

我正在阅读 allan cruse 代码的 smphello.s代码

在接下来的部分中,他试图为每个处理器设置堆栈段。

关键是他在 xadd 的描述中使用 xadd 而不使用 lock 前缀,如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
4

3 回答 3

8

只是为这些理论论点提供一些经验证据:

这是一个测试用例,其中几个线程用于xadd增加共享计数器。在具有 4 核的 i7-8565U 上,它输出

unlocked: counter = 1633267, expected 4000000
locked: counter = 4000000, expected 4000000

这清楚地表明xaddwithoutlock不是原子的。

编码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

unsigned long counter = 0;

#define COUNTS_PER_THREAD 1000000UL
#define THREADS 4

void *unlocked_worker(void *unused) {
    (void)unused;
    for (unsigned long i = 0; i < COUNTS_PER_THREAD; i++) {
        unsigned long inc = 1;
        asm volatile("xaddq %0, %1" : "+r" (inc), "+m" (counter));
    }
    return NULL;
}

void *locked_worker(void *unused) {
    (void)unused;
    for (unsigned long i = 0; i < COUNTS_PER_THREAD; i++) {
        unsigned long inc = 1;
        asm volatile("lock; xaddq %0, %1" : "+r" (inc), "+m" (counter));
    }
    return NULL;
}

void run_threads(int lock) {
    void *(*worker)(void *) = lock ? locked_worker : unlocked_worker;
    counter = 0;
    pthread_t th[THREADS];
    for (int i = 0; i < THREADS; i++) {
        int err = pthread_create(&th[i], NULL, worker, NULL);
        if (err != 0) {
            fprintf(stderr, "pthread_create: %s\n", strerror(err));
            exit(1);
        }
    }
    for (int i = 0; i < THREADS; i++) {
        int err = pthread_join(th[i], NULL);
        if (err != 0) {
            fprintf(stderr, "pthread_join: %s\n", strerror(err));
            exit(1);
        }
    }
    printf("%s: counter = %lu, expected %lu\n",
           lock ? "locked" : "unlocked",
           counter, COUNTS_PER_THREAD * THREADS);
}

int main(void) {
    run_threads(0);
    run_threads(1);
    return 0;
}
于 2020-11-28T23:44:43.880 回答
8

xadd没有lock是原子的。这个核心上的中断,但不是wrt。在其他内核(或 DMA)上运行的代码。就像除xchg. 请参阅此答案,其中涵盖了相同的问题cmpxchg

如果此代码在多个内核上同时运行,则 2 个或更多内核可以读取相同的 值newSS,从而有效地丢失增量并将相同的值分配ss:esp给两个内核。或者一个存储可能会被多个 xadd 延迟,而其他内核恰好是顺序的,将计数器“倒回”到稍后加载看到的某个先前值。或任何问题的组合。 num++ 可以是“int num”的原子吗?

假设newSS是对齐的,加载和存储分别是原子的,但形成原子的 RMW。

如果同时唤醒多个内核(广播 IPI 可能吗?)这似乎很可能是一个真正的问题。如果不是,那么每个内核都可能xadd在下一个内核访问此代码之前完成。(包括提交到 L1d 缓存的存储;变得全局可见。)但这只是一种“正常工作”的行为,除非核心唤醒功能在唤醒另一个核心之前等待收到来自一个核心的回复。

lock xadd如果它想要匹配关于原子增量的评论,它肯定需要。 是原子的。如果线程从不并发运行,则中断很好,只能通过单个内核上的上下文切换。(例如,主线程和信号处理程序之间的原子性,或同一内核上的中断处理程序)。但由于它的标题是smphello.s,单处理器假设似乎不太可能。

于 2020-11-28T20:44:19.333 回答
3

再想一想,我又想到了这个案子的另一个场景。

如果微码实现xadd是这样的:

temp = ax + newSS
newSS = ax 
ax = temp ; the last 2 are actual xchg

那么我们在这种情况下会遇到问题:

假设newSS在 2 个线程之间共享。

线程 No.0(t0withax等于5)加载并添加newSSwithax并将其放入temp register.

假设此时我们有一个上下文切换。然后t1ax等于5尝试加载newSS并将其添加到ax并将结果放入temp register. 然后上下文切换回t0......两个堆栈段寄存器将指向相同的地址。

显然我们这里有问题。除非微码实现是这样的:

tmp register = ax
xchg ax, newSS
ax = ax + tmpRegister

以任何其他方式,变量newSS被多次读取或以不同的指令读写,我们需要锁定。

于 2015-05-08T18:52:15.837 回答