5bf7ce284b
----------------------------- Most of the userland changes are in libc. For both the alpha and the i386 setjmp has been changed to accomodate for the new sigset_t. Internally, libc is mostly rewritten to use the new syscalls. The exception is in compat-43/sigcompat.c The POSIX thread library has also been rewritten to use the new sigset_t. Except, that it currently only handles NSIG signals instead of the maximum _SIG_MAXSIG. This should not be a problem because current applications don't use any signals higher than NSIG. There are version bumps for the following libraries: libdialog libreadline libc libc_r libedit libftpio libss These libraries either a) have one of the modified structures visible in the interface, or b) use sigset_t internally and may cause breakage if new binaries are used against libraries that don't have the sigset_t change. This not an immediate issue, but will be as soon as applications start using the new range to its fullest. NOTE: libncurses already had an version bump and has not been given one now. NOTE: doscmd is a real casualty and has been disconnected for the moment. Reconnection will eventually happen after doscmd has been fixed. I'm aware that being the last one to touch it, I'm automaticly promoted to being maintainer. According to good taste this means that I will receive a badge which either will be glued or mechanically stapled, drilled or otherwise violently forced onto me :-) NOTE: pcvt/vttest cannot be compiled with -traditional. The change cause sys/types to be included along the way which contains the const and volatile modifiers. I don't consider this a solution, but more a workaround.
1118 lines
28 KiB
C
1118 lines
28 KiB
C
/*
|
|
* 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;
|
|
pthread_t last_thread = NULL;
|
|
struct itimerval itimer;
|
|
struct timespec ts, ts1;
|
|
struct timeval tv, tv1;
|
|
int i, 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 (_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:
|
|
/*
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
char bufr[128];
|
|
int count = 0;
|
|
int i, found;
|
|
int kern_pipe_added = 0;
|
|
int nfds = 0;
|
|
int timeout_ms = 0;
|
|
struct pthread *pthread, *pthread_next;
|
|
ssize_t num;
|
|
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, ¤t_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;
|
|
|
|
/*
|
|
* 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: */
|
|
_thread_sig_handle((int) bufr[i], NULL);
|
|
}
|
|
}
|
|
}
|
|
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
|