source: trunk/third/gstreamer/gst/cothreads.c @ 21448

Revision 21448, 17.3 KB checked in by ghudson, 20 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r21447, which included commits to RCS files with non-trunk default branches.
Line 
1/* GStreamer
2 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3 *                    2000 Wim Taymans <wtay@chello.be>
4 *
5 * cothreads.c: Cothreading routines
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 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 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 */
22
23
24#include "gst_private.h"
25#include <glib.h>
26
27#include <sys/time.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <signal.h>
31#include <setjmp.h>
32#include <unistd.h>
33#include <errno.h>
34#include <sys/mman.h>
35
36#include "cothreads.h"
37#include "gstarch.h"
38#include "gstinfo.h"
39#include "gstutils.h"
40
41#ifdef HAVE_UCONTEXT_H
42#include <ucontext.h>
43#endif
44
45#ifndef MAP_ANONYMOUS
46#ifdef MAP_ANON
47/* older glibc's have MAP_ANON instead of MAP_ANONYMOUS */
48#define MAP_ANONYMOUS MAP_ANON
49#else
50/* make due without.  If this fails, we need to open and map /dev/zero */
51#define MAP_ANONYMOUS 0
52#endif
53#endif
54
55#define STACK_SIZE 0x200000
56
57#define COTHREAD_MAGIC_NUMBER 0xabcdef
58
59#define COTHREAD_MAXTHREADS 16
60#define COTHREAD_STACKSIZE (STACK_SIZE/COTHREAD_MAXTHREADS)
61
62static void cothread_destroy (cothread_state * cothread);
63
64struct _cothread_context
65{
66  cothread_state *cothreads[COTHREAD_MAXTHREADS];       /* array of cothread states */
67  int ncothreads;
68  int current;
69  unsigned long stack_top;
70  GHashTable *data;
71  GThread *thread;
72};
73
74/* Disabling this define allows you to shut off a few checks in
75 * cothread_switch.  This likely will speed things up fractionally */
76#define COTHREAD_PARANOID
77
78
79/* this _cothread_ctx_key is used as a GThread key to the thread's context
80 * a GThread key is a "pointer" to memory space that is/can be different
81 * (ie. private) for each thread.  The key itself is shared among threads,
82 * so it only needs to be initialized once.
83 */
84static GStaticPrivate _cothread_ctx_key = G_STATIC_PRIVATE_INIT;
85
86/*
87 * This should only after context init, since we do checking.
88 */
89static cothread_context *
90cothread_get_current_context (void)
91{
92  cothread_context *ctx;
93
94  ctx = g_static_private_get (&_cothread_ctx_key);
95  g_assert (ctx);
96
97#ifdef COTHREAD_PARANOID
98  g_assert (ctx->thread == g_thread_self ());
99#endif
100
101  return ctx;
102}
103
104/**
105 * cothread_context_init:
106 *
107 * Create and initialize a new cothread context
108 *
109 * Returns: the new cothread context
110 */
111cothread_context *
112cothread_context_init (void)
113{
114  char __csf;
115  void *current_stack_frame = &__csf;   /* Get pointer inside current stack frame */
116  cothread_context *ctx;
117
118  /* if there already is a cotread context for this thread,
119   * just return it */
120  ctx = g_static_private_get (&_cothread_ctx_key);
121  if (ctx) {
122    GST_CAT_INFO (GST_CAT_COTHREADS,
123        "returning private _cothread_ctx_key %p", ctx);
124    return ctx;
125  }
126
127  /*
128   * initalize the whole of the cothreads context
129   */
130  ctx = (cothread_context *) g_malloc (sizeof (cothread_context));
131
132  /* we consider the initiating process to be cothread 0 */
133  ctx->ncothreads = 1;
134  ctx->current = 0;
135  ctx->data = g_hash_table_new (g_str_hash, g_str_equal);
136  ctx->thread = g_thread_self ();
137
138  GST_CAT_INFO (GST_CAT_COTHREADS, "initializing cothreads");
139
140  /* set this thread's context pointer */
141  GST_CAT_INFO (GST_CAT_COTHREADS,
142      "setting private _cothread_ctx_key to %p in thread %p", ctx,
143      g_thread_self ());
144  g_static_private_set (&_cothread_ctx_key, ctx, NULL);
145
146  g_assert (ctx == cothread_get_current_context ());
147
148  /* clear the cothread data */
149  memset (ctx->cothreads, 0, sizeof (ctx->cothreads));
150
151  /* FIXME this may not be 64bit clean
152   *       could use casts to uintptr_t from inttypes.h
153   *       if only all platforms had inttypes.h
154   */
155  /* stack_top is the address of the first byte past our stack segment. */
156  /* FIXME: an assumption is made that the stack segment is STACK_SIZE
157   * aligned. */
158  ctx->stack_top = ((gulong) current_stack_frame | (STACK_SIZE - 1)) + 1;
159  GST_CAT_DEBUG (GST_CAT_COTHREADS, "stack top is 0x%08lx", ctx->stack_top);
160
161  /*
162   * initialize the 0th cothread
163   */
164  ctx->cothreads[0] = (cothread_state *) g_malloc0 (sizeof (cothread_state));
165  ctx->cothreads[0]->ctx = ctx;
166  ctx->cothreads[0]->cothreadnum = 0;
167  ctx->cothreads[0]->func = NULL;
168  ctx->cothreads[0]->argc = 0;
169  ctx->cothreads[0]->argv = NULL;
170  ctx->cothreads[0]->priv = NULL;
171  ctx->cothreads[0]->flags = COTHREAD_STARTED;
172  ctx->cothreads[0]->sp = (void *) current_stack_frame;
173
174  GST_CAT_INFO (GST_CAT_COTHREADS, "0th cothread is %p at sp:%p",
175      ctx->cothreads[0], ctx->cothreads[0]->sp);
176
177  return ctx;
178}
179
180/**
181 * cothread_context_free:
182 * @ctx: the cothread context to free
183 *
184 * Free the cothread context.
185 */
186void
187cothread_context_free (cothread_context * ctx)
188{
189  gint i;
190
191  g_return_if_fail (ctx != NULL);
192  g_assert (ctx->thread == g_thread_self ());
193  g_assert (ctx->current == 0);
194
195  GST_CAT_INFO (GST_CAT_COTHREADS, "free cothread context");
196
197  for (i = 1; i < COTHREAD_MAXTHREADS; i++) {
198    if (ctx->cothreads[i]) {
199      cothread_destroy (ctx->cothreads[i]);
200    }
201  }
202  if (ctx->cothreads[0]) {
203    g_free (ctx->cothreads[0]);
204    ctx->cothreads[0] = NULL;
205  }
206  g_hash_table_destroy (ctx->data);
207  /* make sure we free the private key for cothread context */
208  GST_CAT_INFO (GST_CAT_COTHREADS,
209      "setting private _cothread_ctx_key to NULL in thread %p",
210      g_thread_self ());
211  g_static_private_set (&_cothread_ctx_key, NULL, NULL);
212  g_free (ctx);
213}
214
215/**
216 * cothread_create:
217 * @ctx: the cothread context
218 *
219 * Create a new cothread state in the given context
220 *
221 * Returns: the new cothread state or NULL on error
222 */
223cothread_state *
224cothread_create (cothread_context * ctx)
225{
226  cothread_state *cothread;
227  void *mmaped = NULL;
228  gint slot = 0;
229  unsigned long page_size;
230
231  g_return_val_if_fail (ctx != NULL, NULL);
232
233  GST_CAT_DEBUG (GST_CAT_COTHREADS, "manager sef %p, cothread self %p",
234      ctx->thread, g_thread_self ());
235
236  if (ctx->ncothreads == COTHREAD_MAXTHREADS) {
237    /* this is pretty fatal */
238    g_warning ("cothread_create: attempt to create > COTHREAD_MAXTHREADS");
239    return NULL;
240  }
241  /* find a free spot in the stack, note slot 0 has the main thread */
242  for (slot = 1; slot < ctx->ncothreads; slot++) {
243    if (ctx->cothreads[slot] == NULL)
244      break;
245    else if (ctx->cothreads[slot]->flags & COTHREAD_DESTROYED &&
246        slot != ctx->current) {
247      cothread_destroy (ctx->cothreads[slot]);
248      break;
249    }
250  }
251
252  GST_CAT_DEBUG (GST_CAT_COTHREADS, "Found free cothread slot %d", slot);
253
254  /* cothread stack space of the thread is mapped in reverse, with cothread 0
255   * stack space at the top */
256  cothread =
257      (cothread_state *) (ctx->stack_top - (slot + 1) * COTHREAD_STACKSIZE);
258  GST_CAT_DEBUG (GST_CAT_COTHREADS, "cothread pointer is %p", cothread);
259
260#if 0
261  /* This tests to see whether or not we can grow down the stack */
262  {
263    unsigned long ptr;
264
265    for (ptr = ctx->stack_top - 4096; ptr > (unsigned long) cothread;
266        ptr -= 4096) {
267      GST_CAT_DEBUG (GST_CAT_COTHREADS, "touching location 0x%08lx", ptr);
268      *(volatile unsigned int *) ptr = *(volatile unsigned int *) ptr;
269      GST_CAT_DEBUG (GST_CAT_COTHREADS, "ok (0x%08x)", *(unsigned int *) ptr);
270    }
271  }
272#endif
273
274#ifdef _SC_PAGESIZE
275  page_size = sysconf (_SC_PAGESIZE);
276#else
277  page_size = getpagesize ();
278#endif
279
280  /* The mmap is necessary on Linux/i386, and possibly others, since the
281   * kernel is picky about when we can expand our stack. */
282  GST_CAT_DEBUG (GST_CAT_COTHREADS, "mmaping %p, size 0x%08x", cothread,
283      COTHREAD_STACKSIZE);
284  /* Remap with a guard page. This decreases our stack size by 8 kB (for
285   * 4 kB pages) and also wastes almost 4 kB for the cothreads
286   * structure */
287  munmap ((void *) cothread, COTHREAD_STACKSIZE);
288  mmaped = mmap ((void *) cothread, page_size,
289      PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
290  mmaped = mmap (((void *) cothread) + page_size * 2,
291      COTHREAD_STACKSIZE - page_size * 2,
292      PROT_READ | PROT_WRITE | PROT_EXEC,
293      MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
294  GST_CAT_DEBUG (GST_CAT_COTHREADS, "coming out of mmap");
295  if (mmaped == MAP_FAILED) {
296    perror ("mmap'ing cothread stack space");
297    return NULL;
298  }
299  if (mmaped != (void *) cothread + page_size * 2) {
300    g_warning ("could not mmap requested memory for cothread");
301    return NULL;
302  }
303
304  cothread->magic_number = COTHREAD_MAGIC_NUMBER;
305  GST_CAT_DEBUG (GST_CAT_COTHREADS,
306      "create  cothread %d with magic number 0x%x", slot,
307      cothread->magic_number);
308  cothread->ctx = ctx;
309  cothread->cothreadnum = slot;
310  cothread->flags = 0;
311  cothread->priv = NULL;
312  cothread->sp = ((guchar *) cothread + COTHREAD_STACKSIZE);
313  cothread->stack_size = COTHREAD_STACKSIZE - page_size * 2;
314  cothread->stack_base = (void *) cothread + 2 * page_size;
315
316  GST_CAT_INFO (GST_CAT_COTHREADS,
317      "created cothread #%d in slot %d: %p at sp:%p",
318      ctx->ncothreads, slot, cothread, cothread->sp);
319
320  ctx->cothreads[slot] = cothread;
321  ctx->ncothreads++;
322
323  return cothread;
324}
325
326/**
327 * cothread_free:
328 * @cothread: the cothread state
329 *
330 * Free the given cothread state
331 */
332void
333cothread_free (cothread_state * cothread)
334{
335  g_return_if_fail (cothread != NULL);
336
337  GST_CAT_INFO (GST_CAT_COTHREADS, "flag cothread %d for destruction",
338      cothread->cothreadnum);
339
340  /* we simply flag the cothread for destruction here */
341  if (cothread)
342    cothread->flags |= COTHREAD_DESTROYED;
343  else
344    g_warning ("somebody set up us the bomb");
345}
346
347static void
348cothread_destroy (cothread_state * cothread)
349{
350  cothread_context *ctx;
351  gint cothreadnum;
352
353  g_return_if_fail (cothread != NULL);
354
355  cothreadnum = cothread->cothreadnum;
356  ctx = cothread->ctx;
357  g_assert (ctx->thread == g_thread_self ());
358  g_assert (ctx == cothread_get_current_context ());
359
360  GST_CAT_INFO (GST_CAT_COTHREADS, "destroy cothread %d %p %d",
361      cothreadnum, cothread, ctx->current);
362
363  /* cothread 0 needs to be destroyed specially */
364  g_assert (cothreadnum != 0);
365
366  /* doing cleanups of the cothread create */
367  GST_CAT_DEBUG (GST_CAT_COTHREADS,
368      "destroy cothread %d with magic number 0x%x", cothreadnum,
369      cothread->magic_number);
370  g_assert (cothread->magic_number == COTHREAD_MAGIC_NUMBER);
371
372  g_assert (cothread->priv == NULL);
373
374  memset (cothread, 0, sizeof (*cothread));
375
376  ctx->cothreads[cothreadnum] = NULL;
377  ctx->ncothreads--;
378}
379
380/**
381 * cothread_setfunc:
382 * @cothread: the cothread state
383 * @func: the function to call
384 * @argc: argument count for the cothread function
385 * @argv: arguments for the cothread function
386 *
387 * Set the cothread function
388 */
389void
390cothread_setfunc (cothread_state * cothread, cothread_func func, int argc,
391    char **argv)
392{
393  cothread->func = func;
394  cothread->argc = argc;
395  cothread->argv = argv;
396}
397
398/**
399 * cothread_stop:
400 * @cothread: the cothread to stop
401 *
402 * Stop the cothread and reset the stack and program counter.
403 */
404void
405cothread_stop (cothread_state * cothread)
406{
407  cothread->flags &= ~COTHREAD_STARTED;
408}
409
410/**
411 * cothread_main:
412 * @ctx: cothread context to find main cothread of.
413 *
414 * Gets the main thread.
415 *
416 * Returns: the #cothread_state of the main (0th) cothread.
417 */
418cothread_state *
419cothread_main (cothread_context * ctx)
420{
421  g_assert (ctx->thread == g_thread_self ());
422
423  GST_CAT_DEBUG (GST_CAT_COTHREADS, "returning %p, the 0th cothread",
424      ctx->cothreads[0]);
425  return ctx->cothreads[0];
426}
427
428/**
429 * cothread_current_main:
430 *
431 * Get the main thread in the current GThread.
432 *
433 * Returns: the #cothread_state of the main (0th) thread in the current GThread
434 */
435cothread_state *
436cothread_current_main (void)
437{
438  cothread_context *ctx = cothread_get_current_context ();
439
440  return ctx->cothreads[0];
441}
442
443/**
444 * cothread_current:
445 *
446 * Get the currenttly executing cothread
447 *
448 * Returns: the #cothread_state of the current cothread
449 */
450cothread_state *
451cothread_current (void)
452{
453  cothread_context *ctx = cothread_get_current_context ();
454
455  return ctx->cothreads[ctx->current];
456}
457
458static void
459cothread_stub (void)
460{
461  cothread_context *ctx = cothread_get_current_context ();
462  cothread_state *cothread = ctx->cothreads[ctx->current];
463
464#ifndef GST_DISABLE_GST_DEBUG
465  char __csf;
466  void *current_stack_frame = &__csf;
467#endif
468
469  GST_CAT_DEBUG (GST_CAT_COTHREADS, "stack addr %p", &ctx);
470
471  cothread->flags |= COTHREAD_STARTED;
472
473  while (TRUE) {
474    cothread->func (cothread->argc, cothread->argv);
475
476    GST_CAT_DEBUG (GST_CAT_COTHREADS, "cothread[%d] thread->func exited",
477        ctx->current);
478
479    GST_CAT_DEBUG (GST_CAT_COTHREADS, "sp=%p", current_stack_frame);
480    GST_CAT_DEBUG (GST_CAT_COTHREADS, "ctx=%p current=%p", ctx,
481        cothread_get_current_context ());
482    g_assert (ctx == cothread_get_current_context ());
483
484    g_assert (ctx->current != 0);
485
486    /* we do this to avoid ever returning, we just switch to 0th thread */
487    cothread_switch (cothread_main (ctx));
488  }
489}
490
491/**
492 * cothread_getcurrent:
493 *
494 * Get the current cothread id
495 *
496 * Returns: the current cothread id
497 */
498int
499cothread_getcurrent (void)
500    G_GNUC_NO_INSTRUMENT;
501
502     int cothread_getcurrent (void)
503{
504  cothread_context *ctx = cothread_get_current_context ();
505
506  if (!ctx)
507    return -1;
508
509  return ctx->current;
510}
511
512/**
513 * cothread_set_private:
514 * @cothread: the cothread state
515 * @data: the data
516 *
517 * set private data for the cothread.
518 */
519void
520cothread_set_private (cothread_state * cothread, gpointer data)
521{
522  cothread->priv = data;
523}
524
525/**
526 * cothread_context_set_data:
527 * @cothread: the cothread state
528 * @key: a key for the data
529 * @data: the data
530 *
531 * adds data to a cothread
532 */
533void
534cothread_context_set_data (cothread_state * cothread, gchar * key,
535    gpointer data)
536{
537  cothread_context *ctx = cothread_get_current_context ();
538
539  g_hash_table_insert (ctx->data, key, data);
540}
541
542/**
543 * cothread_get_private:
544 * @cothread: the cothread state
545 *
546 * get the private data from the cothread
547 *
548 * Returns: the private data of the cothread
549 */
550gpointer
551cothread_get_private (cothread_state * cothread)
552{
553  return cothread->priv;
554}
555
556/**
557 * cothread_context_get_data:
558 * @cothread: the cothread state
559 * @key: a key for the data
560 *
561 * get data from the cothread
562 *
563 * Returns: the data associated with the key
564 */
565gpointer
566cothread_context_get_data (cothread_state * cothread, gchar * key)
567{
568  cothread_context *ctx = cothread_get_current_context ();
569
570  return g_hash_table_lookup (ctx->data, key);
571}
572
573/**
574 * cothread_switch:
575 * @cothread: cothread state to switch to
576 *
577 * Switches to the given cothread state
578 */
579void
580cothread_switch (cothread_state * cothread)
581{
582  cothread_context *ctx;
583  cothread_state *current;
584  int enter;
585
586#ifdef COTHREAD_PARANOID
587  if (cothread == NULL)
588    goto nothread;
589#endif
590  ctx = cothread->ctx;
591
592  /* paranoia check to make sure we're in the right thread */
593  g_assert (ctx->thread == g_thread_self ());
594
595#ifdef COTHREAD_PARANOID
596  if (ctx == NULL)
597    goto nocontext;
598#endif
599
600  current = ctx->cothreads[ctx->current];
601#ifdef COTHREAD_PARANOID
602  if (current == NULL)
603    goto nocurrent;
604#endif
605  if (current == cothread)
606    goto selfswitch;
607
608
609  /* find the number of the thread to switch to */
610  GST_CAT_INFO (GST_CAT_COTHREAD_SWITCH,
611      "switching from cothread #%d to cothread #%d",
612      ctx->current, cothread->cothreadnum);
613  ctx->current = cothread->cothreadnum;
614
615  /* save the current stack pointer, frame pointer, and pc */
616#ifdef GST_ARCH_PRESETJMP
617  GST_ARCH_PRESETJMP ();
618#endif
619  enter = setjmp (current->jmp);
620  if (enter != 0) {
621    GST_CAT_DEBUG (GST_CAT_COTHREADS,
622        "enter cothread #%d %d sp=%p jmpbuf=%p",
623        current->cothreadnum, enter, current->sp, current->jmp);
624    return;
625  }
626  GST_CAT_DEBUG (GST_CAT_COTHREADS, "exit cothread #%d %d sp=%p jmpbuf=%p",
627      current->cothreadnum, enter, current->sp, current->jmp);
628  enter = 1;
629
630  if (current->flags & COTHREAD_DESTROYED) {
631    cothread_destroy (current);
632  }
633
634  GST_CAT_DEBUG (GST_CAT_COTHREADS, "set stack to %p", cothread->sp);
635  /* restore stack pointer and other stuff of new cothread */
636  if (cothread->flags & COTHREAD_STARTED) {
637    GST_CAT_DEBUG (GST_CAT_COTHREADS, "via longjmp() jmpbuf %p", cothread->jmp);
638    /* switch to it */
639    longjmp (cothread->jmp, 1);
640  } else {
641#ifdef HAVE_MAKECONTEXT
642    ucontext_t ucp;
643
644    GST_CAT_DEBUG (GST_CAT_COTHREADS, "making context");
645
646    g_assert (cothread != cothread_main (ctx));
647
648    getcontext (&ucp);
649    ucp.uc_stack.ss_sp = (void *) cothread->stack_base;
650    ucp.uc_stack.ss_size = cothread->stack_size;
651    makecontext (&ucp, cothread_stub, 0);
652    setcontext (&ucp);
653#else
654    GST_ARCH_SETUP_STACK ((char *) cothread->sp);
655    GST_ARCH_SET_SP (cothread->sp);
656    /* start it */
657    GST_ARCH_CALL (cothread_stub);
658#endif
659
660    GST_CAT_DEBUG (GST_CAT_COTHREADS, "exit thread ");
661    ctx->current = 0;
662  }
663
664  return;
665
666#ifdef COTHREAD_PARANOID
667nothread:
668  g_warning ("cothread: can't switch to NULL cothread!");
669  return;
670nocontext:
671  g_warning ("cothread: there's no context, help!");
672  exit (2);
673nocurrent:
674  g_warning ("cothread: there's no current thread, help!");
675  exit (2);
676#endif /* COTHREAD_PARANOID */
677selfswitch:
678  return;
679}
Note: See TracBrowser for help on using the repository browser.