NASM Windows中的多核:线程随机执行

时间:2019-03-03 18:06:20

标签: windows multithreading x86-64 nasm

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

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

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

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

每个线程都应写入步幅值(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 个答案:

答案 0 :(得分:1)

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

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节的长度会不必要地长。该解决方案可以避免打电话,但是我还没有实现。