Ravenbrook / Projects / Memory Pool System / Master Product Sources / Design Documents

Memory Pool System Project


               LINUX IMPLEMENTATION OF PROTECTION MODULE
                           design.mps.protli
                             incomplete doc
                            tony 2000-02-03

INTRODUCTION

.readership: Any MPS developer

.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

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


MISC

.improve.sigvec: Note 1 of ProtSetup notes that we can't honour the sigvec(2) 
entries of the next handler in the chain.  What if when we want to pass on the 
signal instead of calling the handler we call sigvec with the old entry and use 
kill to send the signal to ourselves and then restore our handler using sigvec 
again. [need more detail and analysis here].


DATASTRUCTURES

.data.signext: This is static.  Because that is the only communications channel 
available to signal handlers. [write a little more here]


FUNCTIONS

.fun.setup: ProtSetup installs a signal handler for the signal SIGSEGV to catch 
and handle protection faults (this handler is the function sigHandle, see 
.fun.sighandle). The previous handler is recorded (in the variable sigNext, see 
.data.signext) so that it can be reached from sigHandle if it fails to handle 
the fault. .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).

.fun.set: ProtSet uses mprotect to adjust the protection for pages. 
void ProtSet(Addr base, Addr limit, AccessSet mode)

.fun.set.convert: The requested protection (which is expressed in the mode 
parameter, see design.mps.prot.if.set) is translated into an OS protection.  If 
read accesses are to be forbidden then all accesses are forbidden, this is done 
by setting the protection of the page to 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.

.fun.set.assume.mprotect: We assume that the call to mprotect always succeeds.  
.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).

.fun.sync: ProtSync does nothing in this implementation as ProtSet sets the 
protection without any delay.
void ProtSync(Space space);

.fun.tramp: The protection trampoline is trivial under Linux, as there is 
nothing that needs to be done in the dynamic context of the mutator in  order 
to catch faults.  (Contrast this with Win32 Structured Exception Handling.)
void ProtTramp(void **resultReturn, void *(*f)(void *, size_t), void *p, size_t 
s);


THREADS

.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
).

.threads.suspend: The 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. 

.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.

.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 [need a reference for this]). In these circumstances, it's OK to 
call arbitrary POSIX functions inside the handler.

.threads.async.other: If the signal handler is invoked for some other reason 
(i.e. 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.

.threads.async.choice: The signal handler calls ArenaAccess to determine 
whether the SEGV was the result of an MPS access. ArenaAccess will claim 
various MPS locks (i.e. the arena ring lock and some arena locks). The code 
calls no other POSIX functions in the case where the SEGV is not an MPS access. 
The locks are implemented as mutexes and are claimed by calling 
pthread_mutex_lock, which is not defined to be async-signal safe. 
.threads.async.choice.ok: However, despite the fact that PThreads documentation 
doesn't define the behaviour of pthread_mutex_lock in these circumstances, we 
expect the LinuxThreads implementation will be well-behaved unless the SEGV 
occurs while while in the process of locking or unlocking one of the MPS locks 
(see .threads.async.linux-mutex). But we can assume that a SEGV 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 ArenaAccess directly from the 
signal handler.

.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 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 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 kill. sigsuspend and 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.

.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 implementaion 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). 
.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. 

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



A. References

B. Document History

2002-06-07 RB Converted from MMInfo database design document.