rumpuser_pth.c revision 1.45 1 /* $NetBSD: rumpuser_pth.c,v 1.45 2015/09/18 10:56:25 pooka Exp $ */
2
3 /*
4 * Copyright (c) 2007-2010 Antti Kantee. All Rights Reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
16 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28 #include "rumpuser_port.h"
29
30 #if !defined(lint)
31 __RCSID("$NetBSD: rumpuser_pth.c,v 1.45 2015/09/18 10:56:25 pooka Exp $");
32 #endif /* !lint */
33
34 #include <sys/queue.h>
35
36 #if defined(HAVE_SYS_ATOMIC_H)
37 #include <sys/atomic.h>
38 #endif
39
40 #include <assert.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <pthread.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdint.h>
48 #include <unistd.h>
49
50 #include <rump/rumpuser.h>
51
52 #include "rumpuser_int.h"
53
54 int
55 rumpuser_thread_create(void *(*f)(void *), void *arg, const char *thrname,
56 int joinable, int priority, int cpuidx, void **ptcookie)
57 {
58 pthread_t ptid;
59 pthread_t *ptidp;
60 pthread_attr_t pattr;
61 int rv, i;
62
63 if ((rv = pthread_attr_init(&pattr)) != 0)
64 return rv;
65
66 if (joinable) {
67 NOFAIL(ptidp = malloc(sizeof(*ptidp)));
68 pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
69 } else {
70 ptidp = &ptid;
71 pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_DETACHED);
72 }
73
74 for (i = 0; i < 10; i++) {
75 const struct timespec ts = {0, 10*1000*1000};
76
77 rv = pthread_create(ptidp, &pattr, f, arg);
78 if (rv != EAGAIN)
79 break;
80 nanosleep(&ts, NULL);
81 }
82
83 #if defined(HAVE_PTHREAD_SETNAME3)
84 if (rv == 0 && thrname) {
85 pthread_setname_np(*ptidp, thrname, NULL);
86 }
87 #elif defined(HAVE_PTHREAD_SETNAME2)
88 if (rv == 0 && thrname) {
89 pthread_setname_np(*ptidp, thrname);
90 }
91 #endif
92
93 if (joinable) {
94 assert(ptcookie);
95 *ptcookie = ptidp;
96 }
97
98 pthread_attr_destroy(&pattr);
99
100 ET(rv);
101 }
102
103 __dead void
104 rumpuser_thread_exit(void)
105 {
106
107 /*
108 * FIXXXME: with glibc on ARM pthread_exit() aborts because
109 * it fails to unwind the stack. In the typical case, only
110 * the mountroothook thread will exit and even that's
111 * conditional on vfs being present.
112 */
113 #if (defined(__ARMEL__) || defined(__ARMEB__)) && defined(__GLIBC__)
114 for (;;)
115 pause();
116 #endif
117
118 pthread_exit(NULL);
119 }
120
121 int
122 rumpuser_thread_join(void *ptcookie)
123 {
124 pthread_t *pt = ptcookie;
125 int rv;
126
127 KLOCK_WRAP((rv = pthread_join(*pt, NULL)));
128 if (rv == 0)
129 free(pt);
130
131 ET(rv);
132 }
133
134 struct rumpuser_mtx {
135 pthread_mutex_t pthmtx;
136 struct lwp *owner;
137 int flags;
138 };
139
140 void
141 rumpuser_mutex_init(struct rumpuser_mtx **mtxp, int flags)
142 {
143 struct rumpuser_mtx *mtx;
144 pthread_mutexattr_t att;
145 size_t allocsz;
146
147 allocsz = (sizeof(*mtx)+RUMPUSER_LOCKALIGN) & ~(RUMPUSER_LOCKALIGN-1);
148 NOFAIL(mtx = aligned_alloc(RUMPUSER_LOCKALIGN, allocsz));
149
150 pthread_mutexattr_init(&att);
151 pthread_mutexattr_settype(&att, PTHREAD_MUTEX_ERRORCHECK);
152 NOFAIL_ERRNO(pthread_mutex_init(&mtx->pthmtx, &att));
153 pthread_mutexattr_destroy(&att);
154
155 mtx->owner = NULL;
156 assert(flags != 0);
157 mtx->flags = flags;
158
159 *mtxp = mtx;
160 }
161
162 static void
163 mtxenter(struct rumpuser_mtx *mtx)
164 {
165
166 if (!(mtx->flags & RUMPUSER_MTX_KMUTEX))
167 return;
168
169 assert(mtx->owner == NULL);
170 mtx->owner = rumpuser_curlwp();
171 }
172
173 static void
174 mtxexit(struct rumpuser_mtx *mtx)
175 {
176
177 if (!(mtx->flags & RUMPUSER_MTX_KMUTEX))
178 return;
179
180 assert(mtx->owner != NULL);
181 mtx->owner = NULL;
182 }
183
184 void
185 rumpuser_mutex_enter(struct rumpuser_mtx *mtx)
186 {
187
188 if (mtx->flags & RUMPUSER_MTX_SPIN) {
189 rumpuser_mutex_enter_nowrap(mtx);
190 return;
191 }
192
193 assert(mtx->flags & RUMPUSER_MTX_KMUTEX);
194 if (pthread_mutex_trylock(&mtx->pthmtx) != 0)
195 KLOCK_WRAP(NOFAIL_ERRNO(pthread_mutex_lock(&mtx->pthmtx)));
196 mtxenter(mtx);
197 }
198
199 void
200 rumpuser_mutex_enter_nowrap(struct rumpuser_mtx *mtx)
201 {
202
203 assert(mtx->flags & RUMPUSER_MTX_SPIN);
204 NOFAIL_ERRNO(pthread_mutex_lock(&mtx->pthmtx));
205 mtxenter(mtx);
206 }
207
208 int
209 rumpuser_mutex_tryenter(struct rumpuser_mtx *mtx)
210 {
211 int rv;
212
213 rv = pthread_mutex_trylock(&mtx->pthmtx);
214 if (rv == 0) {
215 mtxenter(mtx);
216 }
217
218 ET(rv);
219 }
220
221 void
222 rumpuser_mutex_exit(struct rumpuser_mtx *mtx)
223 {
224
225 mtxexit(mtx);
226 NOFAIL_ERRNO(pthread_mutex_unlock(&mtx->pthmtx));
227 }
228
229 void
230 rumpuser_mutex_destroy(struct rumpuser_mtx *mtx)
231 {
232
233 NOFAIL_ERRNO(pthread_mutex_destroy(&mtx->pthmtx));
234 free(mtx);
235 }
236
237 void
238 rumpuser_mutex_owner(struct rumpuser_mtx *mtx, struct lwp **lp)
239 {
240
241 if (__predict_false(!(mtx->flags & RUMPUSER_MTX_KMUTEX))) {
242 printf("panic: rumpuser_mutex_held unsupported on non-kmtx\n");
243 abort();
244 }
245
246 *lp = mtx->owner;
247 }
248
249 /*
250 * rwlocks. these are mostly simple, except that NetBSD wants to
251 * support something called downgrade, which means we need to swap
252 * our exclusive lock for a shared lock. to accommodate this,
253 * we need to check *after* acquiring a lock in case someone was
254 * downgrading it. if so, we couldn't actually have it and maybe
255 * need to retry later.
256 */
257
258 struct rumpuser_rw {
259 pthread_rwlock_t pthrw;
260 #if !defined(__APPLE__) && !defined(__ANDROID__)
261 char pad[64 - sizeof(pthread_rwlock_t)];
262 pthread_spinlock_t spin;
263 #endif
264 unsigned int readers;
265 struct lwp *writer;
266 int downgrade; /* someone is downgrading (hopefully lock holder ;) */
267 };
268
269 static int
270 rw_amwriter(struct rumpuser_rw *rw)
271 {
272
273 return rw->writer == rumpuser_curlwp() && rw->readers == (unsigned)-1;
274 }
275
276 static int
277 rw_nreaders(struct rumpuser_rw *rw)
278 {
279 unsigned nreaders = rw->readers;
280
281 return nreaders != (unsigned)-1 ? nreaders : 0;
282 }
283
284 static int
285 rw_setwriter(struct rumpuser_rw *rw, int retry)
286 {
287
288 /*
289 * Don't need the spinlock here, we already have an
290 * exclusive lock and "downgrade" is stable until complete.
291 */
292 if (rw->downgrade) {
293 pthread_rwlock_unlock(&rw->pthrw);
294 if (retry) {
295 struct timespec ts;
296
297 /* portable yield, essentially */
298 ts.tv_sec = 0;
299 ts.tv_nsec = 1;
300 KLOCK_WRAP(nanosleep(&ts, NULL));
301 }
302 return EBUSY;
303 }
304 assert(rw->readers == 0);
305 rw->writer = rumpuser_curlwp();
306 rw->readers = (unsigned)-1;
307 return 0;
308 }
309
310 static void
311 rw_clearwriter(struct rumpuser_rw *rw)
312 {
313
314 assert(rw_amwriter(rw));
315 rw->readers = 0;
316 rw->writer = NULL;
317 }
318
319 static inline void
320 rw_readup(struct rumpuser_rw *rw)
321 {
322
323 #if defined(__NetBSD__) || defined(__APPLE__) || defined(__ANDROID__)
324 atomic_inc_uint(&rw->readers);
325 #else
326 pthread_spin_lock(&rw->spin);
327 ++rw->readers;
328 pthread_spin_unlock(&rw->spin);
329 #endif
330 }
331
332 static inline void
333 rw_readdown(struct rumpuser_rw *rw)
334 {
335
336 #if defined(__NetBSD__) || defined(__APPLE__) || defined(__ANDROID__)
337 atomic_dec_uint(&rw->readers);
338 #else
339 pthread_spin_lock(&rw->spin);
340 assert(rw->readers > 0);
341 --rw->readers;
342 pthread_spin_unlock(&rw->spin);
343 #endif
344 }
345
346 void
347 rumpuser_rw_init(struct rumpuser_rw **rwp)
348 {
349 struct rumpuser_rw *rw;
350 size_t allocsz;
351
352 allocsz = (sizeof(*rw)+RUMPUSER_LOCKALIGN) & ~(RUMPUSER_LOCKALIGN-1);
353
354 NOFAIL(rw = aligned_alloc(RUMPUSER_LOCKALIGN, allocsz));
355 NOFAIL_ERRNO(pthread_rwlock_init(&rw->pthrw, NULL));
356 #if !defined(__APPLE__) && !defined(__ANDROID__)
357 NOFAIL_ERRNO(pthread_spin_init(&rw->spin, PTHREAD_PROCESS_PRIVATE));
358 #endif
359 rw->readers = 0;
360 rw->writer = NULL;
361 rw->downgrade = 0;
362
363 *rwp = rw;
364 }
365
366 void
367 rumpuser_rw_enter(int enum_rumprwlock, struct rumpuser_rw *rw)
368 {
369 enum rumprwlock lk = enum_rumprwlock;
370
371 switch (lk) {
372 case RUMPUSER_RW_WRITER:
373 do {
374 if (pthread_rwlock_trywrlock(&rw->pthrw) != 0)
375 KLOCK_WRAP(NOFAIL_ERRNO(
376 pthread_rwlock_wrlock(&rw->pthrw)));
377 } while (rw_setwriter(rw, 1) != 0);
378 break;
379 case RUMPUSER_RW_READER:
380 if (pthread_rwlock_tryrdlock(&rw->pthrw) != 0)
381 KLOCK_WRAP(NOFAIL_ERRNO(
382 pthread_rwlock_rdlock(&rw->pthrw)));
383 rw_readup(rw);
384 break;
385 }
386 }
387
388 int
389 rumpuser_rw_tryenter(int enum_rumprwlock, struct rumpuser_rw *rw)
390 {
391 enum rumprwlock lk = enum_rumprwlock;
392 int rv;
393
394 switch (lk) {
395 case RUMPUSER_RW_WRITER:
396 rv = pthread_rwlock_trywrlock(&rw->pthrw);
397 if (rv == 0)
398 rv = rw_setwriter(rw, 0);
399 break;
400 case RUMPUSER_RW_READER:
401 rv = pthread_rwlock_tryrdlock(&rw->pthrw);
402 if (rv == 0)
403 rw_readup(rw);
404 break;
405 default:
406 rv = EINVAL;
407 break;
408 }
409
410 ET(rv);
411 }
412
413 int
414 rumpuser_rw_tryupgrade(struct rumpuser_rw *rw)
415 {
416
417 /*
418 * Not supported by pthreads. Since the caller needs to
419 * back off anyway to avoid deadlock, always failing
420 * is correct.
421 */
422 ET(EBUSY);
423 }
424
425 /*
426 * convert from exclusive to shared lock without allowing anyone to
427 * obtain an exclusive lock in between. actually, might allow
428 * someone to obtain the lock, we just don't allow that thread to
429 * return from the hypercall with it.
430 */
431 void
432 rumpuser_rw_downgrade(struct rumpuser_rw *rw)
433 {
434
435 assert(rw->downgrade == 0);
436 rw->downgrade = 1;
437 rumpuser_rw_exit(rw);
438 /*
439 * though the competition can't get out of the hypervisor, it
440 * might have rescheduled itself after we released the lock.
441 * so need a wrap here.
442 */
443 KLOCK_WRAP(NOFAIL_ERRNO(pthread_rwlock_rdlock(&rw->pthrw)));
444 rw->downgrade = 0;
445 rw_readup(rw);
446 }
447
448 void
449 rumpuser_rw_exit(struct rumpuser_rw *rw)
450 {
451
452 if (rw_nreaders(rw))
453 rw_readdown(rw);
454 else
455 rw_clearwriter(rw);
456 NOFAIL_ERRNO(pthread_rwlock_unlock(&rw->pthrw));
457 }
458
459 void
460 rumpuser_rw_destroy(struct rumpuser_rw *rw)
461 {
462
463 NOFAIL_ERRNO(pthread_rwlock_destroy(&rw->pthrw));
464 #if !defined(__APPLE__) && ! defined(__ANDROID__)
465 NOFAIL_ERRNO(pthread_spin_destroy(&rw->spin));
466 #endif
467 free(rw);
468 }
469
470 void
471 rumpuser_rw_held(int enum_rumprwlock, struct rumpuser_rw *rw, int *rv)
472 {
473 enum rumprwlock lk = enum_rumprwlock;
474
475 switch (lk) {
476 case RUMPUSER_RW_WRITER:
477 *rv = rw_amwriter(rw);
478 break;
479 case RUMPUSER_RW_READER:
480 *rv = rw_nreaders(rw);
481 break;
482 }
483 }
484
485 /*
486 * condvar
487 */
488
489 struct rumpuser_cv {
490 pthread_cond_t pthcv;
491 int nwaiters;
492 };
493
494 void
495 rumpuser_cv_init(struct rumpuser_cv **cv)
496 {
497
498 NOFAIL(*cv = malloc(sizeof(struct rumpuser_cv)));
499 NOFAIL_ERRNO(pthread_cond_init(&((*cv)->pthcv), NULL));
500 (*cv)->nwaiters = 0;
501 }
502
503 void
504 rumpuser_cv_destroy(struct rumpuser_cv *cv)
505 {
506
507 NOFAIL_ERRNO(pthread_cond_destroy(&cv->pthcv));
508 free(cv);
509 }
510
511 static void
512 cv_unschedule(struct rumpuser_mtx *mtx, int *nlocks)
513 {
514
515 rumpkern_unsched(nlocks, mtx);
516 mtxexit(mtx);
517 }
518
519 static void
520 cv_reschedule(struct rumpuser_mtx *mtx, int nlocks)
521 {
522
523 /*
524 * If the cv interlock is a spin mutex, we must first release
525 * the mutex that was reacquired by pthread_cond_wait(),
526 * acquire the CPU context and only then relock the mutex.
527 * This is to preserve resource allocation order so that
528 * we don't deadlock. Non-spinning mutexes don't have this
529 * problem since they don't use a hold-and-wait approach
530 * to acquiring the mutex wrt the rump kernel CPU context.
531 *
532 * The more optimal solution would be to rework rumpkern_sched()
533 * so that it's possible to tell the scheduler
534 * "if you need to block, drop this lock first", but I'm not
535 * going poking there without some numbers on how often this
536 * path is taken for spin mutexes.
537 */
538 if ((mtx->flags & (RUMPUSER_MTX_SPIN | RUMPUSER_MTX_KMUTEX)) ==
539 (RUMPUSER_MTX_SPIN | RUMPUSER_MTX_KMUTEX)) {
540 NOFAIL_ERRNO(pthread_mutex_unlock(&mtx->pthmtx));
541 rumpkern_sched(nlocks, mtx);
542 rumpuser_mutex_enter_nowrap(mtx);
543 } else {
544 mtxenter(mtx);
545 rumpkern_sched(nlocks, mtx);
546 }
547 }
548
549 void
550 rumpuser_cv_wait(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx)
551 {
552 int nlocks;
553
554 cv->nwaiters++;
555 cv_unschedule(mtx, &nlocks);
556 NOFAIL_ERRNO(pthread_cond_wait(&cv->pthcv, &mtx->pthmtx));
557 cv_reschedule(mtx, nlocks);
558 cv->nwaiters--;
559 }
560
561 void
562 rumpuser_cv_wait_nowrap(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx)
563 {
564
565 cv->nwaiters++;
566 mtxexit(mtx);
567 NOFAIL_ERRNO(pthread_cond_wait(&cv->pthcv, &mtx->pthmtx));
568 mtxenter(mtx);
569 cv->nwaiters--;
570 }
571
572 int
573 rumpuser_cv_timedwait(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx,
574 int64_t sec, int64_t nsec)
575 {
576 struct timespec ts;
577 int rv, nlocks;
578
579 /*
580 * Get clock already here, just in case we will be put to sleep
581 * after releasing the kernel context.
582 *
583 * The condition variables should use CLOCK_MONOTONIC, but since
584 * that's not available everywhere, leave it for another day.
585 */
586 clock_gettime(CLOCK_REALTIME, &ts);
587
588 cv->nwaiters++;
589 cv_unschedule(mtx, &nlocks);
590
591 ts.tv_sec += sec;
592 ts.tv_nsec += nsec;
593 if (ts.tv_nsec >= 1000*1000*1000) {
594 ts.tv_sec++;
595 ts.tv_nsec -= 1000*1000*1000;
596 }
597 rv = pthread_cond_timedwait(&cv->pthcv, &mtx->pthmtx, &ts);
598
599 cv_reschedule(mtx, nlocks);
600 cv->nwaiters--;
601
602 ET(rv);
603 }
604
605 void
606 rumpuser_cv_signal(struct rumpuser_cv *cv)
607 {
608
609 NOFAIL_ERRNO(pthread_cond_signal(&cv->pthcv));
610 }
611
612 void
613 rumpuser_cv_broadcast(struct rumpuser_cv *cv)
614 {
615
616 NOFAIL_ERRNO(pthread_cond_broadcast(&cv->pthcv));
617 }
618
619 void
620 rumpuser_cv_has_waiters(struct rumpuser_cv *cv, int *nwaiters)
621 {
622
623 *nwaiters = cv->nwaiters;
624 }
625
626 /*
627 * curlwp
628 */
629
630 static pthread_key_t curlwpkey;
631
632 /*
633 * the if0'd curlwp implementation is not used by this hypervisor,
634 * but serves as test code to check that the intended usage works.
635 */
636 #if 0
637 struct rumpuser_lwp {
638 struct lwp *l;
639 LIST_ENTRY(rumpuser_lwp) l_entries;
640 };
641 static LIST_HEAD(, rumpuser_lwp) lwps = LIST_HEAD_INITIALIZER(lwps);
642 static pthread_mutex_t lwplock = PTHREAD_MUTEX_INITIALIZER;
643
644 void
645 rumpuser_curlwpop(enum rumplwpop op, struct lwp *l)
646 {
647 struct rumpuser_lwp *rl, *rliter;
648
649 switch (op) {
650 case RUMPUSER_LWP_CREATE:
651 rl = malloc(sizeof(*rl));
652 rl->l = l;
653 pthread_mutex_lock(&lwplock);
654 LIST_FOREACH(rliter, &lwps, l_entries) {
655 if (rliter->l == l) {
656 fprintf(stderr, "LWP_CREATE: %p exists\n", l);
657 abort();
658 }
659 }
660 LIST_INSERT_HEAD(&lwps, rl, l_entries);
661 pthread_mutex_unlock(&lwplock);
662 break;
663 case RUMPUSER_LWP_DESTROY:
664 pthread_mutex_lock(&lwplock);
665 LIST_FOREACH(rl, &lwps, l_entries) {
666 if (rl->l == l)
667 break;
668 }
669 if (!rl) {
670 fprintf(stderr, "LWP_DESTROY: %p does not exist\n", l);
671 abort();
672 }
673 LIST_REMOVE(rl, l_entries);
674 pthread_mutex_unlock(&lwplock);
675 free(rl);
676 break;
677 case RUMPUSER_LWP_SET:
678 assert(pthread_getspecific(curlwpkey) == NULL && l != NULL);
679
680 pthread_mutex_lock(&lwplock);
681 LIST_FOREACH(rl, &lwps, l_entries) {
682 if (rl->l == l)
683 break;
684 }
685 if (!rl) {
686 fprintf(stderr,
687 "LWP_SET: %p does not exist\n", l);
688 abort();
689 }
690 pthread_mutex_unlock(&lwplock);
691
692 pthread_setspecific(curlwpkey, rl);
693 break;
694 case RUMPUSER_LWP_CLEAR:
695 assert(((struct rumpuser_lwp *)
696 pthread_getspecific(curlwpkey))->l == l);
697 pthread_setspecific(curlwpkey, NULL);
698 break;
699 }
700 }
701
702 struct lwp *
703 rumpuser_curlwp(void)
704 {
705 struct rumpuser_lwp *rl;
706
707 rl = pthread_getspecific(curlwpkey);
708 return rl ? rl->l : NULL;
709 }
710
711 #else
712
713 void
714 rumpuser_curlwpop(int enum_rumplwpop, struct lwp *l)
715 {
716 enum rumplwpop op = enum_rumplwpop;
717
718 switch (op) {
719 case RUMPUSER_LWP_CREATE:
720 break;
721 case RUMPUSER_LWP_DESTROY:
722 break;
723 case RUMPUSER_LWP_SET:
724 assert(pthread_getspecific(curlwpkey) == NULL);
725 pthread_setspecific(curlwpkey, l);
726 break;
727 case RUMPUSER_LWP_CLEAR:
728 assert(pthread_getspecific(curlwpkey) == l);
729 pthread_setspecific(curlwpkey, NULL);
730 break;
731 }
732 }
733
734 struct lwp *
735 rumpuser_curlwp(void)
736 {
737
738 return pthread_getspecific(curlwpkey);
739 }
740 #endif
741
742
743 void
744 rumpuser__thrinit(void)
745 {
746 pthread_key_create(&curlwpkey, NULL);
747 }
748