Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 6 May 2016 16:20:44 +0300
From:      Konstantin Belousov <kostikbel@gmail.com>
To:        Martin Simmons <martin@lispworks.com>
Cc:        threads@freebsd.org, arch@freebsd.org
Subject:   Re: Robust mutexes implementation
Message-ID:  <20160506132044.GA89104@kib.kiev.ua>
In-Reply-To: <201605061300.u46D0mhN008053@higson.cam.lispworks.com>
References:  <20160505131029.GE2422@kib.kiev.ua> <201605051720.u45HKZ76021094@higson.cam.lispworks.com> <20160505185810.GF2422@kib.kiev.ua> <201605061300.u46D0mhN008053@higson.cam.lispworks.com>

next in thread | previous in thread | raw e-mail | index | archive | help
On Fri, May 06, 2016 at 02:00:48PM +0100, Martin Simmons wrote:
> >>>>> On Thu, 5 May 2016 21:58:10 +0300, Konstantin Belousov said:
> > WRT possible reordering, we in fact only care that enqueue writes do
> > not become visible before lock is obtained, and dequeue must finish
> > for external observers before lock is released.  This is ensured by
> > the umutex lock semantic, which has neccessary acquire barrier on
> > lock and release barrier on unlock.
> 
> I meant the case where CPU 1 claims the lock, executing this from
> enqueue_mutex:
> 
> m->m_lock.m_rb_lnk = 0;           /* A */
> *rl = (uintptr_t)&m->m_lock;      /* B */
> 
> and then the thread dies and CPU 2 cleans up the dead thread executing this
> from umtx_handle_rb:
> 
> error = copyin((void *)rbp, &m, sizeof(m));     /* C */
> *rb_list = m.m_rb_lnk;                          /* D */
> 
> where rbp is the value stored into *rl at B by CPU 1.
> 
> What ensures that CPU 1's store at A is seen by CPU 2 when it reads the value
> at D?  I'm hoping this is covered by something that I don't understand, but I
> was worried by the comment "consistent even in the case of thread termination
> at arbitrary moment" above enqueue_mutex.
> 
The cleanup is performed by the same thread which did the lock, in the
kernel mode. Thread can only die by explicit kernel action, and this
action is executed by the dying thread.

In fact, there is one (or two) cases when the cleanup is performed by
thread other than the lock owner.  Namely, when we execve(2) (or _exit(2)
the whole process, but the mechanics is same), we first put the process
into so-called single-threading state, where all threads other than the
executing the syscall, are put into sleep at the safe moment.  Then,
the old address space if destroyed and new image activated.  Only after
that, other threads are killed.  It is done to allow the error return,
where we need to keep threads around on failed execve.

And, right before the old address space is destroyed, robust mutexes for
all threads are terminated, since we need access to usermode VA to operate
on locks.  But there, the single-threading mechanics contains neccessary
barriers to ensure visibility in right order.  In essence, there are so
many locks/unlocks with barrier semantic on single-threading that this is
not a problem.



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20160506132044.GA89104>