forked from xuzhongxing/fuchsia-notes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
thread.txt
222 lines (159 loc) · 7.67 KB
/
thread.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
在launchpad.c里,加载elf可执行文件的时候,会检查是否需要interpreter, 如果需要,
则加载ld.so,然后把它的入口设置为entry
====
线程有2部分数据结构,内核里和用户态。
====
创建bootstrap2线程时,thread_t 是在malloc里创建的。stack也是在malloc.
如果是用户进程通过系统调用创建线程,则stack是通过kernel aspece分配出来的vm object.
user sp是通过vm object mapping得到的。
放到stack frame里的入口是initial_thread_func。这个函数会调用thread_t的entry.
thread_t的entry是ThreadDispatcher::StartRoutine. StartRoutine里会调用
user_entry_,这个是在ThreadDispatcher::Start()里设置的。
thread的启动:ThreadDispatcher::Start()里设置好user_entry_,然后触发线程调度,
切换到线程的stack上,然后进入stack frame里指定的入口initial_thread_func.
thread的退出:在thread_create_etc()里,线程入口被设置为initial_thread_func。
在这个函数里,如果是用户线程,则不会回来了。用户线程自己调用process_exit退出。
如果是kernel线程,会回到这里调用thread_exit()。在这个函数里,线程把自己从调度队列里拿下来,然后
触发调度函数,进入别的线程。
中断:中断会保存spsr_el1,然后切换到sp_el1,然后进入中断向量
在启动进程的时候,传递的第一个参数是一个channel handle.
libc start main在拿到这个channel handle之后,读取它。channel里的message是MessagePack
里面的data是zx_proc_args_t。这个东西是由另一头用channel_write()写进来的。
userboot里面是手工组装这个消息。如果是普通应用层应该会把这个封装掉。
zx_proc_args_t是传递进程参数的固定的消息格式。作为data写入MessagePack.
进入main()之前的操作
应用程序的入口是main(),在此之前,libc会做一些设置。
launchpad_create(job_copy, name, &lp);
launchpad_create_with_jobs(job, xjob, name, result);
launchpad_load_from_vmo(lp, svchost_vmo);
launchpad_file_load_with_vdso(lp, vmo);
launchpad_file_load(lp, vmo);
先检查是不是#!脚本,如果不是:
launchpad_elf_load_body(lp, first_line, to_read, vmo);
elf_load_start(vmo, hdr_buf, buf_sz, &elf)
elf_load_prepare(vmo, hdr_buf, buf_sz, &header, &phoff)
读程序头
elf_load_get_interp(elf, vmo, &interp, &interp_len);
elf_load_find_interp(info->phdrs, info->header.e_phnum,
寻找动态链接器
handle_interp(lp, vmo, interp, interp_len)
setup_loader_svc(lp);
elf_load_finish(lp_vmar(lp), elf, interp_vmo,
&segments_vmar, &lp->base, &lp->entry)
把动态链接器的入口作为lp的入口
进程创建的时候创建root vmar
launchpad提供了一系列接口,包括:加载可执行文件的vmo,添加启动命令行参数,添加handle。
launchpad_go(launchpad_t* lp, zx_handle_t* proc, const char** errmsg)
launchpad_start(lp, &h)
prepare_start(lp, &data);
zx_channel_create(0, &to_child, &bootstrap);
zx_thread_create(lp_proc(lp), thread_name, strlen(thread_name), 0, &thread);
给新进程创建stack vmo
zx_process_start(data.process, data.thread, data.entry, data.sp,
data.bootstrap, data.vdso_base);
In kernel space:
thread->Start(pc, sp, static_cast<uintptr_t>(arg_nhv),
arg2, /* initial_thread */ true)
动态链接库是libc.so. 入口在dl-entry.S文件里。这里会链接真正的可执行文件,找到
它的入口地址,进入它。实现在zircon/third_party/ulib/musl/ldso/dynlink.c
_dl_start(void* start_arg, void* vdso)
__dls3
可执行文件的最初的入口在zircon/third_party/ulib/musl/arch/aarch64/Scrt1.S
mov main@GOTPCREL(%rip), %rsi
jmp *__libc_start_main@GOTPCREL(%rip)
argument在rdi中,main在rsi中。
__libc_start_main(void* arg, int (*main)(int, char**, char**))
bootstrap = (uintptr_t)arg;
把handles取出来
把names取出来
设置一些全局handle
__environ
__zircon_process_self;
__zircon_vmar_root_self;
__zircon_job_default;
__init_main_thread(main_thread_handle);
缺省的stack size是256k
__allocate_thread(attr._a_guardsize, attr._a_stacksize, thread_self_name, NULL);
_zx_vmo_create(vmo_size, 0, &vmo);
----
sys_vmo_create
map_block(_zx_vmar_root_self(), vmo, 0, tcb_size, PAGE_SIZE, PAGE_SIZE, &tcb, &tcb_region)
给tcb分配地址范围并映射
先创建vmar, 然后把vmo相应的部分映射到vmar相应的地址区间。
copy_tls(tcb.iov_base, tcb.iov_len)
对x86, TLS_ABOVE_TP是不定义的,所以pthread头上是tcb
map_block(_zx_vmar_root_self(), vmo, tcb_size, stack_size, guard_size, 0, &td->safe_stack, &td->safe_stack_region)
分配程序使用的stack.
之前在launchpad里分配的是临时stack,这里切换成真正的stack
start_main(const struct start_params* p)
__libc_extensions_init(p->nhandles, p->handles, p->handle_info,
p->namec, p->names);
会把所有的name对应的handle拿出来,创建fdio root namespace.
fdio_ns_create(&fdio_root_ns)
fdio_ns_bind(fdio_root_ns, names[arg], h);
把h绑定在对应路径的remote上。
fdio_root_handle = fdio_ns_open_root(fdio_root_ns);
fdio_dir_create_locked(ns, &ns->root);
root目录的io ops是dir->io.ops = &dir_ops;
__libc_startup_handles_init(p->nhandles, p->handles, p->handle_info);
// Run static constructors et al.
__libc_start_init();
// Pass control to the application.
exit((*p->main)(p->argc, p->argv, __environ));
====
note:
llvm codegen
X86ISelLowering.cpp:
LowerToTLSExecModel
Get the Thread Pointer, which is %gs:0 (32-bit) or %fs:0 (64-bit).
====
llvm 知道fs:0是thread Pointer
在fuchsia里这个的设置是:
dynlink.c: __init_main_thread
tls.h: zxr_tp_set(thread_self, pthread_to_tp(td));
_zx_object_set_property(self, ZX_PROP_REGISTER_FS, (uintptr_t*)&tp, sizeof(uintptr_t));
====
percpu是内核数据结构,在内核里对应gs:0
====
wrmsr写入的时候等价于写入了gs.base寄存器
====
%gs.base状态的变化:
第一次切出内核时:
swapgs
%gs.base: 0
MSR: &percpu
切入内核时:
swapgs
%gs.base: &percpu
MSR: 0
线程切换时:
swapgs: %gs.base: 0, MSR: &percpu
read gs: oldstruct: 0
write gs: %gs.base: 0
swapgs: %gs.base: &percpu, MSR: 0
切出内核:
swapgs
%gs.base: 0
MSR: &percpu
====
目前发现2个地方会调用pthread的__allocate_thread
一个是__pthread_create
另一个是__init_main_thread
这2个地方一个是为主线程创建对应的tls,另一个是pthread_create,
pthread_create会另外创建对应的内核线程
它们都会设置%fs.base为线程指针
====
当一个线程被创建时,initial_thread_func会被放到context switch frame上面 thread_create_etc
x86_64_context_switch会用retq指令跳转到这个入口地址上
然后通过x86_uspace_entry进入用户态
用户态的stack都是launchpad分配的vmo
内核栈在arch_context_switch()时通过
x86_set_tss_sp(newthread->stack.top);
保存在percpu default tss里
也就是说,当一个cpu运行在用户态时,它的percpu default tss里的rsp0存放的是运行的线程的内核stack rsp
在线程进行syscall进入内核时,它的用户态stack sp存入tss
====
有几种情况会导致内核态和用户态的切换
1. 中断 excpetion.S
2. syscall syscall.S
3. 新建的线程,通过x86_uspace_entry进入用户态