summaryrefslogtreecommitdiffstats
path: root/lib/libpthread/thread/thr_kern.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libpthread/thread/thr_kern.c')
-rw-r--r--lib/libpthread/thread/thr_kern.c1135
1 files changed, 1135 insertions, 0 deletions
diff --git a/lib/libpthread/thread/thr_kern.c b/lib/libpthread/thread/thr_kern.c
new file mode 100644
index 0000000..b3fbc3a
--- /dev/null
+++ b/lib/libpthread/thread/thr_kern.c
@@ -0,0 +1,1135 @@
+/*
+ * Copyright (c) 1995-1998 John Birrell <jb@cimlogic.com.au>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by John Birrell.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY JOHN BIRRELL 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ *
+ */
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#ifdef _THREAD_SAFE
+#include <pthread.h>
+#include "pthread_private.h"
+
+/* Static function prototype definitions: */
+static void
+_thread_kern_poll(int wait_reqd);
+
+static void
+dequeue_signals(void);
+
+static inline void
+thread_run_switch_hook(pthread_t thread_out, pthread_t thread_in);
+
+void
+_thread_kern_sched(ucontext_t * scp)
+{
+#ifndef __alpha__
+ char *fdata;
+#endif
+ pthread_t pthread, pthread_h = NULL;
+ struct itimerval itimer;
+ struct timespec ts, ts1;
+ struct timeval tv, tv1;
+ int set_timer = 0;
+
+ /*
+ * Flag the pthread kernel as executing scheduler code
+ * to avoid a scheduler signal from interrupting this
+ * execution and calling the scheduler again.
+ */
+ _thread_kern_in_sched = 1;
+
+ /* Check if this function was called from the signal handler: */
+ if (scp != NULL) {
+ /*
+ * Copy the signal context to the current thread's jump
+ * buffer:
+ */
+ memcpy(&_thread_run->saved_sigcontext, scp, sizeof(_thread_run->saved_sigcontext));
+
+#ifndef __alpha__
+ /* Point to the floating point data in the running thread: */
+ fdata = _thread_run->saved_fp;
+
+ /* Save the floating point data: */
+__asm__("fnsave %0": :"m"(*fdata));
+#endif
+
+ /* Flag the signal context as the last state saved: */
+ _thread_run->sig_saved = 1;
+ }
+ /* Save the state of the current thread: */
+ else if (setjmp(_thread_run->saved_jmp_buf) != 0) {
+ /*
+ * This point is reached when a longjmp() is called to
+ * restore the state of a thread.
+ *
+ * This is the normal way out of the scheduler.
+ */
+ _thread_kern_in_sched = 0;
+
+ if (((_thread_run->cancelflags & PTHREAD_AT_CANCEL_POINT) == 0) &&
+ ((_thread_run->cancelflags & PTHREAD_CANCEL_ASYNCHRONOUS) != 0)) {
+ /*
+ * Cancellations override signals.
+ *
+ * Stick a cancellation point at the start of
+ * each async-cancellable thread's resumption.
+ *
+ * We allow threads woken at cancel points to do their
+ * own checks.
+ */
+ pthread_testcancel();
+ }
+
+ if (_sched_switch_hook != NULL) {
+ /* Run the installed switch hook: */
+ thread_run_switch_hook(_last_user_thread, _thread_run);
+ }
+
+ return;
+ } else
+ /* Flag the jump buffer was the last state saved: */
+ _thread_run->sig_saved = 0;
+
+ /* If the currently running thread is a user thread, save it: */
+ if ((_thread_run->flags & PTHREAD_FLAGS_PRIVATE) == 0)
+ _last_user_thread = _thread_run;
+
+ /*
+ * Enter a scheduling loop that finds the next thread that is
+ * ready to run. This loop completes when there are no more threads
+ * in the global list or when a thread has its state restored by
+ * either a sigreturn (if the state was saved as a sigcontext) or a
+ * longjmp (if the state was saved by a setjmp).
+ */
+ while (!(TAILQ_EMPTY(&_thread_list))) {
+ /* Get the current time of day: */
+ gettimeofday(&tv, NULL);
+ TIMEVAL_TO_TIMESPEC(&tv, &ts);
+
+ /*
+ * Protect the scheduling queues from access by the signal
+ * handler.
+ */
+ _queue_signals = 1;
+
+ if (_thread_run != &_thread_kern_thread) {
+
+ /*
+ * This thread no longer needs to yield the CPU.
+ */
+ _thread_run->yield_on_sig_undefer = 0;
+
+ /*
+ * Save the current time as the time that the thread
+ * became inactive:
+ */
+ _thread_run->last_inactive.tv_sec = tv.tv_sec;
+ _thread_run->last_inactive.tv_usec = tv.tv_usec;
+
+ /*
+ * Place the currently running thread into the
+ * appropriate queue(s).
+ */
+ switch (_thread_run->state) {
+ case PS_DEAD:
+ case PS_STATE_MAX: /* to silence -Wall */
+ /*
+ * Dead threads are not placed in any queue:
+ */
+ break;
+
+ case PS_RUNNING:
+ /*
+ * Runnable threads can't be placed in the
+ * priority queue until after waiting threads
+ * are polled (to preserve round-robin
+ * scheduling).
+ */
+ if ((_thread_run->slice_usec != -1) &&
+ (_thread_run->attr.sched_policy != SCHED_FIFO)) {
+ /*
+ * Accumulate the number of microseconds that
+ * this thread has run for:
+ */
+ _thread_run->slice_usec +=
+ (_thread_run->last_inactive.tv_sec -
+ _thread_run->last_active.tv_sec) * 1000000 +
+ _thread_run->last_inactive.tv_usec -
+ _thread_run->last_active.tv_usec;
+
+ /* Check for time quantum exceeded: */
+ if (_thread_run->slice_usec > TIMESLICE_USEC)
+ _thread_run->slice_usec = -1;
+ }
+ break;
+
+ /*
+ * States which do not depend on file descriptor I/O
+ * operations or timeouts:
+ */
+ case PS_DEADLOCK:
+ case PS_FDLR_WAIT:
+ case PS_FDLW_WAIT:
+ case PS_FILE_WAIT:
+ case PS_JOIN:
+ case PS_MUTEX_WAIT:
+ case PS_SIGSUSPEND:
+ case PS_SIGTHREAD:
+ case PS_SIGWAIT:
+ case PS_SUSPENDED:
+ case PS_WAIT_WAIT:
+ /* No timeouts for these states: */
+ _thread_run->wakeup_time.tv_sec = -1;
+ _thread_run->wakeup_time.tv_nsec = -1;
+
+ /* Restart the time slice: */
+ _thread_run->slice_usec = -1;
+
+ /* Insert into the waiting queue: */
+ PTHREAD_WAITQ_INSERT(_thread_run);
+ break;
+
+ /* States which can timeout: */
+ case PS_COND_WAIT:
+ case PS_SLEEP_WAIT:
+ /* Restart the time slice: */
+ _thread_run->slice_usec = -1;
+
+ /* Insert into the waiting queue: */
+ PTHREAD_WAITQ_INSERT(_thread_run);
+ break;
+
+ /* States that require periodic work: */
+ case PS_SPINBLOCK:
+ /* No timeouts for this state: */
+ _thread_run->wakeup_time.tv_sec = -1;
+ _thread_run->wakeup_time.tv_nsec = -1;
+
+ /* Increment spinblock count: */
+ _spinblock_count++;
+
+ /* fall through */
+ case PS_FDR_WAIT:
+ case PS_FDW_WAIT:
+ case PS_POLL_WAIT:
+ case PS_SELECT_WAIT:
+ /* Restart the time slice: */
+ _thread_run->slice_usec = -1;
+
+ /* Insert into the waiting queue: */
+ PTHREAD_WAITQ_INSERT(_thread_run);
+
+ /* Insert into the work queue: */
+ PTHREAD_WORKQ_INSERT(_thread_run);
+ break;
+ }
+ }
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+
+ /*
+ * Poll file descriptors to update the state of threads
+ * waiting on file I/O where data may be available:
+ */
+ _thread_kern_poll(0);
+
+ /* Protect the scheduling queues: */
+ _queue_signals = 1;
+
+ /*
+ * Wake up threads that have timedout. This has to be
+ * done after polling in case a thread does a poll or
+ * select with zero time.
+ */
+ PTHREAD_WAITQ_SETACTIVE();
+ while (((pthread = TAILQ_FIRST(&_waitingq)) != NULL) &&
+ (pthread->wakeup_time.tv_sec != -1) &&
+ (((pthread->wakeup_time.tv_sec == 0) &&
+ (pthread->wakeup_time.tv_nsec == 0)) ||
+ (pthread->wakeup_time.tv_sec < ts.tv_sec) ||
+ ((pthread->wakeup_time.tv_sec == ts.tv_sec) &&
+ (pthread->wakeup_time.tv_nsec <= ts.tv_nsec)))) {
+ switch (pthread->state) {
+ case PS_POLL_WAIT:
+ case PS_SELECT_WAIT:
+ /* Return zero file descriptors ready: */
+ pthread->data.poll_data->nfds = 0;
+ /* fall through */
+ default:
+ /*
+ * Remove this thread from the waiting queue
+ * (and work queue if necessary) and place it
+ * in the ready queue.
+ */
+ PTHREAD_WAITQ_CLEARACTIVE();
+ if (pthread->flags & PTHREAD_FLAGS_IN_WORKQ)
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread, PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+ break;
+ }
+ /*
+ * Flag the timeout in the thread structure:
+ */
+ pthread->timeout = 1;
+ }
+ PTHREAD_WAITQ_CLEARACTIVE();
+
+ /*
+ * Check if there is a current runnable thread that isn't
+ * already in the ready queue:
+ */
+ if ((_thread_run != &_thread_kern_thread) &&
+ (_thread_run->state == PS_RUNNING) &&
+ ((_thread_run->flags & PTHREAD_FLAGS_IN_PRIOQ) == 0)) {
+ if (_thread_run->slice_usec == -1) {
+ /*
+ * The thread exceeded its time
+ * quantum or it yielded the CPU;
+ * place it at the tail of the
+ * queue for its priority.
+ */
+ PTHREAD_PRIOQ_INSERT_TAIL(_thread_run);
+ } else {
+ /*
+ * The thread hasn't exceeded its
+ * interval. Place it at the head
+ * of the queue for its priority.
+ */
+ PTHREAD_PRIOQ_INSERT_HEAD(_thread_run);
+ }
+ }
+
+ /*
+ * Get the highest priority thread in the ready queue.
+ */
+ pthread_h = PTHREAD_PRIOQ_FIRST();
+
+ /* Check if there are no threads ready to run: */
+ if (pthread_h == NULL) {
+ /*
+ * Lock the pthread kernel by changing the pointer to
+ * the running thread to point to the global kernel
+ * thread structure:
+ */
+ _thread_run = &_thread_kern_thread;
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+
+ /*
+ * There are no threads ready to run, so wait until
+ * something happens that changes this condition:
+ */
+ _thread_kern_poll(1);
+ }
+ else {
+ /* Remove the thread from the ready queue: */
+ PTHREAD_PRIOQ_REMOVE(pthread_h);
+
+ /* Get first thread on the waiting list: */
+ pthread = TAILQ_FIRST(&_waitingq);
+
+ /* Check to see if there is more than one thread: */
+ if (pthread_h != TAILQ_FIRST(&_thread_list) ||
+ TAILQ_NEXT(pthread_h, tle) != NULL)
+ set_timer = 1;
+ else
+ set_timer = 0;
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+
+ /*
+ * Check for signals queued while the scheduling
+ * queues were protected:
+ */
+ while (_sigq_check_reqd != 0) {
+ /* Clear before handling queued signals: */
+ _sigq_check_reqd = 0;
+
+ /* Protect the scheduling queues again: */
+ _queue_signals = 1;
+
+ dequeue_signals();
+
+ /*
+ * Check for a higher priority thread that
+ * became runnable due to signal handling.
+ */
+ if (((pthread = PTHREAD_PRIOQ_FIRST()) != NULL) &&
+ (pthread->active_priority > pthread_h->active_priority)) {
+ /*
+ * Insert the lower priority thread
+ * at the head of its priority list:
+ */
+ PTHREAD_PRIOQ_INSERT_HEAD(pthread_h);
+
+ /* Remove the thread from the ready queue: */
+ PTHREAD_PRIOQ_REMOVE(pthread);
+
+ /* There's a new thread in town: */
+ pthread_h = pthread;
+ }
+
+ /* Get first thread on the waiting list: */
+ pthread = TAILQ_FIRST(&_waitingq);
+
+ /*
+ * Check to see if there is more than one
+ * thread:
+ */
+ if (pthread_h != TAILQ_FIRST(&_thread_list) ||
+ TAILQ_NEXT(pthread_h, tle) != NULL)
+ set_timer = 1;
+ else
+ set_timer = 0;
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+ }
+
+ /* Make the selected thread the current thread: */
+ _thread_run = pthread_h;
+
+ /*
+ * Save the current time as the time that the thread
+ * became active:
+ */
+ _thread_run->last_active.tv_sec = tv.tv_sec;
+ _thread_run->last_active.tv_usec = tv.tv_usec;
+
+ /*
+ * Define the maximum time before a scheduling signal
+ * is required:
+ */
+ itimer.it_value.tv_sec = 0;
+ itimer.it_value.tv_usec = TIMESLICE_USEC;
+
+ /*
+ * The interval timer is not reloaded when it
+ * times out. The interval time needs to be
+ * calculated every time.
+ */
+ itimer.it_interval.tv_sec = 0;
+ itimer.it_interval.tv_usec = 0;
+
+ /* Get first thread on the waiting list: */
+ if ((pthread != NULL) &&
+ (pthread->wakeup_time.tv_sec != -1)) {
+ /*
+ * Calculate the time until this thread
+ * is ready, allowing for the clock
+ * resolution:
+ */
+ ts1.tv_sec = pthread->wakeup_time.tv_sec
+ - ts.tv_sec;
+ ts1.tv_nsec = pthread->wakeup_time.tv_nsec
+ - ts.tv_nsec + _clock_res_nsec;
+
+ /*
+ * Check for underflow of the nanosecond field:
+ */
+ while (ts1.tv_nsec < 0) {
+ /*
+ * Allow for the underflow of the
+ * nanosecond field:
+ */
+ ts1.tv_sec--;
+ ts1.tv_nsec += 1000000000;
+ }
+ /*
+ * Check for overflow of the nanosecond field:
+ */
+ while (ts1.tv_nsec >= 1000000000) {
+ /*
+ * Allow for the overflow of the
+ * nanosecond field:
+ */
+ ts1.tv_sec++;
+ ts1.tv_nsec -= 1000000000;
+ }
+ /*
+ * Convert the timespec structure to a
+ * timeval structure:
+ */
+ TIMESPEC_TO_TIMEVAL(&tv1, &ts1);
+
+ /*
+ * Check if the thread will be ready
+ * sooner than the earliest ones found
+ * so far:
+ */
+ if (timercmp(&tv1, &itimer.it_value, <)) {
+ /*
+ * Update the time value:
+ */
+ itimer.it_value.tv_sec = tv1.tv_sec;
+ itimer.it_value.tv_usec = tv1.tv_usec;
+ }
+ }
+
+ /*
+ * Check if this thread is running for the first time
+ * or running again after using its full time slice
+ * allocation:
+ */
+ if (_thread_run->slice_usec == -1) {
+ /* Reset the accumulated time slice period: */
+ _thread_run->slice_usec = 0;
+ }
+
+ /* Check if there is more than one thread: */
+ if (set_timer != 0) {
+ /*
+ * Start the interval timer for the
+ * calculated time interval:
+ */
+ if (setitimer(_ITIMER_SCHED_TIMER, &itimer, NULL) != 0) {
+ /*
+ * Cannot initialise the timer, so
+ * abort this process:
+ */
+ PANIC("Cannot set scheduling timer");
+ }
+ }
+
+ /* Check if a signal context was saved: */
+ if (_thread_run->sig_saved == 1) {
+#ifndef __alpha__
+ /*
+ * Point to the floating point data in the
+ * running thread:
+ */
+ fdata = _thread_run->saved_fp;
+
+ /* Restore the floating point state: */
+ __asm__("frstor %0": :"m"(*fdata));
+#endif
+ /*
+ * Do a sigreturn to restart the thread that
+ * was interrupted by a signal:
+ */
+ _thread_kern_in_sched = 0;
+
+ /*
+ * If we had a context switch, run any
+ * installed switch hooks.
+ */
+ if ((_sched_switch_hook != NULL) &&
+ (_last_user_thread != _thread_run)) {
+ thread_run_switch_hook(_last_user_thread,
+ _thread_run);
+ }
+ _thread_sys_sigreturn(&_thread_run->saved_sigcontext);
+ } else {
+ /*
+ * Do a longjmp to restart the thread that
+ * was context switched out (by a longjmp to
+ * a different thread):
+ */
+ longjmp(_thread_run->saved_jmp_buf, 1);
+ }
+
+ /* This point should not be reached. */
+ PANIC("Thread has returned from sigreturn or longjmp");
+ }
+ }
+
+ /* There are no more threads, so exit this process: */
+ exit(0);
+}
+
+void
+_thread_kern_sched_state(enum pthread_state state, char *fname, int lineno)
+{
+ /*
+ * Flag the pthread kernel as executing scheduler code
+ * to avoid a scheduler signal from interrupting this
+ * execution and calling the scheduler again.
+ */
+ _thread_kern_in_sched = 1;
+
+ /*
+ * Prevent the signal handler from fiddling with this thread
+ * before its state is set and is placed into the proper queue.
+ */
+ _queue_signals = 1;
+
+ /* Change the state of the current thread: */
+ _thread_run->state = state;
+ _thread_run->fname = fname;
+ _thread_run->lineno = lineno;
+
+ /* Schedule the next thread that is ready: */
+ _thread_kern_sched(NULL);
+ return;
+}
+
+void
+_thread_kern_sched_state_unlock(enum pthread_state state,
+ spinlock_t *lock, char *fname, int lineno)
+{
+ /*
+ * Flag the pthread kernel as executing scheduler code
+ * to avoid a scheduler signal from interrupting this
+ * execution and calling the scheduler again.
+ */
+ _thread_kern_in_sched = 1;
+
+ /*
+ * Prevent the signal handler from fiddling with this thread
+ * before its state is set and it is placed into the proper
+ * queue(s).
+ */
+ _queue_signals = 1;
+
+ /* Change the state of the current thread: */
+ _thread_run->state = state;
+ _thread_run->fname = fname;
+ _thread_run->lineno = lineno;
+
+ _SPINUNLOCK(lock);
+
+ /* Schedule the next thread that is ready: */
+ _thread_kern_sched(NULL);
+ return;
+}
+
+static void
+_thread_kern_poll(int wait_reqd)
+{
+ int count = 0;
+ int i, found;
+ int kern_pipe_added = 0;
+ int nfds = 0;
+ int timeout_ms = 0;
+ struct pthread *pthread;
+ struct timespec ts;
+ struct timeval tv;
+
+ /* Check if the caller wants to wait: */
+ if (wait_reqd == 0) {
+ timeout_ms = 0;
+ }
+ else {
+ /* Get the current time of day: */
+ gettimeofday(&tv, NULL);
+ TIMEVAL_TO_TIMESPEC(&tv, &ts);
+
+ _queue_signals = 1;
+ pthread = TAILQ_FIRST(&_waitingq);
+ _queue_signals = 0;
+
+ if ((pthread == NULL) || (pthread->wakeup_time.tv_sec == -1)) {
+ /*
+ * Either there are no threads in the waiting queue,
+ * or there are no threads that can timeout.
+ */
+ timeout_ms = INFTIM;
+ }
+ else {
+ /*
+ * Calculate the time left for the next thread to
+ * timeout allowing for the clock resolution:
+ */
+ timeout_ms = ((pthread->wakeup_time.tv_sec - ts.tv_sec) *
+ 1000) + ((pthread->wakeup_time.tv_nsec - ts.tv_nsec +
+ _clock_res_nsec) / 1000000);
+ /*
+ * Don't allow negative timeouts:
+ */
+ if (timeout_ms < 0)
+ timeout_ms = 0;
+ }
+ }
+
+ /* Protect the scheduling queues: */
+ _queue_signals = 1;
+
+ /*
+ * Check to see if the signal queue needs to be walked to look
+ * for threads awoken by a signal while in the scheduler.
+ */
+ if (_sigq_check_reqd != 0) {
+ /* Reset flag before handling queued signals: */
+ _sigq_check_reqd = 0;
+
+ dequeue_signals();
+ }
+
+ /*
+ * Check for a thread that became runnable due to a signal:
+ */
+ if (PTHREAD_PRIOQ_FIRST() != NULL) {
+ /*
+ * Since there is at least one runnable thread,
+ * disable the wait.
+ */
+ timeout_ms = 0;
+ }
+
+ /*
+ * Form the poll table:
+ */
+ nfds = 0;
+ if (timeout_ms != 0) {
+ /* Add the kernel pipe to the poll table: */
+ _thread_pfd_table[nfds].fd = _thread_kern_pipe[0];
+ _thread_pfd_table[nfds].events = POLLRDNORM;
+ _thread_pfd_table[nfds].revents = 0;
+ nfds++;
+ kern_pipe_added = 1;
+ }
+
+ PTHREAD_WAITQ_SETACTIVE();
+ TAILQ_FOREACH(pthread, &_workq, qe) {
+ switch (pthread->state) {
+ case PS_SPINBLOCK:
+ /*
+ * If the lock is available, let the thread run.
+ */
+ if (pthread->data.spinlock->access_lock == 0) {
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+ /* One less thread in a spinblock state: */
+ _spinblock_count--;
+ /*
+ * Since there is at least one runnable
+ * thread, disable the wait.
+ */
+ timeout_ms = 0;
+ }
+ break;
+
+ /* File descriptor read wait: */
+ case PS_FDR_WAIT:
+ /* Limit number of polled files to table size: */
+ if (nfds < _thread_dtablesize) {
+ _thread_pfd_table[nfds].events = POLLRDNORM;
+ _thread_pfd_table[nfds].fd = pthread->data.fd.fd;
+ nfds++;
+ }
+ break;
+
+ /* File descriptor write wait: */
+ case PS_FDW_WAIT:
+ /* Limit number of polled files to table size: */
+ if (nfds < _thread_dtablesize) {
+ _thread_pfd_table[nfds].events = POLLWRNORM;
+ _thread_pfd_table[nfds].fd = pthread->data.fd.fd;
+ nfds++;
+ }
+ break;
+
+ /* File descriptor poll or select wait: */
+ case PS_POLL_WAIT:
+ case PS_SELECT_WAIT:
+ /* Limit number of polled files to table size: */
+ if (pthread->data.poll_data->nfds + nfds <
+ _thread_dtablesize) {
+ for (i = 0; i < pthread->data.poll_data->nfds; i++) {
+ _thread_pfd_table[nfds + i].fd =
+ pthread->data.poll_data->fds[i].fd;
+ _thread_pfd_table[nfds + i].events =
+ pthread->data.poll_data->fds[i].events;
+ }
+ nfds += pthread->data.poll_data->nfds;
+ }
+ break;
+
+ /* Other states do not depend on file I/O. */
+ default:
+ break;
+ }
+ }
+ PTHREAD_WAITQ_CLEARACTIVE();
+
+ /*
+ * Wait for a file descriptor to be ready for read, write, or
+ * an exception, or a timeout to occur:
+ */
+ count = _thread_sys_poll(_thread_pfd_table, nfds, timeout_ms);
+
+ if (kern_pipe_added != 0)
+ /*
+ * Remove the pthread kernel pipe file descriptor
+ * from the pollfd table:
+ */
+ nfds = 1;
+ else
+ nfds = 0;
+
+ /*
+ * Check if it is possible that there are bytes in the kernel
+ * read pipe waiting to be read:
+ */
+ if (count < 0 || ((kern_pipe_added != 0) &&
+ (_thread_pfd_table[0].revents & POLLRDNORM))) {
+ /*
+ * If the kernel read pipe was included in the
+ * count:
+ */
+ if (count > 0) {
+ /* Decrement the count of file descriptors: */
+ count--;
+ }
+
+ if (_sigq_check_reqd != 0) {
+ /* Reset flag before handling signals: */
+ _sigq_check_reqd = 0;
+
+ dequeue_signals();
+ }
+ }
+
+ /*
+ * Check if any file descriptors are ready:
+ */
+ if (count > 0) {
+ /*
+ * Enter a loop to look for threads waiting on file
+ * descriptors that are flagged as available by the
+ * _poll syscall:
+ */
+ PTHREAD_WAITQ_SETACTIVE();
+ TAILQ_FOREACH(pthread, &_workq, qe) {
+ switch (pthread->state) {
+ case PS_SPINBLOCK:
+ /*
+ * If the lock is available, let the thread run.
+ */
+ if (pthread->data.spinlock->access_lock == 0) {
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+
+ /*
+ * One less thread in a spinblock state:
+ */
+ _spinblock_count--;
+ }
+ break;
+
+ /* File descriptor read wait: */
+ case PS_FDR_WAIT:
+ if ((nfds < _thread_dtablesize) &&
+ (_thread_pfd_table[nfds].revents & POLLRDNORM)) {
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+ }
+ nfds++;
+ break;
+
+ /* File descriptor write wait: */
+ case PS_FDW_WAIT:
+ if ((nfds < _thread_dtablesize) &&
+ (_thread_pfd_table[nfds].revents & POLLWRNORM)) {
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+ }
+ nfds++;
+ break;
+
+ /* File descriptor poll or select wait: */
+ case PS_POLL_WAIT:
+ case PS_SELECT_WAIT:
+ if (pthread->data.poll_data->nfds + nfds <
+ _thread_dtablesize) {
+ /*
+ * Enter a loop looking for I/O
+ * readiness:
+ */
+ found = 0;
+ for (i = 0; i < pthread->data.poll_data->nfds; i++) {
+ if (_thread_pfd_table[nfds + i].revents != 0) {
+ pthread->data.poll_data->fds[i].revents =
+ _thread_pfd_table[nfds + i].revents;
+ found++;
+ }
+ }
+
+ /* Increment before destroying: */
+ nfds += pthread->data.poll_data->nfds;
+
+ if (found != 0) {
+ pthread->data.poll_data->nfds = found;
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+ }
+ }
+ else
+ nfds += pthread->data.poll_data->nfds;
+ break;
+
+ /* Other states do not depend on file I/O. */
+ default:
+ break;
+ }
+ }
+ PTHREAD_WAITQ_CLEARACTIVE();
+ }
+ else if (_spinblock_count != 0) {
+ /*
+ * Enter a loop to look for threads waiting on a spinlock
+ * that is now available.
+ */
+ PTHREAD_WAITQ_SETACTIVE();
+ TAILQ_FOREACH(pthread, &_workq, qe) {
+ if (pthread->state == PS_SPINBLOCK) {
+ /*
+ * If the lock is available, let the thread run.
+ */
+ if (pthread->data.spinlock->access_lock == 0) {
+ PTHREAD_WAITQ_CLEARACTIVE();
+ PTHREAD_WORKQ_REMOVE(pthread);
+ PTHREAD_NEW_STATE(pthread,PS_RUNNING);
+ PTHREAD_WAITQ_SETACTIVE();
+
+ /*
+ * One less thread in a spinblock state:
+ */
+ _spinblock_count--;
+ }
+ }
+ }
+ PTHREAD_WAITQ_CLEARACTIVE();
+ }
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+
+ while (_sigq_check_reqd != 0) {
+ /* Handle queued signals: */
+ _sigq_check_reqd = 0;
+
+ /* Protect the scheduling queues: */
+ _queue_signals = 1;
+
+ dequeue_signals();
+
+ /* Unprotect the scheduling queues: */
+ _queue_signals = 0;
+ }
+
+ /* Nothing to return. */
+ return;
+}
+
+void
+_thread_kern_set_timeout(struct timespec * timeout)
+{
+ struct timespec current_time;
+ struct timeval tv;
+
+ /* Reset the timeout flag for the running thread: */
+ _thread_run->timeout = 0;
+
+ /* Check if the thread is to wait forever: */
+ if (timeout == NULL) {
+ /*
+ * Set the wakeup time to something that can be recognised as
+ * different to an actual time of day:
+ */
+ _thread_run->wakeup_time.tv_sec = -1;
+ _thread_run->wakeup_time.tv_nsec = -1;
+ }
+ /* Check if no waiting is required: */
+ else if (timeout->tv_sec == 0 && timeout->tv_nsec == 0) {
+ /* Set the wake up time to 'immediately': */
+ _thread_run->wakeup_time.tv_sec = 0;
+ _thread_run->wakeup_time.tv_nsec = 0;
+ } else {
+ /* Get the current time: */
+ gettimeofday(&tv, NULL);
+ TIMEVAL_TO_TIMESPEC(&tv, &current_time);
+
+ /* Calculate the time for the current thread to wake up: */
+ _thread_run->wakeup_time.tv_sec = current_time.tv_sec + timeout->tv_sec;
+ _thread_run->wakeup_time.tv_nsec = current_time.tv_nsec + timeout->tv_nsec;
+
+ /* Check if the nanosecond field needs to wrap: */
+ if (_thread_run->wakeup_time.tv_nsec >= 1000000000) {
+ /* Wrap the nanosecond field: */
+ _thread_run->wakeup_time.tv_sec += 1;
+ _thread_run->wakeup_time.tv_nsec -= 1000000000;
+ }
+ }
+ return;
+}
+
+void
+_thread_kern_sig_defer(void)
+{
+ /* Allow signal deferral to be recursive. */
+ _thread_run->sig_defer_count++;
+}
+
+void
+_thread_kern_sig_undefer(void)
+{
+ pthread_t pthread;
+ int need_resched = 0;
+
+ /*
+ * Perform checks to yield only if we are about to undefer
+ * signals.
+ */
+ if (_thread_run->sig_defer_count > 1) {
+ /* Decrement the signal deferral count. */
+ _thread_run->sig_defer_count--;
+ }
+ else if (_thread_run->sig_defer_count == 1) {
+ /* Reenable signals: */
+ _thread_run->sig_defer_count = 0;
+
+ /*
+ * Check if there are queued signals:
+ */
+ while (_sigq_check_reqd != 0) {
+ /* Defer scheduling while we process queued signals: */
+ _thread_run->sig_defer_count = 1;
+
+ /* Clear the flag before checking the signal queue: */
+ _sigq_check_reqd = 0;
+
+ /* Dequeue and handle signals: */
+ dequeue_signals();
+
+ /*
+ * Avoiding an unnecessary check to reschedule, check
+ * to see if signal handling caused a higher priority
+ * thread to become ready.
+ */
+ if ((need_resched == 0) &&
+ (((pthread = PTHREAD_PRIOQ_FIRST()) != NULL) &&
+ (pthread->active_priority > _thread_run->active_priority))) {
+ need_resched = 1;
+ }
+
+ /* Reenable signals: */
+ _thread_run->sig_defer_count = 0;
+ }
+
+ /* Yield the CPU if necessary: */
+ if (need_resched || _thread_run->yield_on_sig_undefer != 0) {
+ _thread_run->yield_on_sig_undefer = 0;
+ _thread_kern_sched(NULL);
+ }
+ }
+}
+
+static void
+dequeue_signals(void)
+{
+ char bufr[128];
+ int i, num;
+ pthread_t pthread;
+
+ /*
+ * Enter a loop to read and handle queued signals from the
+ * pthread kernel pipe:
+ */
+ while (((num = _thread_sys_read(_thread_kern_pipe[0], bufr,
+ sizeof(bufr))) > 0) || (num == -1 && errno == EINTR)) {
+ /*
+ * The buffer read contains one byte per signal and
+ * each byte is the signal number.
+ */
+ for (i = 0; i < num; i++) {
+ if ((int) bufr[i] == _SCHED_SIGNAL) {
+ /*
+ * Scheduling signals shouldn't ever be
+ * queued; just ignore it for now.
+ */
+ }
+ else {
+ /* Handle this signal: */
+ pthread = _thread_sig_handle((int) bufr[i],
+ NULL);
+ if (pthread != NULL)
+ _thread_sig_deliver(pthread,
+ (int) bufr[i]);
+ }
+ }
+ }
+ if ((num < 0) && (errno != EAGAIN)) {
+ /*
+ * The only error we should expect is if there is
+ * no data to read.
+ */
+ PANIC("Unable to read from thread kernel pipe");
+ }
+}
+
+static inline void
+thread_run_switch_hook(pthread_t thread_out, pthread_t thread_in)
+{
+ pthread_t tid_out = thread_out;
+ pthread_t tid_in = thread_in;
+
+ if ((tid_out != NULL) &&
+ (tid_out->flags & PTHREAD_FLAGS_PRIVATE) != 0)
+ tid_out = NULL;
+ if ((tid_in != NULL) &&
+ (tid_in->flags & PTHREAD_FLAGS_PRIVATE) != 0)
+ tid_in = NULL;
+
+ if ((_sched_switch_hook != NULL) && (tid_out != tid_in)) {
+ /* Run the scheduler switch hook: */
+ _sched_switch_hook(tid_out, tid_in);
+ }
+}
+#endif
OpenPOWER on IntegriCloud