【发布时间】: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,rbx、add rax,8、and rax,24、xchg [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