【问题标题】:Multicore in NASM Windows: threads execute randomlyNASM Windows 中的多核:线程随机执行
【发布时间】:2019-03-03 18:06:20
【问题描述】:

我在 Windows 的 NASM(64 位)中有代码,可以在四核 Windows x86-64 机器上同时运行四个线程(每个线程分配给一个单独的核心)。

线程是在循环中创建的。创建线程后,它调用 WaitForMultipleObjects 来协调线程。要调用的函数是 Test_Function(参见下面的代码)。

每个线程(核心)跨一个大数组执行 Test_Function。第一个内核从数据元素 0 开始,第二个内核从 1 开始,第三个内核从 2 开始,第四个内核从 3 开始,每个内核递增 4(例如,0、4、8、12)。

在 Test_Function 中,我创建了一个小型测试程序,将其中一个输入数据值写入与其起始字节对应的位置,以验证我是否已成功创建四个线程并且它们返回正确的数据。

每个线程都应该写入stride值(32),但测试显示四个字段是随机填写的,有些字段显示为零。如果我多次重复测试,我发现哪些字段的值为 32(其他字段始终显示为 0)并不一致。这可能是 WaitForMultipleObjects 的副作用,但我在文档中没有看到任何东西来证实这一点。

此外,WaitForMultipleObjects 等待 CreateThread 返回的 ThreadHandles;当我检查 ThreadHandles 数组时,它总是显示如下:268444374、32、1652、1584。只有第一个元素看起来像句柄的大小,其他元素看起来不像句柄值。

一种可能是堆栈上传递的两个参数可能不在正确的位置:

mov rax,0
mov [rsp+40],rax            ; use default creation flags
mov rax,[ThreadCount]
mov [rsp+32],rax            ; ThreadID

根据文档,ThreadCount 应该是一个指针。当我将行更改为 mov rax,ThreadCount (指针值)时,程序崩溃。当我将其更改为:

mov rax,0
mov [rsp+32],rax            ; use default creation flags
mov rax,ThreadCount
mov [rsp+40],rax            ; ThreadID

现在它可靠地处理第一个线程,但不是线程 2-4。

所以底线是线程正在被创建,但它们随机执行,有些线程根本没有执行,没有特定的顺序。当我更改 CreateThread 参数(如上所示)时,第一个线程执行,但不是线程 2-4。

这是显示相关部分的测试代码。如果需要一个可重现的例子,我可以准备一个。

感谢您的任何想法。

Init_Cores_fn:
; EACH OF THE CORES CALLS Test_Function AND EXECUTES THE WHOLE PROGRAM.  
; WE PASS THE STARTING BYTE (0, 8, 16, 24) AND THE "STRIDE" = NUMBER OF CORES.  
; ON RETURN, WE SYNCHRONIZE ANY DATA.  ON ENTRY TO EACH CORE, SET THE REGISTERS

; Populate the ThreadInfo array with vars to pass
; ThreadInfo: length, startbyte, stride, vars into registers on entry to each core
mov rdi,ThreadInfo
mov rax,ThreadInfoLength
mov [rdi],rax
mov rax,[stride]
mov [rdi+16],rax    ; 8 x number of cores (32 in this example)
; Register Vars
mov [rdi+24],r15
mov [rdi+32],r14
mov [rdi+40],r13
mov [rdi+48],r12
mov [rdi+56],r10

mov rbp,rsp ; preserve caller's stack frame
sub rsp,56 ; Shadow space

; _____

label_0:

mov rdi,ThreadInfo
mov rax,[FirstByte]
mov [rdi+8],rax ; 0, 8, 16, or 24

; _____
; Create Threads

mov rcx,0               ; lpThreadAttributes (Security Attributes)
mov rdx,0               ; dwStackSize
mov r8,Test_Function        ; lpStartAddress (function pointer)
mov r9,ThreadInfo       ; lpParameter (array of data passed to each core)

mov rax,0
mov [rsp+40],rax            ; use default creation flags
mov rax,[ThreadCount]
mov [rsp+32],rax            ; ThreadID

call CreateThread

; Move the handle into ThreadHandles array (returned in rax)
mov rdi,ThreadHandles
mov rcx,[FirstByte]
mov [rdi+rcx],rax

mov rax,[FirstByte]
add rax,8
mov [FirstByte],rax

mov rax,[ThreadCount]
add rax,1
mov [ThreadCount],rax

mov rbx,4
cmp rax,rbx
jl label_0

; _____
; Wait

mov rcx,rax         ; number of handles
mov rdx,ThreadHandles       ; pointer to handles array
mov r8,1                ; wait for all threads to complete
mov r9,1000         ; milliseconds to wait

call WaitForMultipleObjects

; _____

;[ Code HERE to do cleanup if needed after the four threads finish ]

mov rsp,rbp
jmp label_900

; __________________
; The function for all threads to call

Test_Function:

; Populate registers
mov rdi,rcx
mov rax,[rdi]
mov r15,[rdi+24]
mov rax,[rdi+8] ; start byte
mov r13,[rdi+40]
mov r12,[rdi+48]
mov r10,[rdi+56]
xor r11,r11
xor r9,r9
pxor xmm15,xmm15
pxor xmm15,xmm14
pxor xmm15,xmm13

; Now test it - BUT the first thread does not write data
mov rcx,[rdi+8] ; start byte
mov rax,[rdi+16] ; stride
cvtsi2sd xmm0,rax
movsd [r15+rcx],xmm0
ret

【问题讨论】:

  • 第 1 步:学习使用 ASM 调试器。第 2 步:也许将某些部分重写为 C?
  • (1) 我还没有为 Windows 中的 NASM 找到一个好的可视源代码调试器,(2) 调试器不太可能在调用 Windows API 函数时发现问题,( 3)我不希望它在 C 中。
  • 您对每个线程使用相同的ThreadInfo,因此它们会相互竞争以及与主线程竞争。我强烈建议您首先学习高级语言的线程。尝试同时学习线程汇编语言是不切实际的。
  • 如果你想成功,你必须弄清楚如何使用调试器来单步调试这段代码。

标签: windows multithreading x86-64 nasm


【解决方案1】:

我解决了这个问题,这里是解决方案。 Raymond Chen 在上面的 cmets 中提到了这一点,然后敦促我使用更高级别的语言,但直到今天我才明白。我发布了这个答案,以便将来在汇编语言(或任何其他语言)中遇到相同问题的任何人都可以轻松访问和理解,因为 Raymond 的评论(我刚刚赞成)现在被埋在上面的其他 cmets 中。

ThreadInfo 数组,在此处作为第四个参数传递给 CreateThread(在 Windows 的 r9 中)。每个核心都必须有自己单独的 ThreadInfo 副本。在我的应用程序中,除了 StartByte 参数(在 rdi+8 处)之外,ThreadInfo 中的数据都是相同的。相反,我为每个内核(ThreadInfo1、2、3 和 4)创建了一个单独的 ThreadInfo 数组,并将一个指针传递给相应的 ThreadInfo 数组。

我在我的应用程序中将它实现为对以下 dup 函数的调用,但它也可以通过其他方式实现:

DupThreadInfo:
mov rdi,ThreadInfo2
mov rax,8
mov [rdi+8],rax
mov rax,[stride]
mov [rdi+16],rax    ; 8 x number of cores (32 in this example)
; Vars (these registers are populated on main entry)
mov [rdi+24],r15
mov [rdi+32],r14
mov [rdi+40],r13
mov [rdi+48],r12
mov [rdi+56],r10
; _____

mov rdi,ThreadInfo3
mov rax,0
mov [rdi],rax       ; length (number of vars into registers plus 3 elements)
mov rax,16
mov [rdi+8],rax
mov rax,[stride]
mov [rdi+16],rax    ; 8 x number of cores (32 in this example)
; Vars (these registers are populated on main entry)
mov [rdi+24],r15
mov [rdi+32],r14
mov [rdi+40],r13
mov [rdi+48],r12
mov [rdi+56],r10

mov rdi,ThreadInfo4
mov rax,0
mov [rdi],rax       ; length (number of vars into registers plus 3 elements)
mov rax,24
mov [rdi+8],rax
mov rax,[stride]
mov [rdi+16],rax    ; 8 x number of cores (32 in this example)
; Vars (these registers are populated on main entry)
mov [rdi+24],r15
mov [rdi+32],r14
mov [rdi+40],r13
mov [rdi+48],r12
mov [rdi+56],r10
ret

因为 ThreadInfo 数组中的所有数据都相同,除了第二个元素,更有效的方法是传递一个 2 元素数组,其中第一个元素是 StartByte,第二个元素是指向静态 ThreadInfo 数组。当我们使用超过四个内核时,这一点尤其重要,因为 DupThreadInfo 部分会不必要地长。该解决方案将避免调用,但我还没有实现。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-02
    • 2023-03-28
    • 2017-11-14
    相关资源
    最近更新 更多