首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >谁创建和拥有调用堆栈,以及调用堆栈如何在多线程中工作?

谁创建和拥有调用堆栈,以及调用堆栈如何在多线程中工作?
EN

Stack Overflow用户
提问于 2019-10-16 03:57:55
回答 2查看 393关注 0票数 0

我知道每个线程通常都有一个调用堆栈,它只是一个内存块,并使用esp和ebp进行控制。

这些调用堆栈是如何创建的,是谁负责的?我猜是运行时,例如iOS应用程序的Swift运行时。线程是通过esp和ebp或通过运行时直接与自己的调用堆栈对话吗?

2,对于每个调用堆栈,它们必须与esp和ebb cpu寄存器一起工作,如果我有一个具有2个内核的CPU (4个线程),那么假设它有4个核心(指令集)。这是否意味着每个调用堆栈将只处理特定核心中的这些寄存器?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-10-16 11:22:51

XNU内核就是这么做的。Swift线程是POSIX线程,也就是马赫线程。在程序启动期间,XNU内核解析Mach-O可执行格式,并处理现代LC_MAIN或遗留LC_UNIXTHREAD load命令(除其他外)。这是在内核函数中处理的:

代码语言:javascript
运行
复制
static
load_return_t
load_main(
        struct entry_point_command  *epc,
        thread_t        thread,
        int64_t             slide,
        load_result_t       *result
    )

&

代码语言:javascript
运行
复制
static
load_return_t
load_unixthread(
    struct thread_command   *tcp,
    thread_t        thread,
    int64_t             slide,
    load_result_t       *result
)

哪个恰好是开源

LC_MAIN通过thread_userstackdefault初始化堆栈

LC_UNIXTHREAD通过load_threadstack

正如@PeterCordes在注释中提到的那样,只有当内核创建主线程时,已启动的进程本身才能通过一些api (如GCD )或直接通过syscall (bsdthread_create,不确定是否还有其他线程)从它自己的主线程中生成子线程。syscall碰巧有user_addr_t stack作为它的第三个参数(即MacOS使用的x86-64 System内核ABI中的rdx )。MacOS系统参考

我还没有彻底研究过这个特定堆栈参数的细节,但我可以想象它类似于thread_userstackdefault / load_threadstack方法。

我相信您对Swift运行时责任的怀疑可能是因为经常提到数据结构(比如Swift struct --没有双关语)存储在堆栈中(顺便说一下,这是实现的细节,运行时的特性也没有保证)。

更新

他是一个例子,main.swift命令行程序稀释了这个想法。

代码语言:javascript
运行
复制
import Foundation

struct testStruct {
    var a: Int
}

class testClass {
}

func testLocalVariables() {
    print("main thread function with local varablies")
    var struct1 = testStruct(a: 5)
    withUnsafeBytes(of: &struct1) { print($0) }
    var classInstance = testClass()
    print(NSString(format: "%p", unsafeBitCast(classInstance, to: Int.self)))
}
testLocalVariables()

print("Main thread", Thread.isMainThread)
var struct1 = testStruct(a: 5)
var struct1Copy = struct1

withUnsafeBytes(of: &struct1) { print($0) }
withUnsafeBytes(of: &struct1Copy) { print($0) }

var string = "testString"
var stringCopy = string

withUnsafeBytes(of: &string) { print($0) }
withUnsafeBytes(of: &stringCopy) { print($0) }

var classInstance = testClass()
var classInstanceAssignment = classInstance
var classInstance2 = testClass()

print(NSString(format: "%p", unsafeBitCast(classInstance, to: Int.self)))
print(NSString(format: "%p", unsafeBitCast(classInstanceAssignment, to: Int.self)))
print(NSString(format: "%p", unsafeBitCast(classInstance2, to: Int.self)))

DispatchQueue.global(qos: .background).async {
    print("Child thread", Thread.isMainThread)
    withUnsafeBytes(of: &struct1) { print($0) }
    withUnsafeBytes(of: &struct1Copy) { print($0) }
    withUnsafeBytes(of: &string) { print($0) }
    withUnsafeBytes(of: &stringCopy) { print($0) }
    print(NSString(format: "%p", unsafeBitCast(classInstance, to: Int.self)))
    print(NSString(format: "%p", unsafeBitCast(classInstanceAssignment, to: Int.self)))
    print(NSString(format: "%p", unsafeBitCast(classInstance2, to: Int.self)))
}

//Keep main thread alive indefinitely so that process doesn't exit
CFRunLoopRun()

我的输出如下:

代码语言:javascript
运行
复制
main thread function with local varablies
UnsafeRawBufferPointer(start: 0x00007ffeefbfeff8, count: 8)
0x7fcd0940cd30
Main thread true
UnsafeRawBufferPointer(start: 0x000000010058a6f0, count: 8)
UnsafeRawBufferPointer(start: 0x000000010058a6f8, count: 8)
UnsafeRawBufferPointer(start: 0x000000010058a700, count: 16)
UnsafeRawBufferPointer(start: 0x000000010058a710, count: 16)
0x7fcd0940cd40
0x7fcd0940cd40
0x7fcd0940c900
Child thread false
UnsafeRawBufferPointer(start: 0x000000010058a6f0, count: 8)
UnsafeRawBufferPointer(start: 0x000000010058a6f8, count: 8)
UnsafeRawBufferPointer(start: 0x000000010058a700, count: 16)
UnsafeRawBufferPointer(start: 0x000000010058a710, count: 16)
0x7fcd0940cd40
0x7fcd0940cd40
0x7fcd0940c900

现在我们可以观察到一些有趣的事情:

  1. Class实例显然占用了与Structs不同的内存部分。
  2. 将结构赋值给新变量将复制到新的内存地址。
  3. 分配类实例只复制指针。
  4. 当引用全局Structs时,主线程和子线程都指向完全相同的内存
  5. 字符串有一个Struct容器。

Update2 - 4^的证明我们实际上可以检查下面的内存:

代码语言:javascript
运行
复制
x 0x10058a6f0 -c 8
0x10058a6f0: 05 00 00 00 00 00 00 00                          ........
x 0x10058a6f8 -c 8
0x10058a6f8: 05 00 00 00 00 00 00 00                          ........

因此,这肯定是实际的结构原始数据,即,结构本身,

更新3

我添加了一个testLocalVariables()函数,以区分Swift Struct定义为全局变量和局部变量。在这种情况下

代码语言:javascript
运行
复制
x 0x00007ffeefbfeff8 -c 8
0x7ffeefbfeff8: 05 00 00 00 00 00 00 00                          ........

它显然存在于线程堆栈上。

最后但并非最不重要的是,在lldb中,我需要:

代码语言:javascript
运行
复制
re read rsp
rsp = 0x00007ffeefbfefc0  from main thread
re read rsp
rsp = 0x000070000291ea40  from child thread

它为每个线程产生不同的值,因此线程堆栈显然是不同的。

进一步挖掘

这里有一个方便的记忆区 lldb命令,它可以显示出正在发生的事情。

代码语言:javascript
运行
复制
memory region 0x000000010058a6f0
[0x000000010053d000-0x000000010058b000) rw- __DATA

因此,全局Structs位于预先分配的可写可写__DATA内存页面(与您的全局变量所在的页面相同)。对于类0x7fcd0940cd40地址的相同命令没有那么引人注目(我估计是因为这是一个动态分配的堆)。类似于线程堆栈地址0x7ffeefbfefc0,它显然不是进程内存区域。

幸运的是,还有最后一个工具可以进一步深入兔子洞。

vmmap -v -purge pid确实确认类位于MALLOC_ed堆中,同样地,线程堆栈(至少对于主线程)可以交叉引用到Stack

有些相关的问题也是这里

HTH

票数 2
EN

Stack Overflow用户

发布于 2019-10-16 04:16:15

(我假设Swift线程就像其他语言中的线程一样。实际上并没有很多好的选择,无论是普通的OS级线程还是用户空间的“绿色线程”,或者两者的混合。区别只是在发生上下文切换的情况下;主要概念仍然相同)

每个线程都有自己的堆栈,由mmap或父线程在进程的地址空间中分配,或者通过创建线程的同一个系统调用来分配。iOS系统调用。在Linux中,您必须将一个void *child_stack传递给实际创建一个新线程的clone(2)系统调用。直接使用特定于操作系统的低级别系统调用是非常罕见的;语言运行库可能会在像pthread_create这样的线程函数的基础上执行线程处理,而线程库将处理特定于操作系统的详细信息。

是的,每个软件线程都有自己的体系结构状态,包括x86-64或论AArch64上的RSP。(如果您制作了过时的32位x86代码,也可以使用ESP )。我认为框架指针是可选的,对斯威夫特。

是的,每个逻辑核心都有自己的体系结构状态(寄存器包括堆栈指针);软件线程在逻辑核心上运行,并且在软件线程之间切换保存/恢复寄存器。相关的,可能是线程之间共享哪些资源?的副本。

软件线程共享相同的页表(虚拟地址空间),但不共享寄存器。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58405568

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档