mirror of
https://github.com/freebsd/freebsd-src.git
synced 2024-12-02 15:03:41 +00:00
1d013a86ed
thread waiting on an event (I/O, condvar, etc) will, when resumed using pthread_resume_np, return with EINTR. For example, suspending and resuming a thread blocked on read() will not requeue the thread for the read, but will return -1 with errno = EINTR. If the suspended thread is in a critical region, the thread is suspended as soon as it leaves the critical region. Fix a bogon in pthread_kill() where a signal was being delivered twice to threads waiting in sigwait(). Reported by (suspend/resume bug): jdp Reviewed by: jasone
1175 lines
30 KiB
C
1175 lines
30 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/param.h>
|
|
#include <sys/types.h>
|
|
#include <sys/signalvar.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();
|
|
}
|
|
|
|
/*
|
|
* Check for undispatched signals due to calls to
|
|
* pthread_kill().
|
|
*/
|
|
if (SIGNOTEMPTY(_thread_run->sigpend))
|
|
_dispatch_signals();
|
|
|
|
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 */
|
|
case PS_SUSPENDED:
|
|
/*
|
|
* Dead and suspended 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_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 this thread is being continued from a
|
|
* longjmp() out of a signal handler:
|
|
*/
|
|
if ((_thread_run->jmpflags & JMPFLAGS_LONGJMP) != 0) {
|
|
_thread_run->jmpflags = 0;
|
|
__longjmp(_thread_run->nested_jmp.jmp,
|
|
_thread_run->longjmp_val);
|
|
}
|
|
/*
|
|
* Check if this thread is being continued from a
|
|
* _longjmp() out of a signal handler:
|
|
*/
|
|
else if ((_thread_run->jmpflags & JMPFLAGS__LONGJMP) !=
|
|
0) {
|
|
_thread_run->jmpflags = 0;
|
|
___longjmp(_thread_run->nested_jmp.jmp,
|
|
_thread_run->longjmp_val);
|
|
}
|
|
/*
|
|
* Check if this thread is being continued from a
|
|
* siglongjmp() out of a signal handler:
|
|
*/
|
|
else if ((_thread_run->jmpflags & JMPFLAGS_SIGLONGJMP)
|
|
!= 0) {
|
|
_thread_run->jmpflags = 0;
|
|
__siglongjmp(
|
|
_thread_run->nested_jmp.sigjmp,
|
|
_thread_run->longjmp_val);
|
|
}
|
|
/* Check if a signal context was saved: */
|
|
else 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, ¤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;
|
|
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
|