xref: /minix/minix/lib/libmthread/allocate.c (revision 0a6a1f1d)
1 #define ALLOCATE
2 #include <errno.h>
3 #include <minix/mthread.h>
4 #include <string.h>
5 
6 #include <machine/param.h>
7 #include <machine/vmparam.h>
8 
9 #include <sys/mman.h>
10 
11 #include <uvm/uvm_param.h>
12 
13 #include "global.h"
14 #include "proto.h"
15 
16 static int mthread_increase_thread_pool(void);
17 static void mthread_thread_init(mthread_thread_t thread, mthread_attr_t
18 	*tattr, void *(*proc)(void *), void *arg);
19 
20 static void mthread_thread_stop(mthread_thread_t thread);
21 static void mthread_trampoline(void);
22 
23 static int initialized = 0;
24 #define MTHREAD_GUARDSIZE 	(1 << PGSHIFT) 	/* 1 page */
25 
26 static struct __mthread_attr default_attr = {	MTHREAD_STACK_MIN,
27 						NULL,
28 						MTHREAD_CREATE_JOINABLE,
29 						NULL, NULL };
30 
31 /*===========================================================================*
32  *				mthread_equal				     *
33  *===========================================================================*/
34 int mthread_equal(l, r)
35 mthread_thread_t l;
36 mthread_thread_t r;
37 {
38 /* Compare two thread ids */
39 
40   return(l == r);
41 }
42 
43 
44 /*===========================================================================*
45  *				mthread_create				     *
46  *===========================================================================*/
47 int mthread_create(threadid, tattr, proc, arg)
48 mthread_thread_t *threadid;
49 mthread_attr_t *tattr;
50 void *(*proc)(void *);
51 void *arg;
52 {
53 /* Register procedure proc for execution in a thread. */
54   mthread_thread_t thread;
55 
56   if (proc == NULL)
57 	return(EINVAL);
58 
59   if (!mthread_queue_isempty(&free_threads)) {
60   	thread = mthread_queue_remove(&free_threads);
61   	mthread_thread_init(thread, tattr, proc, arg);
62  	used_threads++;
63  	if(threadid != NULL)
64  		*threadid = (mthread_thread_t) thread;
65 #ifdef MDEBUG
66  	printf("Inited thread %d\n", thread);
67 #endif
68  	return(0);
69   } else  {
70   	if (mthread_increase_thread_pool() == -1)
71   		return(EAGAIN);
72 
73   	return mthread_create(threadid, tattr, proc, arg);
74   }
75 }
76 
77 
78 /*===========================================================================*
79  *				mthread_detach				     *
80  *===========================================================================*/
81 int mthread_detach(detach)
82 mthread_thread_t detach;
83 {
84 /* Mark a thread as detached. Consequently, upon exit, resources allocated for
85  * this thread are automatically freed.
86  */
87   mthread_tcb_t *tcb;
88 
89   if (!isokthreadid(detach))
90   	return(ESRCH);
91 
92   tcb = mthread_find_tcb(detach);
93   if (tcb->m_state == MS_DEAD) {
94   	return(ESRCH);
95   } else if (tcb->m_attr.ma_detachstate != MTHREAD_CREATE_DETACHED) {
96   	if (tcb->m_state == MS_EXITING)
97   		mthread_thread_stop(detach);
98   	else
99 		tcb->m_attr.ma_detachstate = MTHREAD_CREATE_DETACHED;
100   }
101 
102   return(0);
103 }
104 
105 
106 /*===========================================================================*
107  *				mthread_exit				     *
108  *===========================================================================*/
109 void mthread_exit(value)
110 void *value;
111 {
112 /* Make a thread stop running and store the result value. */
113   mthread_tcb_t *tcb;
114 
115   tcb = mthread_find_tcb(current_thread);
116 
117   if (tcb->m_state == MS_EXITING)	/* Already stopping, nothing to do. */
118   	return;
119 
120   mthread_cleanup_values();
121 
122   tcb->m_result = value;
123   tcb->m_state = MS_EXITING;
124 
125   if (tcb->m_attr.ma_detachstate == MTHREAD_CREATE_DETACHED) {
126 	mthread_thread_stop(current_thread);
127   } else {
128   	/* Joinable thread; notify possibly waiting thread */
129 	if (mthread_cond_signal(&(tcb->m_exited)) != 0)
130 		mthread_panic("Couldn't signal exit");
131 
132 	/* The thread that's actually doing the join will eventually clean
133 	 * up this thread (i.e., call mthread_thread_stop).
134 	 */
135   }
136 
137   mthread_schedule();
138 }
139 
140 /*===========================================================================*
141  *			mthread_find_tcb				     *
142  *===========================================================================*/
143 mthread_tcb_t * mthread_find_tcb(thread)
144 mthread_thread_t thread;
145 {
146   mthread_tcb_t *rt = NULL;
147 
148   if (!isokthreadid(thread)) mthread_panic("Invalid thread id");
149 
150   if (thread == MAIN_THREAD)
151   	rt = &mainthread;
152   else
153   	rt = threads[thread];
154 
155   return(rt);
156 }
157 
158 
159 /*===========================================================================*
160  *			mthread_increase_thread_pool			     *
161  *===========================================================================*/
162 static int mthread_increase_thread_pool(void)
163 {
164 /* Increase thread pool. No fancy algorithms, just double the size. */
165   mthread_tcb_t **new_tcb;
166   int new_no_threads, old_no_threads, i;
167 
168   old_no_threads = no_threads;
169 
170   if (old_no_threads == 0)
171   	new_no_threads = NO_THREADS;
172   else
173 	new_no_threads = 2 * old_no_threads;
174 
175 
176   if (new_no_threads >= MAX_THREAD_POOL) {
177   	mthread_debug("Reached max number of threads");
178   	return(-1);
179   }
180 
181   /* Allocate space to store pointers to thread control blocks */
182   if (old_no_threads == 0)	/* No data yet: allocate space */
183   	new_tcb = calloc(new_no_threads, sizeof(mthread_tcb_t *));
184   else				/* Preserve existing data: reallocate space */
185 	new_tcb = realloc(threads, new_no_threads * sizeof(mthread_tcb_t *));
186 
187   if (new_tcb == NULL) {
188   	mthread_debug("Can't increase thread pool");
189   	return(-1);
190   }
191 
192   /* Allocate space for thread control blocks itself */
193   for (i = old_no_threads; i < new_no_threads; i++) {
194   	new_tcb[i] = malloc(sizeof(mthread_tcb_t));
195   	if (new_tcb[i] == NULL) {
196   		mthread_debug("Can't allocate space for tcb");
197   		return(-1);
198   	}
199   	memset(new_tcb[i], '\0', sizeof(mthread_tcb_t)); /* Clear entry */
200   }
201 
202   /* We can breath again, let's tell the others about the good news */
203   threads = new_tcb;
204   no_threads = new_no_threads;
205 
206   /* Add newly available threads to free_threads */
207   for (i = old_no_threads; i < new_no_threads; i++) {
208 	mthread_queue_add(&free_threads, i);
209 	mthread_thread_reset(i);
210   }
211 
212 #ifdef MDEBUG
213   printf("Increased thread pool from %d to %d threads\n", old_no_threads,
214   	 new_no_threads);
215 #endif
216   return(0);
217 }
218 
219 
220 /*===========================================================================*
221  *				mthread_init				     *
222  *===========================================================================*/
223 static void __attribute__((__constructor__, __used__)) mthread_init(void)
224 {
225 /* Initialize thread system; allocate thread structures and start creating
226  * threads.
227  */
228 
229   if (initialized) return;
230 
231   no_threads = 0;
232   used_threads = 0;
233   need_reset = 0;
234   running_main_thread = 1;	/* mthread_init can only be called from the
235 				 * main thread. Calling it from a thread will
236 				 * not enter this clause.
237 				 */
238 
239   if (mthread_getcontext(&(mainthread.m_context)) == -1)
240 	mthread_panic("Couldn't save state for main thread");
241   current_thread = MAIN_THREAD;
242 
243   mthread_init_valid_mutexes();
244   mthread_init_valid_conditions();
245   mthread_init_valid_attributes();
246   mthread_init_keys();
247   mthread_init_scheduler();
248 
249   initialized = 1;
250 }
251 
252 
253 /*===========================================================================*
254  *				mthread_join				     *
255  *===========================================================================*/
256 int mthread_join(join, value)
257 mthread_thread_t join;
258 void **value;
259 {
260 /* Wait for a thread to stop running and copy the result. */
261 
262   mthread_tcb_t *tcb;
263 
264   if (!isokthreadid(join))
265   	return(ESRCH);
266   else if (join == current_thread)
267 	return(EDEADLK);
268 
269   tcb = mthread_find_tcb(join);
270   if (tcb->m_state == MS_DEAD)
271   	return(ESRCH);
272   else if (tcb->m_attr.ma_detachstate == MTHREAD_CREATE_DETACHED)
273 	return(EINVAL);
274 
275   /* When the thread hasn't exited yet, we have to wait for that to happen */
276   if (tcb->m_state != MS_EXITING) {
277   	mthread_cond_t *c;
278   	mthread_mutex_t *m;
279 
280   	c = &(tcb->m_exited);
281   	m = &(tcb->m_exitm);
282 
283   	if (mthread_mutex_init(m, NULL) != 0)
284 		mthread_panic("Couldn't initialize mutex to join\n");
285 
286 	if (mthread_mutex_lock(m) != 0)
287 		mthread_panic("Couldn't lock mutex to join\n");
288 
289 	if (mthread_cond_wait(c, m) != 0)
290 		mthread_panic("Couldn't wait for join condition\n");
291 
292 	if (mthread_mutex_unlock(m) != 0)
293 		mthread_panic("Couldn't unlock mutex to join\n");
294 
295 	if (mthread_mutex_destroy(m) != 0)
296 		mthread_panic("Couldn't destroy mutex to join\n");
297   }
298 
299   /* Thread has exited; copy results */
300   if(value != NULL)
301 	*value = tcb->m_result;
302 
303   /* Deallocate resources */
304   mthread_thread_stop(join);
305   return(0);
306 }
307 
308 
309 /*===========================================================================*
310  *				mthread_once				     *
311  *===========================================================================*/
312 int mthread_once(once, proc)
313 mthread_once_t *once;
314 void (*proc)(void);
315 {
316 /* Run procedure proc just once */
317 
318   if (once == NULL || proc == NULL)
319   	return(EINVAL);
320 
321   if (*once != 1) proc();
322   *once = 1;
323   return(0);
324 }
325 
326 
327 /*===========================================================================*
328  *				mthread_self				     *
329  *===========================================================================*/
330 mthread_thread_t mthread_self(void)
331 {
332 /* Return the thread id of the thread calling this function. */
333 
334   return(current_thread);
335 }
336 
337 
338 /*===========================================================================*
339  *				mthread_thread_init			     *
340  *===========================================================================*/
341 static void mthread_thread_init(thread, tattr, proc, arg)
342 mthread_thread_t thread;
343 mthread_attr_t *tattr;
344 void *(*proc)(void *);
345 void *arg;
346 {
347 /* Initialize a thread so that it, when unsuspended, will run the given
348  * procedure with the given parameter. The thread is marked as runnable.
349  */
350 
351 #define THIS_CTX (&(threads[thread]->m_context))
352   mthread_tcb_t *tcb;
353   size_t stacksize;
354   char *stackaddr;
355 
356   tcb = mthread_find_tcb(thread);
357   tcb->m_next = NULL;
358   tcb->m_state = MS_DEAD;
359   tcb->m_proc = proc;
360   tcb->m_arg = arg;
361   /* Threads use a copy of the provided attributes. This way, if another
362    * thread modifies the attributes (such as detach state), already running
363    * threads are not affected.
364    */
365   if (tattr != NULL)
366   	tcb->m_attr = *((struct __mthread_attr *) *tattr);
367   else {
368   	tcb->m_attr = default_attr;
369   }
370 
371   if (mthread_cond_init(&(tcb->m_exited), NULL) != 0)
372   	mthread_panic("Could not initialize thread");
373 
374   tcb->m_context.uc_link = NULL;
375 
376   /* Construct this thread's context to run procedure proc. */
377   if (mthread_getcontext(&(tcb->m_context)) == -1)
378   	mthread_panic("Failed to initialize context state");
379 
380   stacksize = tcb->m_attr.ma_stacksize;
381   stackaddr = tcb->m_attr.ma_stackaddr;
382 
383   if (stacksize == (size_t) 0) {
384 	/* User provided too small a stack size. Forget about that stack and
385 	 * allocate a new one ourselves.
386 	 */
387 	stacksize = (size_t) MTHREAD_STACK_MIN;
388 	tcb->m_attr.ma_stackaddr = stackaddr = NULL;
389   }
390 
391   if (stackaddr == NULL) {
392 	/* Allocate stack space */
393 	size_t guarded_stacksize;
394 	char *guard_start, *guard_end;
395 
396 	stacksize = round_page(stacksize + MTHREAD_GUARDSIZE);
397 	stackaddr = mmap(NULL, stacksize,
398 			       PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE,
399 			       -1, 0);
400 	if (stackaddr == MAP_FAILED)
401   		mthread_panic("Failed to allocate stack to thread");
402 
403 #if defined(__i386__) || defined(__arm__)
404 	guard_start = stackaddr;
405 	guard_end = stackaddr + MTHREAD_GUARDSIZE;
406 	guarded_stacksize = stackaddr + stacksize - guard_end;
407 
408 	/* The stack will be used from (stackaddr+stacksize) to stackaddr. That
409 	 * is, growing downwards. So the "top" of the stack may not grow into
410 	 * stackaddr+MTHREAD_GUARDSIZE.
411 	 *
412 	 * +-------+ stackaddr + stacksize
413 	 * |       |
414 	 * |   |   |
415 	 * |  \|/  |
416 	 * |       |
417 	 * +-------+ stackaddr + MTHREAD_GUARDSIZE
418 	 * | GUARD |
419 	 * +-------+ stackaddr
420 	 */
421 #else
422 # error "Unsupported platform"
423 #endif
424 	stacksize = guarded_stacksize;
425 	/* Mere unmapping could allow a later allocation to fill the gap. */
426         if (mmap(guard_start, MTHREAD_GUARDSIZE, PROT_NONE,
427 		MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0) != guard_start)
428 		mthread_panic("unable to overmap stack space for guard");
429 	tcb->m_context.uc_stack.ss_sp = guard_end;
430   } else
431   	tcb->m_context.uc_stack.ss_sp = stackaddr;
432 
433   tcb->m_context.uc_stack.ss_size = stacksize;
434   makecontext(&(tcb->m_context), mthread_trampoline, 0);
435 
436   mthread_unsuspend(thread); /* Make thread runnable */
437 }
438 
439 
440 /*===========================================================================*
441  *				mthread_thread_reset			     *
442  *===========================================================================*/
443 void mthread_thread_reset(thread)
444 mthread_thread_t thread;
445 {
446 /* Reset the thread to its default values. Free the allocated stack space. */
447 
448   mthread_tcb_t *rt;
449   size_t stacksize;
450   char *stackaddr;
451 
452   if (!isokthreadid(thread)) mthread_panic("Invalid thread id");
453 
454   rt = mthread_find_tcb(thread);
455   rt->m_tid = thread;
456   rt->m_next = NULL;
457   rt->m_state = MS_DEAD;
458   rt->m_proc = NULL;
459   rt->m_arg = NULL;
460   rt->m_result = NULL;
461   rt->m_cond = NULL;
462   if (rt->m_attr.ma_stackaddr == NULL) { /* We allocated stack space */
463 	if (rt->m_context.uc_stack.ss_sp) {
464 		stacksize = rt->m_context.uc_stack.ss_size;
465 		stackaddr = rt->m_context.uc_stack.ss_sp;
466 #if defined(__i386__) || defined(__arm__)
467 		stacksize += MTHREAD_GUARDSIZE;
468 		stackaddr -= MTHREAD_GUARDSIZE;
469 #else
470 # error "Unsupported platform"
471 #endif
472 		if (munmap(stackaddr, stacksize) != 0) {
473 			mthread_panic("unable to unmap memory");
474 		}
475 	}
476 	rt->m_context.uc_stack.ss_sp = NULL;
477   }
478   rt->m_context.uc_stack.ss_size = 0;
479   rt->m_context.uc_link = NULL;
480 }
481 
482 
483 /*===========================================================================*
484  *				mthread_thread_stop			     *
485  *===========================================================================*/
486 static void mthread_thread_stop(thread)
487 mthread_thread_t thread;
488 {
489 /* Stop thread from running. Deallocate resources. */
490   mthread_tcb_t *stop_thread;
491 
492   if (!isokthreadid(thread)) mthread_panic("Invalid thread id");
493 
494   stop_thread = mthread_find_tcb(thread);
495 
496   if (stop_thread->m_state == MS_DEAD) {
497   	/* Already dead, nothing to do */
498   	return;
499   }
500 
501   if (mthread_cond_destroy(&(stop_thread->m_exited)) != 0)
502   	mthread_panic("Could not destroy condition at thread deallocation\n");
503 
504   /* Can't deallocate ourselves (i.e., we're a detached thread) */
505   if (thread == current_thread) {
506 	stop_thread->m_state = MS_NEEDRESET;
507 	need_reset++;
508   } else {
509 	mthread_thread_reset(thread);
510 	used_threads--;
511 	mthread_queue_add(&free_threads, thread);
512   }
513 }
514 
515 
516 /*===========================================================================*
517  *				mthread_trampoline			     *
518  *===========================================================================*/
519 static void mthread_trampoline(void)
520 {
521 /* Execute the /current_thread's/ procedure. Store its result. */
522 
523   mthread_tcb_t *tcb;
524   void *r;
525 
526   tcb = mthread_find_tcb(current_thread);
527 
528   r = (tcb->m_proc)(tcb->m_arg);
529   mthread_exit(r);
530 }
531 
532 /* pthread compatibility layer. */
533 __weak_alias(pthread_create, mthread_create)
534 __weak_alias(pthread_detach, mthread_detach)
535 __weak_alias(pthread_equal, mthread_equal)
536 __weak_alias(pthread_exit, mthread_exit)
537 __weak_alias(pthread_join, mthread_join)
538 __weak_alias(pthread_once, mthread_once)
539 __weak_alias(pthread_self, mthread_self)
540 
541