ios - 谁创建并拥有调用堆栈以及调用堆栈如何在多线程中工作?

标签 ios swift multithreading assembly runtime

我知道每个线程通常有一个调用堆栈,它只是一 block 内存,由 esp 和 ebp 控制。

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

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

最佳答案

XNU 内核做到了。 Swift 线程是 POSIX pthreads aka Mach线程。在程序启动期间,XNU 内核解析 Mach-O 可执行格式并处理现代 LC_MAIN 或遗留 LC_UNIXTHREAD 加载命令(以及其他)。这是在内核函数中处理的:

static
load_return_t
load_main(
        struct entry_point_command  *epc,
        thread_t        thread,
        int64_t             slide,
        load_result_t       *result
    )

&

static
load_return_t
load_unixthread(
    struct thread_command   *tcp,
    thread_t        thread,
    int64_t             slide,
    load_result_t       *result
)

恰好是 open source

LC_MAIN 通过thread_userstackdefault 初始化栈

LC_UNIXTHREADload_threadstack

正如@PeterCordes 在评论中提到的,仅当内核创建主线程时,启动的进程本身可以通过 GCD 等 API 或直接通过系统调用(bsdthread_create,不确定是否还有其他)。系统调用恰好有 user_addr_t stack 因为它是第三个参数(即 MacOS 使用的 x86-64 System V 内核 ABI 中的 rdx)。 Reference for MacOS syscalls
我没有彻底研究这个特定堆栈参数的细节,但我想它类似于 thread_userstackdefault/load_threadstack 方法。

我确实相信你对 Swift 运行时责任的怀疑可能是由于经常提到存储在堆栈上的数据结构(如 Swift struct - 没有双关语意)(顺便说一句,这是实现细节而不是运行时的保证功能)。

更新:
他是一个示例 main.swift 命令行程序,说明了这个想法。

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()

我的输出看起来像这样:

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. 分配类实例只是复制指针。
  5. 当引用全局Structs时,主线程和子线程都指向完全相同的内存
  6. 字符串确实有一个结构容器。

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

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。在这种情况下

x 0x00007ffeefbfeff8 -c 8
0x7ffeefbfeff8: 05 00 00 00 00 00 00 00                          ........

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

最后但同样重要的是,当我在 lldb 中时:

re read rsp
rsp = 0x00007ffeefbfefc0  from main thread
re read rsp
rsp = 0x000070000291ea40  from child thread

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

进一步挖掘
有一个方便的 memory region lldb 命令,它揭示了正在发生的事情。

memory region 0x000000010058a6f0
[0x000000010053d000-0x000000010058b000) rw- __DATA

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

幸运的是,还有最后一个工具可以进一步深入兔子洞。
vmmap -v -purge pid这确实确认类位于 MALLOC_ed 堆中,同样线程堆栈(至少对于主线程)可以交叉引用到 Stack

有点相关的问题也是here .

HTH

关于ios - 谁创建并拥有调用堆栈以及调用堆栈如何在多线程中工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58405568/

相关文章:

java - 停止 java 线程而不引用它

multithreading - eclipse RCP : Only one Job runs at a time?

ios - Xcode 快速查看包装内容

ios - 适用于 iOS 的 yalantis 标签栏

swift - 如何将数组转换为 Realm 中的列表?

ios - 如何使用 Firebase 存储点赞

Java ThreadPoolExecutor - 关闭特定线程?

iphone - AudioPlaybackComplete使用未声明的标识符自调用方法

ios - 如何设置自动布局约束常数通用或设备特定?

ios - 在 map View 中的任何地方单击时隐藏自定义注释 View