diff --git a/mps/code/than.c b/mps/code/than.c index 5723a42b67d..8c5af222898 100644 --- a/mps/code/than.c +++ b/mps/code/than.c @@ -5,11 +5,7 @@ * * This is a single-threaded implementation of the threads manager. * Has stubs for thread suspension. - * See . - * - * .single: We only expect at most one thread on the ring. - * - * This supports the + * See . */ #include "mpm.h" @@ -67,7 +63,6 @@ Res ThreadRegister(Thread *threadReturn, Arena arena) AVERT(Thread, thread); ring = ArenaThreadRing(arena); - AVER(RingCheckSingle(ring)); /* .single */ RingAppend(ring, &thread->arenaRing); diff --git a/mps/design/thread-manager.txt b/mps/design/thread-manager.txt index b358d6aa521..21443f01108 100644 --- a/mps/design/thread-manager.txt +++ b/mps/design/thread-manager.txt @@ -9,60 +9,95 @@ Thread manager :Status: incomplete design :Revision: $Id$ :Copyright: See `Copyright and License`_. -:Index terms: pair: thread manager; design +:Index terms: pair: thread manager; design -Purpose -------- +Introduction +------------ -The Thread Manager handles various thread-related functions required -by the MPS. These are: +_`.intro`: This document describes the design of the thread manager +module. -- stack scanning; -- suspension and resumption of the mutator threads. +_`.readership`: Any MPS developer. + +_`.overview`: The thread manager implements two features that allow +the MPS to work in a multi-threaded environment: exclusive access to +memory, and scanning of roots in a thread's registers and control +stack. -Context -------- +Requirements +------------ -The barrier requires suspension and resumption of threads in order to -ensure that the collector has exclusive access to part of memory. -[design.mps.barrier.@@@@] +_`.req.exclusive`: The thread manager must provide the MPS with +exclusive access to the memory it manages in critical sections of the +code. (This is necessary to avoid for the MPS to be able to flip +atomically from the point of view of the mutator.) -Stack scanning is provided as a service to the client. [Link?@@@@] +_`.req.scan`: The thread manager must be able to locate references in +the registers and control stack of the current thread, or of a +suspended thread. (This is necessary in order to implement +conservative collection, in environments where the registers and +control stack contain ambiguous roots. Scanning of roots is carried +out during the flip, hence while other threads are suspended.) + +_`.req.register.multi`: It must be possible to register the same +thread multiple times. (This is needed to support the situation where +a program that does not use the MPS is calling into MPS-using code +from multiple threads. On entry to the MPS-using code, the thread can +be registered, but it may not be possible to ensure that the thread is +deregistered on exit, because control may be transferred by some +non-local mechanism such as an exception or ``longjmp()``. We don't +want to insist that the client program keep a table of threads it has +registered, because maintaining the table might require allocation, +which might provoke a collection. See request.dylan.160252_.) + +.. _request.dylan.160252: https://info.ravenbrook.com/project/mps/import/2001-11-05/mmprevol/request/dylan/160252/ -Overview --------- +Design +------ + +_`.sol.exclusive`: In order to meet `.req.exclusive`_, the arena +maintains a ring of threads (in ``arena->threadRing``) that have been +registered by the client program. When the MPS needs exclusive access +to memory, it suspends all the threads in the ring except for the +currently running thread. When the MPS no longer needs exclusive +access to memory, it resumes all threads in the ring. + +_`.sol.exclusive.assumption`: This relies on the assumption that any +thread that might refer to, read from, or write to memory in +automatically managed pool classes is registered with the MPS. This is +documented in the manual under ``mps_thread_reg()``. + + +Interface +--------- ``typedef struct mps_thr_s *Thread`` -Each thread is represented by an object of type ``Thread``. The -``Thread`` type is implemented as an ADT. A list of ``Thread`` objects -is maintained in the arena (as the ``Ring`` structure -``arena->threadRing``). The ``Thread`` object contains -operating-system-dependent information about the thread -- information -necessary for manipulating the thread and for scanning the thread -context. Thread "registration" adds or removes the current thread to -the ``Thread`` list in the arena. +The type of threads. It is a pointer to an opaque structure, which +must be defined by the implementation. +``Bool ThreadCheck(Thread thread)`` -Detailed design ---------------- +The check function for threads. See design.mps.check_. -Stack scan -.......... +.. _design.mps.check: check -This is a module providing a stack scanning function. The scanning is -architecture and operating system dependent. Typically the function -will push the subste of the save registers (those preserved across -function calls) which may contain pointers (that is, neither -floating-point or debugging registers) and call ``TraceScanStack()`` -on the appropriate range. +``Bool ThreadCheckSimple(Thread thread)`` +A thread-safe check function for threads, for use by +``mps_thread_dereg()``. It can't use ``AVER(TESTT(Thread, thread))``, +as recommended by design.mps.sig.check.arg.unlocked_, since ``Thread`` +is an opaque type. -Thread interface -................ +.. _design.mps.sig.check.arg.unlocked: sig#check.arg.unlocked + +``Arena ThreadArena(Thread thread)`` + +Return the arena that the thread is registered with. Must be +thread-safe as it is necessarily called before taking the arena lock. ``Res ThreadRegister(Thread *threadReturn, Arena arena)`` @@ -76,84 +111,161 @@ free it. ``void ThreadRingSuspend(Ring threadRing)`` -Suspend all the threads on the list ``threadRing``, except for the -current thread. +Suspend all the threads on ``threadRing``, except for the current +thread. ``void ThreadRingResume(Ring threadRing)`` -Resume all the threads on the list ``threadRing``. +Resume all the threads on ``threadRing``. + +``Thread ThreadRingThread(Ring threadRing)`` + +Return the thread that owns an element of the thread ring. ``Res ThreadScan(ScanState ss, Thread thread, void *stackBot)`` Scan the stacks and root registers of ``thread``, treating each value -found as an ambiguous reference. The exact definition is operating -system and architecture dependent +found as an ambiguous reference. -Single-threaded generic implementation -...................................... +Implementations +--------------- -In ``than.c``. +Generic implementation +...................... -- Single threaded (calling ``ThreadRegister()`` on a second thread - causes an assertion failure). -- ``ThreadRingSuspend()`` and ``ThreadRingResume()`` do nothing - because there are no other threads. -- ``ThreadScan()`` calls ``StackScan()``. +_`.impl.an`: In ``than.c``. + +_`.impl.an.single`: Supports a single thread. (This cannot be enforced +because of `.req.register.multi`_.) + +_`.impl.an.register.multi`: There is no need for any special treatment +of multiple threads, because ``ThreadRingSuspend()`` and +``ThreadRingResume()`` do nothing. + +_`.impl.an.suspend`: ``ThreadRingSuspend()`` does nothing because +there are no other threads. + +_`.impl.an.resume`: ``ThreadRingResume()`` does nothing because no +threads are ever suspended. + +_`.impl.an.scan`: Just calls ``StackScan()`` since there are no +suspended threads. POSIX threads implementation ............................ -In ``thix.c``. See design.mps.pthreadext_. +_`.impl.ix`: In ``thix.c`` and ``pthrdext.c``. See +design.mps.pthreadext_. .. _design.mps.pthreadext: pthreadext +_`.impl.ix.multi`: Supports multiple threads. -Win32 implementation -.................... +_`.impl.ix.register`: ``ThreadRegister()`` records the thread id +the current thread by calling ``pthread_self()``. -In ``thw3.c``. +_`.impl.ix.register.multi`: Multiply-registered threads are handled +specially by the POSIX thread extensions. See +design.mps.pthreadext.req.suspend.multiple_ and +design.mps.pthreadext.req.resume.multiple_. -- Supports multiple threads. -- Structured exception style faults are expected. -- ``ThreadRingSuspend()`` and ``ThreadRingResume()`` loop over threads - and call Win32 API functions ``SuspendThread()`` and - ``ResumeThread()``. -- ``ThreadRegister()`` records information for the current thread: +.. _design.mps.pthreadext.req.suspend.multiple: pthreadext#req.suspend.multiple +.. _design.mps.pthreadext.req.resume.multiple: pthreadext#req.resume.multiple - - A Win32 "handle" with access flags ``THREAD_SUSPEND_RESUME`` and - ``THREAD_GET_CONTEXT``. This handle is needed as parameter to - ``SuspendThread()`` and ``ResumeThread()``. - - The Win32 ``GetCurrentThreadId()`` so that the current thread may - be identified. +_`.impl.ix.suspend`: ``ThreadRingSuspend()`` calls +``PThreadextSuspend()``. See design.mps.pthreadext.if.suspend_. -- Stack scanning is Win32 specific: +.. _design.mps.pthreadext.if.suspend: pthreadext#if.suspend - - ``ThreadScan()`` calls ``StackScan()`` if the thread is current. - ``GetThreadContext()`` doesn't work on the current thread (the - context would not necessarily have the values which were in the - saved registers on entry to the MM). - - Otherwise it calls ``GetThreadContext()`` to get the root - registers and the stack pointer. - - The thread's registers are dumped into a ``CONTEXT`` structure and - fixed in memory. - - Scan the stack (getting the stack pointer from ``CONTEXT.Rsp``). +_`.impl.ix.resume`: ``ThreadRingResume()`` calls +``PThreadextResume()``. See design.mps.pthreadext.if.resume_. + +.. _design.mps.pthreadext.if.resume: pthreadext#if.resume + +_`.impl.ix.scan.current`: ``ThreadScan()`` calls ``StackScan()`` if +the thread is current. + +_`.impl.ix.scan.suspended`: ``PThreadextSuspend()`` records the +context of each suspended thread, and ``ThreadRingSuspend()`` stores +this in the ``Thread`` structure, so that is available by the time +``ThreadScan()`` is called. The thread's registers are dumped into a +``ucontext_t`` structure and fixed in memory. The stack pointer is +obtained from ``ucontext_t.uc_mcontext.mc_esp`` (FreeBSD on IA-32), +``uc_mcontext.gregs[REG_ESP]`` (Linux on IA-32), +``ucontext_t.uc_mcontext.mc_rsp`` (FreeBSD on x86-64), or +``uc_mcontext.gregs[REG_RSP]`` (Linux on x86-64). -Issues ------- +Windows implementation +...................... -#. Scanning after exceptions. ``StackScan()`` relies on the - non-preserved registers having been pushed on the stack. If we want - to scan after a fault we must make sure that these registers are - either already stored on the stack, or, have an extra function to - do this explicitly. +_`.impl.w3`: In ``thw3.c``. -#. Multiple registration. It is not clear whether a thread should be - allowed to be registered multiple times. We do not provide a - mechanism for knowing whether a thread is already registered with - an arena. +_`.impl.w3.multi`: Supports multiple threads. + +_`.impl.w3.register`: ``ThreadRegister()`` records the following +information for the current thread: + + - A ``HANDLE`` to the process, with access flags + ``THREAD_SUSPEND_RESUME`` and ``THREAD_GET_CONTEXT``. This handle + is needed as parameter to ``SuspendThread()`` and + ``ResumeThread()``. + - The result of ``GetCurrentThreadId()``, so that the current thread + may be identified in the ring of threads. + +_`.impl.w3.register.multi`: There is no need for any special treatment +of multiple threads, because Windows maintains a suspend count that is +incremented on ``SuspendThread()`` and decremented on +``ResumeThread()``. + +_`.impl.w3.suspend`: ``ThreadRingSuspend()`` calls ``SuspendThread()``. + +_`.impl.w3.resume`: ``ThreadRingResume()`` calls ``ResumeThread()``. + +_`.impl.w3.scan.current`: ``ThreadScan()`` calls ``StackScan()`` if +the thread is current. This is because ``GetThreadContext()`` doesn't +work on the current thread: the context would not necessarily have the +values which were in the saved registers on entry to the MPS. + +_`.impl.w3.scan.suspended`: Otherwise, ``ThreadScan()`` calls +``GetThreadContext()`` to get the root registers and the stack +pointer. The thread's registers are dumped into a ``CONTEXT`` +structure and fixed in memory. The stack pointer is obtained from +``CONTEXT.Rsp`` (on IA-32) or ``CONTEXT.Rsp`` (on x86-64) and then the +stack is scanned. + + +OS X implementation +................... + +_`.impl.xc`: In ``thxc.c``. + +_`.impl.xc.multi`: Supports multiple threads. + +_`.impl.xc.register`: ``ThreadRegister()`` records the Mach port of +the current thread by calling ``mach_thread_self()``. + +_`.impl.xc.register.multi`: There is no need for any special treatment +of multiple threads, because Mach maintains a suspend count that is +incremented on ``thread_suspend()`` and decremented on +``thread_resume()``. + +_`.impl.xc.suspend`: ``ThreadRingSuspend()`` calls +``thread_suspend()``. + +_`.impl.xc.resume`: ``ThreadRingResume()`` calls ``thread_resume()``. + +_`.impl.xc.scan.current`: ``ThreadScan()`` calls ``StackScan()`` if +the thread is current. + +_`.impl.xc.scan.suspended`: Otherwise, ``ThreadScan()`` calls +``thread_get_state()`` to get the root registers and the stack +pointer. The thread's registers are dumped into a ``THREAD_STATE_S`` +structure and fixed in memory. The stack pointer is obtained from +``THREAD_STATE_S.__esp`` (on IA-32) or ``THREAD_STATE_S.__rsp`` (on +x86-64) and then the stack is scanned. Document History @@ -165,6 +277,8 @@ Document History - 2013-05-26 GDR_ Converted to reStructuredText. +- 2014-10-22 GDR_ Complete design. + .. _RB: http://www.ravenbrook.com/consultants/rb/ .. _GDR: http://www.ravenbrook.com/consultants/gdr/ diff --git a/mps/manual/source/design/index.rst b/mps/manual/source/design/index.rst index 5055bc74f0d..6db472df738 100644 --- a/mps/manual/source/design/index.rst +++ b/mps/manual/source/design/index.rst @@ -25,6 +25,7 @@ Design sig splay testthr + thread-manager type vm vman diff --git a/mps/manual/source/design/old.rst b/mps/manual/source/design/old.rst index e4432f1b0fe..4bfcf934711 100644 --- a/mps/manual/source/design/old.rst +++ b/mps/manual/source/design/old.rst @@ -56,7 +56,6 @@ Old design strategy telemetry tests - thread-manager thread-safety trace version-library diff --git a/mps/manual/source/topic/porting.rst b/mps/manual/source/topic/porting.rst index 45310889d53..1920859bfe5 100644 --- a/mps/manual/source/topic/porting.rst +++ b/mps/manual/source/topic/porting.rst @@ -42,8 +42,10 @@ usable. generic implementation in ``lockan.c``, which cannot actually take any locks and so only works for a single thread. -#. The **threads** module suspends and resumes :term:`threads`, so - that the MPS can gain exclusive access to :term:`memory (2)`. +#. The **thread manager** module suspends and resumes :term:`threads`, + so that the MPS can gain exclusive access to :term:`memory (2)`, + and so that it can scan the :term:`registers` and :term:`control + stack` of suspended threads. See :ref:`design-thread-manager` for the design, and ``th.h`` for the interface. There are implementations for POSIX in ``thix.c`` @@ -75,8 +77,10 @@ usable. #. The **protection mutator context** module figures out what the :term:`mutator` was doing when it caused a :term:`protection - fault`, so that the access can be emulated as described at - :ref:`pool-awl-barrier`. + fault`, or when a thread was suspended, so that its + :term:`registers` and :term:`control stack` can be scanned, and so + that access to a protected region of memory can be emulated as + described at :ref:`pool-awl-barrier`. See :ref:`design-prot` for the design, and ``prot.h`` for the interface. There are eight implementations, a typical example being