Ядро XNU делает это. Swift - это нити POSIX Mach . Во время запуска программы ядро XNU анализирует исполняемый формат Mach-O и обрабатывает либо современную LC_MAIN
, либо устаревшую команду LC_UNIXTHREAD
load (среди прочих). Это выполняется в функциях ядра:
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
)
, которые оказываются открытым исходным кодом
LC_MAIN
инициализируетстек через thread_userstackdefault
LC_UNIXTHREAD
- load_threadstack
.
Как @PeterCordes упоминает в комментариях, только когда ядро создает основной поток, сам запущенный процесс может порождать дочерние потоки из своих собственных. Основной поток либо через некоторые API, такие как GCD, либо непосредственно через системный вызов (bsdthread_create
, не уверен, если есть другие). Системный вызов имеет user_addr_t stack
в качестве третьего аргумента (т. Е. rdx
в ядре x86-64 System V ABI, используемом MacOS). Справочник по системным вызовам MacOS
Я не подробно исследовал детали этого конкретного аргумента стека, но я предположил бы, что он похож на 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
Теперь мы можем наблюдать пару интересных вещей:
Class
экземпляры явно занимают другую часть памяти, чем Structs
- При назначении структуры новой переменной получаетсякопировать на новый адрес памяти
- При назначении экземпляра класса просто копируется указатель.
- И основной поток, и дочерний поток при обращении к глобальному
Structs
указывают на точно такая же память - Строки имеют контейнер Struct.
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
это дает различное значение для каждого потока, поэтому стеки потока четко различаются.
Копаем дальше
Есть удобная область памяти Команда lldb, которая проливает свет на происходящее.
memory region 0x000000010058a6f0
[0x000000010053d000-0x000000010058b000) rw- __DATA
Так глобально Structs
сидеть на предварительно выделенной исполняемой для записи __DATA
странице памяти (той же самой, где живут ваши глобальные переменные). Та же команда для адреса класса 0x7fcd0940cd40
не столь впечатляющая (я считаю, что это динамически размещаемая куча). Аналогично для адреса стека потока 0x7ffeefbfefc0
, который явно не является областью памяти процесса.
К счастью, есть еще один последний инструмент для дальнейшего продвижения по кроличьей норе.
vmmap -v -purge pid , который подтверждает, что классы располагаются в куче MALLOC_
ed и также в потокестек (по крайней мере, для основного потока) может иметь перекрестные ссылки на Stack
.
В некоторой степени связанный с этим вопрос также здесь .
HTH