diff options
author | jb <jb@FreeBSD.org> | 1999-03-23 05:07:56 +0000 |
---|---|---|
committer | jb <jb@FreeBSD.org> | 1999-03-23 05:07:56 +0000 |
commit | a451f520589b5d161425414e8a94a839bffaaa09 (patch) | |
tree | d7d218b8db8e2a200c59653341084c3edc695df6 /lib/libkse | |
parent | 7979064d1d122f5fba48290aeb83ac2e3e7a018f (diff) | |
download | FreeBSD-src-a451f520589b5d161425414e8a94a839bffaaa09.zip FreeBSD-src-a451f520589b5d161425414e8a94a839bffaaa09.tar.gz |
[ The author's description... ]
o Runnable threads are now maintained in priority queues. The
implementation requires two things:
1.) The priority queues must be protected during insertion
and removal of threads. Since the kernel scheduler
must modify the priority queues, a spinlock for
protection cannot be used. The functions
_thread_kern_sched_defer() and _thread_kern_sched_undefer()
were added to {un}defer kernel scheduler activation.
2.) A thread (active) priority change can be performed only
when the thread is removed from the priority queue. The
implementation uses a threads active priority when
inserting it into the queue.
A by-product is that thread switches are much faster. A
separate queue is used for waiting and/or blocked threads,
and it is searched at most 2 times in the kernel scheduler
when there are active threads. It should be possible to
reduce this to once by combining polling of threads waiting
on I/O with the loop that looks for timed out threads and
the minimum timeout value.
o Functions to defer kernel scheduler activation were added. These
are _thread_kern_sched_defer() and _thread_kern_sched_undefer()
and may be called recursively. These routines do not block the
scheduling signal, but latch its occurrence. The signal handler
will not call the kernel scheduler when the running thread has
deferred scheduling, but it will be called when running thread
undefers scheduling.
o Added support for _POSIX_THREAD_PRIORITY_SCHEDULING. All the
POSIX routines required by this should now be implemented.
One note, SCHED_OTHER, SCHED_FIFO, and SCHED_RR are required
to be defined by including pthread.h. These defines are currently
in sched.h. I modified pthread.h to include sched.h but don't
know if this is the proper thing to do.
o Added support for priority protection and inheritence mutexes.
This allows definition of _POSIX_THREAD_PRIO_PROTECT and
_POSIX_THREAD_PRIO_INHERIT.
o Added additional error checks required by POSIX for mutexes and
condition variables.
o Provided a wrapper for sigpending which is marked as a hidden
syscall.
o Added a non-portable function as a debugging aid to allow an
application to monitor thread context switches. An application
can install a routine that gets called everytime a thread
(explicitly created by the application) gets context switched.
The routine gets passed the pthread IDs of the threads that are
being switched in and out.
Submitted by: Dan Eischen <eischen@vigrid.com>
Changes by me:
o Added a PS_SPINBLOCK state to deal with the priority inversion
problem most often (I think) seen by threads calling malloc/free/realloc.
o Dispatch signals to the running thread directly rather than at a
context switch to avoid the situation where the switch never occurs.
Diffstat (limited to 'lib/libkse')
37 files changed, 3157 insertions, 667 deletions
diff --git a/lib/libkse/thread/Makefile.inc b/lib/libkse/thread/Makefile.inc index 004982a..16799cf 100644 --- a/lib/libkse/thread/Makefile.inc +++ b/lib/libkse/thread/Makefile.inc @@ -1,4 +1,4 @@ -# $Id: Makefile.inc,v 1.15 1998/09/12 22:03:20 dt Exp $ +# $Id: Makefile.inc,v 1.16 1998/09/30 06:36:55 jb Exp $ # uthread sources .PATH: ${.CURDIR}/uthread @@ -8,10 +8,18 @@ SRCS+= \ uthread_attr_destroy.c \ uthread_attr_init.c \ uthread_attr_getdetachstate.c \ + uthread_attr_getinheritsched.c \ + uthread_attr_getschedparam.c \ + uthread_attr_getschedpolicy.c \ + uthread_attr_getscope.c \ uthread_attr_getstackaddr.c \ uthread_attr_getstacksize.c \ uthread_attr_setcreatesuspend_np.c \ uthread_attr_setdetachstate.c \ + uthread_attr_setinheritsched.c \ + uthread_attr_setschedparam.c \ + uthread_attr_setschedpolicy.c \ + uthread_attr_setscope.c \ uthread_attr_setstackaddr.c \ uthread_attr_setstacksize.c \ uthread_autoinit.cc \ @@ -44,6 +52,7 @@ SRCS+= \ uthread_getdirentries.c \ uthread_getpeername.c \ uthread_getprio.c \ + uthread_getschedparam.c \ uthread_getsockname.c \ uthread_getsockopt.c \ uthread_info.c \ @@ -57,11 +66,14 @@ SRCS+= \ uthread_mattr_kind_np.c \ uthread_multi_np.c \ uthread_mutex.c \ + uthread_mutex_prioceiling.c \ + uthread_mutex_protocol.c \ uthread_mutexattr_destroy.c \ uthread_nanosleep.c \ uthread_once.c \ uthread_open.c \ uthread_pipe.c \ + uthread_priority_queue.c \ uthread_queue.c \ uthread_read.c \ uthread_readv.c \ @@ -76,12 +88,14 @@ SRCS+= \ uthread_sendto.c \ uthread_seterrno.c \ uthread_setprio.c \ + uthread_setschedparam.c \ uthread_setsockopt.c \ uthread_shutdown.c \ uthread_sig.c \ uthread_sigaction.c \ uthread_sigblock.c \ uthread_sigmask.c \ + uthread_sigpending.c \ uthread_sigprocmask.c \ uthread_sigsetmask.c \ uthread_sigsuspend.c \ @@ -92,6 +106,7 @@ SRCS+= \ uthread_spec.c \ uthread_spinlock.c \ uthread_suspend_np.c \ + uthread_switch_np.c \ uthread_vfork.c \ uthread_wait4.c \ uthread_write.c \ diff --git a/lib/libkse/thread/thr_attr_getinheritsched.c b/lib/libkse/thread/thr_attr_getinheritsched.c new file mode 100644 index 0000000..38851ca --- /dev/null +++ b/lib/libkse/thread/thr_attr_getinheritsched.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_getinheritsched(pthread_attr_t *attr, int *sched_inherit) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL)) + ret = EINVAL; + else + *sched_inherit = (*attr)->sched_inherit; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_getschedparam.c b/lib/libkse/thread/thr_attr_getschedparam.c new file mode 100644 index 0000000..ea5e19d --- /dev/null +++ b/lib/libkse/thread/thr_attr_getschedparam.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || (param == NULL)) + ret = EINVAL; + else + param->sched_priority = (*attr)->prio; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_getschedpolicy.c b/lib/libkse/thread/thr_attr_getschedpolicy.c new file mode 100644 index 0000000..0b9ff59 --- /dev/null +++ b/lib/libkse/thread/thr_attr_getschedpolicy.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || (policy == NULL)) + ret = EINVAL; + else + *policy = (*attr)->sched_policy; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_getscope.c b/lib/libkse/thread/thr_attr_getscope.c new file mode 100644 index 0000000..f84b104 --- /dev/null +++ b/lib/libkse/thread/thr_attr_getscope.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_getscope(pthread_attr_t *attr, int *contentionscope) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || (contentionscope == NULL)) + /* Return an invalid argument: */ + ret = EINVAL; + + else + *contentionscope = (*attr)->flags & PTHREAD_SCOPE_SYSTEM ? + PTHREAD_SCOPE_SYSTEM : PTHREAD_SCOPE_PROCESS; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_setinheritsched.c b/lib/libkse/thread/thr_attr_setinheritsched.c new file mode 100644 index 0000000..017b7df --- /dev/null +++ b/lib/libkse/thread/thr_attr_setinheritsched.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_setinheritsched(pthread_attr_t *attr, int sched_inherit) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL)) + ret = EINVAL; + else + (*attr)->sched_inherit = sched_inherit; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_setschedparam.c b/lib/libkse/thread/thr_attr_setschedparam.c new file mode 100644 index 0000000..5c860a8 --- /dev/null +++ b/lib/libkse/thread/thr_attr_setschedparam.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || (param == NULL)) + ret = EINVAL; + else + (*attr)->prio = param->sched_priority; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_setschedpolicy.c b/lib/libkse/thread/thr_attr_setschedpolicy.c new file mode 100644 index 0000000..3d5aa3c --- /dev/null +++ b/lib/libkse/thread/thr_attr_setschedpolicy.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || (policy < SCHED_FIFO) || + (policy > SCHED_RR)) + ret = EINVAL; + else + (*attr)->sched_policy = policy; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_attr_setscope.c b/lib/libkse/thread/thr_attr_setscope.c new file mode 100644 index 0000000..24dead6 --- /dev/null +++ b/lib/libkse/thread/thr_attr_setscope.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_attr_setscope(pthread_attr_t *attr, int contentionscope) +{ + int ret = 0; + + if ((attr == NULL) || (*attr == NULL) || + (contentionscope != PTHREAD_SCOPE_PROCESS) || + (contentionscope != PTHREAD_SCOPE_SYSTEM)) + /* Return an invalid argument: */ + ret = EINVAL; + + else if (contentionscope == PTHREAD_SCOPE_SYSTEM) + /* We don't support system wide contention: */ +#ifdef NOT_YET + ret = ENOTSUP; +#else + ret = EOPNOTSUPP; +#endif + + else + (*attr)->flags |= contentionscope; + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_cond.c b/lib/libkse/thread/thr_cond.c index a085ea6..c090d79 100644 --- a/lib/libkse/thread/thr_cond.c +++ b/lib/libkse/thread/thr_cond.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -37,6 +37,14 @@ #include <pthread.h> #include "pthread_private.h" +/* + * Prototypes + */ +static inline pthread_t cond_queue_deq(pthread_cond_t); +static inline void cond_queue_remove(pthread_cond_t, pthread_t); +static inline void cond_queue_enq(pthread_cond_t, pthread_t); + + int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * cond_attr) { @@ -83,9 +91,10 @@ pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * cond_attr) * Initialise the condition variable * structure: */ - _thread_queue_init(&pcond->c_queue); + TAILQ_INIT(&pcond->c_queue); pcond->c_flags |= COND_FLAGS_INITED; pcond->c_type = type; + pcond->c_mutex = NULL; memset(&pcond->lock,0,sizeof(pcond->lock)); *cond = pcond; } @@ -144,33 +153,57 @@ pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex) switch ((*cond)->c_type) { /* Fast condition variable: */ case COND_TYPE_FAST: - /* Wait forever: */ - _thread_run->wakeup_time.tv_sec = -1; - - /* - * Queue the running thread for the condition - * variable: - */ - _thread_queue_enq(&(*cond)->c_queue, _thread_run); - - /* Unlock the mutex: */ - if ((rval = pthread_mutex_unlock(mutex)) != 0) { - /* - * Cannot unlock the mutex, so remove the - * running thread from the condition - * variable queue: - */ - _thread_queue_deq(&(*cond)->c_queue); - + if ((mutex == NULL) || (((*cond)->c_mutex != NULL) && + ((*cond)->c_mutex != *mutex))) { /* Unlock the condition variable structure: */ _SPINUNLOCK(&(*cond)->lock); + + /* Return invalid argument error: */ + rval = EINVAL; } else { - /* Schedule the next thread: */ - _thread_kern_sched_state_unlock(PS_COND_WAIT, - &(*cond)->lock, __FILE__, __LINE__); + /* Reset the timeout flag: */ + _thread_run->timeout = 0; - /* Lock the mutex: */ - rval = pthread_mutex_lock(mutex); + /* + * Queue the running thread for the condition + * variable: + */ + cond_queue_enq(*cond, _thread_run); + + /* Remember the mutex that is being used: */ + (*cond)->c_mutex = *mutex; + + /* Wait forever: */ + _thread_run->wakeup_time.tv_sec = -1; + + /* Unlock the mutex: */ + if ((rval = _mutex_cv_unlock(mutex)) != 0) { + /* + * Cannot unlock the mutex, so remove + * the running thread from the condition + * variable queue: + */ + cond_queue_remove(*cond, _thread_run); + + /* Check for no more waiters: */ + if (TAILQ_FIRST(&(*cond)->c_queue) == + NULL) + (*cond)->c_mutex = NULL; + + /* Unlock the condition variable structure: */ + _SPINUNLOCK(&(*cond)->lock); + } + else { + /* + * Schedule the next thread and unlock + * the condition variable structure: + */ + _thread_kern_sched_state_unlock(PS_COND_WAIT, + &(*cond)->lock, __FILE__, __LINE__); + + /* Lock the mutex: */ + rval = _mutex_cv_lock(mutex); + } } break; @@ -183,7 +216,6 @@ pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex) rval = EINVAL; break; } - } /* Return the completion status: */ @@ -213,42 +245,88 @@ pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, switch ((*cond)->c_type) { /* Fast condition variable: */ case COND_TYPE_FAST: - /* Set the wakeup time: */ - _thread_run->wakeup_time.tv_sec = abstime->tv_sec; - _thread_run->wakeup_time.tv_nsec = abstime->tv_nsec; - - /* Reset the timeout flag: */ - _thread_run->timeout = 0; - - /* - * Queue the running thread for the condition - * variable: - */ - _thread_queue_enq(&(*cond)->c_queue, _thread_run); - - /* Unlock the mutex: */ - if ((rval = pthread_mutex_unlock(mutex)) != 0) { - /* - * Cannot unlock the mutex, so remove the - * running thread from the condition - * variable queue: - */ - _thread_queue_deq(&(*cond)->c_queue); + if ((mutex == NULL) || (((*cond)->c_mutex != NULL) && + ((*cond)->c_mutex != *mutex))) { + /* Return invalid argument error: */ + rval = EINVAL; /* Unlock the condition variable structure: */ _SPINUNLOCK(&(*cond)->lock); } else { - /* Schedule the next thread: */ - _thread_kern_sched_state_unlock(PS_COND_WAIT, - &(*cond)->lock, __FILE__, __LINE__); + /* Set the wakeup time: */ + _thread_run->wakeup_time.tv_sec = + abstime->tv_sec; + _thread_run->wakeup_time.tv_nsec = + abstime->tv_nsec; - /* Lock the mutex: */ - if ((rval = pthread_mutex_lock(mutex)) != 0) { - } - /* Check if the wait timed out: */ - else if (_thread_run->timeout) { - /* Return a timeout error: */ - rval = ETIMEDOUT; + /* Reset the timeout flag: */ + _thread_run->timeout = 0; + + /* + * Queue the running thread for the condition + * variable: + */ + cond_queue_enq(*cond, _thread_run); + + /* Remember the mutex that is being used: */ + (*cond)->c_mutex = *mutex; + + /* Unlock the mutex: */ + if ((rval = _mutex_cv_unlock(mutex)) != 0) { + /* + * Cannot unlock the mutex, so remove + * the running thread from the condition + * variable queue: + */ + cond_queue_remove(*cond, _thread_run); + + /* Check for no more waiters: */ + if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) + (*cond)->c_mutex = NULL; + + /* Unlock the condition variable structure: */ + _SPINUNLOCK(&(*cond)->lock); + } else { + /* + * Schedule the next thread and unlock + * the condition variable structure: + */ + _thread_kern_sched_state_unlock(PS_COND_WAIT, + &(*cond)->lock, __FILE__, __LINE__); + + /* Check if the wait timedout: */ + if (_thread_run->timeout == 0) { + /* Lock the mutex: */ + rval = _mutex_cv_lock(mutex); + } + else { + /* Lock the condition variable structure: */ + _SPINLOCK(&(*cond)->lock); + + /* + * The wait timed out; remove + * the thread from the condition + * variable queue: + */ + cond_queue_remove(*cond, + _thread_run); + + /* Check for no more waiters: */ + if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) + (*cond)->c_mutex = NULL; + + /* Unock the condition variable structure: */ + _SPINUNLOCK(&(*cond)->lock); + + /* Return a timeout error: */ + rval = ETIMEDOUT; + + /* + * Lock the mutex and ignore + * any errors: + */ + (void)_mutex_cv_lock(mutex); + } } } break; @@ -273,7 +351,6 @@ int pthread_cond_signal(pthread_cond_t * cond) { int rval = 0; - int status; pthread_t pthread; if (cond == NULL || *cond == NULL) @@ -286,11 +363,22 @@ pthread_cond_signal(pthread_cond_t * cond) switch ((*cond)->c_type) { /* Fast condition variable: */ case COND_TYPE_FAST: - /* Bring the next thread off the condition queue: */ - if ((pthread = _thread_queue_deq(&(*cond)->c_queue)) != NULL) { + /* + * Enter a loop to dequeue threads from the condition + * queue until we find one that hasn't previously + * timed out. + */ + while (((pthread = cond_queue_deq(*cond)) != NULL) && + (pthread->timeout != 0)) { + } + + if (pthread != NULL) /* Allow the thread to run: */ PTHREAD_NEW_STATE(pthread,PS_RUNNING); - } + + /* Check for no more waiters: */ + if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) + (*cond)->c_mutex = NULL; break; /* Trap invalid condition variable types: */ @@ -312,12 +400,21 @@ int pthread_cond_broadcast(pthread_cond_t * cond) { int rval = 0; - int status; pthread_t pthread; if (cond == NULL || *cond == NULL) rval = EINVAL; else { + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. In addition, we must assure + * that all threads currently waiting on the condition + * variable are signaled and are not timedout by a + * scheduling signal that causes a preemption. + */ + _thread_kern_sched_defer(); + /* Lock the condition variable structure: */ _SPINLOCK(&(*cond)->lock); @@ -329,11 +426,17 @@ pthread_cond_broadcast(pthread_cond_t * cond) * Enter a loop to bring all threads off the * condition queue: */ - while ((pthread = - _thread_queue_deq(&(*cond)->c_queue)) != NULL) { - /* Allow the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + while ((pthread = cond_queue_deq(*cond)) != NULL) { + /* + * The thread is already running if the + * timeout flag is set. + */ + if (pthread->timeout == 0) + PTHREAD_NEW_STATE(pthread,PS_RUNNING); } + + /* There are no more waiting threads: */ + (*cond)->c_mutex = NULL; break; /* Trap invalid condition variable types: */ @@ -345,9 +448,74 @@ pthread_cond_broadcast(pthread_cond_t * cond) /* Unlock the condition variable structure: */ _SPINUNLOCK(&(*cond)->lock); + + /* Reenable preemption and yield if necessary. + */ + _thread_kern_sched_undefer(); } /* Return the completion status: */ return (rval); } + +/* + * Dequeue a waiting thread from the head of a condition queue in + * descending priority order. + */ +static inline pthread_t +cond_queue_deq(pthread_cond_t cond) +{ + pthread_t pthread; + + if ((pthread = TAILQ_FIRST(&cond->c_queue)) != NULL) { + TAILQ_REMOVE(&cond->c_queue, pthread, qe); + pthread->flags &= ~PTHREAD_FLAGS_QUEUED; + } + + return(pthread); +} + +/* + * Remove a waiting thread from a condition queue in descending priority + * order. + */ +static inline void +cond_queue_remove(pthread_cond_t cond, pthread_t pthread) +{ + /* + * Because pthread_cond_timedwait() can timeout as well + * as be signaled by another thread, it is necessary to + * guard against removing the thread from the queue if + * it isn't in the queue. + */ + if (pthread->flags & PTHREAD_FLAGS_QUEUED) { + TAILQ_REMOVE(&cond->c_queue, pthread, qe); + pthread->flags &= ~PTHREAD_FLAGS_QUEUED; + } +} + +/* + * Enqueue a waiting thread to a condition queue in descending priority + * order. + */ +static inline void +cond_queue_enq(pthread_cond_t cond, pthread_t pthread) +{ + pthread_t tid = TAILQ_LAST(&cond->c_queue, cond_head); + + /* + * For the common case of all threads having equal priority, + * we perform a quick check against the priority of the thread + * at the tail of the queue. + */ + if ((tid == NULL) || (pthread->active_priority <= tid->active_priority)) + TAILQ_INSERT_TAIL(&cond->c_queue, pthread, qe); + else { + tid = TAILQ_FIRST(&cond->c_queue); + while (pthread->active_priority <= tid->active_priority) + tid = TAILQ_NEXT(tid, qe); + TAILQ_INSERT_BEFORE(tid, pthread, qe); + } + pthread->flags |= PTHREAD_FLAGS_QUEUED; +} #endif diff --git a/lib/libkse/thread/thr_create.c b/lib/libkse/thread/thr_create.c index 4169461..438e527 100644 --- a/lib/libkse/thread/thr_create.c +++ b/lib/libkse/thread/thr_create.c @@ -99,12 +99,6 @@ pthread_create(pthread_t * thread, const pthread_attr_t * attr, */ new_thread->magic = PTHREAD_MAGIC; - if (pattr->suspend == PTHREAD_CREATE_SUSPENDED) { - PTHREAD_NEW_STATE(new_thread,PS_SUSPENDED); - } else { - PTHREAD_NEW_STATE(new_thread,PS_RUNNING); - } - /* Initialise the thread for signals: */ new_thread->sigmask = _thread_run->sigmask; @@ -162,21 +156,26 @@ pthread_create(pthread_t * thread, const pthread_attr_t * attr, */ if (new_thread->attr.flags & PTHREAD_INHERIT_SCHED) { /* Copy the scheduling attributes: */ - new_thread->pthread_priority = _thread_run->pthread_priority; - new_thread->attr.prio = _thread_run->pthread_priority; - new_thread->attr.schedparam_policy = _thread_run->attr.schedparam_policy; + new_thread->base_priority = _thread_run->base_priority; + new_thread->attr.prio = _thread_run->base_priority; + new_thread->attr.sched_policy = _thread_run->attr.sched_policy; } else { /* * Use just the thread priority, leaving the * other scheduling attributes as their * default values: */ - new_thread->pthread_priority = new_thread->attr.prio; + new_thread->base_priority = new_thread->attr.prio; } + new_thread->active_priority = new_thread->base_priority; + new_thread->inherited_priority = 0; /* Initialise the join queue for the new thread: */ _thread_queue_init(&(new_thread->join_queue)); + /* Initialize the mutex queue: */ + TAILQ_INIT(&new_thread->mutexq); + /* Initialise hooks in the thread structure: */ new_thread->specific_data = NULL; new_thread->cleanup = NULL; @@ -200,6 +199,27 @@ pthread_create(pthread_t * thread, const pthread_attr_t * attr, /* Unlock the thread list: */ _unlock_thread_list(); + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. + */ + _thread_kern_sched_defer(); + + if (pattr->suspend == PTHREAD_CREATE_SUSPENDED) { + new_thread->state = PS_SUSPENDED; + PTHREAD_WAITQ_INSERT(new_thread); + } else { + new_thread->state = PS_RUNNING; + PTHREAD_PRIOQ_INSERT_TAIL(new_thread); + } + + /* + * Reenable preemption and yield if a scheduling + * signal occurred while in the critical region. + */ + _thread_kern_sched_undefer(); + /* Return a pointer to the thread structure: */ (*thread) = new_thread; diff --git a/lib/libkse/thread/thr_detach.c b/lib/libkse/thread/thr_detach.c index da456bf..05da832 100644 --- a/lib/libkse/thread/thr_detach.c +++ b/lib/libkse/thread/thr_detach.c @@ -52,11 +52,24 @@ pthread_detach(pthread_t pthread) /* Flag the thread as detached: */ pthread->attr.flags |= PTHREAD_DETACHED; + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. + */ + _thread_kern_sched_defer(); + /* Enter a loop to bring all threads off the join queue: */ while ((next_thread = _thread_queue_deq(&pthread->join_queue)) != NULL) { /* Make the thread run: */ PTHREAD_NEW_STATE(next_thread,PS_RUNNING); } + + /* + * Reenable preemption and yield if a scheduling signal + * occurred while in the critical region. + */ + _thread_kern_sched_undefer(); } else /* Return an error: */ rval = EINVAL; diff --git a/lib/libkse/thread/thr_exit.c b/lib/libkse/thread/thr_exit.c index a5fc400..93b8b83 100644 --- a/lib/libkse/thread/thr_exit.c +++ b/lib/libkse/thread/thr_exit.c @@ -49,7 +49,7 @@ void _exit(int status) itimer.it_interval.tv_usec = 0; itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = 0; - setitimer(ITIMER_VIRTUAL, &itimer, NULL); + setitimer(_ITIMER_SCHED_TIMER, &itimer, NULL); /* Close the pthread kernel pipe: */ _thread_sys_close(_thread_kern_pipe[0]); @@ -127,6 +127,13 @@ pthread_exit(void *status) /* Run the thread-specific data destructors: */ _thread_cleanupspecific(); } + + /* + * Guard against preemption by a scheduling signal. A change of + * thread state modifies the waiting and priority queues. + */ + _thread_kern_sched_defer(); + /* Check if there are any threads joined to this one: */ while ((pthread = _thread_queue_deq(&(_thread_run->join_queue))) != NULL) { /* Wake the joined thread and let it detach this thread: */ @@ -134,6 +141,12 @@ pthread_exit(void *status) } /* + * Reenable preemption and yield if a scheduling signal + * occurred while in the critical region. + */ + _thread_kern_sched_undefer(); + + /* * Lock the garbage collector mutex to ensure that the garbage * collector is not using the dead thread list. */ diff --git a/lib/libkse/thread/thr_fork.c b/lib/libkse/thread/thr_fork.c index 960c1de..5582c1e 100644 --- a/lib/libkse/thread/thr_fork.c +++ b/lib/libkse/thread/thr_fork.c @@ -41,7 +41,7 @@ pid_t fork(void) { - int flags; + int i, flags; pid_t ret; pthread_t pthread; pthread_t pthread_next; @@ -88,6 +88,11 @@ fork(void) else if (_thread_sys_fcntl(_thread_kern_pipe[1], F_SETFL, flags | O_NONBLOCK) == -1) { /* Abort this application: */ abort(); + /* Initialize the ready queue: */ + } else if (_pq_init(&_readyq, PTHREAD_MIN_PRIORITY, + PTHREAD_MAX_PRIORITY) != 0) { + /* Abort this application: */ + PANIC("Cannot allocate priority ready queue."); } else { /* Point to the first thread in the list: */ pthread = _thread_link_list; @@ -119,6 +124,33 @@ fork(void) /* Point to the next thread: */ pthread = pthread_next; } + + /* Re-init the waiting queues. */ + TAILQ_INIT(&_waitingq); + + /* Initialize the scheduling switch hook routine: */ + _sched_switch_hook = NULL; + + /* Clear out any locks in the file descriptor table: */ + for (i = 0; i < _thread_dtablesize; i++) { + if (_thread_fd_table[i] != NULL) { + /* Initialise the file locks: */ + memset(&_thread_fd_table[i]->lock, 0, + sizeof(_thread_fd_table[i]->lock)); + _thread_fd_table[i]->r_owner = NULL; + _thread_fd_table[i]->w_owner = NULL; + _thread_fd_table[i]->r_fname = NULL; + _thread_fd_table[i]->w_fname = NULL; + _thread_fd_table[i]->r_lineno = 0;; + _thread_fd_table[i]->w_lineno = 0;; + _thread_fd_table[i]->r_lockcount = 0;; + _thread_fd_table[i]->w_lockcount = 0;; + + /* Initialise the read/write queues: */ + _thread_queue_init(&_thread_fd_table[i]->r_queue); + _thread_queue_init(&_thread_fd_table[i]->w_queue); + } + } } } diff --git a/lib/libkse/thread/thr_getprio.c b/lib/libkse/thread/thr_getprio.c index 708b8f1..b2c94d6 100644 --- a/lib/libkse/thread/thr_getprio.c +++ b/lib/libkse/thread/thr_getprio.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -38,12 +38,11 @@ int pthread_getprio(pthread_t pthread) { - int ret; + int policy, ret; + struct sched_param param; - /* Find the thread in the list of active threads: */ - if ((ret = _find_thread(pthread)) == 0) - /* Get the thread priority: */ - ret = pthread->pthread_priority; + if ((ret = pthread_getschedparam(pthread, &policy, ¶m)) == 0) + ret = param.sched_priority; else { /* Invalid thread: */ errno = ret; diff --git a/lib/libkse/thread/thr_getschedparam.c b/lib/libkse/thread/thr_getschedparam.c new file mode 100644 index 0000000..e7d18d9 --- /dev/null +++ b/lib/libkse/thread/thr_getschedparam.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_getschedparam(pthread_t pthread, int *policy, struct sched_param *param) +{ + int ret; + + if ((param == NULL) || (policy == NULL)) + /* Return an invalid argument error: */ + ret = EINVAL; + + /* Find the thread in the list of active threads: */ + else if ((ret = _find_thread(pthread)) == 0) { + /* Return the threads base priority and scheduling policy: */ + param->sched_priority = pthread->base_priority; + *policy = pthread->attr.sched_policy; + } + + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_info.c b/lib/libkse/thread/thr_info.c index f66dd2d..d2d97da 100644 --- a/lib/libkse/thread/thr_info.c +++ b/lib/libkse/thread/thr_info.c @@ -60,9 +60,11 @@ static const struct s_thread_info thread_info[] = { {PS_WAIT_WAIT , "Waiting process"}, {PS_SIGSUSPEND , "Suspended, waiting for a signal"}, {PS_SIGWAIT , "Waiting for a signal"}, + {PS_SPINBLOCK , "Waiting for a spinlock"}, {PS_JOIN , "Waiting to join"}, {PS_SUSPENDED , "Suspended"}, {PS_DEAD , "Dead"}, + {PS_DEADLOCK , "Deadlocked"}, {PS_STATE_MAX , "Not a real state!"} }; @@ -75,6 +77,7 @@ _thread_dump_info(void) int j; pthread_t pthread; char tmpfile[128]; + pq_list_t *pq_list; for (i = 0; i < 100000; i++) { snprintf(tmpfile, sizeof(tmpfile), "/tmp/uthread.dump.%u.%i", @@ -116,7 +119,7 @@ _thread_dump_info(void) snprintf(s, sizeof(s), "--------------------\nThread %p (%s) prio %3d state %s [%s:%d]\n", pthread, (pthread->name == NULL) ? - "":pthread->name, pthread->pthread_priority, + "":pthread->name, pthread->base_priority, thread_info[j].name, pthread->fname,pthread->lineno); _thread_sys_write(fd, s, strlen(s)); @@ -167,6 +170,50 @@ _thread_dump_info(void) } } + /* Output a header for ready threads: */ + strcpy(s, "\n\n=============\nREADY THREADS\n\n"); + _thread_sys_write(fd, s, strlen(s)); + + /* Enter a loop to report each thread in the ready queue: */ + TAILQ_FOREACH (pq_list, &_readyq.pq_queue, pl_link) { + TAILQ_FOREACH(pthread, &pq_list->pl_head, pqe) { + /* Find the state: */ + for (j = 0; j < (sizeof(thread_info) / + sizeof(struct s_thread_info)) - 1; j++) + if (thread_info[j].state == pthread->state) + break; + /* Output a record for the current thread: */ + snprintf(s, sizeof(s), + "--------------------\nThread %p (%s) prio %3d state %s [%s:%d]\n", + pthread, (pthread->name == NULL) ? + "":pthread->name, pthread->base_priority, + thread_info[j].name, + pthread->fname,pthread->lineno); + _thread_sys_write(fd, s, strlen(s)); + } + } + + /* Output a header for waiting threads: */ + strcpy(s, "\n\n=============\nWAITING THREADS\n\n"); + _thread_sys_write(fd, s, strlen(s)); + + /* Enter a loop to report each thread in the waiting queue: */ + TAILQ_FOREACH (pthread, &_waitingq, pqe) { + /* Find the state: */ + for (j = 0; j < (sizeof(thread_info) / + sizeof(struct s_thread_info)) - 1; j++) + if (thread_info[j].state == pthread->state) + break; + /* Output a record for the current thread: */ + snprintf(s, sizeof(s), + "--------------------\nThread %p (%s) prio %3d state %s [%s:%d]\n", + pthread, (pthread->name == NULL) ? + "":pthread->name, pthread->base_priority, + thread_info[j].name, + pthread->fname,pthread->lineno); + _thread_sys_write(fd, s, strlen(s)); + } + /* Check if there are no dead threads: */ if (_thread_dead == NULL) { /* Output a record: */ @@ -186,7 +233,7 @@ _thread_dump_info(void) /* Output a record for the current thread: */ snprintf(s, sizeof(s), "Thread %p prio %3d [%s:%d]\n", - pthread, pthread->pthread_priority, + pthread, pthread->base_priority, pthread->fname,pthread->lineno); _thread_sys_write(fd, s, strlen(s)); } diff --git a/lib/libkse/thread/thr_init.c b/lib/libkse/thread/thr_init.c index 50f3bef..e4411ce 100644 --- a/lib/libkse/thread/thr_init.c +++ b/lib/libkse/thread/thr_init.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -147,6 +147,11 @@ _thread_init(void) /* Abort this application: */ PANIC("Cannot get kernel write pipe flags"); } + /* Initialize the ready queue: */ + else if (_pq_init(&_readyq, PTHREAD_MIN_PRIORITY, PTHREAD_MAX_PRIORITY) != 0) { + /* Abort this application: */ + PANIC("Cannot allocate priority ready queue."); + } /* Allocate memory for the thread structure of the initial thread: */ else if ((_thread_initial = (pthread_t) malloc(sizeof(struct pthread))) == NULL) { /* @@ -157,10 +162,25 @@ _thread_init(void) } else { /* Zero the global kernel thread structure: */ memset(&_thread_kern_thread, 0, sizeof(struct pthread)); + _thread_kern_thread.flags = PTHREAD_FLAGS_PRIVATE; memset(_thread_initial, 0, sizeof(struct pthread)); + /* Initialize the waiting queue: */ + TAILQ_INIT(&_waitingq); + + /* Initialize the scheduling switch hook routine: */ + _sched_switch_hook = NULL; + + /* + * Write a magic value to the thread structure + * to help identify valid ones: + */ + _thread_initial->magic = PTHREAD_MAGIC; + /* Default the priority of the initial thread: */ - _thread_initial->pthread_priority = PTHREAD_DEFAULT_PRIORITY; + _thread_initial->base_priority = PTHREAD_DEFAULT_PRIORITY; + _thread_initial->active_priority = PTHREAD_DEFAULT_PRIORITY; + _thread_initial->inherited_priority = 0; /* Initialise the state of the initial thread: */ _thread_initial->state = PS_RUNNING; @@ -168,7 +188,13 @@ _thread_init(void) /* Initialise the queue: */ _thread_queue_init(&(_thread_initial->join_queue)); + /* Initialize the owned mutex queue and count: */ + TAILQ_INIT(&(_thread_initial->mutexq)); + _thread_initial->priority_mutex_count = 0; + /* Initialise the rest of the fields: */ + _thread_initial->sched_defer_count = 0; + _thread_initial->yield_on_sched_undefer = 0; _thread_initial->specific_data = NULL; _thread_initial->cleanup = NULL; _thread_initial->queue = NULL; @@ -206,9 +232,9 @@ _thread_init(void) * signals that the user-thread kernel needs. Actually * SIGINFO isn't really needed, but it is nice to have. */ - if (_thread_sys_sigaction(SIGVTALRM, &act, NULL) != 0 || - _thread_sys_sigaction(SIGINFO , &act, NULL) != 0 || - _thread_sys_sigaction(SIGCHLD , &act, NULL) != 0) { + if (_thread_sys_sigaction(_SCHED_SIGNAL, &act, NULL) != 0 || + _thread_sys_sigaction(SIGINFO, &act, NULL) != 0 || + _thread_sys_sigaction(SIGCHLD, &act, NULL) != 0) { /* * Abort this process if signal initialisation fails: */ @@ -256,6 +282,8 @@ _thread_init(void) pthread_cond_init(&_gc_cond,NULL) != 0) PANIC("Failed to initialise garbage collector mutex or condvar"); + gettimeofday(&kern_inc_prio_time, NULL); + return; } diff --git a/lib/libkse/thread/thr_kern.c b/lib/libkse/thread/thr_kern.c index 3a6966b..626f1d4 100644 --- a/lib/libkse/thread/thr_kern.c +++ b/lib/libkse/thread/thr_kern.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -53,16 +53,18 @@ static void _thread_kern_select(int wait_reqd); +static inline void +thread_run_switch_hook(pthread_t thread_out, pthread_t thread_in); + void _thread_kern_sched(struct sigcontext * scp) { #ifndef __alpha__ char *fdata; #endif - int prio = -1; pthread_t pthread; pthread_t pthread_h = NULL; - pthread_t pthread_s = NULL; + pthread_t last_thread = NULL; struct itimerval itimer; struct timespec ts; struct timespec ts1; @@ -105,18 +107,21 @@ __asm__("fnsave %0": :"m"(*fdata)); */ _thread_kern_in_sched = 0; - /* - * There might be pending signals for this thread, so - * dispatch any that aren't blocked: - */ - _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 the scheduling loop that finds the next thread that is + * 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 @@ -134,12 +139,48 @@ __asm__("fnsave %0": :"m"(*fdata)); _thread_kern_select(0); /* - * Enter a loop to look for sleeping threads that are ready: + * 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; + + /* + * Enter a loop to look for sleeping threads that are ready + * or timedout. While we're at it, also find the smallest + * timeout value for threads waiting for a time. */ - for (pthread = _thread_link_list; pthread != NULL; - pthread = pthread->nxt) { + _waitingq_check_reqd = 0; /* reset flag before loop */ + TAILQ_FOREACH(pthread, &_waitingq, pqe) { + /* Check if this thread is ready: */ + if (pthread->state == PS_RUNNING) { + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + } + + /* + * Check if this thread is blocked by an + * atomic lock: + */ + else if (pthread->state == PS_SPINBLOCK) { + /* + * If the lock is available, let + * the thread run. + */ + if (pthread->data.spinlock->access_lock == 0) { + PTHREAD_NEW_STATE(pthread,PS_RUNNING); + } + /* Check if this thread is to timeout: */ - if (pthread->state == PS_COND_WAIT || + } else if (pthread->state == PS_COND_WAIT || pthread->state == PS_SLEEP_WAIT || pthread->state == PS_FDR_WAIT || pthread->state == PS_FDW_WAIT || @@ -163,9 +204,9 @@ __asm__("fnsave %0": :"m"(*fdata)); */ if (pthread->state == PS_SELECT_WAIT) { /* - * The select has timed out, - * so zero the file - * descriptor sets: + * The select has timed out, so + * zero the file descriptor + * sets: */ FD_ZERO(&pthread->data.select_data->readfds); FD_ZERO(&pthread->data.select_data->writefds); @@ -189,13 +230,72 @@ __asm__("fnsave %0": :"m"(*fdata)); * it to be restarted: */ PTHREAD_NEW_STATE(pthread,PS_RUNNING); + } else { + /* + * 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: + */ + if (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: + */ + if (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 there is a current thread: */ if (_thread_run != &_thread_kern_thread) { /* + * This thread no longer needs to yield the CPU. + */ + _thread_run->yield_on_sched_undefer = 0; + + /* * Save the current time as the time that the thread * became inactive: */ @@ -204,194 +304,64 @@ __asm__("fnsave %0": :"m"(*fdata)); /* * Accumulate the number of microseconds that this - * thread has run for: + * thread has run for: */ - if (_thread_run->slice_usec != -1) { - _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 if this thread has reached its allocated - * time slice period: - */ - if (_thread_run->slice_usec > TIMESLICE_USEC) { - /* - * Flag the allocated time slice period as - * up: - */ - _thread_run->slice_usec = -1; + if ((_thread_run->slice_usec != -1) && + (_thread_run->attr.sched_policy != SCHED_FIFO)) { + _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; } - } - /* Check if an incremental priority update is required: */ - if (((tv.tv_sec - kern_inc_prio_time.tv_sec) * 1000000 + - tv.tv_usec - kern_inc_prio_time.tv_usec) > INC_PRIO_USEC) { - /* - * Enter a loop to look for run-enabled threads that - * have not run since the last time that an - * incremental priority update was performed: - */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { - /* Check if this thread is unable to run: */ - if (pthread->state != PS_RUNNING) { - } - /* - * Check if the last time that this thread - * was run (as indicated by the last time it - * became inactive) is before the time that - * the last incremental priority check was - * made: - */ - else if (timercmp(&pthread->last_inactive, &kern_inc_prio_time, <)) { + if (_thread_run->state == PS_RUNNING) { + 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 { /* - * Increment the incremental priority - * for this thread in the hope that - * it will eventually get a chance to - * run: + * The thread hasn't exceeded its + * interval. Place it at the head + * of the queue for its priority. */ - (pthread->inc_prio)++; + PTHREAD_PRIOQ_INSERT_HEAD(_thread_run); } } - - /* Save the new incremental priority update time: */ - kern_inc_prio_time.tv_sec = tv.tv_sec; - kern_inc_prio_time.tv_usec = tv.tv_usec; - } - /* - * Enter a loop to look for the first thread of the highest - * priority that is ready to run: - */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { - /* Check if the current thread is unable to run: */ - if (pthread->state != PS_RUNNING) { - } - /* - * Check if no run-enabled thread has been seen or if - * the current thread has a priority higher than the - * highest seen so far: - */ - else if (pthread_h == NULL || (pthread->pthread_priority + pthread->inc_prio) > prio) { + else if (_thread_run->state == PS_DEAD) { /* - * Save this thread as the highest priority - * thread seen so far: + * Don't add dead threads to the waiting + * queue, because when they're reaped, it + * will corrupt the queue. */ - pthread_h = pthread; - prio = pthread->pthread_priority + pthread->inc_prio; } - } - - /* - * Enter a loop to look for a thread that: 1. Is run-enabled. - * 2. Has the required agregate priority. 3. Has not been - * allocated its allocated time slice. 4. Became inactive - * least recently. - */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { - /* Check if the current thread is unable to run: */ - if (pthread->state != PS_RUNNING) { - /* Ignore threads that are not ready to run. */ - } - - /* - * Check if the current thread as an agregate - * priority not equal to the highest priority found - * above: - */ - else if ((pthread->pthread_priority + pthread->inc_prio) != prio) { + else { /* - * Ignore threads which have lower agregate - * priority. + * This thread has changed state and needs + * to be placed in the waiting queue. */ - } - - /* - * Check if the current thread reached its time slice - * allocation last time it ran (or if it has not run - * yet): - */ - else if (pthread->slice_usec == -1) { - } + PTHREAD_WAITQ_INSERT(_thread_run); - /* - * Check if an eligible thread has not been found - * yet, or if the current thread has an inactive time - * earlier than the last one seen: - */ - else if (pthread_s == NULL || timercmp(&pthread->last_inactive, &tv1, <)) { - /* - * Save the pointer to the current thread as - * the most eligible thread seen so far: - */ - pthread_s = pthread; - - /* - * Save the time that the selected thread - * became inactive: - */ - tv1.tv_sec = pthread->last_inactive.tv_sec; - tv1.tv_usec = pthread->last_inactive.tv_usec; + /* Restart the time slice: */ + _thread_run->slice_usec = -1; } } /* - * Check if no thread was selected according to incomplete - * time slice allocation: + * Get the highest priority thread in the ready queue. */ - if (pthread_s == NULL) { - /* - * Enter a loop to look for any other thread that: 1. - * Is run-enabled. 2. Has the required agregate - * priority. 3. Became inactive least recently. - */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { - /* - * Check if the current thread is unable to - * run: - */ - if (pthread->state != PS_RUNNING) { - /* - * Ignore threads that are not ready - * to run. - */ - } - /* - * Check if the current thread as an agregate - * priority not equal to the highest priority - * found above: - */ - else if ((pthread->pthread_priority + pthread->inc_prio) != prio) { - /* - * Ignore threads which have lower - * agregate priority. - */ - } - /* - * Check if an eligible thread has not been - * found yet, or if the current thread has an - * inactive time earlier than the last one - * seen: - */ - else if (pthread_s == NULL || timercmp(&pthread->last_inactive, &tv1, <)) { - /* - * Save the pointer to the current - * thread as the most eligible thread - * seen so far: - */ - pthread_s = pthread; + pthread_h = PTHREAD_PRIOQ_FIRST; - /* - * Save the time that the selected - * thread became inactive: - */ - tv1.tv_sec = pthread->last_inactive.tv_sec; - tv1.tv_usec = pthread->last_inactive.tv_usec; - } - } - } /* Check if there are no threads ready to run: */ - if (pthread_s == NULL) { + if (pthread_h == NULL) { /* * Lock the pthread kernel by changing the pointer to * the running thread to point to the global kernel @@ -406,7 +376,10 @@ __asm__("fnsave %0": :"m"(*fdata)); _thread_kern_select(1); } else { /* Make the selected thread the current thread: */ - _thread_run = pthread_s; + _thread_run = pthread_h; + + /* Remove the thread from the ready queue. */ + PTHREAD_PRIOQ_REMOVE(_thread_run); /* * Save the current time as the time that the thread @@ -424,149 +397,22 @@ __asm__("fnsave %0": :"m"(*fdata)); /* Reset the accumulated time slice period: */ _thread_run->slice_usec = 0; } - /* - * Reset the incremental priority now that this - * thread has been given the chance to run: - */ - _thread_run->inc_prio = 0; /* Check if there is more than one thread: */ if (_thread_run != _thread_link_list || _thread_run->nxt != NULL) { /* - * Define the maximum time before a SIGVTALRM - * 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; - - /* - * Enter a loop to look for threads waiting - * for a time: - */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { - /* - * Check if this thread is to - * timeout: - */ - if (pthread->state == PS_COND_WAIT || - pthread->state == PS_SLEEP_WAIT || - pthread->state == PS_FDR_WAIT || - pthread->state == PS_FDW_WAIT || - pthread->state == PS_SELECT_WAIT) { - /* - * Check if this thread is to - * wait forever: - */ - if (pthread->wakeup_time.tv_sec == -1) { - } - /* - * Check if this thread is to - * wakeup immediately: - */ - else if (pthread->wakeup_time.tv_sec == 0 && - pthread->wakeup_time.tv_nsec == 0) { - } - /* - * Check if the current time - * is after the wakeup time: - */ - else if ((ts.tv_sec > pthread->wakeup_time.tv_sec) || - ((ts.tv_sec == pthread->wakeup_time.tv_sec) && - (ts.tv_nsec > pthread->wakeup_time.tv_nsec))) { - } else { - /* - * 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: - */ - if (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: - */ - if (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(&tv, &ts1); - - /* - * Check if the - * thread will be - * ready sooner than - * the earliest one - * found so far: - */ - if (timercmp(&tv, &itimer.it_value, <)) { - /* - * Update the - * time - * value: - */ - itimer.it_value.tv_sec = tv.tv_sec; - itimer.it_value.tv_usec = tv.tv_usec; - } - } - } - } - - /* * Start the interval timer for the * calculated time interval: */ - if (setitimer(ITIMER_VIRTUAL, &itimer, NULL) != 0) { + if (setitimer(_ITIMER_SCHED_TIMER, &itimer, NULL) != 0) { /* * Cannot initialise the timer, so * abort this process: */ - PANIC("Cannot set virtual timer"); + PANIC("Cannot set scheduling timer"); } } + /* Check if a signal context was saved: */ if (_thread_run->sig_saved == 1) { #ifndef __alpha__ @@ -579,20 +425,30 @@ __asm__("fnsave %0": :"m"(*fdata)); /* 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; + _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 + } 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"); @@ -679,7 +535,8 @@ _thread_kern_select(int wait_reqd) * Enter a loop to process threads waiting on either file descriptors * or times: */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { + _waitingq_check_reqd = 0; /* reset flag before loop */ + TAILQ_FOREACH (pthread, &_waitingq, pqe) { /* Assume that this state does not time out: */ settimeout = 0; @@ -690,12 +547,12 @@ _thread_kern_select(int wait_reqd) * operations or timeouts: */ case PS_DEAD: + case PS_DEADLOCK: case PS_FDLR_WAIT: case PS_FDLW_WAIT: case PS_FILE_WAIT: case PS_JOIN: case PS_MUTEX_WAIT: - case PS_RUNNING: case PS_SIGTHREAD: case PS_SIGWAIT: case PS_STATE_MAX: @@ -704,6 +561,16 @@ _thread_kern_select(int wait_reqd) /* Nothing to do here. */ break; + case PS_RUNNING: + /* + * A signal occurred and made this thread ready + * while in the scheduler or while the scheduling + * queues were protected. + */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + break; + /* File descriptor read wait: */ case PS_FDR_WAIT: /* Add the file descriptor to the read set: */ @@ -1010,16 +877,16 @@ _thread_kern_select(int wait_reqd) * descriptors that are flagged as available by the * _select syscall: */ - for (pthread = _thread_link_list; pthread != NULL; pthread = pthread->nxt) { + TAILQ_FOREACH (pthread, &_waitingq, pqe) { /* Process according to thread state: */ switch (pthread->state) { /* * States which do not depend on file * descriptor I/O operations: */ - case PS_RUNNING: case PS_COND_WAIT: case PS_DEAD: + case PS_DEADLOCK: case PS_FDLR_WAIT: case PS_FDLW_WAIT: case PS_FILE_WAIT: @@ -1034,6 +901,15 @@ _thread_kern_select(int wait_reqd) /* Nothing to do here. */ break; + case PS_RUNNING: + /* + * A signal occurred and made this thread + * ready while in the scheduler. + */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + break; + /* File descriptor read wait: */ case PS_FDR_WAIT: /* @@ -1047,6 +923,13 @@ _thread_kern_select(int wait_reqd) * is scheduled next: */ pthread->state = PS_RUNNING; + + /* + * Remove it from the waiting queue + * and add it to the ready queue: + */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); } break; @@ -1063,6 +946,13 @@ _thread_kern_select(int wait_reqd) * scheduled next: */ pthread->state = PS_RUNNING; + + /* + * Remove it from the waiting queue + * and add it to the ready queue: + */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); } break; @@ -1269,6 +1159,13 @@ _thread_kern_select(int wait_reqd) * thread to run: */ pthread->state = PS_RUNNING; + + /* + * Remove it from the waiting queue + * and add it to the ready queue: + */ + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); } break; } @@ -1320,4 +1217,80 @@ _thread_kern_set_timeout(struct timespec * timeout) } return; } + +void +_thread_kern_sched_defer(void) +{ + /* Allow scheduling deferral to be recursive. */ + _thread_run->sched_defer_count++; +} + +void +_thread_kern_sched_undefer(void) +{ + pthread_t pthread; + int need_resched = 0; + + /* + * Perform checks to yield only if we are about to undefer + * scheduling. + */ + if (_thread_run->sched_defer_count == 1) { + /* + * Check if the waiting queue needs to be examined for + * threads that are now ready: + */ + while (_waitingq_check_reqd != 0) { + /* Clear the flag before checking the waiting queue: */ + _waitingq_check_reqd = 0; + + TAILQ_FOREACH(pthread, &_waitingq, pqe) { + if (pthread->state == PS_RUNNING) { + PTHREAD_WAITQ_REMOVE(pthread); + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + } + } + } + + /* + * We need to yield if a thread change of state caused a + * higher priority thread to become ready, or if a + * scheduling signal occurred while preemption was disabled. + */ + if ((((pthread = PTHREAD_PRIOQ_FIRST) != NULL) && + (pthread->active_priority > _thread_run->active_priority)) || + (_thread_run->yield_on_sched_undefer != 0)) { + _thread_run->yield_on_sched_undefer = 0; + need_resched = 1; + } + } + + if (_thread_run->sched_defer_count > 0) { + /* Decrement the scheduling deferral count. */ + _thread_run->sched_defer_count--; + + /* Yield the CPU if necessary: */ + if (need_resched) + _thread_kern_sched(NULL); + } +} + +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 diff --git a/lib/libkse/thread/thr_kill.c b/lib/libkse/thread/thr_kill.c index 7572c05..c729179 100644 --- a/lib/libkse/thread/thr_kill.c +++ b/lib/libkse/thread/thr_kill.c @@ -52,6 +52,13 @@ pthread_kill(pthread_t pthread, int sig) /* Find the thread in the list of active threads: */ else if ((ret = _find_thread(pthread)) == 0) { + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. + */ + _thread_kern_sched_defer(); + switch (pthread->state) { case PS_SIGSUSPEND: /* @@ -108,6 +115,12 @@ pthread_kill(pthread_t pthread, int sig) sigaddset(&pthread->sigpend,sig); break; } + + /* + * Reenable preemption and yield if a scheduling signal + * occurred while in the critical region. + */ + _thread_kern_sched_undefer(); } /* Return the completion status: */ diff --git a/lib/libkse/thread/thr_mattr_init.c b/lib/libkse/thread/thr_mattr_init.c index 73226a6..206485f 100644 --- a/lib/libkse/thread/thr_mattr_init.c +++ b/lib/libkse/thread/thr_mattr_init.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) diff --git a/lib/libkse/thread/thr_mutex.c b/lib/libkse/thread/thr_mutex.c index d3801f1..0103a6c 100644 --- a/lib/libkse/thread/thr_mutex.c +++ b/lib/libkse/thread/thr_mutex.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -33,78 +33,116 @@ #include <stdlib.h> #include <errno.h> #include <string.h> +#include <sys/param.h> +#include <sys/queue.h> #ifdef _THREAD_SAFE #include <pthread.h> #include "pthread_private.h" + +/* + * Prototypes + */ +static inline int mutex_self_trylock(pthread_mutex_t); +static inline int mutex_self_lock(pthread_mutex_t); +static inline int mutex_unlock_common(pthread_mutex_t *, int); +static void mutex_priority_adjust(pthread_mutex_t); +static void mutex_rescan_owned (pthread_t, pthread_mutex_t); +static inline pthread_t mutex_queue_deq(pthread_mutex_t); +static inline void mutex_queue_remove(pthread_mutex_t, pthread_t); +static inline void mutex_queue_enq(pthread_mutex_t, pthread_t); + + static spinlock_t static_init_lock = _SPINLOCK_INITIALIZER; + int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * mutex_attr) { - enum pthread_mutextype type; + enum pthread_mutextype type; + int protocol; + int ceiling; pthread_mutex_t pmutex; int ret = 0; - if (mutex == NULL) { + if (mutex == NULL) ret = EINVAL; - } else { - /* Check if default mutex attributes: */ - if (mutex_attr == NULL || *mutex_attr == NULL) - /* Default to a fast mutex: */ - type = PTHREAD_MUTEX_DEFAULT; - else if ((*mutex_attr)->m_type >= MUTEX_TYPE_MAX) - /* Return an invalid argument error: */ - ret = EINVAL; - else - /* Use the requested mutex type: */ - type = (*mutex_attr)->m_type; - - /* Check no errors so far: */ - if (ret == 0) { - if ((pmutex = (pthread_mutex_t) - malloc(sizeof(struct pthread_mutex))) == NULL) - ret = ENOMEM; - else { - /* Reset the mutex flags: */ - pmutex->m_flags = 0; - - /* Process according to mutex type: */ - switch (type) { - /* Fast mutex: */ - case PTHREAD_MUTEX_DEFAULT: - case PTHREAD_MUTEX_NORMAL: - case PTHREAD_MUTEX_ERRORCHECK: - /* Nothing to do here. */ - break; - - /* Counting mutex: */ - case PTHREAD_MUTEX_RECURSIVE: - /* Reset the mutex count: */ - pmutex->m_data.m_count = 0; - break; - - /* Trap invalid mutex types: */ - default: - /* Return an invalid argument error: */ - ret = EINVAL; - break; - } - if (ret == 0) { - /* Initialise the rest of the mutex: */ - _thread_queue_init(&pmutex->m_queue); - pmutex->m_flags |= MUTEX_FLAGS_INITED; - pmutex->m_owner = NULL; - pmutex->m_type = type; - memset(&pmutex->lock, 0, - sizeof(pmutex->lock)); - *mutex = pmutex; - } else { - free(pmutex); - *mutex = NULL; - } + /* Check if default mutex attributes: */ + else if (mutex_attr == NULL || *mutex_attr == NULL) { + /* Default to a (error checking) POSIX mutex: */ + type = PTHREAD_MUTEX_ERRORCHECK; + protocol = PTHREAD_PRIO_NONE; + ceiling = PTHREAD_MAX_PRIORITY; + } + + /* Check mutex type: */ + else if (((*mutex_attr)->m_type < PTHREAD_MUTEX_ERRORCHECK) || + ((*mutex_attr)->m_type >= MUTEX_TYPE_MAX)) + /* Return an invalid argument error: */ + ret = EINVAL; + + /* Check mutex protocol: */ + else if (((*mutex_attr)->m_protocol < PTHREAD_PRIO_NONE) || + ((*mutex_attr)->m_protocol > PTHREAD_MUTEX_RECURSIVE)) + /* Return an invalid argument error: */ + ret = EINVAL; + + else { + /* Use the requested mutex type and protocol: */ + type = (*mutex_attr)->m_type; + protocol = (*mutex_attr)->m_protocol; + ceiling = (*mutex_attr)->m_ceiling; + } + + /* Check no errors so far: */ + if (ret == 0) { + if ((pmutex = (pthread_mutex_t) + malloc(sizeof(struct pthread_mutex))) == NULL) + ret = ENOMEM; + else { + /* Reset the mutex flags: */ + pmutex->m_flags = 0; + + /* Process according to mutex type: */ + switch (type) { + /* case PTHREAD_MUTEX_DEFAULT: */ + case PTHREAD_MUTEX_ERRORCHECK: + case PTHREAD_MUTEX_NORMAL: + /* Nothing to do here. */ + break; + + /* Single UNIX Spec 2 recursive mutex: */ + case PTHREAD_MUTEX_RECURSIVE: + /* Reset the mutex count: */ + pmutex->m_data.m_count = 0; + break; + + /* Trap invalid mutex types: */ + default: + /* Return an invalid argument error: */ + ret = EINVAL; + break; + } + if (ret == 0) { + /* Initialise the rest of the mutex: */ + TAILQ_INIT(&pmutex->m_queue); + pmutex->m_flags |= MUTEX_FLAGS_INITED; + pmutex->m_owner = NULL; + pmutex->m_type = type; + pmutex->m_protocol = protocol; + pmutex->m_refcount = 0; + if (protocol == PTHREAD_PRIO_PROTECT) + pmutex->m_prio = ceiling; + else + pmutex->m_prio = 0; + pmutex->m_saved_prio = 0; + memset(&pmutex->lock, 0, sizeof(pmutex->lock)); + *mutex = pmutex; + } else { + free(pmutex); + *mutex = NULL; } } } @@ -124,16 +162,29 @@ pthread_mutex_destroy(pthread_mutex_t * mutex) _SPINLOCK(&(*mutex)->lock); /* - * Free the memory allocated for the mutex - * structure: + * Check to see if this mutex is in use: */ - free(*mutex); + if (((*mutex)->m_owner != NULL) || + (TAILQ_FIRST(&(*mutex)->m_queue) != NULL) || + ((*mutex)->m_refcount != 0)) { + ret = EBUSY; - /* - * Leave the caller's pointer NULL now that - * the mutex has been destroyed: - */ - *mutex = NULL; + /* Unlock the mutex structure: */ + _SPINUNLOCK(&(*mutex)->lock); + } + else { + /* + * Free the memory allocated for the mutex + * structure: + */ + free(*mutex); + + /* + * Leave the caller's pointer NULL now that + * the mutex has been destroyed: + */ + *mutex = NULL; + } } /* Return the completion status: */ @@ -170,44 +221,100 @@ pthread_mutex_trylock(pthread_mutex_t * mutex) * initialization: */ else if (*mutex != NULL || (ret = init_static(mutex)) == 0) { + /* + * Guard against being preempted by a scheduling signal. + * To support priority inheritence mutexes, we need to + * maintain lists of mutex ownerships for each thread as + * well as lists of waiting threads for each mutex. In + * order to propagate priorities we need to atomically + * walk these lists and cannot rely on a single mutex + * lock to provide protection against modification. + */ + _thread_kern_sched_defer(); + /* Lock the mutex structure: */ _SPINLOCK(&(*mutex)->lock); /* Process according to mutex type: */ - switch ((*mutex)->m_type) { - /* Fast mutex: */ - case PTHREAD_MUTEX_NORMAL: - case PTHREAD_MUTEX_DEFAULT: - case PTHREAD_MUTEX_ERRORCHECK: + switch ((*mutex)->m_protocol) { + /* Default POSIX mutex: */ + case PTHREAD_PRIO_NONE: /* Check if this mutex is not locked: */ if ((*mutex)->m_owner == NULL) { /* Lock the mutex for the running thread: */ (*mutex)->m_owner = _thread_run; - } else { + + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_trylock(*mutex); + else /* Return a busy error: */ ret = EBUSY; - } break; - /* Counting mutex: */ - case PTHREAD_MUTEX_RECURSIVE: - /* Check if this mutex is locked: */ - if ((*mutex)->m_owner != NULL) { + /* POSIX priority inheritence mutex: */ + case PTHREAD_PRIO_INHERIT: + /* Check if this mutex is not locked: */ + if ((*mutex)->m_owner == NULL) { + /* Lock the mutex for the running thread: */ + (*mutex)->m_owner = _thread_run; + + /* Track number of priority mutexes owned: */ + _thread_run->priority_mutex_count++; + /* - * Check if the mutex is locked by the running - * thread: + * The mutex takes on the attributes of the + * running thread when there are no waiters. */ - if ((*mutex)->m_owner == _thread_run) { - /* Increment the lock count: */ - (*mutex)->m_data.m_count++; - } else { - /* Return a busy error: */ - ret = EBUSY; - } - } else { + (*mutex)->m_prio = _thread_run->active_priority; + (*mutex)->m_saved_prio = + _thread_run->inherited_priority; + + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_trylock(*mutex); + else + /* Return a busy error: */ + ret = EBUSY; + break; + + /* POSIX priority protection mutex: */ + case PTHREAD_PRIO_PROTECT: + /* Check for a priority ceiling violation: */ + if (_thread_run->active_priority > (*mutex)->m_prio) + ret = EINVAL; + + /* Check if this mutex is not locked: */ + else if ((*mutex)->m_owner == NULL) { /* Lock the mutex for the running thread: */ (*mutex)->m_owner = _thread_run; - } + + /* Track number of priority mutexes owned: */ + _thread_run->priority_mutex_count++; + + /* + * The running thread inherits the ceiling + * priority of the mutex and executes at that + * priority. + */ + _thread_run->active_priority = (*mutex)->m_prio; + (*mutex)->m_saved_prio = + _thread_run->inherited_priority; + _thread_run->inherited_priority = + (*mutex)->m_prio; + + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_trylock(*mutex); + else + /* Return a busy error: */ + ret = EBUSY; break; /* Trap invalid mutex types: */ @@ -219,6 +326,12 @@ pthread_mutex_trylock(pthread_mutex_t * mutex) /* Unlock the mutex structure: */ _SPINUNLOCK(&(*mutex)->lock); + + /* + * Renable preemption and yield if a scheduling signal + * arrived while in the critical region: + */ + _thread_kern_sched_undefer(); } /* Return the completion status: */ @@ -238,91 +351,200 @@ pthread_mutex_lock(pthread_mutex_t * mutex) * initialization: */ else if (*mutex != NULL || (ret = init_static(mutex)) == 0) { + /* + * Guard against being preempted by a scheduling signal. + * To support priority inheritence mutexes, we need to + * maintain lists of mutex ownerships for each thread as + * well as lists of waiting threads for each mutex. In + * order to propagate priorities we need to atomically + * walk these lists and cannot rely on a single mutex + * lock to provide protection against modification. + */ + _thread_kern_sched_defer(); + /* Lock the mutex structure: */ _SPINLOCK(&(*mutex)->lock); /* Process according to mutex type: */ - switch ((*mutex)->m_type) { - /* What SS2 define as a 'normal' mutex. This has to deadlock - on attempts to get a lock you already own. */ - case PTHREAD_MUTEX_NORMAL: - if ((*mutex)->m_owner == _thread_run) { - /* Intetionally deadlock */ - for (;;) - _thread_kern_sched_state(PS_MUTEX_WAIT, __FILE__, __LINE__); - } - goto COMMON_LOCK; - - /* Return error (not OK) on attempting to re-lock */ - case PTHREAD_MUTEX_ERRORCHECK: - if ((*mutex)->m_owner == _thread_run) { - ret = EDEADLK; - break; - } - - /* Fast mutexes do not check for any error conditions: */ - case PTHREAD_MUTEX_DEFAULT: - COMMON_LOCK: - /* - * Enter a loop to wait for the mutex to be locked by the - * current thread: - */ - while ((*mutex)->m_owner != _thread_run) { - /* Check if the mutex is not locked: */ - if ((*mutex)->m_owner == NULL) { - /* Lock the mutex for this thread: */ - (*mutex)->m_owner = _thread_run; - } else { - /* - * Join the queue of threads waiting to lock - * the mutex: - */ - _thread_queue_enq(&(*mutex)->m_queue, _thread_run); + switch ((*mutex)->m_protocol) { + /* Default POSIX mutex: */ + case PTHREAD_PRIO_NONE: + if ((*mutex)->m_owner == NULL) { + /* Lock the mutex for this thread: */ + (*mutex)->m_owner = _thread_run; - /* Wait for the mutex: */ - _thread_kern_sched_state_unlock( - PS_MUTEX_WAIT, &(*mutex)->lock, - __FILE__, __LINE__); + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); - /* Lock the mutex again: */ - _SPINLOCK(&(*mutex)->lock); - } + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_lock(*mutex); + else { + /* + * Join the queue of threads waiting to lock + * the mutex: + */ + mutex_queue_enq(*mutex, _thread_run); + + /* + * Keep a pointer to the mutex this thread + * is waiting on: + */ + _thread_run->data.mutex = *mutex; + + /* + * Unlock the mutex structure and schedule the + * next thread: + */ + _thread_kern_sched_state_unlock(PS_MUTEX_WAIT, + &(*mutex)->lock, __FILE__, __LINE__); + + /* Lock the mutex structure again: */ + _SPINLOCK(&(*mutex)->lock); + + /* + * This thread is no longer waiting for + * the mutex: + */ + _thread_run->data.mutex = NULL; } break; - /* Counting mutex: */ - case PTHREAD_MUTEX_RECURSIVE: - /* - * Enter a loop to wait for the mutex to be locked by the - * current thread: - */ - while ((*mutex)->m_owner != _thread_run) { - /* Check if the mutex is not locked: */ - if ((*mutex)->m_owner == NULL) { - /* Lock the mutex for this thread: */ - (*mutex)->m_owner = _thread_run; - - /* Reset the lock count for this mutex: */ - (*mutex)->m_data.m_count = 0; - } else { - /* - * Join the queue of threads waiting to lock - * the mutex: - */ - _thread_queue_enq(&(*mutex)->m_queue, _thread_run); + /* POSIX priority inheritence mutex: */ + case PTHREAD_PRIO_INHERIT: + /* Check if this mutex is not locked: */ + if ((*mutex)->m_owner == NULL) { + /* Lock the mutex for this thread: */ + (*mutex)->m_owner = _thread_run; - /* Wait for the mutex: */ - _thread_kern_sched_state_unlock( - PS_MUTEX_WAIT, &(*mutex)->lock, - __FILE__, __LINE__); + /* Track number of priority mutexes owned: */ + _thread_run->priority_mutex_count++; - /* Lock the mutex again: */ - _SPINLOCK(&(*mutex)->lock); - } + /* + * The mutex takes on attributes of the + * running thread when there are no waiters. + */ + (*mutex)->m_prio = _thread_run->active_priority; + (*mutex)->m_saved_prio = + _thread_run->inherited_priority; + _thread_run->inherited_priority = + (*mutex)->m_prio; + + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); + + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_lock(*mutex); + else { + /* + * Join the queue of threads waiting to lock + * the mutex: + */ + mutex_queue_enq(*mutex, _thread_run); + + /* + * Keep a pointer to the mutex this thread + * is waiting on: + */ + _thread_run->data.mutex = *mutex; + + if (_thread_run->active_priority > + (*mutex)->m_prio) + /* Adjust priorities: */ + mutex_priority_adjust(*mutex); + + /* + * Unlock the mutex structure and schedule the + * next thread: + */ + _thread_kern_sched_state_unlock(PS_MUTEX_WAIT, + &(*mutex)->lock, __FILE__, __LINE__); + + /* Lock the mutex structure again: */ + _SPINLOCK(&(*mutex)->lock); + + /* + * This thread is no longer waiting for + * the mutex: + */ + _thread_run->data.mutex = NULL; } + break; + + /* POSIX priority protection mutex: */ + case PTHREAD_PRIO_PROTECT: + /* Check for a priority ceiling violation: */ + if (_thread_run->active_priority > (*mutex)->m_prio) + ret = EINVAL; + + /* Check if this mutex is not locked: */ + else if ((*mutex)->m_owner == NULL) { + /* + * Lock the mutex for the running + * thread: + */ + (*mutex)->m_owner = _thread_run; + + /* Track number of priority mutexes owned: */ + _thread_run->priority_mutex_count++; - /* Increment the lock count for this mutex: */ - (*mutex)->m_data.m_count++; + /* + * The running thread inherits the ceiling + * priority of the mutex and executes at that + * priority: + */ + _thread_run->active_priority = (*mutex)->m_prio; + (*mutex)->m_saved_prio = + _thread_run->inherited_priority; + _thread_run->inherited_priority = + (*mutex)->m_prio; + + /* Add to the list of owned mutexes: */ + TAILQ_INSERT_TAIL(&_thread_run->mutexq, + (*mutex), m_qe); + } else if ((*mutex)->m_owner == _thread_run) + ret = mutex_self_lock(*mutex); + else { + /* + * Join the queue of threads waiting to lock + * the mutex: + */ + mutex_queue_enq(*mutex, _thread_run); + + /* + * Keep a pointer to the mutex this thread + * is waiting on: + */ + _thread_run->data.mutex = *mutex; + + /* Clear any previous error: */ + _thread_run->error = 0; + + /* + * Unlock the mutex structure and schedule the + * next thread: + */ + _thread_kern_sched_state_unlock(PS_MUTEX_WAIT, + &(*mutex)->lock, __FILE__, __LINE__); + + /* Lock the mutex structure again: */ + _SPINLOCK(&(*mutex)->lock); + + /* + * The threads priority may have changed while + * waiting for the mutex causing a ceiling + * violation. + */ + ret = _thread_run->error; + _thread_run->error = 0; + + /* + * This thread is no longer waiting for + * the mutex: + */ + _thread_run->data.mutex = NULL; + } break; /* Trap invalid mutex types: */ @@ -334,6 +556,12 @@ pthread_mutex_lock(pthread_mutex_t * mutex) /* Unlock the mutex structure: */ _SPINUNLOCK(&(*mutex)->lock); + + /* + * Renable preemption and yield if a scheduling signal + * arrived while in the critical region: + */ + _thread_kern_sched_undefer(); } /* Return the completion status: */ @@ -343,56 +571,375 @@ pthread_mutex_lock(pthread_mutex_t * mutex) int pthread_mutex_unlock(pthread_mutex_t * mutex) { - int ret = 0; + return (mutex_unlock_common(mutex, /* add reference */ 0)); +} + +int +_mutex_cv_unlock(pthread_mutex_t * mutex) +{ + return (mutex_unlock_common(mutex, /* add reference */ 1)); +} + +int +_mutex_cv_lock(pthread_mutex_t * mutex) +{ + int ret; + if ((ret = pthread_mutex_lock(mutex)) == 0) + (*mutex)->m_refcount--; + return (ret); +} + +static inline int +mutex_self_trylock(pthread_mutex_t mutex) +{ + int ret = 0; + + switch (mutex->m_type) { + + /* case PTHREAD_MUTEX_DEFAULT: */ + case PTHREAD_MUTEX_ERRORCHECK: + case PTHREAD_MUTEX_NORMAL: + /* + * POSIX specifies that mutexes should return EDEADLK if a + * recursive lock is detected. + */ + ret = EBUSY; + break; + + case PTHREAD_MUTEX_RECURSIVE: + /* Increment the lock count: */ + mutex->m_data.m_count++; + break; + + default: + /* Trap invalid mutex types; */ + ret = EINVAL; + } + + return(ret); +} + +static inline int +mutex_self_lock(pthread_mutex_t mutex) +{ + int ret = 0; + + switch (mutex->m_type) { + /* case PTHREAD_MUTEX_DEFAULT: */ + case PTHREAD_MUTEX_ERRORCHECK: + /* + * POSIX specifies that mutexes should return EDEADLK if a + * recursive lock is detected. + */ + ret = EDEADLK; + break; + + case PTHREAD_MUTEX_NORMAL: + /* + * What SS2 define as a 'normal' mutex. Intentionally + * deadlock on attempts to get a lock you already own. + */ + _thread_kern_sched_state_unlock(PS_DEADLOCK, + &mutex->lock, __FILE__, __LINE__); + break; + + case PTHREAD_MUTEX_RECURSIVE: + /* Increment the lock count: */ + mutex->m_data.m_count++; + break; + + default: + /* Trap invalid mutex types; */ + ret = EINVAL; + } + + return(ret); +} + +static inline int +mutex_unlock_common(pthread_mutex_t * mutex, int add_reference) +{ + int ret = 0; if (mutex == NULL || *mutex == NULL) { ret = EINVAL; } else { + /* + * Guard against being preempted by a scheduling signal. + * To support priority inheritence mutexes, we need to + * maintain lists of mutex ownerships for each thread as + * well as lists of waiting threads for each mutex. In + * order to propagate priorities we need to atomically + * walk these lists and cannot rely on a single mutex + * lock to provide protection against modification. + */ + _thread_kern_sched_defer(); + /* Lock the mutex structure: */ _SPINLOCK(&(*mutex)->lock); /* Process according to mutex type: */ - switch ((*mutex)->m_type) { - /* Default & normal mutexes do not really need to check for - any error conditions: */ - case PTHREAD_MUTEX_NORMAL: - case PTHREAD_MUTEX_DEFAULT: - case PTHREAD_MUTEX_ERRORCHECK: - /* Check if the running thread is not the owner of the mutex: */ + switch ((*mutex)->m_protocol) { + /* Default POSIX mutex: */ + case PTHREAD_PRIO_NONE: + /* + * Check if the running thread is not the owner of the + * mutex: + */ if ((*mutex)->m_owner != _thread_run) { - /* Return an invalid argument error: */ - ret = (*mutex)->m_owner ? EPERM : EINVAL; + /* + * Return an invalid argument error for no + * owner and a permission error otherwise: + */ + ret = (*mutex)->m_owner == NULL ? EINVAL : EPERM; } + else if (((*mutex)->m_type == PTHREAD_MUTEX_RECURSIVE) && + ((*mutex)->m_data.m_count > 1)) { + /* Decrement the count: */ + (*mutex)->m_data.m_count--; + } else { + /* + * Clear the count in case this is recursive + * mutex. + */ + (*mutex)->m_data.m_count = 0; + + /* Remove the mutex from the threads queue. */ + TAILQ_REMOVE(&(*mutex)->m_owner->mutexq, + (*mutex), m_qe); + + /* + * Get the next thread from the queue of + * threads waiting on the mutex: + */ + if (((*mutex)->m_owner = + mutex_queue_deq(*mutex)) != NULL) { + /* + * Allow the new owner of the mutex to + * run: + */ + PTHREAD_NEW_STATE((*mutex)->m_owner, + PS_RUNNING); + } + } + break; + + /* POSIX priority inheritence mutex: */ + case PTHREAD_PRIO_INHERIT: /* - * Get the next thread from the queue of threads waiting on - * the mutex: + * Check if the running thread is not the owner of the + * mutex: */ - else if (((*mutex)->m_owner = _thread_queue_deq(&(*mutex)->m_queue)) != NULL) { - /* Allow the new owner of the mutex to run: */ - PTHREAD_NEW_STATE((*mutex)->m_owner,PS_RUNNING); + if ((*mutex)->m_owner != _thread_run) { + /* + * Return an invalid argument error for no + * owner and a permission error otherwise: + */ + ret = (*mutex)->m_owner == NULL ? EINVAL : EPERM; + } + else if (((*mutex)->m_type == PTHREAD_MUTEX_RECURSIVE) && + ((*mutex)->m_data.m_count > 1)) { + /* Decrement the count: */ + (*mutex)->m_data.m_count--; + } else { + /* + * Clear the count in case this is recursive + * mutex. + */ + (*mutex)->m_data.m_count = 0; + + /* + * Restore the threads inherited priority and + * recompute the active priority (being careful + * not to override changes in the threads base + * priority subsequent to locking the mutex). + */ + _thread_run->inherited_priority = + (*mutex)->m_saved_prio; + _thread_run->active_priority = + MAX(_thread_run->inherited_priority, + _thread_run->base_priority); + + /* + * This thread now owns one less priority mutex. + */ + _thread_run->priority_mutex_count--; + + /* Remove the mutex from the threads queue. */ + TAILQ_REMOVE(&(*mutex)->m_owner->mutexq, + (*mutex), m_qe); + + /* + * Get the next thread from the queue of threads + * waiting on the mutex: + */ + if (((*mutex)->m_owner = + mutex_queue_deq(*mutex)) == NULL) + /* This mutex has no priority. */ + (*mutex)->m_prio = 0; + else { + /* + * Track number of priority mutexes owned: + */ + (*mutex)->m_owner->priority_mutex_count++; + + /* + * Add the mutex to the threads list + * of owned mutexes: + */ + TAILQ_INSERT_TAIL(&(*mutex)->m_owner->mutexq, + (*mutex), m_qe); + + /* + * The owner is no longer waiting for + * this mutex: + */ + (*mutex)->m_owner->data.mutex = NULL; + + /* + * Set the priority of the mutex. Since + * our waiting threads are in descending + * priority order, the priority of the + * mutex becomes the active priority of + * the thread we just dequeued. + */ + (*mutex)->m_prio = + (*mutex)->m_owner->active_priority; + + /* + * Save the owning threads inherited + * priority: + */ + (*mutex)->m_saved_prio = + (*mutex)->m_owner->inherited_priority; + + /* + * The owning threads inherited priority + * now becomes his active priority (the + * priority of the mutex). + */ + (*mutex)->m_owner->inherited_priority = + (*mutex)->m_prio; + + /* + * Allow the new owner of the mutex to + * run: + */ + PTHREAD_NEW_STATE((*mutex)->m_owner, + PS_RUNNING); + } } break; - /* Counting mutex: */ - case PTHREAD_MUTEX_RECURSIVE: - /* Check if the running thread is not the owner of the mutex: */ + /* POSIX priority ceiling mutex: */ + case PTHREAD_PRIO_PROTECT: + /* + * Check if the running thread is not the owner of the + * mutex: + */ if ((*mutex)->m_owner != _thread_run) { - /* Return an invalid argument error: */ - ret = EINVAL; + /* + * Return an invalid argument error for no + * owner and a permission error otherwise: + */ + ret = (*mutex)->m_owner == NULL ? EINVAL : EPERM; } - /* Check if there are still counts: */ - else if ((*mutex)->m_data.m_count > 1) { + else if (((*mutex)->m_type == PTHREAD_MUTEX_RECURSIVE) && + ((*mutex)->m_data.m_count > 1)) { /* Decrement the count: */ (*mutex)->m_data.m_count--; } else { + /* + * Clear the count in case this is recursive + * mutex. + */ (*mutex)->m_data.m_count = 0; + /* - * Get the next thread from the queue of threads waiting on - * the mutex: + * Restore the threads inherited priority and + * recompute the active priority (being careful + * not to override changes in the threads base + * priority subsequent to locking the mutex). + */ + _thread_run->inherited_priority = + (*mutex)->m_saved_prio; + _thread_run->active_priority = + MAX(_thread_run->inherited_priority, + _thread_run->base_priority); + + /* + * This thread now owns one less priority mutex. */ - if (((*mutex)->m_owner = _thread_queue_deq(&(*mutex)->m_queue)) != NULL) { - /* Allow the new owner of the mutex to run: */ - PTHREAD_NEW_STATE((*mutex)->m_owner,PS_RUNNING); + _thread_run->priority_mutex_count--; + + /* Remove the mutex from the threads queue. */ + TAILQ_REMOVE(&(*mutex)->m_owner->mutexq, + (*mutex), m_qe); + + /* + * Enter a loop to find a waiting thread whose + * active priority will not cause a ceiling + * violation: + */ + while ((((*mutex)->m_owner = + mutex_queue_deq(*mutex)) != NULL) && + ((*mutex)->m_owner->active_priority > + (*mutex)->m_prio)) { + /* + * Either the mutex ceiling priority + * been lowered and/or this threads + * priority has been raised subsequent + * to this thread being queued on the + * waiting list. + */ + (*mutex)->m_owner->error = EINVAL; + PTHREAD_NEW_STATE((*mutex)->m_owner, + PS_RUNNING); + } + + /* Check for a new owner: */ + if ((*mutex)->m_owner != NULL) { + /* + * Track number of priority mutexes owned: + */ + (*mutex)->m_owner->priority_mutex_count++; + + /* + * Add the mutex to the threads list + * of owned mutexes: + */ + TAILQ_INSERT_TAIL(&(*mutex)->m_owner->mutexq, + (*mutex), m_qe); + + /* + * The owner is no longer waiting for + * this mutex: + */ + (*mutex)->m_owner->data.mutex = NULL; + + /* + * Save the owning threads inherited + * priority: + */ + (*mutex)->m_saved_prio = + (*mutex)->m_owner->inherited_priority; + + /* + * The owning thread inherits the + * ceiling priority of the mutex and + * executes at that priority: + */ + (*mutex)->m_owner->inherited_priority = + (*mutex)->m_prio; + (*mutex)->m_owner->active_priority = + (*mutex)->m_prio; + + /* + * Allow the new owner of the mutex to + * run: + */ + PTHREAD_NEW_STATE((*mutex)->m_owner, + PS_RUNNING); } } break; @@ -404,11 +951,348 @@ pthread_mutex_unlock(pthread_mutex_t * mutex) break; } + if ((ret == 0) && (add_reference != 0)) { + /* Increment the reference count: */ + (*mutex)->m_refcount++; + } + /* Unlock the mutex structure: */ _SPINUNLOCK(&(*mutex)->lock); + + /* + * Renable preemption and yield if a scheduling signal + * arrived while in the critical region: + */ + _thread_kern_sched_undefer(); } /* Return the completion status: */ return (ret); } + + +/* + * This function is called when a change in base priority occurs + * for a thread that is thread holding, or waiting for, a priority + * protection or inheritence mutex. A change in a threads base + * priority can effect changes to active priorities of other threads + * and to the ordering of mutex locking by waiting threads. + * + * This must be called while thread scheduling is deferred. + */ +void +_mutex_notify_priochange(pthread_t pthread) +{ + /* Adjust the priorites of any owned priority mutexes: */ + if (pthread->priority_mutex_count > 0) { + /* + * Rescan the mutexes owned by this thread and correct + * their priorities to account for this threads change + * in priority. This has the side effect of changing + * the threads active priority. + */ + mutex_rescan_owned(pthread, /* rescan all owned */ NULL); + } + + /* + * If this thread is waiting on a priority inheritence mutex, + * check for priority adjustments. A change in priority can + * also effect a ceiling violation(*) for a thread waiting on + * a priority protection mutex; we don't perform the check here + * as it is done in pthread_mutex_unlock. + * + * (*) It should be noted that a priority change to a thread + * _after_ taking and owning a priority ceiling mutex + * does not affect ownership of that mutex; the ceiling + * priority is only checked before mutex ownership occurs. + */ + if (pthread->state == PS_MUTEX_WAIT) { + /* Lock the mutex structure: */ + _SPINLOCK(&pthread->data.mutex->lock); + + /* + * Check to make sure this thread is still in the same state + * (the spinlock above can yield the CPU to another thread): + */ + if (pthread->state == PS_MUTEX_WAIT) { + /* + * Remove and reinsert this thread into the list of + * waiting threads to preserve decreasing priority + * order. + */ + mutex_queue_remove(pthread->data.mutex, pthread); + mutex_queue_enq(pthread->data.mutex, pthread); + + if (pthread->data.mutex->m_protocol == + PTHREAD_PRIO_INHERIT) { + /* Adjust priorities: */ + mutex_priority_adjust(pthread->data.mutex); + } + } + + /* Unlock the mutex structure: */ + _SPINUNLOCK(&pthread->data.mutex->lock); + } +} + +/* + * Called when a new thread is added to the mutex waiting queue or + * when a threads priority changes that is already in the mutex + * waiting queue. + */ +static void +mutex_priority_adjust(pthread_mutex_t mutex) +{ + pthread_t pthread_next, pthread = mutex->m_owner; + int temp_prio; + pthread_mutex_t m = mutex; + + /* + * Calculate the mutex priority as the maximum of the highest + * active priority of any waiting threads and the owning threads + * active priority(*). + * + * (*) Because the owning threads current active priority may + * reflect priority inherited from this mutex (and the mutex + * priority may have changed) we must recalculate the active + * priority based on the threads saved inherited priority + * and its base priority. + */ + pthread_next = TAILQ_FIRST(&m->m_queue); /* should never be NULL */ + temp_prio = MAX(pthread_next->active_priority, + MAX(m->m_saved_prio, pthread->base_priority)); + + /* See if this mutex really needs adjusting: */ + if (temp_prio == m->m_prio) + /* No need to propagate the priority: */ + return; + + /* Set new priority of the mutex: */ + m->m_prio = temp_prio; + + while (m != NULL) { + /* + * Save the threads priority before rescanning the + * owned mutexes: + */ + temp_prio = pthread->active_priority; + + /* + * Fix the priorities for all the mutexes this thread has + * locked since taking this mutex. This also has a + * potential side-effect of changing the threads priority. + */ + mutex_rescan_owned(pthread, m); + + /* + * If the thread is currently waiting on a mutex, check + * to see if the threads new priority has affected the + * priority of the mutex. + */ + if ((temp_prio != pthread->active_priority) && + (pthread->state == PS_MUTEX_WAIT) && + (pthread->data.mutex->m_protocol == PTHREAD_PRIO_INHERIT)) { + /* Grab the mutex this thread is waiting on: */ + m = pthread->data.mutex; + + /* + * The priority for this thread has changed. Remove + * and reinsert this thread into the list of waiting + * threads to preserve decreasing priority order. + */ + mutex_queue_remove(m, pthread); + mutex_queue_enq(m, pthread); + + /* Grab the waiting thread with highest priority: */ + pthread_next = TAILQ_FIRST(&m->m_queue); + + /* + * Calculate the mutex priority as the maximum of the + * highest active priority of any waiting threads and + * the owning threads active priority. + */ + temp_prio = MAX(pthread_next->active_priority, + MAX(m->m_saved_prio, m->m_owner->base_priority)); + + if (temp_prio != m->m_prio) { + /* + * The priority needs to be propagated to the + * mutex this thread is waiting on and up to + * the owner of that mutex. + */ + m->m_prio = temp_prio; + pthread = m->m_owner; + } + else + /* We're done: */ + m = NULL; + + } + else + /* We're done: */ + m = NULL; + } +} + +static void +mutex_rescan_owned (pthread_t pthread, pthread_mutex_t mutex) +{ + int active_prio, inherited_prio; + pthread_mutex_t m; + pthread_t pthread_next; + + /* + * Start walking the mutexes the thread has taken since + * taking this mutex. + */ + if (mutex == NULL) { + /* + * A null mutex means start at the beginning of the owned + * mutex list. + */ + m = TAILQ_FIRST(&pthread->mutexq); + + /* There is no inherited priority yet. */ + inherited_prio = 0; + } + else { + /* + * The caller wants to start after a specific mutex. It + * is assumed that this mutex is a priority inheritence + * mutex and that its priority has been correctly + * calculated. + */ + m = TAILQ_NEXT(mutex, m_qe); + + /* Start inheriting priority from the specified mutex. */ + inherited_prio = mutex->m_prio; + } + active_prio = MAX(inherited_prio, pthread->base_priority); + + while (m != NULL) { + /* + * We only want to deal with priority inheritence + * mutexes. This might be optimized by only placing + * priority inheritence mutexes into the owned mutex + * list, but it may prove to be useful having all + * owned mutexes in this list. Consider a thread + * exiting while holding mutexes... + */ + if (m->m_protocol == PTHREAD_PRIO_INHERIT) { + /* + * Fix the owners saved (inherited) priority to + * reflect the priority of the previous mutex. + */ + m->m_saved_prio = inherited_prio; + + if ((pthread_next = TAILQ_FIRST(&m->m_queue)) != NULL) + /* Recalculate the priority of the mutex: */ + m->m_prio = MAX(active_prio, + pthread_next->active_priority); + else + m->m_prio = active_prio; + + /* Recalculate new inherited and active priorities: */ + inherited_prio = m->m_prio; + active_prio = MAX(m->m_prio, pthread->base_priority); + } + + /* Advance to the next mutex owned by this thread: */ + m = TAILQ_NEXT(m, m_qe); + } + + /* + * Fix the threads inherited priority and recalculate its + * active priority. + */ + pthread->inherited_priority = inherited_prio; + active_prio = MAX(inherited_prio, pthread->base_priority); + + if (active_prio != pthread->active_priority) { + /* + * If this thread is in the priority queue, it must be + * removed and reinserted for its new priority. + */ + if ((pthread != _thread_run) && + (pthread->state == PS_RUNNING)) { + /* + * Remove the thread from the priority queue + * before changing its priority: + */ + PTHREAD_PRIOQ_REMOVE(pthread); + + /* + * POSIX states that if the priority is being + * lowered, the thread must be inserted at the + * head of the queue for its priority if it owns + * any priority protection or inheritence mutexes. + */ + if ((active_prio < pthread->active_priority) && + (pthread->priority_mutex_count > 0)) { + /* Set the new active priority. */ + pthread->active_priority = active_prio; + + PTHREAD_PRIOQ_INSERT_HEAD(pthread); + } + else { + /* Set the new active priority. */ + pthread->active_priority = active_prio; + + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + } + } + else { + /* Set the new active priority. */ + pthread->active_priority = active_prio; + } + } +} + +/* + * Dequeue a waiting thread from the head of a mutex queue in descending + * priority order. + */ +static inline pthread_t +mutex_queue_deq(pthread_mutex_t mutex) +{ + pthread_t pthread; + + if ((pthread = TAILQ_FIRST(&mutex->m_queue)) != NULL) + TAILQ_REMOVE(&mutex->m_queue, pthread, qe); + + return(pthread); +} + +/* + * Remove a waiting thread from a mutex queue in descending priority order. + */ +static inline void +mutex_queue_remove(pthread_mutex_t mutex, pthread_t pthread) +{ + TAILQ_REMOVE(&mutex->m_queue, pthread, qe); +} + +/* + * Enqueue a waiting thread to a queue in descending priority order. + */ +static inline void +mutex_queue_enq(pthread_mutex_t mutex, pthread_t pthread) +{ + pthread_t tid = TAILQ_LAST(&mutex->m_queue, mutex_head); + + /* + * For the common case of all threads having equal priority, + * we perform a quick check against the priority of the thread + * at the tail of the queue. + */ + if ((tid == NULL) || (pthread->active_priority <= tid->active_priority)) + TAILQ_INSERT_TAIL(&mutex->m_queue, pthread, qe); + else { + tid = TAILQ_FIRST(&mutex->m_queue); + while (pthread->active_priority <= tid->active_priority) + tid = TAILQ_NEXT(tid, qe); + TAILQ_INSERT_BEFORE(tid, pthread, qe); + } +} + #endif diff --git a/lib/libkse/thread/thr_mutex_prioceiling.c b/lib/libkse/thread/thr_mutex_prioceiling.c new file mode 100644 index 0000000..edd9fb5 --- /dev/null +++ b/lib/libkse/thread/thr_mutex_prioceiling.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_mutexattr_getprioceiling(pthread_mutexattr_t *mattr, int *prioceiling) +{ + int ret = 0; + + if ((mattr == NULL) || (*mattr == NULL)) + ret = EINVAL; + else if ((*mattr)->m_protocol != PTHREAD_PRIO_PROTECT) + ret = EINVAL; + else + *prioceiling = (*mattr)->m_ceiling; + + return(ret); +} + +int +pthread_mutexattr_setprioceiling(pthread_mutexattr_t *mattr, int prioceiling) +{ + int ret = 0; + + if ((mattr == NULL) || (*mattr == NULL)) + ret = EINVAL; + else if ((*mattr)->m_protocol != PTHREAD_PRIO_PROTECT) + ret = EINVAL; + else + (*mattr)->m_ceiling = prioceiling; + + return(ret); +} + +int +pthread_mutex_getprioceiling(pthread_mutex_t *mutex, + int *prioceiling) +{ + int ret; + + if ((mutex == NULL) || (*mutex == NULL)) + ret = EINVAL; + else if ((*mutex)->m_protocol != PTHREAD_PRIO_PROTECT) + ret = EINVAL; + else + ret = (*mutex)->m_prio; + + return(ret); +} + +int +pthread_mutex_setprioceiling(pthread_mutex_t *mutex, + int prioceiling, int *old_ceiling) +{ + int ret = 0; + + if ((mutex == NULL) || (*mutex == NULL)) + ret = EINVAL; + else if ((*mutex)->m_protocol != PTHREAD_PRIO_PROTECT) + ret = EINVAL; + else { + /* Lock the mutex: */ + if ((ret = pthread_mutex_lock(mutex)) == 0) { + /* Return the old ceiling and set the new ceiling: */ + *old_ceiling = (*mutex)->m_prio; + (*mutex)->m_prio = prioceiling; + + /* Unlock the mutex: */ + ret = pthread_mutex_unlock(mutex); + } + } + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_mutex_protocol.c b/lib/libkse/thread/thr_mutex_protocol.c new file mode 100644 index 0000000..56c5542 --- /dev/null +++ b/lib/libkse/thread/thr_mutex_protocol.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_mutexattr_getprotocol(pthread_mutexattr_t *mattr, int *protocol) +{ + int ret = 0; + + if ((mattr == NULL) || (*mattr == NULL)) + ret = EINVAL; + else + *protocol = (*mattr)->m_protocol; + + return(ret); +} + +int +pthread_mutexattr_setprotocol(pthread_mutexattr_t *mattr, int protocol) +{ + int ret = 0; + + if ((mattr == NULL) || (*mattr == NULL) || + (protocol < PTHREAD_PRIO_NONE) || (protocol > PTHREAD_PRIO_PROTECT)) + ret = EINVAL; + else { + (*mattr)->m_protocol = protocol; + (*mattr)->m_ceiling = PTHREAD_MAX_PRIORITY; + } + return(ret); +} + +#endif diff --git a/lib/libkse/thread/thr_priority_queue.c b/lib/libkse/thread/thr_priority_queue.c new file mode 100644 index 0000000..516a1e0 --- /dev/null +++ b/lib/libkse/thread/thr_priority_queue.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <stdlib.h> +#include <sys/queue.h> +#include <string.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +/* Prototypes: */ +static void pq_insert_prio_list(pq_queue_t *pq, int prio); + + +int +_pq_init(pq_queue_t *pq, int minprio, int maxprio) +{ + int i, ret = 0; + int prioslots = maxprio - minprio + 1; + + if (pq == NULL) + ret = -1; + + /* Create the priority queue with (maxprio - minprio + 1) slots: */ + else if ((pq->pq_lists = + (pq_list_t *) malloc(sizeof(pq_list_t) * prioslots)) == NULL) + ret = -1; + + else { + /* Initialize the queue for each priority slot: */ + for (i = 0; i < prioslots; i++) { + TAILQ_INIT(&pq->pq_lists[i].pl_head); + pq->pq_lists[i].pl_prio = i; + pq->pq_lists[i].pl_queued = 0; + } + + /* Initialize the priority queue: */ + TAILQ_INIT(&pq->pq_queue); + + /* Remember the queue size: */ + pq->pq_size = prioslots; + } + return (ret); +} + +void +_pq_remove(pq_queue_t *pq, pthread_t pthread) +{ + int prio = pthread->active_priority; + + TAILQ_REMOVE(&pq->pq_lists[prio].pl_head, pthread, pqe); +} + + +void +_pq_insert_head(pq_queue_t *pq, pthread_t pthread) +{ + int prio = pthread->active_priority; + + TAILQ_INSERT_HEAD(&pq->pq_lists[prio].pl_head, pthread, pqe); + if (pq->pq_lists[prio].pl_queued == 0) + /* Insert the list into the priority queue: */ + pq_insert_prio_list(pq, prio); +} + + +void +_pq_insert_tail(pq_queue_t *pq, pthread_t pthread) +{ + int prio = pthread->active_priority; + + TAILQ_INSERT_TAIL(&pq->pq_lists[prio].pl_head, pthread, pqe); + if (pq->pq_lists[prio].pl_queued == 0) + /* Insert the list into the priority queue: */ + pq_insert_prio_list(pq, prio); +} + + +pthread_t +_pq_first(pq_queue_t *pq) +{ + pq_list_t *pql; + pthread_t pthread = NULL; + + while (((pql = TAILQ_FIRST(&pq->pq_queue)) != NULL) && + (pthread == NULL)) { + if ((pthread = TAILQ_FIRST(&pql->pl_head)) == NULL) { + /* + * The priority list is empty; remove the list + * from the queue. + */ + TAILQ_REMOVE(&pq->pq_queue, pql, pl_link); + + /* Mark the list as not being in the queue: */ + pql->pl_queued = 0; + } + } + return (pthread); +} + + +static void +pq_insert_prio_list(pq_queue_t *pq, int prio) +{ + pq_list_t *pql; + + /* + * The priority queue is in descending priority order. Start at + * the beginning of the queue and find the list before which the + * new list should to be inserted. + */ + pql = TAILQ_FIRST(&pq->pq_queue); + while ((pql != NULL) && (pql->pl_prio > prio)) + pql = TAILQ_NEXT(pql, pl_link); + + /* Insert the list: */ + if (pql == NULL) + TAILQ_INSERT_TAIL(&pq->pq_queue, &pq->pq_lists[prio], pl_link); + else + TAILQ_INSERT_BEFORE(pql, &pq->pq_lists[prio], pl_link); + + /* Mark this list as being in the queue: */ + pq->pq_lists[prio].pl_queued = 1; +} + +#endif diff --git a/lib/libkse/thread/thr_private.h b/lib/libkse/thread/thr_private.h index 2d7e723..bf99a3b 100644 --- a/lib/libkse/thread/thr_private.h +++ b/lib/libkse/thread/thr_private.h @@ -55,6 +55,7 @@ #include <sys/time.h> #include <sched.h> #include <spinlock.h> +#include <pthread_np.h> /* * Kernel fatal error handler macro. @@ -65,16 +66,59 @@ #define stdout_debug(_x) _thread_sys_write(1,_x,strlen(_x)); #define stderr_debug(_x) _thread_sys_write(2,_x,strlen(_x)); + /* - * State change macro: + * Priority queue manipulation macros: */ -#define PTHREAD_NEW_STATE(thrd, newstate) { \ +#define PTHREAD_PRIOQ_INSERT_HEAD(thrd) _pq_insert_head(&_readyq,thrd) +#define PTHREAD_PRIOQ_INSERT_TAIL(thrd) _pq_insert_tail(&_readyq,thrd) +#define PTHREAD_PRIOQ_REMOVE(thrd) _pq_remove(&_readyq,thrd) +#define PTHREAD_PRIOQ_FIRST _pq_first(&_readyq) + +/* + * Waiting queue manipulation macros: + */ +#define PTHREAD_WAITQ_INSERT(thrd) TAILQ_INSERT_TAIL(&_waitingq,thrd,pqe) +#define PTHREAD_WAITQ_REMOVE(thrd) TAILQ_REMOVE(&_waitingq,thrd,pqe) + +/* + * State change macro without scheduling queue change: + */ +#define PTHREAD_SET_STATE(thrd, newstate) { \ (thrd)->state = newstate; \ (thrd)->fname = __FILE__; \ (thrd)->lineno = __LINE__; \ } /* + * State change macro with scheduling queue change - This must be + * called with preemption deferred (see thread_kern_sched_[un]defer). + */ +#define PTHREAD_NEW_STATE(thrd, newstate) { \ + if ((thrd)->state != newstate) { \ + if ((thrd)->state == PS_RUNNING) { \ + PTHREAD_PRIOQ_REMOVE(thrd); \ + PTHREAD_WAITQ_INSERT(thrd); \ + } else if (newstate == PS_RUNNING) { \ + PTHREAD_WAITQ_REMOVE(thrd); \ + PTHREAD_PRIOQ_INSERT_TAIL(thrd); \ + } \ + } \ + PTHREAD_SET_STATE(thrd, newstate); \ +} + +/* + * Define the signals to be used for scheduling. + */ +#if defined(_PTHREADS_COMPAT_SCHED) +#define _ITIMER_SCHED_TIMER ITIMER_VIRTUAL +#define _SCHED_SIGNAL SIGVTALRM +#else +#define _ITIMER_SCHED_TIMER ITIMER_PROF +#define _SCHED_SIGNAL SIGPROF +#endif + +/* * Queue definitions. */ struct pthread_queue { @@ -84,10 +128,34 @@ struct pthread_queue { }; /* + * Priority queues. + * + * XXX It'd be nice if these were contained in uthread_priority_queue.[ch]. + */ +typedef struct pq_list { + TAILQ_HEAD(, pthread) pl_head; /* list of threads at this priority */ + TAILQ_ENTRY(pq_list) pl_link; /* link for queue of priority lists */ + int pl_prio; /* the priority of this list */ + int pl_queued; /* is this in the priority queue */ +} pq_list_t; + +typedef struct pq_queue { + TAILQ_HEAD(, pq_list) pq_queue; /* queue of priority lists */ + pq_list_t *pq_lists; /* array of all priority lists */ + int pq_size; /* number of priority lists */ +} pq_queue_t; + + +/* * Static queue initialization values. */ #define PTHREAD_QUEUE_INITIALIZER { NULL, NULL, NULL } +/* + * TailQ initialization values. + */ +#define TAILQ_INITIALIZER { NULL, NULL } + /* * Mutex definitions. */ @@ -98,10 +166,31 @@ union pthread_mutex_data { struct pthread_mutex { enum pthread_mutextype m_type; - struct pthread_queue m_queue; + int m_protocol; + TAILQ_HEAD(mutex_head, pthread) m_queue; struct pthread *m_owner; union pthread_mutex_data m_data; long m_flags; + int m_refcount; + + /* + * Used for priority inheritence and protection. + * + * m_prio - For priority inheritence, the highest active + * priority (threads locking the mutex inherit + * this priority). For priority protection, the + * ceiling priority of this mutex. + * m_saved_prio - mutex owners inherited priority before + * taking the mutex, restored when the owner + * unlocks the mutex. + */ + int m_prio; + int m_saved_prio; + + /* + * Link for list of all mutexes a thread currently owns. + */ + TAILQ_ENTRY(pthread_mutex) m_qe; /* * Lock for accesses to this structure. @@ -120,11 +209,13 @@ struct pthread_mutex { * Static mutex initialization values. */ #define PTHREAD_MUTEX_STATIC_INITIALIZER \ - { MUTEX_TYPE_FAST, PTHREAD_QUEUE_INITIALIZER, \ - NULL, { NULL }, MUTEX_FLAGS_INITED } + { PTHREAD_MUTEX_DEFAULT, PTHREAD_PRIO_NONE, TAILQ_INITIALIZER, \ + NULL, { NULL }, MUTEX_FLAGS_INITED, 0, 0, 0, TAILQ_INITIALIZER } struct pthread_mutex_attr { enum pthread_mutextype m_type; + int m_protocol; + int m_ceiling; long m_flags; }; @@ -137,15 +228,16 @@ enum pthread_cond_type { }; struct pthread_cond { - enum pthread_cond_type c_type; - struct pthread_queue c_queue; - void *c_data; - long c_flags; + enum pthread_cond_type c_type; + TAILQ_HEAD(cond_head, pthread) c_queue; + pthread_mutex_t c_mutex; + void *c_data; + long c_flags; /* * Lock for accesses to this structure. */ - spinlock_t lock; + spinlock_t lock; }; struct pthread_cond_attr { @@ -164,7 +256,8 @@ struct pthread_cond_attr { * Static cond initialization values. */ #define PTHREAD_COND_STATIC_INITIALIZER \ - { COND_TYPE_FAST, PTHREAD_QUEUE_INITIALIZER, NULL, COND_FLAGS_INITED } + { COND_TYPE_FAST, PTHREAD_QUEUE_INITIALIZER, NULL, NULL \ + COND_FLAGS_INITED } /* * Cleanup definitions. @@ -176,7 +269,9 @@ struct pthread_cleanup { }; struct pthread_attr { - int schedparam_policy; + int sched_policy; + int sched_inherit; + int sched_interval; int prio; int suspend; int flags; @@ -254,9 +349,11 @@ enum pthread_state { PS_WAIT_WAIT, PS_SIGSUSPEND, PS_SIGWAIT, + PS_SPINBLOCK, PS_JOIN, PS_SUSPENDED, PS_DEAD, + PS_DEADLOCK, PS_STATE_MAX }; @@ -300,8 +397,8 @@ struct pthread_select_data { }; union pthread_wait_data { - pthread_mutex_t *mutex; - pthread_cond_t *cond; + pthread_mutex_t mutex; + pthread_cond_t cond; const sigset_t *sigwait; /* Waiting on a signal in sigwait */ struct { short fd; /* Used when thread waiting on fd */ @@ -309,6 +406,7 @@ union pthread_wait_data { char *fname; /* Source file name for debugging.*/ } fd; struct pthread_select_data * select_data; + spinlock_t *spinlock; }; /* @@ -419,7 +517,11 @@ struct pthread { struct pthread_queue join_queue; /* - * The current thread can belong to only one queue at a time. + * The current thread can belong to only one scheduling queue + * at a time (ready or waiting queue). It can also belong to + * a queue of threads waiting on mutexes or condition variables. + * Use pqe for the scheduling queue link (both ready and waiting), + * and qe for other links (mutexes and condition variables). * * Pointer to queue (if any) on which the current thread is waiting. * @@ -431,8 +533,11 @@ struct pthread { /* Pointer to next element in queue. */ struct pthread *qnxt; + /* Priority queue entry for this thread: */ + TAILQ_ENTRY(pthread) pqe; + /* Queue entry for this thread: */ - TAILQ_ENTRY(pthread) qe; + TAILQ_ENTRY(pthread) qe; /* Wait data. */ union pthread_wait_data data; @@ -446,10 +551,59 @@ struct pthread { /* Signal number when in state PS_SIGWAIT: */ int signo; + /* + * Set to non-zero when this thread has deferred thread + * scheduling. We allow for recursive deferral. + */ + int sched_defer_count; + + /* + * Set to TRUE if this thread should yield after undeferring + * thread scheduling. + */ + int yield_on_sched_undefer; + /* Miscellaneous data. */ - int flags; -#define PTHREAD_EXITING 0x0100 - char pthread_priority; + int flags; +#define PTHREAD_FLAGS_PRIVATE 0x0001 +#define PTHREAD_EXITING 0x0002 +#define PTHREAD_FLAGS_QUEUED 0x0004 /* in queue (qe is used) */ +#define PTHREAD_FLAGS_TRACE 0x0008 + + /* + * Base priority is the user setable and retrievable priority + * of the thread. It is only affected by explicit calls to + * set thread priority and upon thread creation via a thread + * attribute or default priority. + */ + char base_priority; + + /* + * Inherited priority is the priority a thread inherits by + * taking a priority inheritence or protection mutex. It + * is not affected by base priority changes. Inherited + * priority defaults to and remains 0 until a mutex is taken + * that is being waited on by any other thread whose priority + * is non-zero. + */ + char inherited_priority; + + /* + * Active priority is always the maximum of the threads base + * priority and inherited priority. When there is a change + * in either the real or inherited priority, the active + * priority must be recalculated. + */ + char active_priority; + + /* Number of priority ceiling or protection mutexes owned. */ + int priority_mutex_count; + + /* + * Queue of currently owned mutexes. + */ + TAILQ_HEAD(, pthread_mutex) mutexq; + void *ret; const void **specific_data; int specific_data_count; @@ -475,6 +629,14 @@ SCLASS struct pthread * volatile _thread_run ; #endif +/* Ptr to the thread structure for the last user thread to run: */ +SCLASS struct pthread * volatile _last_user_thread +#ifdef GLOBAL_PTHREAD_PRIVATE += &_thread_kern_thread; +#else +; +#endif + /* * Ptr to the thread running in single-threaded mode or NULL if * running multi-threaded (default POSIX behaviour). @@ -547,7 +709,7 @@ SCLASS struct pthread *_thread_initial /* Default thread attributes: */ SCLASS struct pthread_attr pthread_attr_default #ifdef GLOBAL_PTHREAD_PRIVATE -= { SCHED_RR, PTHREAD_DEFAULT_PRIORITY, PTHREAD_CREATE_RUNNING, += { SCHED_RR, 0, TIMESLICE_USEC, PTHREAD_DEFAULT_PRIORITY, PTHREAD_CREATE_RUNNING, PTHREAD_CREATE_JOINABLE, NULL, NULL, NULL, PTHREAD_STACK_DEFAULT }; #else ; @@ -556,7 +718,7 @@ SCLASS struct pthread_attr pthread_attr_default /* Default mutex attributes: */ SCLASS struct pthread_mutex_attr pthread_mutexattr_default #ifdef GLOBAL_PTHREAD_PRIVATE -= { MUTEX_TYPE_FAST, 0 }; += { PTHREAD_MUTEX_DEFAULT, PTHREAD_PRIO_NONE, 0, 0 }; #else ; #endif @@ -614,6 +776,27 @@ SCLASS pthread_cond_t _gc_cond */ struct sigaction _thread_sigact[NSIG]; +/* + * Scheduling queues: + */ +SCLASS pq_queue_t _readyq; +SCLASS TAILQ_HEAD(, pthread) _waitingq; + +/* Indicates that the waitingq now has threads ready to run. */ +SCLASS volatile int _waitingq_check_reqd +#ifdef GLOBAL_PTHREAD_PRIVATE += 0 +#endif +; + +/* Thread switch hook. */ +SCLASS pthread_switch_routine_t _sched_switch_hook +#ifdef GLOBAL_PTHREAD_PRIVATE += NULL +#endif +; + + /* Undefine the storage class specifier: */ #undef SCLASS @@ -645,6 +828,14 @@ void _lock_thread(void); void _lock_thread_list(void); void _unlock_thread(void); void _unlock_thread_list(void); +int _mutex_cv_lock(pthread_mutex_t *); +int _mutex_cv_unlock(pthread_mutex_t *); +void _mutex_notify_priochange(struct pthread *); +int _pq_init(struct pq_queue *pq, int, int); +void _pq_remove(struct pq_queue *pq, struct pthread *); +void _pq_insert_head(struct pq_queue *pq, struct pthread *); +void _pq_insert_tail(struct pq_queue *pq, struct pthread *); +struct pthread *_pq_first(struct pq_queue *pq); void _thread_exit(char *, int, char *); void _thread_fd_unlock(int, int); void _thread_fd_unlock_debug(int, int, char *, int); @@ -657,6 +848,8 @@ void _thread_kern_sched_state(enum pthread_state,char *fname,int lineno); void _thread_kern_sched_state_unlock(enum pthread_state state, spinlock_t *lock, char *fname, int lineno); void _thread_kern_set_timeout(struct timespec *); +void _thread_kern_sched_defer(void); +void _thread_kern_sched_undefer(void); void _thread_sig_handler(int, int, struct sigcontext *); void _thread_start(void); void _thread_start_sig_handler(void); diff --git a/lib/libkse/thread/thr_resume_np.c b/lib/libkse/thread/thr_resume_np.c index 7c5f46a..885a457 100644 --- a/lib/libkse/thread/thr_resume_np.c +++ b/lib/libkse/thread/thr_resume_np.c @@ -45,8 +45,21 @@ pthread_resume_np(pthread_t thread) if ((ret = _find_thread(thread)) == 0) { /* The thread exists. Is it suspended? */ if (thread->state != PS_SUSPENDED) { + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. + */ + _thread_kern_sched_defer(); + /* Allow the thread to run. */ PTHREAD_NEW_STATE(thread,PS_RUNNING); + + /* + * Reenable preemption and yield if a scheduling + * signal occurred while in the critical region. + */ + _thread_kern_sched_undefer(); } } return(ret); diff --git a/lib/libkse/thread/thr_select.c b/lib/libkse/thread/thr_select.c index d6202db..6d7d7dc 100644 --- a/lib/libkse/thread/thr_select.c +++ b/lib/libkse/thread/thr_select.c @@ -35,6 +35,7 @@ #include <string.h> #include <sys/types.h> #include <sys/time.h> +#include <sys/fcntl.h> #ifdef _THREAD_SAFE #include <pthread.h> #include "pthread_private.h" @@ -47,6 +48,7 @@ select(int numfds, fd_set * readfds, fd_set * writefds, struct timespec ts; struct timeval zero_timeout = {0, 0}; int i, ret = 0, got_all_locks = 1; + int f_wait = 1; struct pthread_select_data data; if (numfds > _thread_dtablesize) { @@ -59,6 +61,8 @@ select(int numfds, fd_set * readfds, fd_set * writefds, /* Set the wake up time: */ _thread_kern_set_timeout(&ts); + if (ts.tv_sec == 0 && ts.tv_nsec == 0) + f_wait = 0; } else { /* Wait for ever: */ _thread_kern_set_timeout(NULL); @@ -110,7 +114,7 @@ select(int numfds, fd_set * readfds, fd_set * writefds, if (exceptfds != NULL) { memcpy(&data.exceptfds, exceptfds, sizeof(data.exceptfds)); } - if ((ret = _thread_sys_select(data.nfds, &data.readfds, &data.writefds, &data.exceptfds, &zero_timeout)) == 0) { + if ((ret = _thread_sys_select(data.nfds, &data.readfds, &data.writefds, &data.exceptfds, &zero_timeout)) == 0 && f_wait) { data.nfds = numfds; FD_ZERO(&data.readfds); FD_ZERO(&data.writefds); diff --git a/lib/libkse/thread/thr_setprio.c b/lib/libkse/thread/thr_setprio.c index dd89f15..008b6b0 100644 --- a/lib/libkse/thread/thr_setprio.c +++ b/lib/libkse/thread/thr_setprio.c @@ -20,7 +20,7 @@ * 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 REGENTS OR CONTRIBUTORS BE LIABLE + * 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) @@ -38,17 +38,13 @@ int pthread_setprio(pthread_t pthread, int prio) { - int ret; + int ret, policy; + struct sched_param param; - /* Check if the priority is invalid: */ - if (prio < PTHREAD_MIN_PRIORITY || prio > PTHREAD_MAX_PRIORITY) - /* Return an invalid argument error: */ - ret = EINVAL; - - /* Find the thread in the list of active threads: */ - else if ((ret = _find_thread(pthread)) == 0) - /* Set the thread priority: */ - pthread->pthread_priority = prio; + if ((ret = pthread_getschedparam(pthread, &policy, ¶m)) == 0) { + param.sched_priority = prio; + ret = pthread_setschedparam(pthread, policy, ¶m); + } /* Return the error status: */ return (ret); diff --git a/lib/libkse/thread/thr_setschedparam.c b/lib/libkse/thread/thr_setschedparam.c new file mode 100644 index 0000000..93635da --- /dev/null +++ b/lib/libkse/thread/thr_setschedparam.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#include <sys/param.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +pthread_setschedparam(pthread_t pthread, int policy, struct sched_param *param) +{ + int old_prio, in_readyq = 0, ret = 0; + + if ((param == NULL) || (param->sched_priority < PTHREAD_MIN_PRIORITY) || + (param->sched_priority > PTHREAD_MAX_PRIORITY) || + (policy < SCHED_FIFO) || (policy > SCHED_RR)) + /* Return an invalid argument error: */ + ret = EINVAL; + + /* Find the thread in the list of active threads: */ + else if ((ret = _find_thread(pthread)) == 0) { + /* + * Guard against being preempted by a scheduling + * signal: + */ + _thread_kern_sched_defer(); + + if (param->sched_priority != pthread->base_priority) { + /* + * Remove the thread from its current priority + * queue before any adjustments are made to its + * active priority: + */ + if ((pthread != _thread_run) && + (pthread->state == PS_RUNNING)) { + in_readyq = 1; + old_prio = pthread->active_priority; + PTHREAD_PRIOQ_REMOVE(pthread); + } + + /* Set the thread base priority: */ + pthread->base_priority = param->sched_priority; + + /* Recalculate the active priority: */ + pthread->active_priority = MAX(pthread->base_priority, + pthread->inherited_priority); + + if (in_readyq) { + if ((pthread->priority_mutex_count > 0) && + (old_prio > pthread->active_priority)) { + /* + * POSIX states that if the priority is + * being lowered, the thread must be + * inserted at the head of the queue for + * its priority if it owns any priority + * protection or inheritence mutexes. + */ + PTHREAD_PRIOQ_INSERT_HEAD(pthread); + } + else + PTHREAD_PRIOQ_INSERT_TAIL(pthread); + } + + /* + * Check for any mutex priority adjustments. This + * includes checking for a priority mutex on which + * this thread is waiting. + */ + _mutex_notify_priochange(pthread); + } + + /* Set the scheduling policy: */ + pthread->attr.sched_policy = policy; + + /* + * Renable preemption and yield if a scheduling signal + * arrived while in the critical region: + */ + _thread_kern_sched_undefer(); + } + return(ret); +} +#endif diff --git a/lib/libkse/thread/thr_sig.c b/lib/libkse/thread/thr_sig.c index 3e55d65..e51d949 100644 --- a/lib/libkse/thread/thr_sig.c +++ b/lib/libkse/thread/thr_sig.c @@ -38,6 +38,19 @@ #include <pthread.h> #include "pthread_private.h" +/* + * State change macro for signal handler: + */ +#define PTHREAD_SIG_NEW_STATE(thrd, newstate) { \ + if ((_thread_run->sched_defer_count == 0) && \ + (_thread_kern_in_sched == 0)) { \ + PTHREAD_NEW_STATE(thrd, newstate); \ + } else { \ + _waitingq_check_reqd = 1; \ + PTHREAD_SET_STATE(thrd, newstate); \ + } \ +} + /* Static variables: */ static int volatile yield_on_unlock_thread = 0; static spinlock_t thread_link_list_lock = _SPINLOCK_INITIALIZER; @@ -94,14 +107,13 @@ _thread_sig_handler(int sig, int code, struct sigcontext * scp) */ _thread_sys_write(_thread_kern_pipe[1], &c, 1); } - /* Check if the signal requires a dump of thread information: */ if (sig == SIGINFO) /* Dump thread information to file: */ _thread_dump_info(); /* Check if an interval timer signal: */ - else if (sig == SIGVTALRM) { + else if (sig == _SCHED_SIGNAL) { /* Check if the scheduler interrupt has come at an * unfortunate time which one of the threads is * modifying the thread list: @@ -115,6 +127,14 @@ _thread_sig_handler(int sig, int code, struct sigcontext * scp) yield_on_unlock_thread = 1; /* + * Check if the scheduler interrupt has come when + * the currently running thread has deferred thread + * scheduling. + */ + else if (_thread_run->sched_defer_count) + _thread_run->yield_on_sched_undefer = 1; + + /* * Check if the kernel has not been interrupted while * executing scheduler code: */ @@ -170,18 +190,17 @@ _thread_sig_handler(int sig, int code, struct sigcontext * scp) } /* - * Enter a loop to process each thread in the linked + * Enter a loop to process each thread in the waiting * list that is sigwait-ing on a signal. Since POSIX * doesn't specify which thread will get the signal * if there are multiple waiters, we'll give it to the * first one we find. */ - for (pthread = _thread_link_list; pthread != NULL; - pthread = pthread->nxt) { + TAILQ_FOREACH(pthread, &_waitingq, pqe) { if ((pthread->state == PS_SIGWAIT) && sigismember(pthread->data.sigwait, sig)) { /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_SIG_NEW_STATE(pthread,PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; @@ -201,11 +220,19 @@ _thread_sig_handler(int sig, int code, struct sigcontext * scp) * list: */ for (pthread = _thread_link_list; pthread != NULL; - pthread = pthread->nxt) + pthread = pthread->nxt) { + pthread_t pthread_saved = _thread_run; + + _thread_run = pthread; _thread_signal(pthread,sig); - /* Dispatch pending signals to the running thread: */ - _dispatch_signals(); + /* + * Dispatch pending signals to the + * running thread: + */ + _dispatch_signals(); + _thread_run = pthread_saved; + } } /* Returns nothing. */ @@ -257,7 +284,7 @@ _thread_signal(pthread_t pthread, int sig) pthread->interrupted = 1; /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_SIG_NEW_STATE(pthread,PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; @@ -277,7 +304,7 @@ _thread_signal(pthread_t pthread, int sig) pthread->interrupted = 1; /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_SIG_NEW_STATE(pthread,PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; @@ -292,7 +319,7 @@ _thread_signal(pthread_t pthread, int sig) if (!sigismember(&pthread->sigmask, sig) && _thread_sigact[sig - 1].sa_handler != SIG_DFL) { /* Change the state of the thread to run: */ - PTHREAD_NEW_STATE(pthread,PS_RUNNING); + PTHREAD_SIG_NEW_STATE(pthread,PS_RUNNING); /* Return the signal number: */ pthread->signo = sig; diff --git a/lib/libkse/thread/thr_sigaction.c b/lib/libkse/thread/thr_sigaction.c index 40f3850..73a3b21 100644 --- a/lib/libkse/thread/thr_sigaction.c +++ b/lib/libkse/thread/thr_sigaction.c @@ -71,7 +71,7 @@ sigaction(int sig, const struct sigaction * act, struct sigaction * oact) * Check if the kernel needs to be advised of a change * in signal action: */ - if (act != NULL && sig != SIGVTALRM && sig != SIGCHLD && + if (act != NULL && sig != _SCHED_SIGNAL && sig != SIGCHLD && sig != SIGINFO) { /* Initialise the global signal action structure: */ gact.sa_mask = act->sa_mask; diff --git a/lib/libkse/thread/thr_sigpending.c b/lib/libkse/thread/thr_sigpending.c new file mode 100644 index 0000000..44a39a6 --- /dev/null +++ b/lib/libkse/thread/thr_sigpending.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1999 Daniel Eischen <eischen@vigrid.com>. + * 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 DANIEL EISCHEN 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 REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ +#include <signal.h> +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include "pthread_private.h" + +int +sigpending(sigset_t * set) +{ + int ret = 0; + + /* Check for a null signal set pointer: */ + if (set == NULL) { + /* Return an invalid argument: */ + ret = EINVAL; + } + else { + *set = _thread_run->sigpend; + } + /* Return the completion status: */ + return (ret); +} +#endif diff --git a/lib/libkse/thread/thr_sigwait.c b/lib/libkse/thread/thr_sigwait.c index 590f9db..98a5359 100644 --- a/lib/libkse/thread/thr_sigwait.c +++ b/lib/libkse/thread/thr_sigwait.c @@ -56,7 +56,7 @@ sigwait(const sigset_t * set, int *sig) */ sigdelset(&act.sa_mask, SIGKILL); sigdelset(&act.sa_mask, SIGSTOP); - sigdelset(&act.sa_mask, SIGVTALRM); + sigdelset(&act.sa_mask, _SCHED_SIGNAL); sigdelset(&act.sa_mask, SIGCHLD); sigdelset(&act.sa_mask, SIGINFO); diff --git a/lib/libkse/thread/thr_spinlock.c b/lib/libkse/thread/thr_spinlock.c index 9da115e..4da3f8c 100644 --- a/lib/libkse/thread/thr_spinlock.c +++ b/lib/libkse/thread/thr_spinlock.c @@ -29,7 +29,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: uthread_spinlock.c,v 1.3 1998/06/06 07:27:06 jb Exp $ + * $Id: uthread_spinlock.c,v 1.4 1998/06/09 23:13:10 jb Exp $ * */ @@ -56,12 +56,9 @@ _spinlock(spinlock_t *lck) * it before we do. */ while(_atomic_lock(&lck->access_lock)) { - /* Give up the time slice: */ - sched_yield(); - - /* Check if already locked by the running thread: */ - if (lck->lock_owner == (long) _thread_run) - return; + /* Block the thread until the lock. */ + _thread_run->data.spinlock = lck; + _thread_kern_sched_state(PS_SPINBLOCK, __FILE__, __LINE__); } /* The running thread now owns the lock: */ @@ -81,24 +78,25 @@ _spinlock(spinlock_t *lck) void _spinlock_debug(spinlock_t *lck, char *fname, int lineno) { + int cnt = 0; + /* * Try to grab the lock and loop if another thread grabs * it before we do. */ while(_atomic_lock(&lck->access_lock)) { - /* Give up the time slice: */ - sched_yield(); - - /* Check if already locked by the running thread: */ - if (lck->lock_owner == (long) _thread_run) { + cnt++; + if (cnt > 100) { char str[256]; - snprintf(str, sizeof(str), "%s - Warning: Thread %p attempted to lock %p from %s (%d) which it had already locked in %s (%d)\n", __progname, _thread_run, lck, fname, lineno, lck->fname, lck->lineno); + snprintf(str, sizeof(str), "%s - Warning: Thread %p attempted to lock %p from %s (%d) was left locked from %s (%d)\n", __progname, _thread_run, lck, fname, lineno, lck->fname, lck->lineno); _thread_sys_write(2,str,strlen(str)); - - /* Create a thread dump to help debug this problem: */ - _thread_dump_info(); - return; + sleep(1); + cnt = 0; } + + /* Block the thread until the lock. */ + _thread_run->data.spinlock = lck; + _thread_kern_sched_state(PS_SPINBLOCK, fname, lineno); } /* The running thread now owns the lock: */ diff --git a/lib/libkse/thread/thr_suspend_np.c b/lib/libkse/thread/thr_suspend_np.c index 871683a..6a6eaf4 100644 --- a/lib/libkse/thread/thr_suspend_np.c +++ b/lib/libkse/thread/thr_suspend_np.c @@ -51,8 +51,21 @@ pthread_suspend_np(pthread_t thread) thread->interrupted = 1; } + /* + * Guard against preemption by a scheduling signal. + * A change of thread state modifies the waiting + * and priority queues. + */ + _thread_kern_sched_defer(); + /* Suspend the thread. */ PTHREAD_NEW_STATE(thread,PS_SUSPENDED); + + /* + * Reenable preemption and yield if a scheduling signal + * occurred while in the critical region. + */ + _thread_kern_sched_undefer(); } return(ret); } diff --git a/lib/libkse/thread/thr_switch_np.c b/lib/libkse/thread/thr_switch_np.c new file mode 100644 index 0000000..8373214 --- /dev/null +++ b/lib/libkse/thread/thr_switch_np.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 1998 Daniel Eischen <eischen@vigrid.com>. + * 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 Daniel Eischen. + * 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 DANIEL EISCHEN 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. + * + */ +#include <errno.h> +#ifdef _THREAD_SAFE +#include <pthread.h> +#include <pthread_np.h> +#include "pthread_private.h" + + +int +pthread_switch_add_np(pthread_switch_routine_t routine) +{ + int ret = 0; + + if (routine == NULL) + /* Return an invalid argument error: */ + ret = EINVAL; + else + /* Shouldn't need a lock to protect this assigment. */ + _sched_switch_hook = routine; + + return(ret); +} + +int +pthread_switch_delete_np(pthread_switch_routine_t routine) +{ + int ret = 0; + + if (routine != _sched_switch_hook) + /* Return an invalid argument error: */ + ret = EINVAL; + else + /* Shouldn't need a lock to protect this assigment. */ + _sched_switch_hook = NULL; + + return(ret); +} +#endif |