【问题标题】:lock cmpxchg fails to execute threads in core orderlock cmpxchg 无法按核心顺序执行线程
【发布时间】:2020-04-13 19:18:31
【问题描述】:

以下 64 位 NASM 代码使用 lock cmpxchg 以核心顺序获取每个核心,执行一些代码,然后使用 xchg 重置核心编号变量,以便下一个核心可以执行代码。每个内核的内核编号存储在 rbx 中——四个内核的编号分别为 0、8、16 和 24。变量 [spin_lock_core] 从零开始,当每个内核完成时,它在最后一行将内核编号更新为 8 xchg [spin_lock_core],rax.

Spin_lock:
xor rax,rax
lock cmpxchg [spin_lock_core],rbx
jnz Spin_lock

; Test
mov rbp,extra_test_array
mov [rbp+rbx],rbx

; Execute some code before looping out
mov rax,1234
mov rdx,23435
add rax,rbx
mov rcx,rax
;jmp label_899

mov rax,rbx
add rax,8
xchg [spin_lock_core],rax

但在代码到达 xchg [spin_lock_core] 之前,rax 第一个核心循环退出程序 (jmp label_899),这会导致其他线程冻结,因为它们将等待 [spin_lock_core] 变量更新,这永远不会发生。但是,所有四个内核都被写入输出数组 extra_test_array,程序退出时会显示在终端上。换句话说,在更新核心编号之前,这无法停止核心。

完整的、最小的代码如下(在这种情况下,尽可能少的 NASM)。代码是为共享对象编写的,如果它得到一个输入数组,它是可重现的(如所写,输入数组是 int 还是 float 无关紧要):

; Header Section
[BITS 64]

[default rel]

global Main_Entry_fn
extern pthread_create, pthread_join, pthread_exit, pthread_self,    sched_getcpu
global FreeMem_fn
extern malloc, realloc, free
extern sprintf

section .data align=16
X_ctr: dq 0
data_master_ptr: dq 0
initial_dynamic_length: dq 0
XMM_Stack: dq 0, 0, 0, 0, 0, 0, 0
ThreadID: dq 0
X_ptr: dq 0
X_length: dq 0
X: dq 0
collect_ptr: dq 0
collect_length: dq 0
collect_ctr: dq 0
even_squares_list_ptrs: dq 0, 0, 0, 0
even_squares_list_ctr: dq 0
even_squares_list_length: dq 0
Number_Of_Cores: dq 32
pthread_attr_t: dq 0
pthread_arg: dq 0
Join_Ret_Val: dq 0
tcounter: dq 0
sched_getcpu_array: times 4 dq 0
ThreadIDLocked: dq 0
spin_lock_core: dq 0
extra_test_array: dq 0

; __________

section .text

Init_Cores_fn:

; _____
; Create Threads

label_0:

mov rdi,ThreadID            ; ThreadCount
mov rsi,pthread_attr_t  ; Thread Attributes
mov rdx,Test_fn         ; Function Pointer
mov rcx,pthread_arg
call pthread_create wrt ..plt

mov rdi,[ThreadID]      ; id to wait on
mov rsi,Join_Ret_Val        ; return value
call pthread_join wrt ..plt

mov rax,[tcounter]
add rax,8
mov [tcounter],rax
mov rbx,[Number_Of_Cores]
cmp rax,rbx
jl label_0

; _____

jmp label_900 ; All threads return here, and exit

; ______________________________________

Test_fn:

; Get the core number
call sched_getcpu wrt ..plt
mov rbx,8 ; multiply by 8
mul rbx
push rax

pop rax
mov rbx,rax
push rax

Spin_lock:
lock cmpxchg [spin_lock_core],rbx
jnz Spin_lock

; Test
mov rbp,extra_test_array
mov [rbp+rbx],rbx

; Execute some code before looping out
mov rax,1234
mov rdx,23435
add rax,rbx
mov rcx,rax
jmp label_899

mov rax,rbx
add rax,8
xchg [spin_lock_core],rax

;__________

label_899:

pop rax

ret

; __________

label_900:

mov rdi,extra_test_array ;audit_array
mov rax,rdi

ret

;__________
;Free the memory

FreeMem_fn:

;The pointer is passed back in rcx (of course)

sub rsp,40
call free wrt ..plt
add rsp,40
ret

; __________
; Main Entry


Main_Entry_fn:
push rdi
push rbp
push rbx
push r15
xor r15,r15
push r14
xor r14,r14
push r13
xor r13,r13
push r12
xor r12,r12
push r11
xor r11,r11
push r10
xor r10,r10
push r9
xor r9,r9
push r8
xor r8,r8
movsd [XMM_Stack+0],xmm13
movsd [XMM_Stack+8],xmm12
movsd [XMM_Stack+16],xmm11
movsd [XMM_Stack+24],xmm15
movsd [XMM_Stack+32],xmm14
movsd [XMM_Stack+40],xmm10
mov [X_ptr],rdi
mov [data_master_ptr],rsi
; Now assign lengths
lea rdi,[data_master_ptr]
mov rbp,[rdi]
xor rcx,rcx
movsd xmm0,qword[rbp+rcx]
cvttsd2si rax,xmm0
mov [X_length],rax
add rcx,8

; __________
; Write variables to assigned registers

mov r15,0
lea rdi,[rel collect_ptr]
mov r14,qword[rdi]
mov r13,[collect_ctr]
mov r12,[collect_length]
lea rdi,[rel X_ptr]
mov r11,qword[rdi]
mov r10,[X_length]

; __________

call Init_Cores_fn

movsd xmm10,[XMM_Stack+0]
movsd xmm14,[XMM_Stack+8]
movsd xmm15,[XMM_Stack+16]
movsd xmm11,[XMM_Stack+24]
movsd xmm12,[XMM_Stack+32]
movsd xmm13,[XMM_Stack+40]
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop rbx
pop rbp
pop rdi
ret

在 [spin_lock_core] 变量更新之前,指令“lock cmpxchg”应该会失败,但它不会这样做。

感谢您在理解为什么 lock cmpxchg 不会阻止核心零之后的核心在此代码区域中触发的任何帮助。

更新:其他研究表明在 Spin_lock: 部分的顶部需要 xor rax,rax。当我插入该行时,它的内容如下:

Spin_lock:
xor rax,rax
lock cmpxchg [spin_lock_core],rbx
jnz Spin_lock

正如预期的那样,随着该更改,它会冻结。但是当我删除 jmp label_899 行时,它仍然会冻结,但它不应该那样做。

编辑 122219:

根据昨天关于这个问题的 cmets,我将自旋锁代码修改为 (1) 消除原子操作以支持更快的 mov 和 cmp 指令,(2) 为每个内核分配一个唯一的内存位置,以及 (3) 分开内存位置 > 256 字节以避免内存在同一高速缓存行上。

当前一个核心完成时,每个核心的内存位置将更改为 1。当每个核心完成后,它会将自己的内存位置设置回 0。

如果所有其他核心在自旋锁之前循环,代码成功执行核心 0。当我让所有四个核心都通过自旋锁时,程序再次挂起。

我已经验证,当前一个内核完成时,每个单独的内存位置都设置为 1。

这是更新的自旋锁部分:

section .data
spin_lock_core: times 140 dq 0
spin_lock_core_offsets: dq 0,264,528,792

section .text

; Calculate the offset to spin_lock_core
mov rbp,spin_lock_core
mov rdi,spin_lock_core_offsets
mov rax,[rdi+rbx]
add rbp,rax

; ________

Spin_lock:
pause
cmp byte[rbp],1
jnz Spin_lock

xor rax,rax
mov [rbp],rax ; Set current memory location to zero

; Execute some code before looping out
mov rax,1234
mov rdx,23435
add rax,rdx
mov rcx,rax

; Loop out if this is the last core
mov rax,rbx
add rax,8
cmp rax,[Number_Of_Cores]
jge label_899

; Set next core to 1 by adding 264 to the base address
add rbp,264
mov rax,1
mov [rbp],rax

为什么这段代码仍然挂起?

【问题讨论】:

  • 阅读手册:felixcloutier.com/x86/cmpxchg 说 CMPXCHG 有一个隐式输入(和失败时的输出):RAX 表示“预期”值。您在底部提到了这一点,但您没有更新问题的其余部分。你认为你的程序应该做什么?您是否尝试过使用调试器和单步执行来停止一个线程?
  • 我用xor rax,rax更新了上面的源代码,jmp label_899这一行被注释掉了。程序应该在其他核心等待时执行核心 0,然后使用 xchg [spin_lock_core],rax 更新核心 0,其中 rax 现在是 8,因此核心 8 将成功锁定 cmpxchg [spin_lock_core],rbx。相反,它会冻结。
  • 我怀疑最后的代码需要一个 AND 来防止“spin_lock_core == 32” - 例如mov rax,rbxadd rax,8and rax,24xchg [spin_lock_core],rax。需要旋转时的xor rax,rax
  • 不要妄想存在缓存一致性问题。问题无疑出在您的程序逻辑上。如果 [spin_lock_core] 开始为 0,并且所有核心清除 rax,然后执行 cmpxchg,则 cmpxchg 将在所有核心上成功。我认为您想在 Spin_lock 循环中使用 mov rax, rbx 初始化 rax。
  • 我完全不明白您为什么要为此使用 cmpxchg。您只需要开头的cmp [spin_lock_core], ebx 和结尾的mov [spin_lock_core], rax

标签: multithreading x86-64 nasm


【解决方案1】:

我认为您根本不应该为此使用 cmpxchg。试试这个:

Spin_lock:
pause
cmp [spin_lock_core],rbx
jnz Spin_lock

; Test
mov rbp,extra_test_array
mov [rbp+rbx],rbx

; Execute some code before looping out
mov rax,1234
mov rdx,23435
add rax,rbx
mov rcx,rax
;jmp label_899

lea rax,[rbx+8]
mov [spin_lock_core],rax

【讨论】:

  • 我想知道您怀疑是哪种缓存问题。通常 x86 是完全缓存一致的,您根本不必担心缓存。
  • 哦,我重读了您对缓存问题的描述——这完全与性能有关,而不是正确性。将所有线程自旋锁定在同一地址上会导致大量缓存一致性流量,从而导致性能问题,但不会导致代码正常工作(只要程序逻辑正确)出现问题。请注意,使用 cmp 而不是 cmpxchg 可以缓解此问题,因为使用 cmp,所有核心都可以将线路保持在共享状态直到它发生变化,而使用 cmpxchg,每个核心都必须在每个循环上独占获取线路。
  • 你确定你的线程ID真的相差8吗?我们知道它们是 CPU 数量的 8 倍,但我不知道我们是否可以假设这 4 个线程正在运行的 CPU 数量是 0、1、2 和 3。
  • 通常你需要在锁的获取端有一个原子 RMW。如果你没有那个,也许你只是将​​所有权交给一个下一个线程。那么它就不再是自旋锁了;解锁后,任何线程都无法获取它。但这可能是@RTC222 真正想要做的事情。
  • @Peter,同意如果他想让 any 线程接下来继续,他需要一个原子 rmw,但因为他试图发出 specific i> 线程下一步,他不需要它。
【解决方案2】:

我解决了这个自旋锁问题,但在下面 Peter Cordes 的评论之后,我发现它不正确。我不会删除这个答案,因为我希望它可以导致解决方案。

我使用 lock cmpxchg [rbp+rbx],rbx,它可以无错误地汇编,但是 NASM 汇编器应该返回“无效的操作数组合”错误,因为源操作数只能是 rax,所以它不应该与任何其他登记册。我还注意到在线资源(例如,https://www.felixcloutier.com/x86/cmpxchg)显示格式为 CMPXCHG r/m64,r64,但源操作数不能是任何 r64——它必须是 rax,因为该条目继续说.

如果没有“mov rax,rbx”行,它可以工作,因为在第一次迭代中,rax 寄存器设置为 0,它与内存位置匹配。在第二次迭代中,它默认成功。

当我添加“mov rax,rbx”(重置 rax)时,程序再次挂起。我真的很感激关于为什么这个程序应该按照书面形式挂起的任何想法。

在这个块的开头 rbx 是核心号:

section .data
spin_lock_core: times 4 dq 0

section .text

[ Code leading up to this spinlock section shown above ]

mov rbp,spin_lock_core

Spin_lock:
pause
mov rax,rbx
lock cmpxchg [rbp+rbx],rax
jnz Spin_lock

mov rax,rbx
add rax,8
cmp rax,[Number_Of_Cores]
jge spin_lock_out

xchg [rbp+rax],rax

spin_lock_out:

与我原帖的不同之处在于:

  1. 每个内核都在其自己的唯一内存位置上旋转(并从中读取)。

  2. 我在自旋锁上使用“暂停”指令。

  3. 每个唯一的内存位置都按核心顺序更新。

但是当我包含 mov rax,rbx 时它不起作用。直觉上这应该有效,所以我真的很感激任何关于为什么它在这种情况下不起作用的想法。

【讨论】:

  • 呵呵,调用这段代码有什么前置条件?它不会在lock cmpxchg 之前设置 RAX。因此,如果它与旧内容不匹配,它会加载然后重试,并且会成功。如果您实际上并不关心旧内容,我根本不明白使用lock cmpxchg 的意义何在;只是做一个商店。 (或xchg 作为顺序一致性存储)。
  • 你是对的。我修改了我的答案(现在更像是一个问题)以显示我做错了什么。当我添加行 mov rax,rbx 时它挂起,但我不明白为什么。
  • 我在这个过程中运行了 strace。挂起前的最后一行是:“rt_sigaction(SIGINT, {sa_handler=0x55e71359f160, sa_mask=[], sa_flags=SA_RESTORE R, sa_restorer=0x7f17491d8f20}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f }, 8) = 0 wait4(-1," -- 所以它正在等待 sa_restorer。
  • 根据stackoverflow.com/questions/42356368/…,“在 x86-64 linux 中,必须提供 sa_restorer。”看起来标志需要重置,但我仍在调查中。
  • 我正在用 gdb 调试它。稍后我会发回我的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-17
相关资源
最近更新 更多