/* * Copyright (C) 2005 Philippe Gerum . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include #include #include #include #include #include #include "internal.h" /** * @ingroup cobalt_api * @defgroup cobalt_api_thread Thread management * * Cobalt (POSIX) thread management services * * @see * * Specification. * *@{ */ static pthread_attr_ex_t default_attr_ex; static int linuxthreads; struct pthread_iargs { struct sched_param_ex param_ex; int policy; int personality; void *(*start)(void *); void *arg; int parent_prio; sem_t sync; int ret; }; static void *cobalt_thread_trampoline(void *p) { /* * Volatile is to prevent (too) smart gcc releases from * trashing the syscall registers (see later comment). */ int personality, parent_prio, policy, std_policy; volatile pthread_t ptid = pthread_self(); void *(*start)(void *), *arg, *retval; struct pthread_iargs *iargs = p; struct sched_param_ex param_ex; struct sched_param std_param; __u32 u_winoff; long ret; cobalt_sigshadow_install_once(); personality = iargs->personality; param_ex = iargs->param_ex; policy = iargs->policy; parent_prio = iargs->parent_prio; start = iargs->start; arg = iargs->arg; /* * We don't have any Xenomai extension yet, set our base * scheduling parameters for the host kernel first. */ std_policy = cobalt_xlate_schedparam(policy, ¶m_ex, &std_param); ret = __STD(pthread_setschedparam(ptid, std_policy, &std_param)); if (ret) goto sync_with_creator; /* * Do _not_ inline the call to pthread_self() in the syscall * macro: this trashes the syscall regs on some archs. */ ret = -XENOMAI_SYSCALL5(sc_cobalt_thread_create, ptid, policy, ¶m_ex, personality, &u_winoff); if (ret == 0) cobalt_set_tsd(u_winoff); /* * We must access anything we'll need from *iargs before * posting the sync semaphore, since our released parent could * unwind the stack space onto which the iargs struct is laid * on before we actually get the CPU back. */ sync_with_creator: iargs->ret = ret; __STD(sem_post(&iargs->sync)); if (ret) return (void *)ret; /* * If the parent thread runs with the same priority as we do, * then we should yield the CPU to it, to preserve the * scheduling order. */ if (param_ex.sched_priority == parent_prio) __STD(sched_yield()); cobalt_thread_harden(); retval = start(arg); pthread_setmode_np(PTHREAD_WARNSW, 0, NULL); return retval; } int pthread_create_ex(pthread_t *ptid_r, const pthread_attr_ex_t *attr_ex, void *(*start) (void *), void *arg) { int inherit, detachstate, ret; struct pthread_iargs iargs; struct sched_param param; struct timespec timeout; pthread_attr_t attr; pthread_t lptid; if (attr_ex == NULL) attr_ex = &default_attr_ex; pthread_getschedparam_ex(pthread_self(), &iargs.policy, &iargs.param_ex); iargs.parent_prio = iargs.param_ex.sched_priority; memcpy(&attr, &attr_ex->std, sizeof(attr)); pthread_attr_getinheritsched(&attr, &inherit); if (inherit == PTHREAD_EXPLICIT_SCHED) { pthread_attr_getschedpolicy_ex(attr_ex, &iargs.policy); pthread_attr_getschedparam_ex(attr_ex, &iargs.param_ex); } if (linuxthreads && geteuid()) { /* * Work around linuxthreads shortcoming: it doesn't * believe that it could have RT power as non-root and * fails the thread creation overeagerly. */ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); param.sched_priority = 0; pthread_attr_setschedpolicy(&attr, SCHED_OTHER); pthread_attr_setschedparam(&attr, ¶m); } else /* * Get the created thread to temporarily inherit the * caller priority (we mean linux/libc priority here, * as we use a libc call to create the thread). */ pthread_attr_setinheritsched(&attr, PTHREAD_INHERIT_SCHED); pthread_attr_getdetachstate(&attr, &detachstate); pthread_attr_getpersonality_ex(attr_ex, &iargs.personality); /* * First start a regular POSIX thread, then mate a Cobalt * thread to it. */ iargs.start = start; iargs.arg = arg; iargs.ret = EAGAIN; __STD(sem_init(&iargs.sync, 0, 0)); ret = __STD(pthread_create(&lptid, &attr, cobalt_thread_trampoline, &iargs)); if (ret) { __STD(sem_destroy(&iargs.sync)); return ret; } __STD(clock_gettime(CLOCK_REALTIME, &timeout)); timeout.tv_sec += 5; timeout.tv_nsec = 0; for (;;) { ret = __STD(sem_timedwait(&iargs.sync, &timeout)); if (ret && errno == EINTR) continue; if (ret == 0) { ret = iargs.ret; if (ret == 0) *ptid_r = lptid; break; } else if (errno == ETIMEDOUT) { ret = EAGAIN; break; } ret = -errno; panic("regular sem_wait() failed with %s", symerror(ret)); } __STD(sem_destroy(&iargs.sync)); cobalt_thread_harden(); /* May fail if regular thread. */ return ret; } /** * @fn int pthread_create(pthread_t *ptid_r, const pthread_attr_t *attr, void *(*start)(void *), void *arg) * @brief Create a new thread * * This service creates a thread managed by the Cobalt core in a dual * kernel configuration. * * Attributes of the new thread depend on the @a attr argument. If @a * attr is NULL, default values for these attributes are used. * * Returning from the @a start routine has the same effect as calling * pthread_exit() with the return value. * * @param ptid_r address where the identifier of the new thread will be stored on * success; * * @param attr thread attributes; * * @param start thread start routine; * * @param arg opaque user-supplied argument passed to @a start; * * @return 0 on success; * @return an error number if: * - EINVAL, @a attr is invalid; * - EAGAIN, insufficient memory available from the system heap to create a new * thread, increase CONFIG_XENO_OPT_SYS_HEAPSZ; * - EINVAL, thread attribute @a inheritsched is set to PTHREAD_INHERIT_SCHED * and the calling thread does not belong to the Cobalt interface; * * @see * * Specification. * * @note * * When creating a Cobalt thread for the first time, libcobalt * installs an internal handler for the SIGSHADOW signal. If you had * previously installed a handler for such signal before that point, * such handler will be exclusively called for any SIGSHADOW * occurrence Xenomai did not send. * * If, however, an application-defined handler for SIGSHADOW is * installed afterwards, overriding the libcobalt handler, the new * handler is required to call cobalt_sigshadow_handler() on * entry. This routine returns a non-zero value for every occurrence * of SIGSHADOW issued by the Cobalt core. If zero instead, the * application-defined handler should process the signal. * * int cobalt_sigshadow_handler(int sig, siginfo_t *si, void *ctxt); * * You should register your handler with sigaction(2), setting the * SA_SIGINFO flag. * * @apitags{thread-unrestricted, switch-secondary} */ COBALT_IMPL(int, pthread_create, (pthread_t *ptid_r, const pthread_attr_t *attr, void *(*start) (void *), void *arg)) { pthread_attr_ex_t attr_ex; struct sched_param param; int policy; if (attr == NULL) attr = &default_attr_ex.std; memcpy(&attr_ex.std, attr, sizeof(*attr)); pthread_attr_getschedpolicy(attr, &policy); attr_ex.nonstd.sched_policy = policy; pthread_attr_getschedparam(attr, ¶m); attr_ex.nonstd.sched_param.sched_priority = param.sched_priority; attr_ex.nonstd.personality = 0; /* Default: use Cobalt. */ return pthread_create_ex(ptid_r, &attr_ex, start, arg); } /** * Set the mode of the current thread. * * This service sets the mode of the calling thread, which affects its * behavior under particular circumstances. @a clrmask and @a setmask * are two masks of mode bits which are respectively cleared and set * by pthread_setmode_np(): * * - PTHREAD_LOCK_SCHED, when set, locks the scheduler, which prevents * the current thread from being switched out until the scheduler is * unlocked. Unless PTHREAD_DISABLE_LOCKBREAK is also set, the * thread may still block, dropping the lock temporarily, in which * case, the lock will be reacquired automatically when the thread * resumes execution. When PTHREAD_LOCK_SCHED is cleared, the * current thread drops the scheduler lock, and the rescheduling * procedure is initiated. * * - When set, PTHREAD_WARNSW enables debugging notifications for the * current thread. A SIGDEBUG (Linux-originated) signal is sent when * the following atypical or abnormal behavior is detected: * * - the current thread switches to secondary mode. Such * notification comes in handy for detecting spurious relaxes, * with one of the following reason codes: * * - SIGDEBUG_MIGRATE_SYSCALL, if the thread issued a regular * Linux system call. * * - SIGDEBUG_MIGRATE_SIGNAL, if the thread had to leave real-time * mode for handling a Linux signal. * * - SIGDEBUG_MIGRATE_FAULT, if the thread had to leave real-time * mode for handling a processor fault/exception. * * - the current thread is sleeping on a Cobalt mutex currently * owned by a thread running in secondary mode, which reveals a * priority inversion. In such an event, the reason code passed to * the signal handler will be SIGDEBUG_MIGRATE_PRIOINV. * * - the current thread is about to sleep while holding a Cobalt * mutex, and CONFIG_XENO_OPT_DEBUG_MUTEX_SLEEP is enabled in the * kernel configuration. In such an event, the reason code passed * to the signal handler will be SIGDEBUG_MUTEX_SLEEP. Blocking * for acquiring a mutex does not trigger such signal though. * * - the current thread has enabled PTHREAD_DISABLE_LOCKBREAK and * PTHREAD_LOCK_SCHED, then attempts to block on a Cobalt service, * which would cause a lock break. In such an event, the reason * code passed to the signal handler will be SIGDEBUG_LOCK_BREAK. * * - PTHREAD_DISABLE_LOCKBREAK disallows breaking the scheduler * lock. Normally, the scheduler lock is dropped implicitly when the * current owner blocks, then reacquired automatically when the * owner resumes execution. If PTHREAD_DISABLE_LOCKBREAK is set, the * scheduler lock owner would return with EINTR immediately from any * blocking call instead (see PTHREAD_WARNSW notifications). * * - PTHREAD_CONFORMING can be passed in @a setmask to switch the * current Cobalt thread to its preferred runtime mode. The only * meaningful use of this switch is to force a real-time thread back * to primary mode eagerly. Other usages have no effect. * * This service is a non-portable extension of the Cobalt interface. * * @param clrmask set of bits to be cleared. * * @param setmask set of bits to be set. * * @param mode_r If non-NULL, @a mode_r must be a pointer to a memory * location which will be written upon success with the previous set * of active mode bits. If NULL, the previous set of active mode bits * will not be returned. * * @return 0 on success, otherwise: * * - EINVAL, some bit in @a clrmask or @a setmask is invalid. * * @note Setting @a clrmask and @a setmask to zero leads to a nop, * only returning the previous mode if @a mode_r is a valid address. * * @attention Issuing PTHREAD_CONFORMING is most likely useless or even * introduces pure overhead in regular applications, since the Cobalt * core performs the necessary mode switches, only when required. * * @apitags{xthread-only, switch-primary} */ int pthread_setmode_np(int clrmask, int setmask, int *mode_r) { return -XENOMAI_SYSCALL3(sc_cobalt_thread_setmode, clrmask, setmask, mode_r); } /** * Set a thread name. * * This service set to @a name, the name of @a thread. This name is used for * displaying information in /proc/xenomai/sched. * * This service is a non-portable extension of the Cobalt interface. * * @param thread target thread; * * @param name name of the thread. * * @return 0 on success; * @return an error number if: * - ESRCH, @a thread is invalid. * * @apitags{xthread-only} */ COBALT_IMPL(int, pthread_setname_np, (pthread_t thread, const char *name)) { return -XENOMAI_SYSCALL2(sc_cobalt_thread_setname, thread, name); } /** * Send a signal to a thread. * * This service send the signal @a sig to the Cobalt thread @a thread * (created with pthread_create()). If @a sig is zero, this service * check for existence of the thread @a thread, but no signal is sent. * * @param thread thread identifier; * * @param sig signal number. * * @return 0 on success; * @return an error number if: * - EINVAL, @a sig is an invalid signal number; * - EAGAIN, the maximum number of pending signals has been exceeded; * - ESRCH, @a thread is an invalid thread identifier. * * @see * * Specification. * * @apitags{thread-unrestricted, switch-primary} */ COBALT_IMPL(int, pthread_kill, (pthread_t thread, int sig)) { int ret; ret = -XENOMAI_SYSCALL2(sc_cobalt_thread_kill, thread, sig); if (ret == ESRCH) return __STD(pthread_kill(thread, sig)); return ret; } /** * Wait for termination of a specified thread. * * If @a thread is running and joinable, this service blocks the * caller until @a thread terminates or detaches. When @a thread * terminates, the caller is unblocked and its return value is stored * at the address @a value_ptr. * * On the other hand, if @a thread has already finished execution, its * return value collected earlier is stored at the address @a * value_ptr and this service returns immediately. * * This service is a cancelation point for Cobalt threads: if the * calling thread is canceled while blocked in a call to this service, * the cancelation request is honored and @a thread remains joinable. * * Multiple simultaneous calls to pthread_join() specifying the same running * target thread block all the callers until the target thread terminates. * * @param thread identifier of the thread to wait for; * * @param retval address where the target thread return value will be stored * on success. * * @return 0 on success; * @return an error number if: * - ESRCH, @a thread is invalid; * - EDEADLK, attempting to join the calling thread; * - EINVAL, @a thread is detached; * - EPERM, the caller context is invalid. * * @see * * Specification. * * @apitags{xthread-only, switch-secondary, switch-primary} */ COBALT_IMPL(int, pthread_join, (pthread_t thread, void **retval)) { int ret; ret = __STD(pthread_join(thread, retval)); if (ret) return ret; ret = cobalt_thread_join(thread); return ret == -EBUSY ? EINVAL : 0; } /** @} */ /** * @ingroup cobalt_api * @defgroup cobalt_api_sched Scheduling management * * Cobalt scheduling management services * @{ */ /** * Set the scheduling policy and parameters of the specified thread. * * This service set the scheduling policy of the Cobalt thread * identified by @a pid to the value @a policy, and its scheduling * parameters (i.e. its priority) to the value pointed to by @a param. * * If pthread_self() is passed, this service turns the current thread * into a Cobalt thread. If @a thread is not the identifier of a * Cobalt thread, this service falls back to the regular * pthread_setschedparam() service. * * @param thread target Cobalt thread; * * @param policy scheduling policy, one of SCHED_FIFO, SCHED_RR, or * SCHED_OTHER; * * @param param address of scheduling parameters. * * @return 0 on success; * @return an error number if: * - ESRCH, @a pid is invalid; * - EINVAL, @a policy or @a param->sched_priority is invalid; * - EAGAIN, insufficient memory available from the system heap, * increase CONFIG_XENO_OPT_SYS_HEAPSZ; * - EFAULT, @a param is an invalid address; * * @see * * Specification. * * @note * * See pthread_create(), pthread_setschedparam_ex(). * * @apitags{thread-unrestricted, switch-secondary, switch-primary} */ COBALT_IMPL(int, pthread_setschedparam, (pthread_t thread, int policy, const struct sched_param *param)) { /* * XXX: We currently assume that all available policies * supported by the host kernel define a single scheduling * parameter only, i.e. a priority level. */ struct sched_param_ex param_ex = { .sched_priority = param->sched_priority, }; return pthread_setschedparam_ex(thread, policy, ¶m_ex); } /** * Set extended scheduling policy of thread * * This service is an extended version of the regular * pthread_setschedparam() service, which supports Cobalt-specific * scheduling policies, not available with the host Linux environment. * * This service set the scheduling policy of the Cobalt thread @a * thread to the value @a policy, and its scheduling parameters * (e.g. its priority) to the value pointed to by @a param_ex. * * If @a thread does not match the identifier of a Cobalt thread, this * action falls back to the regular pthread_setschedparam() service. * * @param thread target Cobalt thread; * * @param policy scheduling policy, one of SCHED_WEAK, SCHED_FIFO, * SCHED_COBALT, SCHED_RR, SCHED_SPORADIC, SCHED_TP, SCHED_QUOTA or * SCHED_NORMAL; * * @param param_ex scheduling parameters address. As a special * exception, a negative sched_priority value is interpreted as if * SCHED_WEAK was given in @a policy, using the absolute value of this * parameter as the weak priority level. * * When CONFIG_XENO_OPT_SCHED_WEAK is enabled, SCHED_WEAK exhibits * priority levels in the [0..99] range (inclusive). Otherwise, * sched_priority must be zero for the SCHED_WEAK policy. * * @return 0 on success; * @return an error number if: * - ESRCH, @a thread is invalid; * - EINVAL, @a policy or @a param_ex->sched_priority is invalid; * - EAGAIN, insufficient memory available from the system heap, * increase CONFIG_XENO_OPT_SYS_HEAPSZ; * - EFAULT, @a param_ex is an invalid address; * - EPERM, the calling process does not have superuser * permissions. * * @see * * Specification. * * @note * * See pthread_create(), pthread_setschedparam(). * * @apitags{thread-unrestricted, switch-secondary, switch-primary} */ int pthread_setschedparam_ex(pthread_t thread, int policy, const struct sched_param_ex *param_ex) { int ret, promoted, std_policy; struct sched_param std_param; __u32 u_winoff, *u_winoff_ptr; /* * If we enter this call over a relaxed context, take the * opportunity to tell the host kernel via the regular libc * about the new schedparams right now, then tell Xenomai * next. Otherwise, send the request to Xenomai only, which * will propagate the change to the host kernel asap, * i.e. almost immediately if the target thread is already * relaxed, or when it relaxes otherwise. * * CAUTION: when lazy propagation has to take place * (i.e. calling pthread_setschedparam_ex() from primary * mode), glibc's cached idea of the current schedparams of * the target thread will be out of sync. This is part of the * trade-off to keep the caller running in primary mode * throughout this service. * * Application which are sensitive to this issue with Xenomai * threads should refrain from mixing APIs for managing * scheduling parameters, and only rely on libcobalt for this. */ if (cobalt_eager_setsched()) { /* True if disabled or shadow not mapped yet. */ std_policy = cobalt_xlate_schedparam(policy, param_ex, &std_param); ret = __STD(pthread_setschedparam(thread, std_policy, &std_param)); if (ret) return ret; } /* only request promotion when this targets the current thread */ u_winoff_ptr = thread == pthread_self() ? &u_winoff : NULL; ret = -XENOMAI_SYSCALL5(sc_cobalt_thread_setschedparam_ex, thread, policy, param_ex, u_winoff_ptr, &promoted); /* * If the kernel has no reference to the target thread. let glibc * handle the call. */ if (ret == ESRCH) { std_policy = cobalt_xlate_schedparam(policy, param_ex, &std_param); return __STD(pthread_setschedparam(thread, std_policy, &std_param)); } if (ret == 0 && promoted) { cobalt_sigshadow_install_once(); cobalt_set_tsd(u_winoff); cobalt_thread_harden(); } return ret; } COBALT_IMPL(int, pthread_setschedprio, (pthread_t thread, int prio)) { __u32 u_winoff, *u_winoff_ptr; int ret, promoted; /* See pthread_setschedparam_ex. */ if (cobalt_eager_setsched()) { ret = __STD(pthread_setschedprio(thread, prio)); if (ret) return ret; } /* only request promotion when this targets the current thread */ u_winoff_ptr = thread == pthread_self() ? &u_winoff : NULL; ret = -XENOMAI_SYSCALL4(sc_cobalt_thread_setschedprio, thread, prio, u_winoff_ptr, &promoted); /* * If the kernel has no reference to the target thread. let glibc * handle the call. */ if (ret == ESRCH) return __STD(pthread_setschedprio(thread, prio)); if (ret == 0 && promoted) { cobalt_sigshadow_install_once(); cobalt_set_tsd(u_winoff); cobalt_thread_harden(); } return ret; } /** * Get the scheduling policy and parameters of the specified thread. * * This service returns, at the addresses @a policy and @a par, the * current scheduling policy and scheduling parameters (i.e. priority) * of the Cobalt thread @a tid. If @a thread is not the identifier of * a Cobalt thread, this service fallback to the regular POSIX * pthread_getschedparam() service. * * @param thread target thread; * * @param policy address where the scheduling policy of @a tid is stored on * success; * * @param param address where the scheduling parameters of @a tid is stored on * success. * * @return 0 on success; * @return an error number if: * - ESRCH, @a tid is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ COBALT_IMPL(int, pthread_getschedparam, (pthread_t thread, int *__restrict__ policy, struct sched_param *__restrict__ param)) { struct sched_param_ex param_ex; int ret; ret = pthread_getschedparam_ex(thread, policy, ¶m_ex); if (ret) return ret; param->sched_priority = param_ex.sched_priority; return 0; } /** * Get extended scheduling policy of thread * * This service is an extended version of the regular * pthread_getschedparam() service, which also supports * Cobalt-specific policies, not available with the host Linux * environment. * * @param thread target thread; * * @param policy_r address where the scheduling policy of @a thread is stored on * success; * * @param param_ex address where the scheduling parameters of @a thread are * stored on success. * * @return 0 on success; * @return an error number if: * - ESRCH, @a thread is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_getschedparam_ex(pthread_t thread, int *__restrict__ policy_r, struct sched_param_ex *__restrict__ param_ex) { struct sched_param short_param; int ret; ret = -XENOMAI_SYSCALL3(sc_cobalt_thread_getschedparam_ex, thread, policy_r, param_ex); if (ret == ESRCH) { ret = __STD(pthread_getschedparam(thread, policy_r, &short_param)); if (ret == 0) param_ex->sched_priority = short_param.sched_priority; } return ret; } /** * Yield the processor. * * This function move the current thread at the end of its priority group. * * @retval 0 * * @see * * Specification. * * @apitags{thread-unrestricted, switch-primary} */ COBALT_IMPL(int, pthread_yield, (void)) { return __WRAP(sched_yield()); } /** @} */ void cobalt_thread_init(void) { #ifdef _CS_GNU_LIBPTHREAD_VERSION char vers[128]; linuxthreads = !confstr(_CS_GNU_LIBPTHREAD_VERSION, vers, sizeof(vers)) || strstr(vers, "linuxthreads"); #else /* !_CS_GNU_LIBPTHREAD_VERSION */ linuxthreads = 1; #endif /* !_CS_GNU_LIBPTHREAD_VERSION */ pthread_attr_init_ex(&default_attr_ex); }