and PPC ports (from CMUCL), control stack exhaustion checking (new)
and native threads support for x86 Linux (new). He also refactored
the garbage collectors for understandability, wrote code
- (e.g. grovel_headers.c and stat_wrapper stuff) to find
+ (e.g. grovel-headers.c and stat_wrapper stuff) to find
machine-dependent and OS-dependent constants automatically, and was
original author of the asdf, asdf-install, sb-bsd-sockets,
sb-executable, sb-grovel and sb-posix contrib packages.
;; Note that no consistent effort to audit the SBCL library code for
;; thread safety has been performed, so caveat executor.
; :sb-thread
+
+ ;; Kernel support for futexes (so-called "fast userspace mutexes") is
+ ;; available in Linux 2.6 and some versions of 2.4 (Red Hat vendor
+ ;; kernels, possibly other vendors too). We can take advantage of
+ ;; these to do faster and probably more reliable mutex and condition
+ ;; variable support. An SBCL built with this feature will fall back
+ ;; to the old system if the futex() syscall is not available at
+ ;; runtime
+ ; :sb-futex
;; This affects the definition of a lot of things in bignum.lisp. It
;; doesn't seem to be documented anywhere what systems it might apply
A filename is a string.
-A pathname is a designator for a file-descriptor: the filename is
-computed using the same mechanism as the implementation would
-use to map pathnames to OS filenames internally.
+A pathname is a designator for a filename: the filename is computed
+using the same mechanism as the implementation would use to map
+pathnames to OS filenames internally.
In an implementation that supports pathnames to files on other hosts,
using mechanisms not available to the underlying OS (for example,
(read fd buffer &optional (length (length buffer))) => bytes-read
b) where C simulates "out" parameters using pointers (for instance, in
-pipe() or socketpair()) we may use multiple return values instead.
-This doesn't apply to data transfer functions that fill buffers.
+pipe() or socketpair()) these may be optional or omitted in the Lisp
+interface: if not provided, appropriate objects will be allocated and
+returned (using multiple return values if necessary).
c) some functions accept objects such as filenames or file
-descriptors. Wherver these are specified as such in the C bindings,
+descriptors. Wherever these are specified as such in the C bindings,
the Lisp interface accepts designators for them as specified in the
'Types' section above
"MAKE-LISTENER-THREAD" "DESTROY-THREAD" "TERMINATE-THREAD"
"INTERRUPT-THREAD" "WITH-RECURSIVE-LOCK"
"MUTEX" "MAKE-MUTEX" "GET-MUTEX" "RELEASE-MUTEX" "WITH-MUTEX"
- "WAITQUEUE" "MAKE-WAITQUEUE" "CONDITION-WAIT" "CONDITION-NOTIFY"
+ "MUTEX-VALUE" "WAITQUEUE" "MAKE-WAITQUEUE"
+ "CONDITION-WAIT" "CONDITION-NOTIFY" "CONDITION-BROADCAST"
"WITH-RECURSIVE-LOCK" "RELEASE-FOREGROUND" "CURRENT-THREAD-ID"))
#s(sb-cold:package-data
;; disabled by default. Joe User can explicitly enable them if
;; desired.
(set-floating-point-modes :traps '(:overflow :invalid :divide-by-zero))
+ (sb!thread::maybe-install-futex-functions)
;; Clear pseudo atomic in case this core wasn't compiled with
;; support.
(defmacro with-recursive-lock ((mutex) &body body)
`(progn ,@body))
+
+
;;; For GENCGC all generations < GEN will be GC'ed.
-(defvar *already-in-gc* nil "System is running SUB-GC")
-(defvar *gc-mutex* (sb!thread:make-mutex :name "GC Mutex"))
+(defvar *already-in-gc*
+ (sb!thread:make-mutex :name "GC lock") "ID of thread running SUB-GC")
(defun sub-gc (&key (gen 0) &aux (pre-gc-dynamic-usage (dynamic-usage)))
- ;; catch attempts to gc recursively or during post-hooks and ignore them
- (when (sb!thread::mutex-value *gc-mutex*) (return-from sub-gc nil))
- (sb!thread:with-mutex (*gc-mutex* :wait-p nil)
+ (let ((me (sb!thread:current-thread-id)))
+ (when (eql (sb!thread::mutex-value *already-in-gc*) me)
+ (return-from sub-gc nil))
(setf *need-to-collect-garbage* t)
(when (zerop *gc-inhibit*)
- (without-interrupts
- (gc-stop-the-world)
- (collect-garbage gen)
- (incf *n-bytes-freed-or-purified*
- (max 0 (- pre-gc-dynamic-usage (dynamic-usage))))
- (setf *need-to-collect-garbage* nil)
- (gc-start-the-world))
- (scrub-control-stack)
- (setf *need-to-collect-garbage* nil)
- (dolist (h *after-gc-hooks*) (carefully-funcall h))))
- (values))
-
-
+ (loop
+ (sb!thread:with-mutex (*already-in-gc*)
+ (unless *need-to-collect-garbage* (return-from sub-gc nil))
+ (without-interrupts
+ (gc-stop-the-world)
+ (collect-garbage gen)
+ (incf *n-bytes-freed-or-purified*
+ (max 0 (- pre-gc-dynamic-usage (dynamic-usage))))
+ (scrub-control-stack)
+ (setf *need-to-collect-garbage* nil)
+ (dolist (h *after-gc-hooks*) (carefully-funcall h))
+ (gc-start-the-world)))))))
;;; This is the user-advertised garbage collection function.
(defun gc (&key (gen 0) (full nil) &allow-other-keys)
(in-package "SB!THREAD")
+;;; FIXME it would be good to define what a thread id is or isn't (our
+;;; current assumption is that it's a fixnum). It so happens that on
+;;; Linux it's a pid, but it might not be on posix thread implementations
+
(sb!alien::define-alien-routine ("create_thread" %create-thread)
sb!alien:unsigned-long
(lisp-fun-address sb!alien:unsigned-long))
+(sb!alien::define-alien-routine "signal_thread_to_dequeue"
+ sb!alien:unsigned-int
+ (thread-pid sb!alien:unsigned-long))
+
+
(defun make-thread (function)
(let ((real-function (coerce function 'function)))
(%create-thread
;;;; the higher-level locking operations are based on waitqueues
+(declaim (inline waitqueue-data-address mutex-value-address))
+
(defstruct waitqueue
(name nil :type (or null simple-base-string))
(lock 0)
(data nil))
+;;; The bare 4 here and 5 below are offsets of the slots in the struct.
+;;; There ought to be some better way to get these numbers
+(defun waitqueue-data-address (lock)
+ (declare (optimize (speed 3)))
+ (sb!ext:truly-the
+ (unsigned-byte 32)
+ (+ (sb!kernel:get-lisp-obj-address lock)
+ (- (* 4 sb!vm:n-word-bytes) sb!vm:instance-pointer-lowtag))))
+
(defstruct (mutex (:include waitqueue))
(value nil))
+(defun mutex-value-address (lock)
+ (declare (optimize (speed 3)))
+ (sb!ext:truly-the
+ (unsigned-byte 32)
+ (+ (sb!kernel:get-lisp-obj-address lock)
+ (- (* 5 sb!vm:n-word-bytes) sb!vm:instance-pointer-lowtag))))
+
(sb!alien:define-alien-routine "block_sigcont" void)
(sb!alien:define-alien-routine "unblock_sigcont_and_sleep" void)
+#!+sb-futex
+(declaim (inline futex-wait futex-wake))
+#!+sb-futex
+(sb!alien:define-alien-routine
+ "futex_wait" int (word unsigned-long) (old-value unsigned-long))
+#!+sb-futex
+(sb!alien:define-alien-routine
+ "futex_wake" int (word unsigned-long) (n unsigned-long))
;;; this should only be called while holding the queue spinlock.
;;; it releases the spinlock before sleeping
;;; this should only be called while holding the queue spinlock.
(defun signal-queue-head (queue)
(let ((p (car (waitqueue-data queue))))
- (when p (sb!unix:unix-kill p sb!unix::sig-dequeue))))
+ (when p (signal-thread-to-dequeue p))))
;;;; mutex
+;;; i suspect there may be a race still in this: the futex version requires
+;;; the old mutex value before sleeping, so how do we get away without it
(defun get-mutex (lock &optional new-value (wait-p t))
- (declare (type mutex lock)
- (optimize (speed 3)))
+ (declare (type mutex lock) (optimize (speed 3)))
(let ((pid (current-thread-id)))
(unless new-value (setf new-value pid))
(assert (not (eql new-value (mutex-value lock))))
(return nil))
(wait-on-queue lock nil))))
+#!+sb-futex
+(defun get-mutex/futex (lock &optional new-value (wait-p t))
+ (declare (type mutex lock) (optimize (speed 3)))
+ (let ((pid (current-thread-id))
+ old)
+ (unless new-value (setf new-value pid))
+ (assert (not (eql new-value (mutex-value lock))))
+ (loop
+ (unless
+ (setf old (sb!vm::%instance-set-conditional lock 4 nil new-value))
+ (return t))
+ (unless wait-p (return nil))
+ (futex-wait (mutex-value-address lock)
+ (sb!kernel:get-lisp-obj-address old)))))
+
(defun release-mutex (lock &optional (new-value nil))
(declare (type mutex lock))
;; we assume the lock is ours to release
(setf (mutex-value lock) new-value)
(signal-queue-head lock)))
+#!+sb-futex
+(defun release-mutex/futex (lock)
+ (declare (type mutex lock))
+ (setf (mutex-value lock) nil)
+ (futex-wake (mutex-value-address lock) 1))
+
(defmacro with-mutex ((mutex &key value (wait-p t)) &body body)
(with-unique-names (got)
(dequeue queue))
(get-mutex lock value))))
+#!+sb-futex
+(defun condition-wait/futex (queue lock)
+ (assert lock)
+ (let ((value (mutex-value lock)))
+ (unwind-protect
+ (let ((me (current-thread-id)))
+ ;; XXX we should do something to ensure that the result of this setf
+ ;; is visible to all CPUs
+ (setf (waitqueue-data queue) me)
+ (release-mutex lock)
+ ;; Now we go to sleep using futex-wait. If anyone else
+ ;; manages to grab LOCK and call CONDITION-NOTIFY during
+ ;; this comment, it will change queue->data, and so
+ ;; futex-wait returns immediately instead of sleeping.
+ ;; Ergo, no lost wakeup
+ (futex-wait (waitqueue-data-address queue)
+ (sb!kernel:get-lisp-obj-address me)))
+ ;; If we are interrupted while waiting, we should do these things
+ ;; before returning. Ideally, in the case of an unhandled signal,
+ ;; we should do them before entering the debugger, but this is
+ ;; better than nothing.
+ (get-mutex lock value))))
+
+
(defun condition-notify (queue)
"Notify one of the processes waiting on QUEUE"
(with-spinlock (queue) (signal-queue-head queue)))
+#!+sb-futex
+(defun condition-notify/futex (queue)
+ "Notify one of the processes waiting on QUEUE."
+ (let ((me (current-thread-id)))
+ ;; no problem if >1 thread notifies during the comment in
+ ;; condition-wait: as long as the value in queue-data isn't the
+ ;; waiting thread's id, it matters not what it is
+ ;; XXX we should do something to ensure that the result of this setf
+ ;; is visible to all CPUs
+ (setf (waitqueue-data queue) me)
+ (futex-wake (waitqueue-data-address queue) 1)))
+
+#!+sb-futex
+(defun condition-broadcast/futex (queue)
+ (let ((me (current-thread-id)))
+ (setf (waitqueue-data queue) me)
+ (futex-wake (waitqueue-data-address queue) (ash 1 30))))
+
+(defun condition-broadcast (queue)
+ "Notify all of the processes waiting on QUEUE."
+ (with-spinlock (queue)
+ (map nil #'signal-thread-to-dequeue (waitqueue-data queue))))
+
+;;; Futexes may be available at compile time but not runtime, so we
+;;; default to not using them unless os_init says they're available
+(defun maybe-install-futex-functions ()
+ #!+sb-futex
+ (unless (zerop (extern-alien "linux_supports_futex" int))
+ (setf (fdefinition 'get-mutex) #'get-mutex/futex
+ (fdefinition 'release-mutex) #'release-mutex/futex
+ (fdefinition 'condition-wait) #'condition-wait/futex
+ (fdefinition 'condition-broadcast) #'condition-broadcast/futex
+ (fdefinition 'condition-notify) #'condition-notify/futex)
+ t))
;;;; multiple independent listeners
;;;; job control
-(defvar *background-threads-wait-for-debugger* t)
-;;; may be T, NIL, or a function called with a stream and thread id
-;;; as its two arguments, returning NIl or T
+
+(defvar *interactive-threads-lock*
+ (make-mutex :name "*interactive-threads* lock"))
+(defvar *interactive-threads* nil)
+(defvar *interactive-threads-queue*
+ (make-waitqueue :name "All threads that need the terminal. First ID on this list is running, the others are waiting"))
+
+(defun init-job-control ()
+ (with-mutex (*interactive-threads-lock*)
+ (setf *interactive-threads* (list (current-thread-id)))
+ (return-from init-job-control t)))
;;; called from top of invoke-debugger
(defun debugger-wait-until-foreground-thread (stream)
"Returns T if thread had been running in background, NIL if it was
-already the foreground thread, or transfers control to the first applicable
-restart if *BACKGROUND-THREADS-WAIT-FOR-DEBUGGER* says to do that instead"
- (let* ((wait-p *background-threads-wait-for-debugger*)
- (*background-threads-wait-for-debugger* nil)
- (lock *session-lock*))
- (when (not (eql (mutex-value lock) (CURRENT-THREAD-ID)))
- (when (functionp wait-p)
- (setf wait-p
- (funcall wait-p stream (CURRENT-THREAD-ID))))
- (cond (wait-p (get-foreground))
- (t (invoke-restart (car (compute-restarts))))))))
-
-;;; install this with
-;;; (setf SB-INT:*REPL-PROMPT-FUN* #'sb-thread::thread-repl-prompt-fun)
-;;; One day it will be default
-(defun thread-repl-prompt-fun (out-stream)
- (let ((lock *session-lock*))
- (get-foreground)
- (let ((stopped-threads (waitqueue-data lock)))
- (when stopped-threads
- (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads))
- (sb!impl::repl-prompt-fun out-stream))))
-
-(defun resume-stopped-thread (id)
- (let ((lock *session-lock*))
- (with-spinlock (lock)
- (setf (waitqueue-data lock)
- (cons id (delete id (waitqueue-data lock)))))
- (release-foreground)))
-
-(defstruct rwlock
- (name nil :type (or null simple-base-string))
- (value 0 :type fixnum)
- (max-readers nil :type (or fixnum null))
- (max-writers 1 :type fixnum))
-#+nil
-(macrolet
- ((make-rwlocking-function (lock-fn unlock-fn increment limit test)
- (let ((do-update '(when (eql old-value
- (sb!vm::%instance-set-conditional
- lock 2 old-value new-value))
- (return (values t old-value))))
- (vars `((timeout (and timeout (+ (get-internal-real-time) timeout)))
- old-value
- new-value
- (limit ,limit))))
- (labels ((do-setfs (v) `(setf old-value (rwlock-value lock)
- new-value (,v old-value ,increment))))
- `(progn
- (defun ,lock-fn (lock timeout)
- (declare (type rwlock lock))
- (let ,vars
- (loop
- ,(do-setfs '+)
- (when ,test
- ,do-update)
- (when (sleep-a-bit timeout) (return nil)) ;expired
- )))
- ;; unlock doesn't need timeout or test-in-range
- (defun ,unlock-fn (lock)
- (declare (type rwlock lock))
- (declare (ignorable limit))
- (let ,(cdr vars)
- (loop
- ,(do-setfs '-)
- ,do-update))))))))
-
- (make-rwlocking-function %lock-for-reading %unlock-for-reading 1
- (rwlock-max-readers lock)
- (and (>= old-value 0)
- (or (null limit) (<= new-value limit))))
- (make-rwlocking-function %lock-for-writing %unlock-for-writing -1
- (- (rwlock-max-writers lock))
- (and (<= old-value 0)
- (>= new-value limit))))
-#+nil
-(defun get-rwlock (lock direction &optional timeout)
- (ecase direction
- (:read (%lock-for-reading lock timeout))
- (:write (%lock-for-writing lock timeout))))
-#+nil
-(defun free-rwlock (lock direction)
- (ecase direction
- (:read (%unlock-for-reading lock))
- (:write (%unlock-for-writing lock))))
-
-;;;; beyond this point all is commented.
-
-;;; Lock-Wait-With-Timeout -- Internal
-;;;
-;;; Wait with a timeout for the lock to be free and acquire it for the
-;;; *current-process*.
-;;;
-#+nil
-(defun lock-wait-with-timeout (lock whostate timeout)
- (declare (type lock lock))
- (process-wait-with-timeout
- whostate timeout
- #'(lambda ()
- (declare (optimize (speed 3)))
- #-i486
- (unless (lock-process lock)
- (setf (lock-process lock) *current-process*))
- #+i486
- (null (kernel:%instance-set-conditional
- lock 2 nil *current-process*)))))
-
-;;; With-Lock-Held -- Public
-;;;
-#+nil
-(defmacro with-lock-held ((lock &optional (whostate "Lock Wait")
- &key (wait t) timeout)
- &body body)
- "Execute the body with the lock held. If the lock is held by another
- process then the current process waits until the lock is released or
- an optional timeout is reached. The optional wait timeout is a time in
- seconds acceptable to process-wait-with-timeout. The results of the
- body are return upon success and NIL is return if the timeout is
- reached. When the wait key is NIL and the lock is held by another
- process then NIL is return immediately without processing the body."
- (let ((have-lock (gensym)))
- `(let ((,have-lock (eq (lock-process ,lock) *current-process*)))
- (unwind-protect
- ,(cond ((and timeout wait)
- `(progn
- (when (and (error-check-lock-p ,lock) ,have-lock)
- (error "Dead lock"))
- (when (or ,have-lock
- #+i486 (null (kernel:%instance-set-conditional
- ,lock 2 nil *current-process*))
- #-i486 (seize-lock ,lock)
- (if ,timeout
- (lock-wait-with-timeout
- ,lock ,whostate ,timeout)
- (lock-wait ,lock ,whostate)))
- ,@body)))
- (wait
- `(progn
- (when (and (error-check-lock-p ,lock) ,have-lock)
- (error "Dead lock"))
- (unless (or ,have-lock
- #+i486 (null (kernel:%instance-set-conditional
- ,lock 2 nil *current-process*))
- #-i486 (seize-lock ,lock))
- (lock-wait ,lock ,whostate))
- ,@body))
- (t
- `(when (or (and (recursive-lock-p ,lock) ,have-lock)
- #+i486 (null (kernel:%instance-set-conditional
- ,lock 2 nil *current-process*))
- #-i486 (seize-lock ,lock))
- ,@body)))
- (unless ,have-lock
- #+i486 (kernel:%instance-set-conditional
- ,lock 2 *current-process* nil)
- #-i486 (when (eq (lock-process ,lock) *current-process*)
- (setf (lock-process ,lock) nil)))))))
-
-
+interactive."
+ (prog1
+ (with-mutex (*interactive-threads-lock*)
+ (not (member (current-thread-id) *interactive-threads*)))
+ (get-foreground)))
+(defun thread-repl-prompt-fun (out-stream)
+ (get-foreground)
+ (let ((stopped-threads (cdr *interactive-threads*)))
+ (when stopped-threads
+ (format out-stream "~{~&Thread ~A suspended~}~%" stopped-threads))
+ (sb!impl::repl-prompt-fun out-stream)))
+
+(defun get-foreground ()
+ (loop
+ (with-mutex (*interactive-threads-lock*)
+ (let ((tid (current-thread-id)))
+ (when (eql (car *interactive-threads*) tid)
+ (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
+ (return-from get-foreground t))
+ (unless (member tid *interactive-threads*)
+ (setf (cdr (last *interactive-threads*)) (list tid)))
+ (condition-wait
+ *interactive-threads-queue* *interactive-threads-lock* )))))
+
+(defun release-foreground (&optional next)
+ "Background this thread. If NEXT is supplied, arrange for it to have the foreground next"
+ (with-mutex (*interactive-threads-lock*)
+ (let ((tid (current-thread-id)))
+ (setf *interactive-threads* (delete tid *interactive-threads*))
+ (sb!sys:enable-interrupt sb!unix:sigint :ignore)
+ (when next (setf *interactive-threads*
+ (list* next (delete next *interactive-threads*))))
+ (condition-broadcast *interactive-threads-queue*))))
\ No newline at end of file
(signal-queue-head queue))
-;;;; multiple independent listeners
-
-(defvar *session-lock* nil)
-
;;;; job control
(defun debugger-wait-until-foreground-thread (stream) t)
+(defun get-foreground () t)
+(defun release-foreground (&optional next) t)
+
(in-package "SB!THREAD")
-(defvar *session-lock*)
-
(sb!xc:defmacro with-recursive-lock ((mutex) &body body)
#!+sb-thread
(with-unique-names (cfp)
#!-sb-thread
`(progn ,@body))
-#!+sb-thread
-(defun get-foreground ()
- (when (not (eql (mutex-value *session-lock*) (current-thread-id)))
- (get-mutex *session-lock*))
- (sb!sys:enable-interrupt sb!unix:sigint #'sb!unix::sigint-handler)
- t)
-#!-sb-thread
-(defun get-foreground () t)
-
-#!+sb-thread
-(defun release-foreground ()
- (sb!sys:enable-interrupt sb!unix:sigint :ignore)
- (release-mutex *session-lock*)
- t)
-#!-sb-thread
-(defun release-foreground () t)
(defun toplevel-init ()
(/show0 "entering TOPLEVEL-INIT")
- (setf sb!thread::*session-lock* (sb!thread:make-mutex :name "the terminal"))
+ (sb!thread::init-job-control)
(sb!thread::get-foreground)
(let (;; value of --sysinit option
(sysinit nil)
return (NULL);
}
-static lispobj*
+lispobj*
search_read_only_space(lispobj *pointer)
{
lispobj* start = (lispobj*)READ_ONLY_SPACE_START;
return (search_space(start, (pointer+2)-start, pointer));
}
-static lispobj *
+lispobj *
search_static_space(lispobj *pointer)
{
lispobj* start = (lispobj*)STATIC_SPACE_START;
}
#ifdef LISP_FEATURE_SB_THREAD
-void handle_rt_signal(int num, siginfo_t *info, void *v_context)
+void interrupt_thread_handler(int num, siginfo_t *info, void *v_context)
{
os_context_t *context = (os_context_t*)arch_os_get_context(&v_context);
struct thread *th=arch_os_get_current_thread();
struct interrupt_data *data=
th ? th->interrupt_data : global_interrupt_data;
- if(maybe_defer_handler(handle_rt_signal,data,num,info,context)){
+ if(maybe_defer_handler(interrupt_thread_handler,data,num,info,context)){
return ;
}
arrange_return_to_lisp_function(context,info->si_value.sival_int);
extern boolean handle_control_stack_guard_triggered(os_context_t *,void *);
extern boolean interrupt_maybe_gc(int, siginfo_t*, void*);
#ifdef LISP_FEATURE_SB_THREAD
-extern void handle_rt_signal(int, siginfo_t*, void*);
+extern void interrupt_thread_handler(int, siginfo_t*, void*);
extern void sig_stop_for_gc_handler(int, siginfo_t*, void*);
#endif
extern void undoably_install_low_level_interrupt_handler (int signal,
#include "thread.h"
size_t os_vm_page_size;
+#ifdef LISP_FEATURE_SB_FUTEX
+#include <asm/unistd.h>
+#include <errno.h>
+
+/* values taken from the kernel's linux/futex.h. This header file
+ doesn't exist in userspace, which is our excuse for not grovelling
+ them automatically */
+#define FUTEX_WAIT (0)
+#define FUTEX_WAKE (1)
+#define FUTEX_FD (2)
+#define FUTEX_REQUEUE (3)
+
+#define __NR_sys_futex __NR_futex
+
+_syscall4(int,sys_futex,
+ int *, futex,
+ int, op,
+ int, val,
+ struct timespec *, rel);
+#endif
+
#include "gc.h"
\f
int linux_sparc_siginfo_bug = 0;
+int linux_supports_futex=0;
void os_init(void)
{
/* Conduct various version checks: do we have enough mmap(), is
* this a sparc running 2.2, can we do threads? */
- {
- struct utsname name;
- int major_version;
- int minor_version;
- char *p;
- uname(&name);
- p=name.release;
- major_version = atoi(p);
- p=strchr(p,'.')+1;
- minor_version = atoi(p);
- if (major_version<2) {
- lose("linux kernel version too old: major version=%d (can't run in version < 2.0.0)",
- major_version);
- }
+ int *futex=0;
+ struct utsname name;
+ int major_version;
+ int minor_version;
+ char *p;
+ uname(&name);
+ p=name.release;
+ major_version = atoi(p);
+ p=strchr(p,'.')+1;
+ minor_version = atoi(p);
+ if (major_version<2) {
+ lose("linux kernel version too old: major version=%d (can't run in version < 2.0.0)",
+ major_version);
+ }
+ if (!(major_version>2 || minor_version >= 4)) {
#ifdef LISP_FEATURE_SB_THREAD
- if ((major_version <2) || (major_version==2 && minor_version < 4)) {
- lose("linux kernel 2.4 required for thread-enabled SBCL");
- }
+ lose("linux kernel 2.4 required for thread-enabled SBCL");
#endif
#ifdef LISP_FEATURE_SPARC
- if ((major_version <2) || (major_version==2 && minor_version < 4)) {
- FSHOW((stderr,"linux kernel %d.%d predates 2.4;\n enabling workarounds for SPARC kernel bugs in signal handling.\n", major_version,minor_version));
- linux_sparc_siginfo_bug = 1;
- }
+ FSHOW((stderr,"linux kernel %d.%d predates 2.4;\n enabling workarounds for SPARC kernel bugs in signal handling.\n", major_version,minor_version));
+ linux_sparc_siginfo_bug = 1;
#endif
}
-
- os_vm_page_size = getpagesize();
- /* This could just as well be in arch_init(), but it's not. */
-#ifdef LISP_FEATURE_X86
- /* FIXME: This used to be here. However, I have just removed it
- with no apparent ill effects (it may be that earlier kernels
- started up a process with a different set of traps, or
- something?) Find out what this was meant to do, and reenable it
- or delete it if possible. -- CSR, 2002-07-15 */
- /* SET_FPU_CONTROL_WORD(0x1372|4|8|16|32); no interrupts */
+#ifdef LISP_FEATURE_SB_FUTEX
+ futex_wait(futex,-1);
+ if(errno!=ENOSYS) linux_supports_futex=1;
#endif
+ os_vm_page_size = getpagesize();
}
sigsegv_handler);
#ifdef LISP_FEATURE_SB_THREAD
undoably_install_low_level_interrupt_handler(SIG_INTERRUPT_THREAD,
- handle_rt_signal);
+ interrupt_thread_handler);
undoably_install_low_level_interrupt_handler(SIG_STOP_FOR_GC,
sig_stop_for_gc_handler);
+ if(!linux_supports_futex)
+ undoably_install_low_level_interrupt_handler(SIG_DEQUEUE,
+ sigcont_handler);
#endif
- undoably_install_low_level_interrupt_handler(SIG_DEQUEUE,
- sigcont_handler);
}
+#ifdef LISP_FEATURE_SB_FUTEX
+int futex_wait(int *lock_word, int oldval) {
+ int t= sys_futex(lock_word,FUTEX_WAIT,oldval, 0);
+ return t;
+}
+int futex_wake(int *lock_word, int n){
+ return sys_futex(lock_word,FUTEX_WAKE,n,0);
+}
+#endif
#define OS_VM_PROT_EXECUTE PROT_EXEC
#define SIG_MEMORY_FAULT SIGSEGV
-#define SIG_INTERRUPT_THREAD SIGRTMIN
+
+#define SIG_INTERRUPT_THREAD (SIGRTMIN)
#define SIG_STOP_FOR_GC (SIGRTMIN+1)
#define SIG_DEQUEUE (SIGRTMIN+2)
-
fprintf(stderr, "/continue\n");
}
th->unbound_marker = UNBOUND_MARKER_WIDETAG;
+ if(arch_os_thread_init(th)==0)
+ return 1; /* failure. no, really */
#ifdef LISP_FEATURE_SB_THREAD
/* wait here until our thread is linked into all_threads: see below */
while(th->pid<1) sched_yield();
lose("th->pid not set up right");
#endif
- if(arch_os_thread_init(th)==0)
- return 1; /* failure. no, really */
+ th->state=STATE_RUNNING;
#if !defined(LISP_FEATURE_SB_THREAD) && defined(LISP_FEATURE_X86)
return call_into_lisp_first_time(function,args,0);
#else
th->binding_stack_pointer=th->binding_stack_start;
th->this=th;
th->pid=0;
- th->state=STATE_RUNNING;
+ th->state=STATE_STOPPED;
#ifdef LISP_FEATURE_STACK_GROWS_DOWNWARD_NOT_UPWARD
th->alien_stack_pointer=((void *)th->alien_stack_start
+ ALIEN_STACK_SIZE-4); /* naked 4. FIXME */
#endif
#ifdef LISP_FEATURE_X86
th->pseudo_atomic_interrupted=0;
- /* runtime.c used to set PSEUDO_ATOMIC_ATOMIC =1 globally. I'm not
- * sure why, but it appears to help */
- th->pseudo_atomic_atomic=make_fixnum(1);
+ th->pseudo_atomic_atomic=0;
#endif
#ifdef LISP_FEATURE_GENCGC
gc_set_region_empty(&th->alloc_region);
return sigqueue(pid, SIG_INTERRUPT_THREAD, sigval);
}
+int signal_thread_to_dequeue (pid_t pid)
+{
+ return kill (pid, SIG_DEQUEUE);
+}
+
+
/* stopping the world is a two-stage process. From this thread we signal
* all the others with SIG_STOP_FOR_GC. The handler for this thread does
* the usual pseudo-atomic checks (we don't want to stop a thread while
{
/* stop all other threads by sending them SIG_STOP_FOR_GC */
struct thread *p,*th=arch_os_get_current_thread();
- struct thread *tail=0;
+ pid_t old_pid;
int finished=0;
do {
get_spinlock(&all_threads_lock,th->pid);
- if(tail!=all_threads) {
- /* new threads always get consed onto the front of all_threads,
- * and may be created by any thread that we haven't signalled
- * yet or hasn't received our signal and stopped yet. So, check
- * for them on each time around */
- for(p=all_threads;p!=tail;p=p->next) {
- if(p==th) continue;
- /* if the head of all_threads is removed during
- * gc_stop_the_world, we may take a second trip through the
- * list and end up counting twice as many threads to wait for
- * as actually exist */
- if(p->state!=STATE_RUNNING) continue;
- countdown_to_gc++;
- p->state=STATE_STOPPING;
- /* Note no return value check from kill(). If the
- * thread had been reaped already, we kill it and
- * increment countdown_to_gc anyway. This is to avoid
- * complicating the logic in destroy_thread, which would
- * otherwise have to know whether the thread died before or
- * after it was killed
- */
- kill(p->pid,SIG_STOP_FOR_GC);
- }
- tail=all_threads;
- } else {
- finished=(countdown_to_gc==0);
+ for(p=all_threads,old_pid=p->pid; p; p=p->next) {
+ if(p==th) continue;
+ if(p->state!=STATE_RUNNING) continue;
+ countdown_to_gc++;
+ p->state=STATE_STOPPING;
+ /* Note no return value check from kill(). If the
+ * thread had been reaped already, we kill it and
+ * increment countdown_to_gc anyway. This is to avoid
+ * complicating the logic in destroy_thread, which would
+ * otherwise have to know whether the thread died before or
+ * after it was killed
+ */
+ kill(p->pid,SIG_STOP_FOR_GC);
}
release_spinlock(&all_threads_lock);
sched_yield();
+ /* if everything has stopped, and there is no possibility that
+ * a new thread has been created, we're done. Otherwise go
+ * round again and signal anything that sprang up since last
+ * time */
+ if(old_pid==all_threads->pid) {
+ finished=1;
+ for_each_thread(p)
+ finished = finished &&
+ ((p==th) || (p->state==STATE_STOPPED));
+ }
} while(!finished);
}
int vlen;
int code;
- FSHOW((stderr, "/[arch_skip_inst at %x]\n", *os_context_pc_addr(context)));
-
+
/* Get and skip the Lisp interrupt code. */
code = *(char*)(*os_context_pc_addr(context))++;
switch (code)
int code = info->si_code;
os_context_t *context = (os_context_t*)void_context;
unsigned int trap;
+ sigset_t ss;
if (single_stepping && (signal==SIGTRAP))
{
case trap_PendingInterrupt:
FSHOW((stderr, "/<trap pending interrupt>\n"));
arch_skip_instruction(context);
+ sigemptyset(&ss);
+ sigaddset(&ss,SIGTRAP);
+ sigprocmask(SIG_UNBLOCK,&ss,0);
interrupt_handle_pending(context);
break;
;;; For one of the interupt-thread tests, we want a foreign function
;;; that does not make syscalls
-(setf SB-INT:*REPL-PROMPT-FUN* #'sb-thread::thread-repl-prompt-fun)
(with-open-file (o "threads-foreign.c" :direction :output)
(format o "void loop_forever() { while(1) ; }~%"))
(sb-ext:run-program
;;; elementary "can we get a lock and release it again"
(let ((l (make-mutex :name "foo"))
(p (current-thread-id)))
- (assert (eql (mutex-value l) nil))
- (assert (eql (mutex-lock l) 0))
+ (assert (eql (mutex-value l) nil) nil "1")
+ (assert (eql (mutex-lock l) 0) nil "2")
(sb-thread:get-mutex l)
- (assert (eql (mutex-value l) p))
- (assert (eql (mutex-lock l) 0))
+ (assert (eql (mutex-value l) p) nil "3")
+ (assert (eql (mutex-lock l) 0) nil "4")
(sb-thread:release-mutex l)
- (assert (eql (mutex-value l) nil))
- (assert (eql (mutex-lock l) 0)))
+ (assert (eql (mutex-value l) nil) nil "5")
+ (assert (eql (mutex-lock l) 0) nil "6")
+ (describe l))
(let ((queue (make-waitqueue :name "queue"))
(lock (make-mutex :name "lock")))
(condition-notify queue))
(sleep 1)))
+(let ((mutex (make-mutex :name "contended")))
+ (labels ((run ()
+ (let ((me (current-thread-id)))
+ (dotimes (i 100)
+ (with-mutex (mutex)
+ (sleep .1)
+ (assert (eql (mutex-value mutex) me)))
+ (assert (not (eql (mutex-value mutex) me))))
+ (format t "done ~A~%" (current-thread-id)))))
+ (let ((kid1 (make-thread #'run))
+ (kid2 (make-thread #'run)))
+ (format t "contention ~A ~A~%" kid1 kid2))))
(defun test-interrupt (function-to-interrupt &optional quit-p)
(let ((child (make-thread function-to-interrupt)))
(terminate-thread child))
(defun alloc-stuff () (copy-list '(1 2 3 4 5)))
+
(let ((c (test-interrupt (lambda () (loop (alloc-stuff))))))
;; NB this only works on x86: other ports don't have a symbol for
;; pseudo-atomic atomicity
+ (format t "new thread ~A~%" c)
(dotimes (i 100)
(sleep (random 1d0))
(interrupt-thread c
(assert (zerop SB-KERNEL:*PSEUDO-ATOMIC-ATOMIC*)))))
(terminate-thread c))
-;; I'm not sure that this one is always successful. Note race potential:
-;; I haven't checked if decf is atomic here
-(let ((done 2))
- (make-thread (lambda () (dotimes (i 100) (sb-ext:gc)) (decf done)))
- (make-thread (lambda () (dotimes (i 25) (sb-ext:gc :full t)) (decf done)))
+(format t "~&interrupt test done~%")
+
+(let (a-done b-done)
+ (make-thread (lambda ()
+ (dotimes (i 100)
+ (sb-ext:gc) (princ "\\") (force-output) )
+ (setf a-done t)))
+ (make-thread (lambda ()
+ (dotimes (i 25)
+ (sb-ext:gc :full t)
+ (princ "/") (force-output))
+ (setf b-done t)))
(loop
- (when (zerop done) (return))
+ (when (and a-done b-done) (return))
(sleep 1)))
+(format t "~&gc test done~%")
+
+#| ;; a cll post from eric marsden
+| (defun crash ()
+| (setq *debugger-hook*
+| (lambda (condition old-debugger-hook)
+| (debug:backtrace 10)
+| (unix:unix-exit 2)))
+| #+live-dangerously
+| (mp::start-sigalrm-yield)
+| (flet ((roomy () (loop (with-output-to-string (*standard-output*) (room)))))
+| (mp:make-process #'roomy)
+| (mp:make-process #'roomy)))
+|#
;; give the other thread time to die before we leave, otherwise the
;; overall exit status is 0, not 104
/* don't need no steenking hand-editing */
printf(
";;;; This is an automatically generated file, please do not hand-edit it.\n\
-;;;; See the program \"grovel_headers.c\".\n\
+;;;; See the program \"grovel-headers.c\".\n\
\n\
");
DEFSIGNAL(SIGXCPU);
DEFSIGNAL(SIGXFSZ);
#endif
-#ifdef LISP_FEATURE_SB_THREAD
- /* FIXME OAOOM alert: this information is duplicated in linux-os.h */
- defconstant("sig-dequeue",SIGRTMIN+2);
-#endif
return 0;
}
;;; checkins which aren't released. (And occasionally for internal
;;; versions, especially for internal versions off the main CVS
;;; branch, it gets hairier, e.g. "0.pre7.14.flaky4.13".)
-"0.8.6.4"
+"0.8.6.5"