diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:13 -0800 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 18:28:13 -0800 |
| commit | 1767f908af327fa388b1c66883760ad851267013 (patch) | |
| tree | 4b825dc642cb6eb9a060e54bf8d69288fbee4904 /libc/bionic/pthread-timers.c | |
| parent | a799b53f10e5a6fd51fef4436cfb7ec99836a516 (diff) | |
| download | bionic-1767f908af327fa388b1c66883760ad851267013.zip bionic-1767f908af327fa388b1c66883760ad851267013.tar.gz bionic-1767f908af327fa388b1c66883760ad851267013.tar.bz2 | |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'libc/bionic/pthread-timers.c')
| -rw-r--r-- | libc/bionic/pthread-timers.c | 636 |
1 files changed, 0 insertions, 636 deletions
diff --git a/libc/bionic/pthread-timers.c b/libc/bionic/pthread-timers.c deleted file mode 100644 index 818b47d..0000000 --- a/libc/bionic/pthread-timers.c +++ /dev/null @@ -1,636 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#include "pthread_internal.h" -#include <linux/time.h> -#include <string.h> -#include <errno.h> - -/* This file implements the support required to implement SIGEV_THREAD posix - * timers. See the following pages for additionnal details: - * - * www.opengroup.org/onlinepubs/000095399/functions/timer_create.html - * www.opengroup.org/onlinepubs/000095399/functions/timer_settime.html - * www.opengroup.org/onlinepubs/000095399/functions/xsh_chap02_04.html#tag_02_04_01 - * - * The Linux kernel doesn't support these, so we need to implement them in the - * C library. We use a very basic scheme where each timer is associated to a - * thread that will loop, waiting for timeouts or messages from the program - * corresponding to calls to timer_settime() and timer_delete(). - * - * Note also an important thing: Posix mandates that in the case of fork(), - * the timers of the child process should be disarmed, but not deleted. - * this is implemented by providing a fork() wrapper (see bionic/fork.c) which - * stops all timers before the fork, and only re-start them in case of error - * or in the parent process. - * - * the stop/start is implemented by the __timer_table_start_stop() function - * below. - */ - -/* normal (i.e. non-SIGEV_THREAD) timer ids are created directly by the kernel - * and are passed as is to/from the caller. - * - * on the other hand, a SIGEV_THREAD timer ID will have its TIMER_ID_WRAP_BIT - * always set to 1. In this implementation, this is always bit 31, which is - * guaranteed to never be used by kernel-provided timer ids - * - * (see code in <kernel>/lib/idr.c, used to manage IDs, to see why) - */ - -#define TIMER_ID_WRAP_BIT 0x80000000 -#define TIMER_ID_WRAP(id) ((timer_t)((id) | TIMER_ID_WRAP_BIT)) -#define TIMER_ID_UNWRAP(id) ((timer_t)((id) & ~TIMER_ID_WRAP_BIT)) -#define TIMER_ID_IS_WRAPPED(id) (((id) & TIMER_ID_WRAP_BIT) != 0) - -/* this value is used internally to indicate a 'free' or 'zombie' - * thr_timer structure. Here, 'zombie' means that timer_delete() - * has been called, but that the corresponding thread hasn't - * exited yet. - */ -#define TIMER_ID_NONE ((timer_t)0xffffffff) - -/* True iff a timer id is valid */ -#define TIMER_ID_IS_VALID(id) ((id) != TIMER_ID_NONE) - -/* the maximum value of overrun counters */ -#define DELAYTIMER_MAX 0x7fffffff - -#define __likely(x) __builtin_expect(!!(x),1) -#define __unlikely(x) __builtin_expect(!!(x),0) - -typedef struct thr_timer thr_timer_t; -typedef struct thr_timer_table thr_timer_table_t; - -/* The Posix spec says the function receives an unsigned parameter, but - * it's really a 'union sigval' a.k.a. sigval_t */ -typedef void (*thr_timer_func_t)( sigval_t ); - -struct thr_timer { - thr_timer_t* next; /* next in free list */ - timer_t id; /* TIMER_ID_NONE iff free or dying */ - clockid_t clock; - pthread_t thread; - pthread_attr_t attributes; - thr_timer_func_t callback; - sigval_t value; - - /* the following are used to communicate between - * the timer thread and the timer_XXX() functions - */ - pthread_mutex_t mutex; /* lock */ - pthread_cond_t cond; /* signal a state change to thread */ - int volatile done; /* set by timer_delete */ - int volatile stopped; /* set by _start_stop() */ - struct timespec volatile expires; /* next expiration time, or 0 */ - struct timespec volatile period; /* reload value, or 0 */ - int volatile overruns; /* current number of overruns */ -}; - -#define MAX_THREAD_TIMERS 32 - -struct thr_timer_table { - pthread_mutex_t lock; - thr_timer_t* free_timer; - thr_timer_t timers[ MAX_THREAD_TIMERS ]; -}; - -/** GLOBAL TABLE OF THREAD TIMERS - **/ - -static void -thr_timer_table_init( thr_timer_table_t* t ) -{ - int nn; - - memset(t, 0, sizeof *t); - pthread_mutex_init( &t->lock, NULL ); - - for (nn = 0; nn < MAX_THREAD_TIMERS; nn++) - t->timers[nn].id = TIMER_ID_NONE; - - t->free_timer = &t->timers[0]; - for (nn = 1; nn < MAX_THREAD_TIMERS; nn++) - t->timers[nn-1].next = &t->timers[nn]; -} - - -static thr_timer_t* -thr_timer_table_alloc( thr_timer_table_t* t ) -{ - thr_timer_t* timer; - - if (t == NULL) - return NULL; - - pthread_mutex_lock(&t->lock); - timer = t->free_timer; - if (timer != NULL) { - t->free_timer = timer->next; - timer->next = NULL; - timer->id = TIMER_ID_WRAP((timer - t->timers)); - } - pthread_mutex_unlock(&t->lock); - return timer; -} - - -static void -thr_timer_table_free( thr_timer_table_t* t, thr_timer_t* timer ) -{ - pthread_mutex_lock( &t->lock ); - timer->id = TIMER_ID_NONE; - timer->thread = 0; - timer->next = t->free_timer; - t->free_timer = timer; - pthread_mutex_unlock( &t->lock ); -} - - -static void -thr_timer_table_start_stop( thr_timer_table_t* t, int stop ) -{ - int nn; - - pthread_mutex_lock(&t->lock); - - for (nn = 0; nn < MAX_THREAD_TIMERS; nn++) { - thr_timer_t* timer = &t->timers[nn]; - - if (TIMER_ID_IS_VALID(timer->id)) { - /* tell the thread to start/stop */ - pthread_mutex_lock(&timer->mutex); - timer->stopped = stop; - pthread_cond_signal( &timer->cond ); - pthread_mutex_unlock(&timer->mutex); - } - } - pthread_mutex_unlock(&t->lock); -} - - -/* convert a timer_id into the corresponding thr_timer_t* pointer - * returns NULL if the id is not wrapped or is invalid/free - */ -static thr_timer_t* -thr_timer_table_from_id( thr_timer_table_t* t, - timer_t id, - int remove ) -{ - unsigned index; - thr_timer_t* timer; - - if (t == NULL || !TIMER_ID_IS_WRAPPED(id)) - return NULL; - - index = (unsigned) TIMER_ID_UNWRAP(id); - if (index >= MAX_THREAD_TIMERS) - return NULL; - - pthread_mutex_lock(&t->lock); - - timer = &t->timers[index]; - - if (!TIMER_ID_IS_VALID(timer->id)) { - timer = NULL; - } else { - /* if we're removing this timer, clear the id - * right now to prevent another thread to - * use the same id after the unlock */ - if (remove) - timer->id = TIMER_ID_NONE; - } - pthread_mutex_unlock(&t->lock); - - return timer; -} - -/* the static timer table - we only create it if the process - * really wants to use SIGEV_THREAD timers, which should be - * pretty infrequent - */ - -static pthread_once_t __timer_table_once = PTHREAD_ONCE_INIT; -static thr_timer_table_t* __timer_table; - -static void -__timer_table_init( void ) -{ - __timer_table = calloc(1,sizeof(*__timer_table)); - - if (__timer_table != NULL) - thr_timer_table_init( __timer_table ); -} - -static thr_timer_table_t* -__timer_table_get(void) -{ - pthread_once( &__timer_table_once, __timer_table_init ); - return __timer_table; -} - -/** POSIX THREAD TIMERS CLEANUP ON FORK - ** - ** this should be called from the 'fork()' wrapper to stop/start - ** all active thread timers. this is used to implement a Posix - ** requirements: the timers of fork child processes must be - ** disarmed but not deleted. - **/ -void -__timer_table_start_stop( int stop ) -{ - if (__timer_table != NULL) { - thr_timer_table_t* table = __timer_table_get(); - thr_timer_table_start_stop(table, stop); - } -} - -static thr_timer_t* -thr_timer_from_id( timer_t id ) -{ - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_from_id( table, id, 0 ); - - return timer; -} - - -static __inline__ void -thr_timer_lock( thr_timer_t* t ) -{ - pthread_mutex_lock(&t->mutex); -} - -static __inline__ void -thr_timer_unlock( thr_timer_t* t ) -{ - pthread_mutex_unlock(&t->mutex); -} - -/** POSIX TIMERS APIs */ - -/* first, declare the syscall stubs */ -extern int __timer_create( clockid_t, struct sigevent*, timer_t* ); -extern int __timer_delete( timer_t ); -extern int __timer_gettime( timer_t, struct itimerspec* ); -extern int __timer_settime( timer_t, int, const struct itimerspec*, struct itimerspec* ); -extern int __timer_getoverrun(timer_t); - -static void* timer_thread_start( void* ); - -/* then the wrappers themselves */ -int -timer_create( clockid_t clockid, struct sigevent* evp, timer_t *ptimerid) -{ - /* if not a SIGEV_THREAD timer, direct creation by the kernel */ - if (__likely(evp == NULL || evp->sigev_notify != SIGEV_THREAD)) - return __timer_create( clockid, evp, ptimerid ); - - // check arguments - if (evp->sigev_notify_function == NULL) { - errno = EINVAL; - return -1; - } - - { - struct timespec dummy; - - /* check that the clock id is supported by the kernel */ - if (clock_gettime( clockid, &dummy ) < 0 && errno == EINVAL ) - return -1; - } - - /* create a new timer and its thread */ - { - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_alloc( table ); - struct sigevent evp0; - - if (timer == NULL) { - errno = ENOMEM; - return -1; - } - - /* copy the thread attributes */ - if (evp->sigev_notify_attributes == NULL) { - pthread_attr_init(&timer->attributes); - } - else { - timer->attributes = ((pthread_attr_t*)evp->sigev_notify_attributes)[0]; - } - - /* Posix says that the default is PTHREAD_CREATE_DETACHED and - * that PTHREAD_CREATE_JOINABLE has undefined behaviour. - * So simply always use DETACHED :-) - */ - pthread_attr_setdetachstate(&timer->attributes, PTHREAD_CREATE_DETACHED); - - timer->callback = evp->sigev_notify_function; - timer->value = evp->sigev_value; - timer->clock = clockid; - - pthread_mutex_init( &timer->mutex, NULL ); - pthread_cond_init( &timer->cond, NULL ); - - timer->done = 0; - timer->stopped = 0; - timer->expires.tv_sec = timer->expires.tv_nsec = 0; - timer->period.tv_sec = timer->period.tv_nsec = 0; - timer->overruns = 0; - - /* create the thread */ - if (pthread_create( &timer->thread, &timer->attributes, timer_thread_start, timer ) < 0) { - thr_timer_table_free( __timer_table, timer ); - errno = ENOMEM; - return -1; - } - - *ptimerid = timer->id; - return 0; - } -} - - -int -timer_delete( timer_t id ) -{ - if ( __likely(!TIMER_ID_IS_WRAPPED(id)) ) - return __timer_delete( id ); - else - { - thr_timer_table_t* table = __timer_table_get(); - thr_timer_t* timer = thr_timer_table_from_id(table, id, 1); - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - - /* tell the timer's thread to stop */ - thr_timer_lock(timer); - timer->done = 1; - pthread_cond_signal( &timer->cond ); - thr_timer_unlock(timer); - - /* NOTE: the thread will call __timer_table_free() to free the - * timer object. the '1' parameter to thr_timer_table_from_id - * above ensured that the object and its timer_id cannot be - * reused before that. - */ - return 0; - } -} - -/* return the relative time until the next expiration, or 0 if - * the timer is disarmed */ -static void -timer_gettime_internal( thr_timer_t* timer, - struct itimerspec* spec) -{ - struct timespec diff; - - diff = timer->expires; - if (!timespec_is_zero(&diff)) - { - struct timespec now; - - clock_gettime( timer->clock, &now ); - timespec_sub(&diff, &now); - - /* in case of overrun, return 0 */ - if (timespec_cmp0(&diff) < 0) { - timespec_zero(&diff); - } - } - - spec->it_value = diff; - spec->it_interval = timer->period; -} - - -int -timer_gettime( timer_t id, struct itimerspec* ospec ) -{ - if (ospec == NULL) { - errno = EINVAL; - return -1; - } - - if ( __likely(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_gettime( id, ospec ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - thr_timer_lock(timer); - timer_gettime_internal( timer, ospec ); - thr_timer_unlock(timer); - } - return 0; -} - - -int -timer_settime( timer_t id, - int flags, - const struct itimerspec* spec, - struct itimerspec* ospec ) -{ - if (spec == NULL) { - errno = EINVAL; - return -1; - } - - if ( __likely(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_settime( id, flags, spec, ospec ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - struct timespec expires, now; - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - thr_timer_lock(timer); - - /* return current timer value if ospec isn't NULL */ - if (ospec != NULL) { - timer_gettime_internal(timer, ospec ); - } - - /* compute next expiration time */ - expires = spec->it_value; - clock_gettime( timer->clock, &now ); - if (!(flags & TIMER_ABSTIME)) { - timespec_add(&expires, &now); - } else { - if (timespec_cmp(&expires, &now) < 0) - expires = now; - } - - timer->expires = expires; - timer->period = spec->it_interval; - thr_timer_unlock( timer ); - - /* signal the change to the thread */ - pthread_cond_signal( &timer->cond ); - } - return 0; -} - - -int -timer_getoverrun(timer_t id) -{ - if ( __likely(!TIMER_ID_IS_WRAPPED(id)) ) { - return __timer_getoverrun( id ); - } else { - thr_timer_t* timer = thr_timer_from_id(id); - int result; - - if (timer == NULL) { - errno = EINVAL; - return -1; - } - - thr_timer_lock(timer); - result = timer->overruns; - thr_timer_unlock(timer); - - return result; - } -} - - -static void* -timer_thread_start( void* _arg ) -{ - thr_timer_t* timer = _arg; - - thr_timer_lock( timer ); - - /* we loop until timer->done is set in timer_delete() */ - while (!timer->done) - { - struct timespec expires = timer->expires; - struct timespec period = timer->period; - struct timespec now; - - /* if the timer is stopped or disarmed, wait indefinitely - * for a state change from timer_settime/_delete/_start_stop - */ - if ( timer->stopped || timespec_is_zero(&expires) ) - { - pthread_cond_wait( &timer->cond, &timer->mutex ); - continue; - } - - /* otherwise, we need to do a timed wait until either a - * state change of the timer expiration time. - */ - clock_gettime(timer->clock, &now); - - if (timespec_cmp( &expires, &now ) > 0) - { - /* cool, there was no overrun, so compute the - * relative timeout as 'expires - now', then wait - */ - int ret; - struct timespec diff = expires; - timespec_sub( &diff, &now ); - - ret = __pthread_cond_timedwait_relative( - &timer->cond, &timer->mutex, &diff); - - /* if we didn't timeout, it means that a state change - * occured, so reloop to take care of it. - */ - if (ret != ETIMEDOUT) - continue; - } - else - { - /* overrun was detected before we could wait ! */ - if (!timespec_is_zero( &period ) ) - { - /* for periodic timers, compute total overrun count */ - do { - timespec_add( &expires, &period ); - if (timer->overruns < DELAYTIMER_MAX) - timer->overruns += 1; - } while ( timespec_cmp( &expires, &now ) < 0 ); - - /* backtrack the last one, because we're going to - * add the same value just a bit later */ - timespec_sub( &expires, &period ); - } - else - { - /* for non-periodic timer, things are simple */ - timer->overruns = 1; - } - } - - /* if we get there, a timeout was detected. - * first reload/disarm the timer has needed - */ - if ( !timespec_is_zero(&period) ) { - timespec_add( &expires, &period ); - } else { - timespec_zero( &expires ); - } - timer->expires = expires; - - /* now call the timer callback function. release the - * lock to allow the function to modify the timer setting - * or call timer_getoverrun(). - * - * NOTE: at this point we trust the callback not to be a - * total moron and pthread_kill() the timer thread - */ - thr_timer_unlock(timer); - timer->callback( timer->value ); - thr_timer_lock(timer); - - /* now clear the overruns counter. it only makes sense - * within the callback */ - timer->overruns = 0; - } - - thr_timer_unlock( timer ); - - /* free the timer object now. there is no need to call - * __timer_table_get() since we're guaranteed that __timer_table - * is initialized in this thread - */ - thr_timer_table_free(__timer_table, timer); - - return NULL; -} |
