.. index::
   pair: Linux; protection interface design
   pair: Linux protection interface; design

.. _design-protli:


Linux implementation of protection module
=========================================

.. mps:prefix:: design.mps.protli
   pair: Linux; protection interface design
   pair: Linux protection interface; design


Introduction
------------

:mps:tag:`readership` Any MPS developer

:mps:tag:`intro` This is the design of the Linux implementation of the
protection module. It makes use of various services provided by Linux.
It is intended to work with LinuxThreads.


Requirements
------------

:mps:tag:`req.general` Required to implement the general protection
interface defined in design.mps.prot.if.


Data structures
---------------

:mps:tag:`data.signext` This is static. Because that is the only
communications channel available to signal handlers.

.. note::

    Write a little more here.


Functions
---------

:mps:tag:`fun.setup` :c:func:`ProtSetup()` installs a signal handler for the
signal :c:macro:`SIGSEGV` to catch and handle protection faults (this handler
is the function :c:func:`sigHandle()`). The previous handler is recorded (in
the variable ``sigNext``, see :mps:ref:`.data.signext`) so that it can be
reached from :c:func:`sigHandle()` if it fails to handle the fault.

:mps:tag:`fun.setup.problem` The problem with this approach is that we can't
honour the wishes of the ``sigvec(2)`` entry for the previous handler
(in terms of masks in particular).

:mps:tag:`improve.sigvec` What if when we want to pass on the signal instead
of calling the handler we call :c:func:`sigvec()` with the old entry and use
:c:func:`kill()` to send the signal to ourselves and then restore our
handler using :c:func:`sigvec()` again?

.. note::

    Need more detail and analysis here.

:mps:tag:`fun.set` :c:func:`ProtSet()` uses :c:func:`mprotect()` to adjust the
protection for pages.

:mps:tag:`fun.set.convert` The requested protection (which is expressed in
the ``mode`` parameter, see design.mps.prot.if.set) is translated into
an operating system protection. If read accesses are to be forbidden
then all accesses are forbidden, this is done by setting the
protection of the page to :c:macro:`PROT_NONE`. If write accesses are to be
forbidden (and not read accesses) then write accesses are forbidden
and read accesses are allowed, this is done by setting the protection
of the page to ``PROT_READ|PROT_EXEC``. Otherwise (all access are
okay), the protection is set to ``PROT_READ|PROT_WRITE|PROT_EXEC``.

:mps:tag:`fun.set.assume.mprotect` We assume that the call to :c:func:`mprotect()`
always succeeds.

:mps:tag:`fun.set.assume.mprotect` This is because we should always call the
function with valid arguments (aligned, references to mapped pages,
and with an access that is compatible with the access of the
underlying object).

:mps:tag:`fun.sync` :c:func:`ProtSync()` does nothing in this implementation as
:c:func:`ProtSet()` sets the protection without any delay.


Threads
-------

:mps:tag:`threads` The design must operate in a multi-threaded environment
(with LinuxThreads) and cooperate with the Linux support for locks
(see design.mps.lock) and the thread suspension mechanism (see
design.mps.pthreadext ).

:mps:tag:`threads.suspend` The :c:macro:`SIGSEGV` signal handler does not mask out
any signals, so a thread may be suspended while the handler is active,
as required by the design (see
design.mps.pthreadext.req.suspend.protection). The signal handlers
simply nest at top of stack.

:mps:tag:`threads.async` POSIX (and hence Linux) imposes some restrictions
on signal handler functions (see
design.mps.pthreadext.anal.signal.safety). Basically the rules say the
behaviour of almost all POSIX functions inside a signal handler is
undefined, except for a handful of functions which are known to be
"async-signal safe". However, if it's known that the signal didn't
happen inside a POSIX function, then it is safe to call arbitrary
POSIX functions inside a handler.

:mps:tag:`threads.async.protection` If the signal handler is invoked because
of an MPS access, then we know the access must have been caused by
client code, because the client is not allowed to permit access to
protectable memory to arbitrary foreign code. In these circumstances,
it's OK to call arbitrary POSIX functions inside the handler.

.. note::

    Need a reference for "the client is not allowed to permit access
    to protectable memory to arbitrary foreign code".

:mps:tag:`threads.async.other` If the signal handler is invoked for some
other reason (that is, one we are not prepared to handle) then there
is less we can say about what might have caused the SEGV. In general
it is not safe to call arbitrary POSIX functions inside the handler in
this case.

:mps:tag:`threads.async.choice` The signal handler calls :c:func:`ArenaAccess()`
to determine whether the segmentation fault was the result of an MPS
access. ArenaAccess will claim various MPS locks (that is, the arena
ring lock and some arena locks). The code calls no other POSIX
functions in the case where the segmentation fault is not an MPS
access. The locks are implemented as mutexes and are claimed by
calling :c:func:`pthread_mutex_lock()`, which is not defined to be
async-signal safe.

:mps:tag:`threads.async.choice.ok` However, despite the fact that PThreads
documentation doesn't define the behaviour of :c:func:`pthread_mutex_lock()`
in these circumstances, we expect the LinuxThreads implementation will
be well-behaved unless the segmentation fault occurs while while in
the process of locking or unlocking one of the MPS locks (see
:mps:ref:`.threads.async.linux-mutex`). But we can assume that a segmentation
fault will not happen then (because we use the locks correctly, and
generally must assume that they work). Hence we conclude that it is OK
to call :c:func:`ArenaAccess()` directly from the signal handler.

:mps:tag:`threads.async.linux-mutex` A study of the LinuxThreads source code
reveals that mutex lock and unlock functions are implemented as a
spinlock (using a locked compare-and-exchange instruction) with a
backup suspension mechanism using :c:func:`sigsuspend()`. On locking, the
spinlock code performs a loop which examines the state of the lock,
and then atomically tests that the state is unchanged while attempting
to modify it. This part of the code is reentrant (and hence
async-signal safe). Eventually, when locking, the spinlock code may
need to block, in which case it calls :c:func:`sigsuspend()`, waiting for
the manager thread to unblock it. The unlocking code is similar,
except that this code may need to release another thread, in which
case it calls :c:func:`kill()`. The functions :c:func:`sigsuspend()` and
:c:func:`kill()` are both defined to be async-signal safe by POSIX. In
summary, the mutex locking functions use primitives which are entirely
async-signal safe. They perform side-effects which modify the fields
of the lock structure only. This code may be safely invoked inside a
signal handler unless the interrupted function is in the process of
manipulating the fields of that lock structure.

:mps:tag:`threads.async.improve` In future it would be preferable to not
have to assume reentrant mutex locking and unlocking functions. By
making the assumption we also assume that the implementation of
mutexes in LinuxThreads will not be completely re-designed in future
(which is not wise for the long term). An alternative approach would
be necessary anyway when supporting another platform which doesn't
offer reentrant locks (if such a platform does exist).

:mps:tag:`threads.async.improve.how` We could avoid the assumption if we had
a means of testing whether an address lies within an arena chunk
without the need to claim any locks. Such a test might actually be
possible. For example, arenas could update a global datastructure
describing the ranges of all chunks, using atomic updates rather than
locks; the handler code would be allowed to read this without locking.
However, this is somewhat tricky; a particular consideration is that
it's not clear when it's safe to deallocate stale portions of the
datastructure.

:mps:tag:`threads.sig-stack` We do not handle signals on a separate signal
stack. Separate signal stacks apparently don't work properly with
Pthreads.