]>
Commit | Line | Data |
---|---|---|
00dccaf1 KW |
1 | /* |
2 | * ucontext coroutine initialization code | |
3 | * | |
4 | * Copyright (C) 2006 Anthony Liguori <[email protected]> | |
5 | * Copyright (C) 2011 Kevin Wolf <[email protected]> | |
6 | * | |
7 | * This library is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.0 of the License, or (at your option) any later version. | |
11 | * | |
12 | * This library is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | |
19 | */ | |
20 | ||
21 | /* XXX Is there a nicer way to disable glibc's stack check for longjmp? */ | |
22 | #ifdef _FORTIFY_SOURCE | |
23 | #undef _FORTIFY_SOURCE | |
24 | #endif | |
25 | #include <stdlib.h> | |
26 | #include <setjmp.h> | |
27 | #include <stdint.h> | |
28 | #include <pthread.h> | |
29 | #include <ucontext.h> | |
30 | #include "qemu-common.h" | |
31 | #include "qemu-coroutine-int.h" | |
32 | ||
3f4349dc KW |
33 | #ifdef CONFIG_VALGRIND_H |
34 | #include <valgrind/valgrind.h> | |
35 | #endif | |
36 | ||
00dccaf1 KW |
37 | enum { |
38 | /* Maximum free pool size prevents holding too many freed coroutines */ | |
39 | POOL_MAX_SIZE = 64, | |
40 | }; | |
41 | ||
39a7a362 | 42 | /** Free list to speed up creation */ |
1bbbdabd | 43 | static QSLIST_HEAD(, Coroutine) pool = QSLIST_HEAD_INITIALIZER(pool); |
39a7a362 AK |
44 | static unsigned int pool_size; |
45 | ||
00dccaf1 KW |
46 | typedef struct { |
47 | Coroutine base; | |
48 | void *stack; | |
49 | jmp_buf env; | |
3f4349dc KW |
50 | |
51 | #ifdef CONFIG_VALGRIND_H | |
52 | unsigned int valgrind_stack_id; | |
53 | #endif | |
54 | ||
00dccaf1 KW |
55 | } CoroutineUContext; |
56 | ||
57 | /** | |
58 | * Per-thread coroutine bookkeeping | |
59 | */ | |
60 | typedef struct { | |
61 | /** Currently executing coroutine */ | |
62 | Coroutine *current; | |
63 | ||
00dccaf1 KW |
64 | /** The default coroutine */ |
65 | CoroutineUContext leader; | |
66 | } CoroutineThreadState; | |
67 | ||
68 | static pthread_key_t thread_state_key; | |
69 | ||
70 | /* | |
71 | * va_args to makecontext() must be type 'int', so passing | |
72 | * the pointer we need may require several int args. This | |
73 | * union is a quick hack to let us do that | |
74 | */ | |
75 | union cc_arg { | |
76 | void *p; | |
77 | int i[2]; | |
78 | }; | |
79 | ||
80 | static CoroutineThreadState *coroutine_get_thread_state(void) | |
81 | { | |
82 | CoroutineThreadState *s = pthread_getspecific(thread_state_key); | |
83 | ||
84 | if (!s) { | |
7267c094 | 85 | s = g_malloc0(sizeof(*s)); |
00dccaf1 | 86 | s->current = &s->leader.base; |
00dccaf1 KW |
87 | pthread_setspecific(thread_state_key, s); |
88 | } | |
89 | return s; | |
90 | } | |
91 | ||
92 | static void qemu_coroutine_thread_cleanup(void *opaque) | |
93 | { | |
94 | CoroutineThreadState *s = opaque; | |
39a7a362 AK |
95 | |
96 | g_free(s); | |
97 | } | |
98 | ||
99 | static void __attribute__((destructor)) coroutine_cleanup(void) | |
100 | { | |
00dccaf1 KW |
101 | Coroutine *co; |
102 | Coroutine *tmp; | |
103 | ||
1bbbdabd | 104 | QSLIST_FOREACH_SAFE(co, &pool, pool_next, tmp) { |
7267c094 AL |
105 | g_free(DO_UPCAST(CoroutineUContext, base, co)->stack); |
106 | g_free(co); | |
00dccaf1 | 107 | } |
00dccaf1 KW |
108 | } |
109 | ||
110 | static void __attribute__((constructor)) coroutine_init(void) | |
111 | { | |
112 | int ret; | |
113 | ||
114 | ret = pthread_key_create(&thread_state_key, qemu_coroutine_thread_cleanup); | |
115 | if (ret != 0) { | |
116 | fprintf(stderr, "unable to create leader key: %s\n", strerror(errno)); | |
117 | abort(); | |
118 | } | |
119 | } | |
120 | ||
121 | static void coroutine_trampoline(int i0, int i1) | |
122 | { | |
123 | union cc_arg arg; | |
124 | CoroutineUContext *self; | |
125 | Coroutine *co; | |
126 | ||
127 | arg.i[0] = i0; | |
128 | arg.i[1] = i1; | |
129 | self = arg.p; | |
130 | co = &self->base; | |
131 | ||
132 | /* Initialize longjmp environment and switch back the caller */ | |
133 | if (!setjmp(self->env)) { | |
134 | longjmp(*(jmp_buf *)co->entry_arg, 1); | |
135 | } | |
136 | ||
137 | while (true) { | |
138 | co->entry(co->entry_arg); | |
139 | qemu_coroutine_switch(co, co->caller, COROUTINE_TERMINATE); | |
140 | } | |
141 | } | |
142 | ||
143 | static Coroutine *coroutine_new(void) | |
144 | { | |
145 | const size_t stack_size = 1 << 20; | |
146 | CoroutineUContext *co; | |
147 | ucontext_t old_uc, uc; | |
148 | jmp_buf old_env; | |
32b74677 | 149 | union cc_arg arg = {0}; |
00dccaf1 KW |
150 | |
151 | /* The ucontext functions preserve signal masks which incurs a system call | |
152 | * overhead. setjmp()/longjmp() does not preserve signal masks but only | |
153 | * works on the current stack. Since we need a way to create and switch to | |
154 | * a new stack, use the ucontext functions for that but setjmp()/longjmp() | |
155 | * for everything else. | |
156 | */ | |
157 | ||
158 | if (getcontext(&uc) == -1) { | |
159 | abort(); | |
160 | } | |
161 | ||
7267c094 AL |
162 | co = g_malloc0(sizeof(*co)); |
163 | co->stack = g_malloc(stack_size); | |
00dccaf1 KW |
164 | co->base.entry_arg = &old_env; /* stash away our jmp_buf */ |
165 | ||
166 | uc.uc_link = &old_uc; | |
167 | uc.uc_stack.ss_sp = co->stack; | |
168 | uc.uc_stack.ss_size = stack_size; | |
169 | uc.uc_stack.ss_flags = 0; | |
170 | ||
3f4349dc KW |
171 | #ifdef CONFIG_VALGRIND_H |
172 | co->valgrind_stack_id = | |
173 | VALGRIND_STACK_REGISTER(co->stack, co->stack + stack_size); | |
174 | #endif | |
175 | ||
00dccaf1 KW |
176 | arg.p = co; |
177 | ||
178 | makecontext(&uc, (void (*)(void))coroutine_trampoline, | |
179 | 2, arg.i[0], arg.i[1]); | |
180 | ||
181 | /* swapcontext() in, longjmp() back out */ | |
182 | if (!setjmp(old_env)) { | |
183 | swapcontext(&old_uc, &uc); | |
184 | } | |
185 | return &co->base; | |
186 | } | |
187 | ||
188 | Coroutine *qemu_coroutine_new(void) | |
189 | { | |
00dccaf1 KW |
190 | Coroutine *co; |
191 | ||
1bbbdabd | 192 | co = QSLIST_FIRST(&pool); |
00dccaf1 | 193 | if (co) { |
1bbbdabd | 194 | QSLIST_REMOVE_HEAD(&pool, pool_next); |
39a7a362 | 195 | pool_size--; |
00dccaf1 KW |
196 | } else { |
197 | co = coroutine_new(); | |
198 | } | |
199 | return co; | |
200 | } | |
201 | ||
3f4349dc KW |
202 | #ifdef CONFIG_VALGRIND_H |
203 | /* Work around an unused variable in the valgrind.h macro... */ | |
204 | #pragma GCC diagnostic ignored "-Wunused-but-set-variable" | |
205 | static inline void valgrind_stack_deregister(CoroutineUContext *co) | |
206 | { | |
207 | VALGRIND_STACK_DEREGISTER(co->valgrind_stack_id); | |
208 | } | |
209 | #pragma GCC diagnostic error "-Wunused-but-set-variable" | |
210 | #endif | |
211 | ||
00dccaf1 KW |
212 | void qemu_coroutine_delete(Coroutine *co_) |
213 | { | |
00dccaf1 KW |
214 | CoroutineUContext *co = DO_UPCAST(CoroutineUContext, base, co_); |
215 | ||
39a7a362 | 216 | if (pool_size < POOL_MAX_SIZE) { |
1bbbdabd | 217 | QSLIST_INSERT_HEAD(&pool, &co->base, pool_next); |
00dccaf1 | 218 | co->base.caller = NULL; |
39a7a362 | 219 | pool_size++; |
00dccaf1 KW |
220 | return; |
221 | } | |
222 | ||
3f4349dc KW |
223 | #ifdef CONFIG_VALGRIND_H |
224 | valgrind_stack_deregister(co); | |
225 | #endif | |
226 | ||
7267c094 AL |
227 | g_free(co->stack); |
228 | g_free(co); | |
00dccaf1 KW |
229 | } |
230 | ||
231 | CoroutineAction qemu_coroutine_switch(Coroutine *from_, Coroutine *to_, | |
232 | CoroutineAction action) | |
233 | { | |
234 | CoroutineUContext *from = DO_UPCAST(CoroutineUContext, base, from_); | |
235 | CoroutineUContext *to = DO_UPCAST(CoroutineUContext, base, to_); | |
236 | CoroutineThreadState *s = coroutine_get_thread_state(); | |
237 | int ret; | |
238 | ||
239 | s->current = to_; | |
240 | ||
241 | ret = setjmp(from->env); | |
242 | if (ret == 0) { | |
243 | longjmp(to->env, action); | |
244 | } | |
245 | return ret; | |
246 | } | |
247 | ||
248 | Coroutine *qemu_coroutine_self(void) | |
249 | { | |
250 | CoroutineThreadState *s = coroutine_get_thread_state(); | |
251 | ||
252 | return s->current; | |
253 | } | |
254 | ||
255 | bool qemu_in_coroutine(void) | |
256 | { | |
257 | CoroutineThreadState *s = pthread_getspecific(thread_state_key); | |
258 | ||
259 | return s && s->current->caller; | |
260 | } |