Questions about mutex implementation in kern/kern_mutex.c

Andrey Simonenko simon at comsys.ntu-kpi.kiev.ua
Thu Sep 16 17:33:09 UTC 2010


On Wed, Sep 15, 2010 at 08:46:00AM -0700, Matthew Fleming wrote:
> I'll take a stab at answering these...
> 
> On Wed, Sep 15, 2010 at 6:44 AM, Andrey Simonenko
> <simon at comsys.ntu-kpi.kiev.ua> wrote:
> > Hello,
> >
> > I have questions about mutex implementation in kern/kern_mutex.c
> > and sys/mutex.h files (current versions of these files):
> >
> > 1. Is the following statement correct for a volatile pointer or integer
> >  variable: if a volatile variable is updated by the compare-and-set
> >  instruction (e.g. atomic_cmpset_ptr(&val, ...)), then the current
> >  value of such variable can be read without any special instruction
> >  (e.g. v = val)?
> >
> >  I checked Assembler code for a function with "v = val" and "val = v"
> >  like statements generated for volatile variable and simple variable
> >  and found differences: on ia64 "v = val" was implemented by ld.acq and
> >  "val = v" was implemented by st.rel; on mips and sparc64 Assembler code
> >  can have different order of lines for volatile and simple variable
> >  (depends on the code of a function).
> 
> I think this depends somewhat on the hardware and what you mean by
> "current" value.

"Current" value means that the value of a variable read by one thread
is equal to the value of this variable successfully updated by another
thread by the compare-and-set instruction.  As I understand from the kernel
source code, atomic_cmpset_ptr() allows to update a variable in a way that
all other CPUs will invalidate corresponding cache lines that contain
the value of this variable.

The mtx_owned(9) macro uses this property, mtx_owned() does not use anything
special to compare the value of m->mtx_lock (volatile) with current thread
pointer, all other functions that update m->mtx_lock of unowned mutex use
compare-and-set instruction.  Also I cannot find anything special in
generated Assembler code for volatile variables (except for ia64 where
acquire loads and release stores are used).

> 
> If you want a value that is not in-flux, then something like
> atomic_cmpset_ptr() setting to the current value is needed, so that
> you force any other atomic_cmpset to fail.  However, since there is no
> explicit lock involved, there is no strong meaning for "current" value
> and a read that does not rely on a value cached in a register is
> likely sufficient.  While the "volatile" keyword in C has no explicit
> hardware meaning, it often means that a load from memory (or,
> presumably, L1-L3 cache) is required.

The "volatile" keyword here and all questions are related to the base C
compiler, current version and currently supported architectures in FreeBSD.
Yes, here under "volatile" I want to say that the value of a variable is
not cached in a register and it is referenced by its address in all
commands.

There are some places in the kernel where a variable is updated in
something like "do { v = value; } while (!atomic_cmpset_int(&value, ...));"
and that variable is not "volatile", but the compiler generates correct
Assembler code.  So "volatile" is not a requirement for all cases.

> 
> > 2. Let there is a default (sleep) mutex and adaptive mutexes is enabled.
> >  A thread tries to obtain lock quickly and fails, _mtx_lock_sleep()
> >  is called, it gets the address of the current mutex's owner thread
> >  and checks whether that owner thread is running (on another CPU).
> >  How does _mtx_lock_sleep() know that that thread still exists
> >  (lines 311-337 in kern_mutex.c)?
> >
> >  When adaptive mutexes was implemented there was explicit locking
> >  around adaptive mutexes code. When turnstile in mutex code was
> >  implemented that's locking logic was changed.
> 
> It appears that it's possible for the thread pointer to be recycled
> between fetching the value of owner and looking at TD_IS_RUNNING.  On
> actual hardware, this race is unlikely to occur due to the time it
> takes for a thread to release a lock and perform all of thread exit
> code before the struct thread is returned to the uma zone.  However,
> even once returned to the uma zone on many FreeBSD implementations the
> access is safe as the address of the thread is still dereferenceable,
> due to the implementation of uma zones.

I checked exactly this scenario, that's why asked this question to verify
my understanding.

> 
> > 3. Why there is no any memory barrier in mtx_init()? If another thread
> >  (on another CPU) finds that mutex is initialized using mtx_initialized()
> >  then it can mtx_lock() it and mtx_lock() it second time, as a result
> >  mtx_recurse field will be increased, but its value still can be
> >  uninitialized on architecture with relaxed memory ordering model.
> 
> It seems to me that it's generally a programming error to rely on the
> return of mtx_initialized(), as there is no serialization with e.g. a
> thread calling mtx_destroy().  A fully correct serialization model
> would require that a single thread initialize the mtx and then create
> any worker threads that will use the mtx.

I agree that this should not happen in practice.  Another thread can get
a pointer to just initialized mutex and begin to work with it, so
mtx_initialized() is not a requirement.  I just want to say that when
mtx_init() is finished, it does not mean that just initialized mutex by
one thread is ready to be used by another thread.

Thank you for answers.


More information about the freebsd-hackers mailing list