summaryrefslogtreecommitdiff
path: root/lib/libc/thread/thrd_create.c
blob: fa7ada7824055105c7a274a5bf74b6edd8f3b173 (plain)
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
#include "stddef.h"
#include "sys/cdefs.h"
#include <__thread.h>
#include <libc.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <syscall.h>
#include <threads.h>

struct thread_start {
	struct __thread_self *self;
	thrd_start_t func;
	void *arg;
};

static long __thread_start(void *arg)
{
	struct thread_start *ts = (struct thread_start *)arg;
	syscall(set_tid_address, &ts->self->tid);
	int r = ts->func(ts->arg);
	thrd_exit(r);
	__builtin_unreachable();
}

int thrd_create(thrd_t *thr, thrd_start_t func, void *arg)
{
	struct thread_start *ts;

	if ((void *)thr == NULL || func == NULL)
		return thrd_error;

	/* Ensure callers never observe an uninitialized thread handle on failure. */
	*thr = NULL;

	void *map;
	size_t size = THREAD_GUARD_SIZE + THREAD_STACK_SIZE + __libc.tls.size + sizeof(struct __thread_self);
	map = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

	if (__predict_false(map == MAP_FAILED))
		return thrd_nomem;

	if (__predict_false(mprotect((unsigned char *)map + THREAD_GUARD_SIZE, size - THREAD_GUARD_SIZE,
				     PROT_READ | PROT_WRITE) < 0)) {
		munmap(map, size);
		return thrd_error;
	}

	unsigned char *base = (unsigned char *)map;

	/* Place TCB at the very end of the mapping. */
	struct __thread_self *tcb = (struct __thread_self *)(base + size - sizeof(struct __thread_self));

	/* Place TLS immediately below the TCB, aligned down. */
	unsigned char *tls_end = (unsigned char *)tcb;

	size_t tls_align = __libc.tls.align;
	if (tls_align == 0 || (tls_align & (tls_align - 1)) != 0)
		tls_align = sizeof(void *);
	if (tls_align < sizeof(void *))
		tls_align = sizeof(void *);

	unsigned char *tls_start =
		(unsigned char *)(((uintptr_t)(tls_end - __libc.tls.size)) & ~((uintptr_t)tls_align - 1));

	__libc_tls_copy(tls_start);

	/* Build initial stack just below TLS. */
	unsigned char *stack_top = tls_start;
	ts = (struct thread_start *)(stack_top - sizeof(struct thread_start));
	stack_top -= sizeof(struct thread_start);

	ts->self = tcb;
	ts->func = func;
	ts->arg = arg;

	stack_top = (unsigned char *)((uintptr_t)stack_top & ~15UL);

	tcb->self = tcb;
	tcb->map_base = map;
	tcb->map_size = size;
	tcb->errno_v = 0;
	tcb->dtv = 0;
	tcb->state = THREAD_STATE_JOINABLE;

	unsigned long flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
			      CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID;

#if defined(__x86_64__)
	long tid = __clone(__thread_start, ts, flags, stack_top, &tcb->tid, tcb, &tcb->tid);
#endif

	if (__predict_false(tid < 0)) {
		munmap(map, size);
		return thrd_error;
	}

#if !defined(__x86_64__)
	if (tid == 0) {
		__thread_start(ts);
		__builtin_unreachable();
	}
#endif

	tcb->tid = tid;

	*thr = tcb;

	return thrd_success;
}